首页 > 其他分享 >GDB 调试 - 正确地加载调试符号文件

GDB 调试 - 正确地加载调试符号文件

时间:2023-02-13 20:46:53浏览次数:53  
标签:文件 符号 GDB test gdb 地址 调试 加载

一、开发流程

1. 编译可执行文件
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 
 4 void test()
 5 {
 6         char * s  = "hello world\n";
 7         while(1){
 8                 //int v = 0/0;
 9                 printf("%s\n", s);
10                 sleep(1);
11         }
12 }
13 
14 int main()
15 {
16         test();
17 }
View Code 2. 生成独立的调试符号文件
gcc -ggdb3 test.c -o test 
3. 删除可执行文件内的调试符号
objcopy --only-keep-debug test test.debug strip --strip-debug --strip-unneeded test 
4. 一般情况 在 ELF 文件内埋桩指定符号文件,gdb 就可以在调试的时候自动加载调试符号了:
objcopy --add-gnu-debuglink=test.debug test
在 ELF文件内也可以看到 .gnu_debuglink 段内保存的调试符号文件。

但是,当我们换了调试环境,或者符号文件路径和 ELF 文件内的路径不匹配的时候,就会报符号找不到的错,这时候仍然需要手动处理符号文件。

二、开始gdb调试

未通过 .gnu_debuglink 指定符号文件时,通过 bt 查看进程的调用栈,我们会看到缺失的符号无法解析。gdb 执行 bt 指令时, 会拿地址信息在已知的符号表内搜索最接近的地址。显示问号,就是说 gdb 完全找不到接近的符号,这时候,我们通过 info symbol [address] 指令也是得不到地址对应的符号信息的。   尝试手动加载调试符号文件 gdb 需要知道 [symbol] -> [address] 的映射信息,而 gdb 正好提供了 symbol-file 和 add-symbol-file 指令来手动添加这个信息。因此,我们要做的就是把 .debug 文件内的符号信息告诉 gdb 。先看下 .debug 文件内保存的符号信息:   其中 _start 符号是个特殊的符号,一般是 ELF 文件的入口。通过 readelf -h 命令,我们可以看到 ELF 文件的入口地址与 _start 符号地址是相同的。 该地址也是 readelf -S 输出的 .text 段的地址

很明显,这里的地址和 bt trace 里的地址相差甚远,由于操作系统的保护机制(ASLR) ,ELF 文件并不会直接加载到这个地址,而是会加载到一个随机地址上。因此,我们需要找到这个地址,并把符号表内所有符号的地址都加上这个偏移量(gdb 应该也是可以自动完成这个操作的?)

找到 .text 段加载的位置:

通过 cat /proc/[pid]/maps 我们可以获得进程内存镜像内所有段信息,其中以 ELF 文件名命名并且带有可执行权限的段,就是该 ELF 文件 .text 段加载的地址,这也是我们希望 gdb: info address _start 指令得到的地址。  

gdb 加载调试符号: 

由于我们的目标是让 gdb 解析 .text 段地址为 addr(.text) , 也就是这里的 0x56266ea06000, 而当前符号文件内的 .text 段地址是 addr(elf.entry_point),也就是 0x1060,所以我们偏移量的计算方法是: addr(.text) - addr(elf.entry_point) 添加符号文件指令: symbol-file xxx.debug -o offset 给个调试符号文件,给这个调试文件一个偏移地址,gdb加载符号文件的时候,会自动把符号对应的地址都加上这个偏移量。 ok, 符号解析出来了,查看对应内存上的数据和 ELF文件内的数据是对的上的。   看下 trace,诡异,仍然不对...

detach & attach 后一切正常了,似乎是 gdb 的一个bug,先attach后加载符号文件会有问题。

  如果需要解析 ELF 的其它 section 同理 ... 似乎写个脚本自动处理更好~

三、动态加载的SO添加符号文件:

代码:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include "test.h"
 4 
 5 static void test()
 6 {
 7         char * s  = "hello so\n";
 8         while(1){
 9                 printf("%s\n", s);
10                 sleep(1);
11         }
12 }
13 
14 void test_so()
15 {
16         test();
17 }
View Code
1 #include "test.h"
2 
3 int main()
4 {
5         test_so();
6 }
View Code

编译 & 链接 & strip:

gcc test.c -ggdb3 -shared -fPIC -o libtest.so
gcc test.c -ggdb3 -shared  -o libtest.so
objcopy --only-keep-debug libtest.so libtest.so.debug
strip --strip-debug --strip-unneeded libtest.so 

开始加载调试符号:

我最开始是按照上面主程序的方法,从 /proc/pid/maps 找到动态库加载的地址来计算偏移量的,但是失败了,符号解析的地址和内存内的地址总是差几十个字节。 最后发现,通过 gdb info shared 获得的链接库地址和 maps 内的地址有点不同: 用这个地址计算得到的符号文件偏移量是正确的。 /proc/pid/maps 提示 .text 段在 0x7f931b532000,info shared 提示动态库被加载的地址是 0x7f931b532060,看下这部分内存放的到底是啥。.text 段确实加载到了 0x7f931b532060,那么在这之间的内存放的是什么呢?其实,从名字上也应该猜出来了,应该是链接的时候添加的 plt 相关的处理代码。  

googles 上有好多处理这种自动计算符号文件偏移量的代码,例如 https://stackoverflow.com/questions/20380204/how-to-load-multiple-symbol-files-in-gdb。但是我试了下,在我的环境下都不太可行。这些代码,有的只拿 elf 文件的 .text 段地址来计算,有的只拿进程内的段加载地址,很明显和我的环境都不搭。这个可能是和 gdb 实现的具体逻辑有关?毕竟 gdb 是能够获取这些地址信息并自动帮我们处理的。

总结:

gdb 单独指定符号文件时,原则是要 ELF 文件的 .text 段和进程镜像里的 .text 段加载地址匹配上,需要根据具体环境计算相关的偏移量,不可盲信 google 上的相关代码啊~

参考:

https://naliferopoulos.github.io/ThinkingInBinary/symbolicating-stripped-elf-files-manually  

标签:文件,符号,GDB,test,gdb,地址,调试,加载
From: https://www.cnblogs.com/codingMozart/p/17117698.html

相关文章

  • gdb 调试
    启动调试生成可执行文件gcc-ghello.cpp-ohello启动调试gdbhellogdb交互命令启动gdb后,进入到交互模式,通过以下命令完成对程序的调试;注意高频使用的命令一般都会......
  • 8.8程序加载时会生成栈和堆
        EXE文件的内容分为再配置信息、变量组和函数组,这一点想必大家都清楚了吧。不过,当程序加载到内存后,除此之外还会额外生成两个组,那就是栈和堆。栈是用来存储函数......
  • 027_测试_加载测试专用配置
    MsgConfig.java   ConfigurationTest.java ......
  • 断点调试/认证/权限/频率-源码分析/基于APIView编写分页/异常处理
    内容概要断点调试认证/权限/频率-源码分析基于APIView编写分页异常处理断点调试#程序以debug模式运行,可以在任意位置停下,查看当前情况下变量数据的变化情况#p......
  • 026_测试_加载测试专用属性
        ......
  • 接口调试时如何请求一个需要登录才能访问的接口
    在后台在开发、调试接口时,常常会遇到需要登陆才能请求的接口。比如:获取登陆用户的收藏列表,此时,我们就需要模拟登陆状态进行接口调试了。如图:今天,我们讲解利用Apipost......
  • 调试 Node.js
    调试Node.js调试器调试器是一种软件工具,用于通过分析方法观察和控制程序的执行流设计目标:帮助找出bug的根本原因,并帮助你解决它工作方式:将程序托管在自己的执行进......
  • react 中componentDidMount 只加载一次的解决办法
    项目中遇到bug:componentDidMount只能在刚开始渲染时取值,无法随着父组件中值的改变而改变;   此处的data只能取到页面一开始渲染时的数据,若父组件更新data的值则无法......
  • AJAX动态加载下拉框数据
    1、type表数据2、前端页面现在的想法是点击商品类型下拉框,动态加载所有商品类型利用select标签的id属性3、jQuery代码部分这句放在自执行函数里面loadProductType("/ssm_tes......
  • Mybatis使用注解实现一对一复杂关系映射及延迟加载
    一、问题引入:在加载账户信息时同时加载该账户的用户信息,根据情况可实现延时加载(注解方式实现)数据库字段如下:user表:account表:二、添加User实体类和Account类us......