You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:13:31