问题描述
我在macOS上有一个绕行的定制实现,并使用了一个测试应用程序,它是用C语言编写的,是为macOS x86_64编译的,并在Intel i9处理器上运行。
该实现可以很好地使用多种功能。但是,如果我绕行pthread_create
,则会遇到奇怪的行为:通过绕行的pthread_create生成的线程不会执行指令。我可以一步一步地完成说明,但是continue
一经执行就无法执行。不涉及互斥锁或同步,该函数的结果为0(成功)。完全绕过弯路的应用程序也可以正常工作,因此不太可能成为罪魁祸首。
这并非一直发生-有时情况很好,但有时测试应用程序停在以下状态:
(lldb) bt all
* thread #1,queue = 'com.apple.main-thread',stop reason = signal SIGSTOP
* frame #0: 0x00007fff7296f55e libsystem_kernel.dylib`__ulock_wait + 10
frame #1: 0x00007fff72a325c2 libsystem_pthread.dylib`_pthread_join + 347
frame #2: 0x0000000100001186 DetoursTestApp`main + 262
frame #3: 0x00007fff7282ccc9 libdyld.dylib`start + 1
frame #4: 0x00007fff7282ccc9 libdyld.dylib`start + 1
thread #2
frame #0: 0x00007fff72a2cb7c libsystem_pthread.dylib`thread_start
相关的内存页已设置了可执行标志。拦截线程创建的绕行功能看起来像这样:
static int pthread_create_detour(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void*),void* arg)
{
detour_count++;
pthread_fn original = (pthread_fn)detour_original(dlsym((void*)-1,"pthread_create"));
return original(thread,attr,start_routine,arg);
}
detour_original
在哪里检索指向[原始函数+函数序号的大小]的指针。
仔细查看说明,一切似乎都正常运行,并且pthread_create
成功终止。通过dtruss
跟踪应用程序的系统调用确实显示对
bsdthread_create(0x10DB964B0,0x0,0x7000080DB000) = 29646848 0
我确认的是正确的论点。
仅在发行版本中会观察到此行为-调试工作正常,但两种情况下反绕pthread_create
和相关绕道代码的反汇编和执行似乎都是相同的。
解决方法
对于这个问题,我发现了一些奇怪的解决方法,它们没有太大的意义。有了绕行功能,可以将许多东西替换为以下内容:
static int pthread_create_detour(pthread_t* thread,"pthread_create"));
<...> <== SUBSTITUTE HERE
return original(thread,arg);
}
- 刷新缓存
__asm__ __volatile__("" ::: "memory");
_mm_clflush(real_pthread_create);
缓存?
所有这些似乎都指向过时的指令缓存。但是,英特尔手册规定以下内容:
写入当前当前在处理器中缓存的代码段中的内存位置会导致关联的缓存行无效。该检查基于指令的物理地址。此外,P6系列和Pentium处理器检查对代码段的写入是否可以修改已预取的指令以执行。如果写入影响预取指令,则预取队列无效。后面的检查基于指令的线性地址。
更有趣的是,必须针对每个创建的新线程执行这些解决方法,并且执行发生在主线程上,因此不太可能成为高速缓存。我也曾尝试在每次写指令的内存写入中放入缓存刷新,但这无济于事。我还编写了一个memcpy
,它使用Intel固有的_mm_stream_si32
绕过了缓存,并在实现中的每个指令存储器写入中都将其交换了出来,但没有成功。
种族状况?
排队的下一个嫌疑犯是种族状况。但是,目前尚不清楚会出现什么竞赛,因为起初没有其他线程。我对随机生成的数字进行了斐波那契数列计算,这仍然会使新产生的线程停滞。
问题
是什么原因导致此问题?哪些其他机制可能对此负责?
目前,我已经没有足够的东西来检查了,所以任何建议都将受到欢迎。
解决方法
我发现生成的线程未执行指令的原因是由于我的绕行问题导致r8
寄存器在执行pthread_create
的正确时间未清除实施。
如果我们看一下该函数的反汇编,则将其分为两部分-在内部_pthread_create
函数中找到的“头部”和“主体”。头做两件事-将r8
调零并跳到身体:
libsystem_pthread.dylib`pthread_create:
0x7fff72a2e236 <+0>: 45 31 c0 xor r8d,r8d
0x7fff72a2e239 <+3>: e9 40 37 00 00 jmp 0x7fff72a3197e ; _pthread_create
libsystem_pthread.dylib`_pthread_create:
0x7fff72a3197e <+0>: 55 push rbp
0x7fff72a3197f <+1>: 48 89 e5 mov rbp,rsp
0x7fff72a31982 <+4>: 41 57 push r15
<...> // the rest of the 1409 instructions
我的实现将绕过内部_pthread_create
函数,而不是包含实际入口点的头部,这意味着r8
将在错误的时间(绕行之前)被清除。由于绕行功能将包含一些功能,因此执行过程将类似于:
pthread_create
(r8
被清除)-> _pthread_create
->跳链-> pthread_create_detour
->蹦床(包含_pthread_create
的开头) -> _pthread_create + 6
这意味着根据pthread_create_detour
函数的内容,r8
返回内部函数时并不总是以0结尾。
目前尚不清楚为什么在r8
之前将_pthread_create
设置为0之外的值不会导致崩溃,而是以锁定状态启动线程。一个重要的细节是,停滞的线程会将rflags
寄存器设置为0x200
,而根据Intel's manual永远不会这样。这就是导致我更仔细地检查CPU状态并得出答案的原因。