导语:Troll支持以C编程语言编写的源代码程序的源代码级调试,并以ELF格式编译为可执行文件,其中也包含DWARF调试格式信息。

u=1248844111,3149000020&fm=23&gp=0.jpg

Troll是用于ARM Cortex-M系统的C语言源代码级调试器,可通过高端的blackmagic硬件调试探针进行访问,并且还可以使用blackmagic的定制工具vx / blackstrike。目前Troll只支持以C编程语言编写的源代码程序的源代码级调试,并以ELF格式编译为可执行文件,其中也包含DWARF调试格式信息。

在此请大家注意,Troll是调试器的一个非常特殊的例子:

1. 它只针对基于ARM Cortex-M的系统

2. 源代码调试仅支持C语言

3. 只支持blackmagic和blackstrike硬件调试探针

4. Trevroll本身是用C++编写的

5. Troll本身严重依赖于Qt框架

以下是Troll的图形用户界面截图,截至2017.2.28:

1.png

下面是上面截图的注释版本,显示了Troll的各个部分的含义:

2.png

以下是一个简化的,更具有实战性质的日常使用的屏幕截图,截至2017. 2.28 :

3.png

测试Troll

对Troll的测试是非常简单和容易的,为了方便调试和评估,我会在静态测试驱动模式对Troll进行测试。在这种模式下,我没有建立与实时目标系统的连接,而是让Troll从文件读取目标内存和寄存器的内容。这样,Troll就不会调用任何其他依赖的外部工具,测试所需的所有数据都将从具有硬编码名称的文件中获取。这种静态测试驱动器的操作方式主要是对Troll进行快速原型设计和调试,以及展示其各个功能。

让我首先从github把它的源代码复制过来,并初始化和更新子模块。

git clone https://github.com/stoyan-shopov/troll.gitcd troll
git submodule init
git submodule update

接下来,就是构建Troll。由于Troll利用的是Qt框架,所以我更喜欢在Qt Creator IDE中构建Troll,但也可以直接使用qmake和make实用程序来构建它。

最后,从以下这个链接可以获取一个静态测试驱动的测试文件集:

https://github.com/stoyan-shopov/troll-test-drive-snapshots

在troll的工作目录中提取troll-test-drive-files目录。

现在测试准备都已经设置好了,就让我开始运行Troll吧!

Troll的运行原理

截至目前,Troll已经断断续续地发展了大约六个月,截至目前Troll项目还处于实验阶段,非常不成熟。由于该代码目前看起来非常具有攻击性,且代码很小,所以我认为它看起来很有发展潜力。一般的Troll代码只有两个源代码文件,即troll.cxx和libtroll.hxx。文件troll.cxx中的代码是调试器的前端,它能驱动图形用户界面并与正在调试的目标设备进行通信,troll.cxx文件大致包含1500行代码。文件libtroll.hxx中的代码负责对DWARF调试信息进行解码,并为前端提供易于使用的信息,例如如何展开目标调用栈、数据对象的数据类型、数据对象所在的位置生成的机器代码的目标、生成的地址或地址对应于源代码中的哪一行(这对于在断点上的操作是有用的)、子程序中的局部变量是多少等等。该文件目前大约有2400行代码。这样算起来,troll.调试器前端(在文件troll.cxx中)的代码和调试信息处理引擎(在libtroll.hxx文件中)总共大约有4000行代码。

尽管在许多方面的功能还是不完整的,但这4000行代码当前还是提供了以下这些调试功能:

1. 目标调用栈的展开,包括从中断服务程序中进行的堆栈展开,

2. 结构化的数据显示, 数组显示,数据结构分层显示,支持位字段以及将C语言枚举器的值解码为符号名称

3. 会在正在调试的程序中显示当前源代码位置,并可选择性地拆卸源代码

4. 提供源代码程序中的子程序(C函数),全局和本地(子程序)数据对象的列表,以及源代码映射到子程序和数据对象的定义

5. 目标执行控制,对断点进行设置或清除,步进指令,恢复、暂停和重新启动目标执行,

6. 目标闪存编程和验证

troll的代码需要不断地增长,才能完全顺利地支持上述所有功能,但是如何利用这4000行代码来支持上面描述的这些功能呢?

为了让troll的代码尽可能保持简洁,所以在实现以上那些功能时要注意:

1.  troll不会尝试支持除ARM Cortex-M以外的任何目标架构,以及除C语言之外的任何目标源代码语言,此外,troll以C ++编写,并大量使用Qt框架,以及Qt框架提供的功能

2. 在正常情况下,调试器会利用一些库来提供对编译器生成的可执行文件的结构化访问,例如,以ELF文件格式。除此之外,调试器还会访问调试信息部分和驻留在目标中生成的机器代码。例如,libbfd库、二进制文件描述符库或libelf库。由于troll不使用这样的库,所以为了在ELF文件格式的编译器生成的可执行文件中找到DWARF调试信息部分,troll将简单地执行objdump实用程序,以显示调试信息部分的位置,然后解析objdump的函数输出,并最终使用objdump实用程序提供的信息来简单地读取调试信息部分(通过简单的文件访问操作)。利用几行C ++代码,而不是整合和利用库,如libbfd / libelf库。此外,为了提取驻留在目标中的机器代码,troll会执行objcopy实用程序,以生成ELF可执行文件中的s-record文件(通常用于编程目标芯片)。解析s-record文件非常简单,只需要几行C ++代码,并且不需要使用库,如libbfd / libelf。

3. 同样,troll不使用反汇编库,例如,libopcodes库,而是运行objdump实用程序,以生成目标可执行ELF文件的反汇编列表,最后解析出反汇编输出,为此输出构建一个索引,并使用索引为目标地址提供反汇编文本。这种方法非常简单,只需几行源代码即可实现,避免了对外部库进行重组。

4. 存在访问DWARF调试信息的高级通用库,如libdwarf库。troll的一个关键设计就是不使用这样的库,特别是libdwarf库。troll是我第三个尝试编写的调试器,所以我知道,当创建一个功能非常有限的专用调试器,如troll 时,解码和处理DWARF调试信息就会变得更简单,更有效率,例如:

(1)在现在的程序中,可以对调用栈进行追溯,

(2)显示本地和全局变量的位置,

(3)变量的数据类型是什么, 

(4)目标设备地址对应于源代码中的哪一行,以便可以在那里设置一个断点,

(5)通过目标设备地址所对应的源代码文件,显示出源代码的反汇编。

通用库(如libdwarf)很难提供这样的功能,因为它们只能提供对DWARF调试信息的相对较低级别的访问。

总而言之,就是不要使用通用库来通过troll访问DWARF调试信息,因为这会使troll代码更简单、更小、更直接、更高效、更容易维护、理解和修改。

5.  FORTH,这一步实现起来,需要非常复杂的技术,因为由编译器生成的设备代码程序中的变量的位置以及如何展开目标调用栈都可能是复杂的。在DWARF中,通过定义一个虚拟的,面向堆栈的设备以及为该设备定义的一组指令来解决这个问题。这实质上是FORTH语言。在troll中,数据对象位置的计算和调用栈unwind都会被写入FORTH。

源链接

Hacking more

...