如何在 iOS 中获取任意线程的正确帧指针?
HOWTO get the correct Frame Pointer of an arbitrary thread in iOS?
获取帧指针的方法
在 iPhone 5s 设备/ Xcode 7 上的演示应用程序 运行 上,我尝试获取 任意 [=] 的 frame pointer
116=] 线程使用 thread_get_state
,但总是导致不正确的线程:
- (BOOL)fillThreadState:(thread_t)thread intoMachineContext:(_STRUCT_MCONTEXT *)machineContext {
mach_msg_type_number_t state_count = MACHINE_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, MACHINE_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
if (kr != KERN_SUCCESS) {
char *str = mach_error_string(kr);
printf("%s\n", str);
return NO;
}
return YES;
}
我这样读取帧指针:uintptr_t fp = machineContext.__ss.__fp;
,根据 Apple Doc (ARMv6 and ARM64),
Register R7 is used as a frame pointer on ARMv6
而 ARM64 上的 x29
The frame pointer register (x29) must always address a valid frame record, although some functions—such as leaf functions or tail calls—may elect not to create an entry in this list. As a result, stack traces will always be meaningful, even without debug information.
_STRUCT_ARM_THREAD_STATE64
{
__uint64_t __x[29]; /* General purpose registers x0-x28 */
__uint64_t __fp; /* Frame pointer x29 */
__uint64_t __lr; /* Link register x30 */
__uint64_t __sp; /* Stack pointer x31 */
__uint64_t __pc; /* Program counter */
__uint32_t __cpsr; /* Current program status register */
__uint32_t __pad; /* Same size for 32-bit or 64-bit clients */
};
证明帧指针错误的方法
如何证明 fp (machineContext.__ss.__fp
) 不是正确的帧指针?
如果参数为当前线程,例如mach_thread_self(),则fp始终为0,这与__builtin_frame_address(0)
;
如果参数不是当前线程,比如主线程,fp
指向上一帧指针的指针在两三帧就会为NULL,太短了对于正常的 link-堆栈帧列表;
仍然不是当前线程,我在sleep
之前的主线程上使用backtrace
打印出调用堆栈地址。然后在另一个线程上,我挂起主线程并使用 thread_get_state
读取帧指针来遍历调用堆栈,两个回溯缓冲区完全不同;
让我困惑的是什么
Apple Doc 说 帧指针寄存器 (x29) 必须始终寻址有效的帧记录,但我可以读取 ZERO 它的价值。
此外,ARM Doc
声明 在过程调用标准的所有变体中,寄存器 r16、r17、r29 和 r30 具有特殊作用。他们在这些角色中
用于保存地址时标记为 IP0、IP1、FP 和 LR。
下面是 _STRUCT_MCONTEXT
部分值的示例:
Printing description of machineContext:
(__darwin_mcontext64) machineContext = {
__es = (__far = 0, __esr = 0, __exception = 0)
__ss = {
__x = {
[0] = 292057776134
[1] = 6142843584
[2] = 3
[3] = 40
[4] = 624
[5] = 17923
[6] = 0
[7] = 0
[8] = 3968
[9] = 4294966207
[10] = 3603
[11] = 70
[12] = 0
[13] = 33332794515418112
[14] = 0
[15] = 4294967295
[16] = 4294967284
[17] = 18446744073709551585
[18] = 4295035980
[19] = 0
[20] = 0
[21] = 0
[22] = 17923
[23] = 624
[24] = 6142843584
[25] = 3
[26] = 40
[27] = 3
[28] = 0
}
__fp = 0
__lr = 6142843568
__sp = 6877072044
__pc = 6142843488
__cpsr = 2582105136
__pad = 1
}
__ns = {
我在找什么
我现在正在寻找一种方法来获取任意线程Frame Pointer
的正确值。
感谢您的帮助!
更新 1
当然,我想要的是获取任意线程的叶栈帧的正确Frame Pointer
,然后沿着[=30=的Previous Pointer
遍历调用栈].
在此之前,我阅读了这些 links:
Getting a backtrace of other thread
How To Loop Through All Active Thread in iPad app
Printing a stack trace from another thread
再次感谢。
更新 2
我在其他平台上尝试过,但结果相同:错误的帧指针。
- (uintptr_t)framePointerOfMachineContext:(_STRUCT_MCONTEXT *)machineContext {
#if defined (__i386__)
return machineContext->__ss.__ebp;
#endif
#if defined (__x86_64__)
return machineContext->__ss.__rbp;
#endif
#if defined (__arm64__)
return machineContext->__ss.__fp;
#endif
}
值 framePointerOfMachineContext
returns 与 __builtin_frame_address(0)
不同。
更新 3
受同事的启发,尝试了inline asm,并在i386上做了:
uintptr_t ebp = 1;
__asm__ ("mov %%ebp, %0;"
:"=r"(ebp));
uintptr_t builtinFP = (uintptr_t)__builtin_frame_address(0);
现在 ebp
变量与 builtinFP
的值相同。但是如何在任意线程上执行此操作?
终于找到问题了,
- (BOOL)fillThreadState:(thread_t)thread intoMachineContext:(_STRUCT_MCONTEXT *)machineContext {
mach_msg_type_number_t state_count = MACHINE_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, MACHINE_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
if (kr != KERN_SUCCESS) {
char *str = mach_error_string(kr);
printf("%s\n", str);
return NO;
}
return YES;
}
thread_get_state
可以,但参数应该是特定于体系结构的,例如x86_THREAD_STATE32
、x86_THREAD_STATE64
等
我误解了宏MACHINE_THREAD_STATE
,它给了我一个针对不同架构的通用含义。
获取帧指针的方法
在 iPhone 5s 设备/ Xcode 7 上的演示应用程序 运行 上,我尝试获取 任意 [=] 的 frame pointer
116=] 线程使用 thread_get_state
,但总是导致不正确的线程:
- (BOOL)fillThreadState:(thread_t)thread intoMachineContext:(_STRUCT_MCONTEXT *)machineContext {
mach_msg_type_number_t state_count = MACHINE_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, MACHINE_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
if (kr != KERN_SUCCESS) {
char *str = mach_error_string(kr);
printf("%s\n", str);
return NO;
}
return YES;
}
我这样读取帧指针:uintptr_t fp = machineContext.__ss.__fp;
,根据 Apple Doc (ARMv6 and ARM64),
Register R7 is used as a frame pointer on ARMv6
而 ARM64 上的 x29
The frame pointer register (x29) must always address a valid frame record, although some functions—such as leaf functions or tail calls—may elect not to create an entry in this list. As a result, stack traces will always be meaningful, even without debug information.
_STRUCT_ARM_THREAD_STATE64
{
__uint64_t __x[29]; /* General purpose registers x0-x28 */
__uint64_t __fp; /* Frame pointer x29 */
__uint64_t __lr; /* Link register x30 */
__uint64_t __sp; /* Stack pointer x31 */
__uint64_t __pc; /* Program counter */
__uint32_t __cpsr; /* Current program status register */
__uint32_t __pad; /* Same size for 32-bit or 64-bit clients */
};
证明帧指针错误的方法
如何证明 fp (machineContext.__ss.__fp
) 不是正确的帧指针?
如果参数为当前线程,例如mach_thread_self(),则fp始终为0,这与
__builtin_frame_address(0)
;如果参数不是当前线程,比如主线程,
fp
指向上一帧指针的指针在两三帧就会为NULL,太短了对于正常的 link-堆栈帧列表;仍然不是当前线程,我在
sleep
之前的主线程上使用backtrace
打印出调用堆栈地址。然后在另一个线程上,我挂起主线程并使用thread_get_state
读取帧指针来遍历调用堆栈,两个回溯缓冲区完全不同;
让我困惑的是什么
Apple Doc 说 帧指针寄存器 (x29) 必须始终寻址有效的帧记录,但我可以读取 ZERO 它的价值。
此外,ARM Doc
声明 在过程调用标准的所有变体中,寄存器 r16、r17、r29 和 r30 具有特殊作用。他们在这些角色中
用于保存地址时标记为 IP0、IP1、FP 和 LR。
下面是 _STRUCT_MCONTEXT
部分值的示例:
Printing description of machineContext:
(__darwin_mcontext64) machineContext = {
__es = (__far = 0, __esr = 0, __exception = 0)
__ss = {
__x = {
[0] = 292057776134
[1] = 6142843584
[2] = 3
[3] = 40
[4] = 624
[5] = 17923
[6] = 0
[7] = 0
[8] = 3968
[9] = 4294966207
[10] = 3603
[11] = 70
[12] = 0
[13] = 33332794515418112
[14] = 0
[15] = 4294967295
[16] = 4294967284
[17] = 18446744073709551585
[18] = 4295035980
[19] = 0
[20] = 0
[21] = 0
[22] = 17923
[23] = 624
[24] = 6142843584
[25] = 3
[26] = 40
[27] = 3
[28] = 0
}
__fp = 0
__lr = 6142843568
__sp = 6877072044
__pc = 6142843488
__cpsr = 2582105136
__pad = 1
}
__ns = {
我在找什么
我现在正在寻找一种方法来获取任意线程Frame Pointer
的正确值。
感谢您的帮助!
更新 1
当然,我想要的是获取任意线程的叶栈帧的正确Frame Pointer
,然后沿着[=30=的Previous Pointer
遍历调用栈].
在此之前,我阅读了这些 links:
Getting a backtrace of other thread
How To Loop Through All Active Thread in iPad app
Printing a stack trace from another thread
再次感谢。
更新 2
我在其他平台上尝试过,但结果相同:错误的帧指针。
- (uintptr_t)framePointerOfMachineContext:(_STRUCT_MCONTEXT *)machineContext {
#if defined (__i386__)
return machineContext->__ss.__ebp;
#endif
#if defined (__x86_64__)
return machineContext->__ss.__rbp;
#endif
#if defined (__arm64__)
return machineContext->__ss.__fp;
#endif
}
值 framePointerOfMachineContext
returns 与 __builtin_frame_address(0)
不同。
更新 3
受同事的启发,尝试了inline asm,并在i386上做了:
uintptr_t ebp = 1;
__asm__ ("mov %%ebp, %0;"
:"=r"(ebp));
uintptr_t builtinFP = (uintptr_t)__builtin_frame_address(0);
现在 ebp
变量与 builtinFP
的值相同。但是如何在任意线程上执行此操作?
终于找到问题了,
- (BOOL)fillThreadState:(thread_t)thread intoMachineContext:(_STRUCT_MCONTEXT *)machineContext {
mach_msg_type_number_t state_count = MACHINE_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, MACHINE_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
if (kr != KERN_SUCCESS) {
char *str = mach_error_string(kr);
printf("%s\n", str);
return NO;
}
return YES;
}
thread_get_state
可以,但参数应该是特定于体系结构的,例如x86_THREAD_STATE32
、x86_THREAD_STATE64
等
我误解了宏MACHINE_THREAD_STATE
,它给了我一个针对不同架构的通用含义。