Linux调试器实战解读

©   老男孩老师    /    2017-10-13



不管是正在学习Linux系统或者是已经从事系统开发一段时间,很多同学咨询了我关于Linux调试器的实战部分,今天整理了一下,希望能对大家有帮助!

远程调试

远程调试对于嵌入式系统或对不同环境进行调试非常有用。它还在高级调试器操作和与操作系统和硬件的交互之间设置了一个很好的分界线。事实上,像 GDB 和 LLDB 这样的调试器即使在调试本地程序时也可以作为远程调试器运行。一般架构是这样的:



调试器是我们通过命令行交互的组件。也许如果你使用的是 IDE,那么在其上有另一个层可以通过机器接口与调试器进行通信。在目标机器上(可能与本机一样)有一个调试存根 ,理论上它是一个非常小的操作系统调试库的包装程序,它执行所有的低级调试任务,如在地址上设置断点。我说“在理论上”,因为如今调试存根变得越来越大。例如,我机器上的 LLDB 调试存根大小是 7.6MB。调试存根通过使用一些特定于操作系统的功能(在我们的例子中是 ptrace)和被调试进程以及通过远程协议的调试器通信。

最常见的远程调试协议是 GDB 远程协议。这是一种基于文本的数据包格式,用于在调试器和调试存根之间传递命令和信息。我不会详细介绍它,但你可以在这里进一步阅读。如果你启动 LLDB 并执行命令 log enable gdb-remote packets,那么你将获得通过远程协议发送的所有数据包的跟踪信息。在 GDB 上,你可以用 set remotelogfile做同样的事情。

作为一个简单的例子,这是设置断点的数据包:


    $Z0,400570,1#43

$ 标记数据包的开始。Z0 是插入内存断点的命令。400570 和 1 是参数,其中前者是设置断点的地址,后者是特定目标的断点类型说明符。最后,#43 是校验值,以确保数据没有损坏。

GDB 远程协议非常易于扩展自定义数据包,这对于实现平台或语言特定的功能非常有用。
共享库和动态加载支持

调试器需要知道被调试程序加载了哪些共享库,以便它可以设置断点、获取源代码级别的信息和符号等。除查找被动态链接的库之外,调试器还必须跟踪在运行时通过 dlopen 加载的库。为了达到这个目的,动态链接器维护一个 交汇结构体。该结构体维护共享库描述符的链表,以及一个指向每当更新链表时调用的函数的指针。这个结构存储在 ELF 文件的 .dynamic 段中,在程序执行之前被初始化。

一个简单的跟踪算法:

追踪程序在 ELF 头中查找程序的入口(或者可以使用存储在 /proc//aux 中的辅助向量)。
追踪程序在程序的入口处设置一个断点,并开始执行。
当到达断点时,通过在 ELF 文件中查找 .dynamic 的加载地址找到交汇结构体的地址。
检查交汇结构体以获取当前加载的库的列表。
链接器更新函数上设置断点。
每当到达断点时,列表都会更新。
追踪程序无限循环,继续执行程序并等待信号,直到追踪程序信号退出。



本文章版权归老男孩教育所有。转载请注明出处。有任何疑问可以随时给我们网站留言。
原文链接:http://www.oldboyedu.com/tuijian_wenzhang/index/id/121.html

(0)

分享至