利用GDB调试ARM代码

在本文中,我们将简要介绍如何利用GDB完成ARM二进制文件的编译和基本调试。当然,阅读过程中,如果读者想要对ARM汇编代码动手进行实验的话,则需要准备一个备用的ARM设备,或者在虚拟机中设置相应的实验室环境,具体操作请参考How-To这篇文章。

此外,您还将通过下面取自 Part 7 – Stack and Functions这篇文章中的代码来熟悉GDB的基本调试功能。

.section .text

.global _start

_start:

push {r11, lr} /* Start of the prologue. Saving Frame Pointer and LR onto the stack */

add r11, sp, #0 /* Setting up the bottom of the stack frame */

sub sp, sp, #16 /* End of the prologue. Allocating some buffer on the stack */

mov r0, #1 /* setting up local variables (a=1). This also serves as setting up the first parameter for the max function */

mov r1, #2 /* setting up local variables (b=2). This also serves as setting up the second parameter for the max function */

bl max /* Calling/branching to function max */

sub sp, r11, #0 /* Start of the epilogue. Readjusting the Stack Pointer */

pop {r11, pc} /* End of the epilogue. Restoring Frame pointer from the stack, jumping to previously saved LR via direct load into PC */

max:

push {r11} /* Start of the prologue. Saving Frame Pointer onto the stack */

add r11, sp, #0 /* Setting up the bottom of the stack frame */

sub sp, sp, #12 /* End of the prologue. Allocating some buffer on the stack */

cmp r0, r1 /* Implementation of if(a

movlt r0, r1 /* if r0 was lower than r1, store r1 into r0 */

add sp, r11, #0 /* Start of the epilogue. Readjusting the Stack Pointer */

pop {r11} /* restoring frame pointer */

bx lr /* End of the epilogue. Jumping back to main via LR register */

就个人而言,我更喜欢使用作为GDB增强版的GEF,它用起来要更加得心应手,具体下载地址https://github.com/hugsy/gef

将上述代码保存到名为max.s的文件中,然后使用以下命令进行编译:

$ as max.s -o max.o

$ ld max.o -o max

这个调试器是一个强大的工具,可以:

在代码崩溃后加载内存dump(事后剖析调试)

附加到正在运行的进程(用于服务器进程)

启动程序并进行调试

根据二进制文件、核心文件或进程ID启动GDB:

附加到一个进程:$ gdb -pid $(pidof

)

调试二进制代码:$ gdb ./file

检查内核(崩溃)文件:$ gdb -c ./core.3243

$ gdb max

如果您安装了GEF,将会显示gef>提示符。

可以通过下列方式获取帮助:

(gdb) h

(gdb) apropos

gef> apropos registers

collect -- Specify one or more data items to be collected at a tracepoint

core-file -- Use FILE as core dump for examining memory and registers

info all-registers -- List of all registers and their contents

info r -- List of integer registers and their contents

info registers -- List of integer registers and their contents

maintenance print cooked-registers -- Print the internal register configuration including cooked values

maintenance print raw-registers -- Print the internal register configuration including raw values

maintenance print registers -- Print the internal register configuration

maintenance print remote-registers -- Print the internal register configuration including each register's

p -- Print value of expression EXP

print -- Print value of expression EXP

registers -- Display full details on one

set may-write-registers -- Set permission to write into registers

set observer -- Set whether gdb controls the inferior in observer mode

show may-write-registers -- Show permission to write into registers

show observer -- Show whether gdb controls the inferior in observer mode

tui reg float -- Display only floating point registers

tui reg general -- Display only general registers

tui reg system -- Display only system registers

断点命令:

break (or just b)

break

break filename:function

break filename:line-number

break *

break +

break –

tbreak (设置一个临时断点)

del (删除编号为x的断点)

delete (删除所有断点)

delete (删除指定编号范围内的断点)

disable/enable (不删除断点,只是启用/禁用它们)

continue (or just c) – (继续执行,直到下一个断点)

continue (继续,但忽略当前断点指定次数。对循环内的断点非常有用)

{C}finish继续,直至函数末尾)

gef> break _start

gef> info break

Num Type Disp Enb Address What

1 breakpoint keep y 0x00008054

breakpoint already hit 1 time

gef> del 1

gef> break *0x0000805c

Breakpoint 2 at 0x805c

gef> break _start

这将删除第一个断点,并在指定的内存地址处设置一个断点。当您运行程序时,它将在这个指定的位置停下来。 如果不删除第一个断点,然后又设置一个断点并运行,则它还是在第一个断点处停下来。

启动和停止:

启动一个程序,从头开始执行

run

r

run

停止程序的运行

kill

退出GDB调试器

quit

q

gef> run

现在,我们的程序在指定的位置停下来了,这样就可以开始检查内存了。 命令“x”可以用来以各种格式显示内存内容。

语法 : x/

格式单位

x – 十六进制 b - 字节

d - 十进制h - 半字(2字节)

i - 指令w - 字(4字节)

t - 二进制(two)g - 巨字(8字节)

o - 八进制

u - 无符号整数

s - 字符串

c - 字符

gef> x/10i $pc

=> 0x8054 : push {r11, lr}

0x8058 : add r11, sp, #0

0x805c : sub sp, sp, #16

0x8060 : mov r0, #1

0x8064 : mov r1, #2

0x8068 : bl 0x8074

0x806c : sub sp, r11, #0

0x8070 : pop {r11, pc}

0x8074 : push {r11}

0x8078 : add r11, sp, #0

gef> x/16xw $pc

0x8068 : 0xeb000001 0xe24bd000 0xe8bd8800 0xe92d0800

0x8078 : 0xe28db000 0xe24dd00c 0xe1500001 0xb1a00001

0x8088 : 0xe28bd000 0xe8bd0800 0xe12fff1e 0x00001741

0x8098: 0x61656100 0x01006962 0x0000000d 0x01080206

用于单步调试的命令:单步执行下一条命令。可以进入函数内部

stepi

s

step

执行下一行代码。不会进入函数内部

nexti

n

next

继续处理,直到达到指定的行号、函数名称、地址、文件名函数或文件名:行号

until

until 、

显示当前行号以及所在的函数

where

gef> nexti 5

...

0x8068 bl 0x8074

0x806c sub sp, r11, #0

0x8070 pop {r11, pc}

0x8074 push {r11}

0x8078 add r11, sp, #0

0x807c sub sp, sp, #12

0x8080 cmp r0, r1

0x8084 movlt r0, r1

0x8088 add sp, r11, #0

使用info registers或i r命令检查寄存器的值

gef> info registers

r0 0x1 1

r1 0x2 2

r2 0x0 0

r3 0x0 0

r4 0x0 0

r5 0x0 0

r6 0x0 0

r7 0x0 0

r8 0x0 0

r9 0x0 0

r10 0x0 0

r11 0xbefff7e8 3204446184

r12 0x0 0

sp 0xbefff7d8 0xbefff7d8

lr 0x0 0

pc 0x8068 0x8068

cpsr 0x10 16

命令“info registers”能够提供当前的寄存器状态。 我们可以看到,这里包括通用寄存器r0-r12,专用寄存器SP、LR和PC,以及状态寄存器CPSR。 函数的前四个参数通常存储在r0-r3中。 在这种情况下,我们可以通过手动方式将其值移动到r0和r1。

显示进程内存映射:

gef> info proc map

process 10225

Mapped address spaces:

Start Addr End Addr Size Offset objfile

0x8000 0x9000 0x1000 0 /home/pi/lab/max

0xb6fff000 0xb7000000 0x1000 0 [sigpage]

0xbefdf000 0xbf000000 0x21000 0 [stack]

0xffff0000 0xffff1000 0x1000 0 [vectors]

通过命令“disassemble”,我们可以查看函数max的反汇编输出。

gef> disassemble max

Dump of assembler code for function max:

0x00008074 : push {r11}

0x00008078 : add r11, sp, #0

0x0000807c : sub sp, sp, #12

0x00008080 : cmp r0, r1

{C} 0x00008084 : movlt r0, r1

0x00008088 : add sp, r11, #0

0x0000808c : pop {r11}

0x00008090 : bx lr

End of assembler dump.

GEF特有的命令(可以使用命令“gef”查看更多命令):

将所有已加载的ELF镜像的所有节dump到进程内存中

X档案

proc map的增强版本,包括映射页面中的RWX属性

vmmap

给定地址的内存属性

xinfo

检查运行的二进制文件内置的编译器级保护措施

checksec

gef> xfiles

Start End Name File

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

0x00008054 0x00008094 .text /home/pi/lab/max

gef> vmmap

Start End Offset Perm Path

0x00008000 0x00009000 0x00000000 r-x /home/pi/lab/max

0xb6fff000 0xb7000000 0x00000000 r-x [sigpage]

0xbefdf000 0xbf000000 0x00000000 rwx [stack]

0xffff0000 0xffff1000 0x00000000 r-x [vectors]

gef> xinfo 0xbefff7e8

----------------------------------------[ xinfo: 0xbefff7e8 ]----------------------------------------

Found 0xbefff7e8

Page: 0xbefdf000 -> 0xbf000000 (size=0x21000)

Permissions: rwx

Pathname: [stack]

Offset (from page): +0x207e8

Inode: 0

gef> checksec

[+] checksec for '/home/pi/lab/max'

Canary: No

NX Support: Yes

PIE Support: No

RPATH: No

RUNPATH: No

Partial RelRO: No

Full RelRO: No

故障排除

为了更高效地使用GDB进行调试,很有必要了解某些分支/跳转的目标地址。 某些(较新的)GDB版本能够解析分支指令的地址,并能显示目标函数的名称。 例如,下面是缺乏这些功能的GDB版本的输出内容:

0x000104f8 : bl 0x10334

0x000104fc : mov r0, #8

0x00010500 : bl 0x1034c

0x00010504 : mov r3, r0

...

而下面则是提供了上述功能的GDB版本的的输出结果:

0x000104f8 : bl 0x10334 free@plt>

0x000104fc : mov r0, #8

0x00010500 : bl 0x1034c

0x00010504 : mov r3, r0

如果您的GDB版本中没有提供这些功能,可以升级Linux(前提是它们提供了更新的GDB),或者自己编译较新的GDB。 如果您选择自己编译GDB,可以使用以下命令:

cd /tmp

wget https://ftp.gnu.org/gnu/gdb/gdb-7.12.tar.gz

tar vxzf gdb-7.12.tar.gz

sudo apt-get update

sudo apt-get install libreadline-dev python-dev texinfo -y

cd gdb-7.12

./configure --prefix=/usr --with-system-readline --with-python && make -j4

sudo make -j4 -C gdb/ install

gdb --version

我使用上面提供的命令来下载、编译和运行Raspbian(jessie)上的GDB,并且没有遇到任何问题。同时,这些命令也将取代以前版本的GDB。如果您不想这样做的话,请跳过以单词install结尾的命令。此外,我在QEMU中模拟Raspbian时也是这样做的,不过这个过程非常耗时,大概需要几个小时,因为模拟环境的资源(CPU)有限。 我使用的GDB版本为7.12,但是你还可以使用更高的版本,为此可以点击此处查看其他版本。