x64架构Ubuntu14.04下定时器中断时如何获取栈中Program Counter?
嘿,我来帮你搞定这个问题!在x64架构的Ubuntu 14.04下,定时器中断发生时获取栈中的程序计数器(也就是RIP寄存器的值),其实有两种可行的方法——一种是纯C就能实现的简单方案,另一种是你提到的汇编+C组合方案,我都会给你讲得明明白白,不用担心汇编的复杂度~
方法一:利用ucontext_t直接获取(纯C,最推荐)
在Ubuntu用户态程序里,定时器中断对应的是SIGALRM信号。当你给这个信号注册处理函数时,只要设置SA_SIGINFO标志,就能拿到一个ucontext_t类型的参数——这个结构体里保存了中断发生瞬间的所有寄存器状态,包括我们要找的RIP。
直接看代码示例:
#include <stdio.h> #include <signal.h> #include <ucontext.h> #include <unistd.h> void timer_handler(int sig, siginfo_t *info, void *context) { ucontext_t *uc = (ucontext_t *)context; // 直接从上下文结构体中取出中断发生时的RIP unsigned long long rip = uc->uc_mcontext.gregs[REG_RIP]; printf("定时器中断触发!中断时的RIP = 0x%llx\n", rip); } int main() { struct sigaction sa; sa.sa_sigaction = timer_handler; sa.sa_flags = SA_SIGINFO; // 必须设置这个标志才能拿到上下文参数 sigemptyset(&sa.sa_mask); if (sigaction(SIGALRM, &sa, NULL) == -1) { perror("sigaction注册失败"); return 1; } // 设置每2秒触发一次定时器中断 alarm(2); while (1) { pause(); // 阻塞等待信号 alarm(2); // 重置定时器 } return 0; }
编译运行只需要一行命令:
gcc timer_rip.c -o timer_rip && ./timer_rip
这个方案完全不用写汇编,而且依赖内核提供的标准接口,稳定性和兼容性都更好,非常适合C开发者。
方法二:直接从栈中读取(汇编+C)
如果你确实需要手动从栈里读取RIP的值,那我们需要一小段汇编代码来定位栈中的RIP位置。先给你讲清楚原理:当SIGALRM信号触发时,内核会把中断上下文(包括RFLAGS、CS、RIP)压入栈中,然后才调用你的信号处理函数。进入处理函数后,栈的结构大致是这样的(栈向下生长,地址从高到低):
[返回地址] → [siginfo_t参数] → [voidcontext参数] → [RFLAGS] → [CS] → [RIP] → ...
所以我们需要计算偏移量,跳过前面的内容,直接读取RIP的位置。
第一步:编写汇编代码(get_rip.s)
.global get_rip_from_stack get_rip_from_stack: # 计算偏移:返回地址(8字节) + 两个参数(各8字节) + RFLAGS(8字节) + CS(8字节) = 40字节 movq 40(%rsp), %rax # 把栈中对应位置的RIP值放到rax寄存器中返回 ret
第二步:编写C代码调用汇编函数
#include <stdio.h> #include <signal.h> #include <unistd.h> // 声明汇编函数,告诉C编译器这个函数的存在 unsigned long long get_rip_from_stack(); void timer_handler(int sig, siginfo_t *info, void *context) { unsigned long long rip = get_rip_from_stack(); printf("定时器中断触发!从栈中读取的RIP = 0x%llx\n", rip); } int main() { struct sigaction sa; sa.sa_sigaction = timer_handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); if (sigaction(SIGALRM, &sa, NULL) == -1) { perror("sigaction注册失败"); return 1; } alarm(2); while (1) { pause(); alarm(2); } return 0; }
编译运行
gcc timer_rip_stack.c get_rip.s -o timer_rip_stack && ./timer_rip_stack
重要注意事项
- 方法二中的栈偏移量是针对Ubuntu 14.04的3.x内核栈帧结构设计的,如果换了其他内核版本,偏移量可能会变化,所以方法一更可靠。
- 如果你是在编写内核模块处理内核态定时器中断,栈结构会完全不同,那需要另一种实现方式,但从你的描述来看,应该是用户态程序,上面的方案足够用了。
- 你可以用
gdb调试验证RIP的值是否正确,比如在定时器处理函数里加断点,查看寄存器状态对比。
内容的提问来源于stack exchange,提问作者M.Shuai
相关产品推荐
相关产品推荐

