当前位置: 首页 > news >正文

Linux C语言基于FP寄存器进行栈回溯

文章目录

  • 前言
  • 一、简介
  • 二、C语言例子
    • 2.1 demo1
    • 2.2 demo2
    • 2.3 demo3

前言

前置知识:
https://xiaolizai.blog.csdn.net/article/details/136690200
https://xiaolizai.blog.csdn.net/article/details/138706194

一、简介

基于帧指针可以进行栈回溯:
在X86中,通常使用RBP寄存器作为帧指针使用,RBP寄存器所指向的栈单元中保存的是前一个RBP寄存器的值,通常也就是caller函数的RBP值。帧指针不仅对函数中的代码起到定位变量和参数的参照物作用,而且将栈中的一个个栈帧串联在一起,形成了一个可以遍历所有栈帧的链条,这也就是栈回溯的基本原理。

main() -> funa -> funb -> func 

如下图所示:
在这里插入图片描述

二、C语言例子

2.1 demo1

代码如下:

#define _GNU_SOURCE // 启用GNU扩展功能(如dladdr的扩展信息)#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>#include <link.h>
#include <dlfcn.h>static int get_stack_range(unsigned long *start, unsigned long *end) {FILE *maps;char line[1024];maps = fopen("/proc/self/maps", "r");if (!maps) {perror("fopen");return -1;}while (fgets(line, sizeof(line), maps)) {if (strstr(line, "[stack]")) {sscanf(line, "%lx-%lx", start, end);break;}}fclose(maps);return 0;
}/* 检查指针是否可能合法 */
static bool is_valid_address(void *p) {uintptr_t addr = (uintptr_t)p;unsigned long stack_start, stack_end;get_stack_range(&stack_start, &stack_end);/* 16字节对齐检查 */if(addr >= stack_start && addr <= stack_end && (addr & 0xF) == 0)return true;elsereturn false;
}void my_backtrace() {void **rbp;asm volatile ("mov %%rbp, %0" : "=r"(rbp));  // 读取当前rbp寄存器的值(当前栈帧的基址)printf("Backtrace:\n");int frame_count = 0;int max_frames = 20;  // 防止无限循环while (rbp && max_frames-- && is_valid_address(rbp)) {printf("rbp = %p\n", rbp);void *ret_addr = *(rbp + 1); //rbp+1存储返回地址(调用者的下一条指令)printf("#%-2d ret_addr = %p\n", frame_count++, ret_addr);rbp = (void **)*rbp;  // 移动到上一个栈帧的rbp(栈帧链表的下一个节点)}
}// 测试函数
__attribute__((noinline)) void func3() { my_backtrace(); }
__attribute__((noinline)) void func2() { func3(); }
__attribute__((noinline)) void func1() { func2(); }int main() {func1();return 0;
}

rbp 寄存器存的是 当前栈帧基址。
[rbp] 存的是 调用者的 rbp(上一帧的基址)。
[rbp+8] 存的是 返回地址(call 指令压栈的地址)。

双指针 void **rbp,因为 RBP 寄存器里存的就是上一个栈帧的基址地址(一个指针),而我们要访问它指向的内容(返回地址、上一个 RBP),这就需要“指向指针的指针”。

$ gcc  backtrace.c -o backtrace
$ ./backtrace
Backtrace:
rbp = 0x7ffc45ed1700
#0  ret_addr = 0x55b233d39382
rbp = 0x7ffc45ed1710
#1  ret_addr = 0x55b233d39393
rbp = 0x7ffc45ed1720
#2  ret_addr = 0x55b233d393a4
rbp = 0x7ffc45ed1730
#3  ret_addr = 0x55b233d393b5
rbp = 0x7ffc45ed1740
#4  ret_addr = 0x7f7e84a4a24a

2.2 demo2

给栈回溯增加符号:

#define _GNU_SOURCE#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>#include <link.h>
#include <dlfcn.h>static int get_stack_range(unsigned long *start, unsigned long *end) {FILE *maps;char line[1024];maps = fopen("/proc/self/maps", "r");if (!maps) {perror("fopen");return -1;}while (fgets(line, sizeof(line), maps)) {if (strstr(line, "[stack]")) {sscanf(line, "%lx-%lx", start, end);break;}}fclose(maps);return 0;
}/* 检查指针是否可能合法 */
static bool is_valid_address(void *p) {uintptr_t addr = (uintptr_t)p;unsigned long stack_start, stack_end;get_stack_range(&stack_start, &stack_end);/* 16字节对齐检查 */if(addr >= stack_start && addr <= stack_end && (addr & 0xF) == 0)return true;elsereturn false;
}/* 符号解析函数 */
static void resolve_symbol(void *addr) {Dl_info info;if (dladdr(addr, &info)) {if (info.dli_sname) {printf("  %s()", info.dli_sname);if (info.dli_fname) {printf(" in %s", info.dli_fname);}// 尝试获取偏移量unsigned long offset = (unsigned long)addr - (unsigned long)info.dli_saddr;if (offset < 0x1000) {  // 只显示小偏移量printf("+0x%lx", offset);}} else {printf("  ?? [%p]", addr);}} else {printf("  ?? [%p]", addr);}
}void my_backtrace() {void **rbp;asm volatile ("mov %%rbp, %0" : "=r"(rbp));printf("Backtrace:\n");int frame_count = 0;int max_frames = 20;  // 防止无限循环while (rbp && max_frames-- && is_valid_address(rbp)) {//返回地址是 call 的下一条指令void *ret_addr = *(rbp + 1);printf("#%-2d ", frame_count++);resolve_symbol(ret_addr);printf("\n");rbp = (void **)*rbp;}
}// 测试函数
__attribute__((noinline)) void func3() { my_backtrace(); }
__attribute__((noinline)) void func2() { func3(); }
__attribute__((noinline)) void func1() { func2(); }int main() {// 编译时需要加上 -rdynamic 选项以导出符号// 例如: gcc -rdynamic  backtrace.c -o backtracefunc1();return 0;
}
$ gcc -rdynamic 2.c -o backtrace
$ ./backtrace
Backtrace:
#0    func3() in ./backtrace+0xe
#1    func2() in ./backtrace+0xe
#2    func1() in ./backtrace+0xe
#3    main() in ./backtrace+0xe
#4    ?? [0x7f89ceed024a]

2.3 demo3

增加.so:

#define _GNU_SOURCE#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dlfcn.h>
#include <link.h>  // 用于获取库基地址#define MAX_FRAMES 20// 获取栈内存范围
static int get_stack_range(unsigned long *start, unsigned long *end) {FILE *maps = fopen("/proc/self/maps", "r");if (!maps) {perror("fopen");return -1;}char line[1024];while (fgets(line, sizeof(line), maps)) {if (strstr(line, "[stack]")) {sscanf(line, "%lx-%lx", start, end);break;}}fclose(maps);return 0;
}// 检查指针是否合法
static bool is_valid_address(void *p) {uintptr_t addr = (uintptr_t)p;unsigned long stack_start, stack_end;if (get_stack_range(&stack_start, &stack_end) != 0) {return false;}return (addr >= stack_start && addr <= stack_end && (addr & 0xF) == 0);
}// 增强版符号解析
/*
Dl_info 结构体:用于存储 dladdr 解析出的符号信息,包含以下关键字段:
dli_sname:符号名(如函数名 func3)。
dli_fname:符号所在的文件名(如 ./a.out 或 libc.so.6)。
dli_fbase:符号所在文件的加载基地址(动态库的起始地址)。
dli_saddr:符号对应的内存地址(函数入口地址)。
*/
static void resolve_symbol(void *addr) {Dl_info info;if (dladdr(addr, &info)) {// 基本符号信息printf("  0x%016lx: ", (unsigned long)addr);//解析并打印符号名(函数名)if (info.dli_sname) {printf("%s()", info.dli_sname);} else {printf("??");}// 库信息if (info.dli_fname) {printf(" in %s", info.dli_fname);// 如果是动态库,显示基地址和偏移量if (strstr(info.dli_fname, ".so")) {printf(" (base: 0x%lx, offset: 0x%lx)",(unsigned long)info.dli_fbase,(unsigned long)addr - (unsigned long)info.dli_fbase);}}// 如果是在主程序中,显示偏移量if (info.dli_saddr && !strstr(info.dli_fname ? info.dli_fname : "", ".so")) {printf("+0x%lx", (unsigned long)addr - (unsigned long)info.dli_saddr);}} else {printf("  0x%016lx: ?? [未知地址]", (unsigned long)addr);}
}// 打印所有已加载的库(调试用)
void print_loaded_libs() {printf("\n已加载的库:\n");struct link_map *lm = _r_debug.r_map;while (lm) {printf("  0x%016lx %s\n", (unsigned long)lm->l_addr, lm->l_name);lm = lm->l_next;}
}void backtrace() {void **rbp;asm volatile ("mov %%rbp, %0" : "=r"(rbp));printf("调用栈回溯:\n");int frame_count = 0;while (rbp && frame_count < MAX_FRAMES && is_valid_address(rbp)) {void *ret_addr = *(rbp + 1);printf("#%-2d ", frame_count++);resolve_symbol(ret_addr);printf("\n");rbp = (void **)*rbp;}print_loaded_libs();  // 可选:显示加载的库
}// 测试函数
__attribute__((noinline)) void func3() { backtrace(); }
__attribute__((noinline)) void func2() { func3(); }
__attribute__((noinline)) void func1() { func2(); }int main() {printf("=== 开始栈回溯测试 ===\n");func1();printf("=== 测试结束 ===\n");return 0;
}
$ gcc -rdynamic 3.c -o backtrace
$ ./backtrace
=== 开始栈回溯测试 ===
调用栈回溯:
#0    0x000055cae91a0563: func3() in ./backtrace+0xe
#1    0x000055cae91a0574: func2() in ./backtrace+0xe
#2    0x000055cae91a0585: func1() in ./backtrace+0xe
#3    0x000055cae91a05a5: main() in ./backtrace+0x1d
#4    0x00007f9e75d3f24a: ?? in /lib/x86_64-linux-gnu/libc.so.6 (base: 0x7f9e75d18000, offset: 0x2724a)已加载的库:0x000055cae919f0000x00007ffe819fe000 linux-vdso.so.10x00007f9e75d18000 /lib/x86_64-linux-gnu/libc.so.60x00007f9e75f21000 /lib64/ld-linux-x86-64.so.2
=== 测试结束 ===
http://www.dtcms.com/a/396343.html

相关文章:

  • 网站项目团队介绍怎么写网站建设需要哪些的ps
  • 网站做好了怎么办用rp怎么做网站功能按钮
  • 水果购物网站|基于java+vue的水果购物网站系统(源码+数据库+文档)
  • 巩义移动网站建设如何制作个人网页缴费
  • 深圳做企业网站1个亿用户的服务器多少钱
  • 电子商务有限公司网站济宁鱼台网站建设
  • 从“万能 ES”到专业 ClickHouse:一次埋点数据存储的选择
  • ICCV-2025 | 斯坦福人形机器人自主导航!LookOut:真实环境人形机器人第一人称视角导航
  • 网络销售网站数据库设计工具
  • stm32定时器:什么是 Timer Trigger Output Event
  • 网站推广有哪些方式如何用群晖做自己的网站
  • 网站后台管理员职责页游做的好的是哪个网站
  • 汕头网站设计浩森宇特自己做游戏app的网站
  • 做火锅加盟哪个网站好域名网安备案
  • seo网站推广与优化方案成交功能网站
  • 动漫网站模板建网站 温州
  • 北京网站建设 seo公司哪家好营口东站营销网站建设
  • 有自己域名如何做网站qq登录网页版登录入口
  • 做网站用到哪些软件万网归一
  • 吴桥网站建设价格个人网站可以备案了吗
  • 建设网站加盟苏州园区做网站公司
  • 网站服务器端口设置房屋建筑学课程设计图纸
  • 做昆特牌的网站网站内容的设计与实现
  • 网站建设栏目这一块怎么写深圳做互联网教网站公司
  • 什么是网站实施wordpress 默认播放器
  • 东明住房和城乡建设局网站小白怎么做无货源电商
  • 可以看的网站都有哪些网页设计表格跨行代码
  • 学校信息化网站建设广告策划书前言范文
  • 网页制作制作网站公司部门职位
  • 自己做的网站如何上首页室内设计师的工作内容