Intel Pin技术问题:如何获取系统调用的返回地址
获取Intel Pin中系统调用的返回地址
在Intel Pin里,常规函数调用能用IARG_RETURN_IP拿到返回地址,但系统调用的机制不一样——它不是普通的call指令,而是通过syscall(x86-64)或sysenter(32位)这类指令进入内核,返回地址的存储逻辑和普通调用完全不同,所以直接在SyscallExitFunction里读REG_INST_PTR是拿不到用户态的返回地址的(此时这个寄存器指向的是内核态的指令地址,或者已经是返回后的下一条指令,取决于Pin的插桩时机)。
正确的做法是在系统调用入口时保存返回地址,到出口时再取出,具体步骤如下:
1. 准备线程本地存储(TLS)
因为多线程环境下,每个线程的系统调用是独立的,不能用全局变量存返回地址,得用TLS来隔离每个线程的数据:
static TLS_KEY tls_key_return_ip;
在Pin初始化的时候创建这个TLS键:
PIN_InitSymbols(); tls_key_return_ip = PIN_CreateThreadDataKey(NULL);
2. 在系统调用入口保存返回地址
根据不同的CPU架构,系统调用的返回地址存在不同的寄存器里:
- x86-64(TARGET_IA32E):
syscall指令会把当前用户态的RIP存入RCX寄存器 - 32位(TARGET_IA32):
sysenter指令会把返回地址存入EDX寄存器
在SyscallEntryFunction里读取对应寄存器的值,并存入TLS:
VOID SyscallEntry(THREADID tid, CONTEXT* ctx, SYSCALL_STANDARD std, VOID* v) { ADDRINT return_ip; // 适配不同架构的寄存器 #ifdef TARGET_IA32 return_ip = PIN_GetContextReg(ctx, REG_EDX); #elif defined(TARGET_IA32E) return_ip = PIN_GetContextReg(ctx, REG_RCX); #endif // 将返回地址存入当前线程的TLS PIN_SetThreadData(tls_key_return_ip, (VOID*)return_ip, tid); }
3. 在系统调用出口取出返回地址
到SyscallExitFunction时,直接从TLS里取出之前保存的地址即可:
VOID SyscallExit(THREADID tid, CONTEXT* ctx, SYSCALL_STANDARD std, VOID* v) { // 从TLS中获取当前线程的系统调用返回地址 ADDRINT return_ip = (ADDRINT)PIN_GetThreadData(tls_key_return_ip, tid); // 这里可以对返回地址做任意处理,比如打印、记录等 printf("系统调用完成,返回地址:0x%lx\n", return_ip); }
4. 注册插桩函数
最后在主函数里注册这两个插桩函数:
int main(int argc, char* argv[]) { if (PIN_Init(argc, argv)) { // 这里可以返回自定义的用法提示 return 1; } PIN_AddSyscallEntryFunction(SyscallEntry, NULL); PIN_AddSyscallExitFunction(SyscallExit, NULL); PIN_StartProgram(); return 0; }
注意事项
- 如果需要兼容
int 0x80这种老式32位系统调用(SYSCALL_STANDARD_IA32_INT80),返回地址是存在用户栈里的,这时候需要用PIN_GetContextReg(ctx, REG_ESP)拿到栈指针,再读取栈上的对应位置(一般是栈顶的第一个值)。 - 一定要用TLS,否则多线程同时触发系统调用时,返回地址会被覆盖,导致数据错误。
内容的提问来源于stack exchange,提问作者Simus
相关产品推荐
相关产品推荐

