关于该主题的上一篇文章“ strace和GDB的乐趣 ”涵盖了使用这些工具来探索系统并附加到已经在运行的程序中以查看其功能的基础知识。 本文着重自定义调试器,以使体验更加个性化和高效。
当GDB(GNU项目调试器)启动时,它将在当前用户的主目录中查找一个名为.gdbinit的文件; 如果文件存在,则GDB执行文件中的所有命令。 通常,此文件用于简单的配置命令,例如设置所需的默认汇编器格式(Intel®或Motorola)或默认基数以显示输入和输出数据(十进制或十六进制)。 它还可以读取宏编码语言,从而可以进行更强大的自定义。 该语言遵循以下基本格式:
define <command>
<code>
end
document <command>
<help text>
end
该命令称为用户命令 。 所有其他标准GDB命令都可以与流控制指令和传递的参数结合使用,以创建一种语言,使您可以针对要调试的特定应用程序定制调试器的行为。
简单开始:清除屏幕
从简单开始并从那里开始逐步建立总是很好的。 启动xterm,启动您喜欢的编辑器,让我们开始创建一个有用的.gdbinit文件! 调试器的输出可能是混乱的,并且,出于个人喜好,许多人希望在使用任何可能产生混乱的工具时能够清除屏幕。 GDB没有内置的清除屏幕的命令,但是它可以调用Shell函数。 以下代码步骤在调试器外部使用cls
命令清除xterm控制台:
define cls
shell clear
end
document cls
Clears the screen with a simple command.
end
定义的上半部分由define ... end
动词define ... end
,构成了调用命令时执行的代码。
当您键入help cls
时,GDB命令解释器使用定义的下部(由document ... end
界定)来显示与cls
命令关联的帮助文本。
在.gdbinit文件中键入代码后,启动GDB并输入cls
命令。 屏幕将清除,您看到的只是GDB提示。 您进入GDB定制的旅程已经开始!
文件的重要性
如果输入help user
命令,则会看到在.gdbinit文件中输入的所有用户命令的摘要。 .gdbinit用户定义命令的设计者提供了一个重要的功能,在编写自己的命令时不应忽略: document ... end
子句。 随着这些命令数量的增加,维护有关命令如何工作的功能性文档变得至关重要。
您可能已经遇到了问题。 假设您几年前编写了一些代码; 然后,当您重新访问它时(也许是要修复错误或通过添加新功能来对其进行修改),您会发现您很难理解自己的代码。 优秀的程序员习惯养成使代码简短,简单和文档齐全,以便于维护的习惯。
通常,对于编程代码而言,正确的是对调试器代码而言的。 在完成这一职业生涯中最有意义的工作时,记下仔细的笔记和记录良好的代码将对您有好处。
GDB的社区用途
人类通过许多方式学习新事物,包括通过研究他人的所作所为。 崭露头角的汽车工程师首先要打开他们的第一辆汽车的引擎盖,取出工具,然后取下零件进行清洁和研究。 这样的练习可以让他们在学习汽车发动机工作原理的同时保持机器清洁。
崭露头角的计算机科学家没有什么不同,他们只是想看看程序是如何工作的-它们如何与动态库和本机操作系统进行交互。 看看这些事情如何工作的工具是调试器。 计算机编程是一项复杂的活动,通过能够与志趣相投的人进行交流,提出问题并获得答案,新的计算机科学家可以满足他们对知识的需求。
在全球编程社区中,总有很多人渴望知识。 他们不满足于仅在计算机上运行程序-他们想了解更多。 他们想知道这些程序是如何运行的,并且他们喜欢使用为此目的可用的最佳工具来探索系统的功能。 通过逆向工程 ,这是一种通过在调试器下运行程序并了解其工作方式来学习程序工作方式的方法,您可以从正在研究的程序的作者那里学到很多东西。 编程中涉及的许多底层细节均未记录。 了解它们的唯一方法是看到它们的运行。
逆向工程作为一种妖black的艺术而名不虚传,只有黑客和犯罪分子试图破坏复制保护系统并编写蠕虫和病毒来危害计算机世界时,才采用黑科技 。 尽管有这样的人,但使用调试器和逆向工程研究程序的工作原理的绝大多数人是现在和将来的软件工程师,他们希望并且需要知道这些事情是如何工作的。 他们已经建立了在线社区,以分享他们的知识和发现; 阻止这种活动具有破坏性,并阻碍了计算机科学的未来发展。
本文中定义的许多用户功能都来自此类饥饿的知识寻求者社区。 如果您想进一步了解它们,建议您学习本文档“ 相关主题”部分中引用的网站。
断点别名
众所周知,许多GDB命令过于冗长。 即使可以缩写,GDB宏语言也可以进行更大的简化。 诸如info breakpoints
类的命令可以像bpl
一样简单。 清单1显示了很多这样简单且非常有用的断点别名用户命令,它们可以编辑到不断增长的.gdbinit文件中。
清单1:断点别名命令
define bpl
info breakpoints
end
document bpl
List breakpoints
end
define bp
set $SHOW_CONTEXT = 1
break * $arg0
end
document bp
Set a breakpoint on address
Usage: bp addr
end
define bpc
clear $arg0
end
document bpc
Clear breakpoint at function/address
Usage: bpc addr
end
define bpe
enable $arg0
end
document bpe
Enable breakpoint #
Usage: bpe num
end
define bpd
disable $arg0
end
document bpd
Disable breakpoint #
Usage: bpd num
end
define bpt
set $SHOW_CONTEXT = 1
tbreak $arg0
end
document bpt
Set a temporary breakpoint on address
Usage: bpt addr
end
define bpm
set $SHOW_CONTEXT = 1
awatch $arg0
end
document bpm
Set a read/write breakpoint on address
Usage: bpm addr
end
一旦习惯了使用断点别名命令,调试会话就会变得更加有意义。 这些命令极大地提高了调试器的效率,因为您可以用更少的精力完成更多的工作。
显示过程信息
GDB用户定义的命令可以被其他用户定义的命令调用,从而使它们的有效性更高。 这是编程语言的增量性质-编写逐渐被较高级别的函数调用的较低级别的函数,直到这些工具以最小的努力方便地完成您希望它们执行的任务。 合并到.gdbinit文件中的下一组GDB定义在被调用时将显示有用的过程信息,如清单2所示。
清单2:流程信息命令
define argv
show args
end
document argv
Print program arguments
end
define stack
info stack
end
document stack
Print call stack
end
define frame
info frame
info args
info locals
end
document frame
Print stack frame
end
define flags
if (($eflags >> 0xB) & 1 )
printf "O "
else
printf "o "
end
if (($eflags >> 0xA) & 1 )
printf "D "
else
printf "d "
end
if (($eflags >> 9) & 1 )
printf "I "
else
printf "i "
end
if (($eflags >> 8) & 1 )
printf "T "
else
printf "t "
end
if (($eflags >> 7) & 1 )
printf "S "
else
printf "s "
end
if (($eflags >> 6) & 1 )
printf "Z "
else
printf "z "
end
if (($eflags >> 4) & 1 )
printf "A "
else
printf "a "
end
if (($eflags >> 2) & 1 )
printf "P "
else
printf "p "
end
if ($eflags & 1)
printf "C "
else
printf "c "
end
printf "\n"
end
document flags
Print flags register
end
define eflags
printf " OF <%d> DF <%d> IF <%d> TF <%d>",\
(($eflags >> 0xB) & 1 ), (($eflags >> 0xA) & 1 ), \
(($eflags >> 9) & 1 ), (($eflags >> 8) & 1 )
printf " SF <%d> ZF <%d> AF <%d> PF <%d> CF <%d>\n",\
(($eflags >> 7) & 1 ), (($eflags >> 6) & 1 ),\
(($eflags >> 4) & 1 ), (($eflags >> 2) & 1 ), ($eflags & 1)
printf " ID <%d> VIP <%d> VIF <%d> AC <%d>",\
(($eflags >> 0x15) & 1 ), (($eflags >> 0x14) & 1 ), \
(($eflags >> 0x13) & 1 ), (($eflags >> 0x12) & 1 )
printf " VM <%d> RF <%d> NT <%d> IOPL <%d>\n",\
(($eflags >> 0x11) & 1 ), (($eflags >> 0x10) & 1 ),\
(($eflags >> 0xE) & 1 ), (($eflags >> 0xC) & 3 )
end
document eflags
Print entire eflags register
end
define reg
printf " eax:%08X ebx:%08X ecx:%08X ", $eax, $ebx, $ecx
printf " edx:%08X eflags:%08X\n", $edx, $eflags
printf " esi:%08X edi:%08X esp:%08X ", $esi, $edi, $esp
printf " ebp:%08X eip:%08X\n", $ebp, $eip
printf " cs:%04X ds:%04X es:%04X", $cs, $ds, $es
printf " fs:%04X gs:%04X ss:%04X ", $fs, $gs, $ss
flags
end
document reg
Print CPU registers
end
define func
info functions
end
document func
Print functions in target
end
define var
info variables
end
document var
Print variables (symbols) in target
end
define lib
info sharedlibrary
end
document lib
Print shared libraries linked to target
end
define sig
info signals
end
document sig
Print signal actions for target
end
define thread
info threads
end
document thread
Print threads in target
end
define u
info udot
end
document u
Print kernel 'user' struct for target
end
define dis
disassemble $arg0
end
document dis
Disassemble address
Usage: dis addr
end
十六进制和ASCII转储命令
合并到.gdbinit文件中的下一组定义包括增强的十六进制和ASCII转储函数,如清单3所示。 程序员请注意:如果您要创建出色的软件,则可以添加对宏进行编程的功能,从而使您的用户社区能够根据自己的喜好来增强您的工具。 GDB是很棒的软件!
清单3:十六进制和ASCII转储命令
define ascii_char
set $_c=*(unsigned char *)($arg0)
if ( $_c < 0x20 || $_c > 0x7E )
printf "."
else
printf "%c", $_c
end
end
document ascii_char
Print the ASCII value of arg0 or '.' if value is unprintable
end
define hex_quad
printf "%02X %02X %02X %02X %02X %02X %02X %02X", \
*(unsigned char*)($arg0), *(unsigned char*)($arg0 + 1), \
*(unsigned char*)($arg0 + 2), *(unsigned char*)($arg0 + 3), \
*(unsigned char*)($arg0 + 4), *(unsigned char*)($arg0 + 5), \
*(unsigned char*)($arg0 + 6), *(unsigned char*)($arg0 + 7)
end
document hex_quad
Print eight hexadecimal bytes starting at arg0
end
define hexdump
printf "%08X : ", $arg0
hex_quad $arg0
printf " - "
hex_quad ($arg0+8)
printf " "
ascii_char ($arg0)
ascii_char ($arg0+1)
ascii_char ($arg0+2)
ascii_char ($arg0+3)
ascii_char ($arg0+4)
ascii_char ($arg0+5)
ascii_char ($arg0+6)
ascii_char ($arg0+7)
ascii_char ($arg0+8)
ascii_char ($arg0+9)
ascii_char ($arg0+0xA)
ascii_char ($arg0+0xB)
ascii_char ($arg0+0xC)
ascii_char ($arg0+0xD)
ascii_char ($arg0+0xE)
ascii_char ($arg0+0xF)
printf "\n"
end
document hexdump
Display a 16-byte hex/ASCII dump of arg0
end
define ddump
printf "[%04X:%08X]------------------------", $ds, $data_addr
printf "---------------------------------[ data]\n"
set $_count=0
while ( $_count < $arg0 )
set $_i=($_count*0x10)
hexdump ($data_addr+$_i)
set $_count++
end
end
document ddump
Display $arg0 lines of hexdump for address $data_addr
end
define dd
if ( ($arg0 & 0x40000000) || ($arg0 & 0x08000000) || ($arg0 & 0xBF000000) )
set $data_addr=$arg0
ddump 0x10
else
printf "Invalid address: %08X\n", $arg0
end
end
document dd
Display 16 lines of a hex dump for $arg0
end
define datawin
if ( ($esi & 0x40000000) || ($esi & 0x08000000) || ($esi & 0xBF000000) )
set $data_addr=$esi
else
if ( ($edi & 0x40000000) || ($edi & 0x08000000) || ($edi & 0xBF000000) )
set $data_addr=$edi
else
if ( ($eax & 0x40000000) || ($eax & 0x08000000) || \
($eax & 0xBF000000) )
set $data_addr=$eax
else
set $data_addr=$esp
end
end
end
ddump 2
end
document datawin
Display esi, edi, eax, or esp in the data window
end
处理上下文命令
最后,在调试正在运行的流程时,通常需要获得流程上下文的整体视图。 清单4中有用的流程上下文命令是通过使用先前定义的数据转储函数构建的。
清单4:流程上下文命令
define context
printf "_______________________________________"
printf "________________________________________\n"
reg
printf "[%04X:%08X]------------------------", $ss, $esp
printf "---------------------------------[stack]\n"
hexdump $sp+0x30
hexdump $sp+0x20
hexdump $sp+0x10
hexdump $sp
datawin
printf "[%04X:%08X]------------------------", $cs, $eip
printf "---------------------------------[ code]\n"
x /6i $pc
printf "---------------------------------------"
printf "---------------------------------------\n"
end
document context
Print regs, stack, ds:esi, and disassemble cs:eip
end
define context-on
set $SHOW_CONTEXT = 1
end
document context-on
Enable display of context on every program stop
end
define context-off
set $SHOW_CONTEXT = 1
end
document context-on
Disable display of context on every program stop
end
# Calls "context" at every breakpoint.
define hook-stop
context
end
# Init parameters
set output-radix 0x10
set input-radix 0x10
set disassembly-flavor intel
hook-stop
是GDB在每个断点事件处调用的特殊定义。 在这种情况下,将生成context
列表,以便您可以清楚地看到处理器正在执行的每条指令的效果。
具有新功能的调试会话
让我们尝试一下新的工具集,看看它们在调试老朋友时的工作方式,这是IBM developerWorks撰稿人Nigel Griffiths编写的nweb服务器代码。 (请参阅相关的主题部分的链接,Nigel的文章“NWEB:一个微小的,安全的Web服务器(只有静态页面)。”)
将es-nweb.zip文件下载到$ HOME / downloads目录后,键入以下命令以提取,编译和启动nweb。 (请注意,这是假定您正在将程序编译到以Intel Pentium作为中央处理单元(CPU)的Linux®工作站上-.gdbinit代码是为Intel Pentium型处理器编写的,并且仅兼容。)
$ cd src
$ mkdir nweb
$ cd nweb
$ unzip $HOME/downloads/es-nweb.zip
$ gcc -ggdb -O -DLINUX nweb.c -o nweb
$ ./nweb 9090 $HOME/src/nweb &
注意:此示例中的-ggdb
选项与Nigel的文章不同之处在于,它告诉GNU编译器集合(GCC)优化程序,以便使用GDB进行调试。
接下来,要验证nweb服务器是否正在运行,请使用ps
命令对其进行检查:
$ ps
PID TTY TIME CMD
2913 pts/5 00:00:00 bash
4009 pts/5 00:00:00 nweb
4011 pts/5 00:00:00 ps
最后,在计算机上启动Web浏览器,然后在地址栏中键入http:// localhost:9090。
接下来,启动GDB,并像以前一样,将其附加到当前正在运行的nweb实例,如清单5所示。
清单5:启动GDB
$ gdb --quiet
(gdb) attach 4009
Attaching to process 4009
Reading symbols from /home/bill/src/nweb/nweb...done.
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
_______________________________________________________________________________
eax:FFFFFE00 ebx:00000005 ecx:BFFFF680 edx:00000001 eflags:00000246
esi:00000005 edi:00000000 esp:BFFFF66C ebp:BFFFF6A8 eip:FFFFE410
cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s Z a P c
[007B:BFFFF66C]---------------------------------------------------------[stack]
BFFFF69C : 14 0A 13 42 60 53 01 40 - 24 8F 04 08 C8 F6 FF BF ...B`S.@$.......
BFFFF68C : A6 8E 04 08 14 0A 13 42 - 70 C6 00 40 10 00 00 00 .......Bp..@....
BFFFF67C : 82 8E 04 08 00 00 00 00 - C4 C6 04 08 98 F6 FF BF ................
BFFFF66C : A8 F6 FF BF 01 00 00 00 - 80 F6 FF BF 81 EA 0D 42 ...............B
[007B:FFFFFE00]---------------------------------------------------------[ data]
FFFFFE00 : Error while running hook_stop:
Cannot access memory at address 0xfffffe00
0xffffe410 in ?? ()
(gdb)
-quiet
选项告诉GDB调试器仅显示其提示,而不显示通常显示的所有其他启动信息。 如果需要其他文本,请关闭-quiet
选项。
attach 4009
命令开始调试当前正在运行的nweb服务器,GDB调试器通过读取有关它可以运行的进程的所有符号信息进行响应。
您会注意到context
代码已经运行并显示了有关当前进程的许多有用信息,但是它无法访问数据段中的内存。 这不是一个严重的问题,应该忽略。 有时,保护模式处理器的保护方案无法让您看到所有可能想要看到的内容。 在这种情况下,这并不重要。
接下来,使用info
命令列出有关您正在探索的程序的信息(请参见清单6 )。
清单6:info命令列出程序信息
(gdb) info proc
process 4009
cmdline = './nweb'
cwd = '/home/bill/src/nweb'
exe = '/home/bill/src/nweb/nweb'
(gdb)
现场观看
因为你看一个实际运行的程序,你可以设置断点,然后看看该程序,因为它回复到浏览器的请求和传输.html和.jpg文件到提出请求的浏览器。 清单7显示了如何做到这一点。
清单7:设置断点
(gdb) b 188
Breakpoint 1 at 0x8048e70: file nweb.c, line 188.
(gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>continue
>end
(gdb) c
Continuing.
此时,GDB调试器设置为在nweb服务器接受浏览器请求的那一行中断。 调试器将仅显示请求并继续处理其他请求,而不会中断正在运行的程序。 几次在浏览器中刷新http://localhost:9090/
页面,并观看GDB调试器显示断点并继续运行。
在刷新浏览器页面时,您应该看到断点信息,如清单8所示,在GDB Debugger xterm中滚动。 您可以通过按Ctrl + C停止在nweb服务器中进行调试。 停止跟踪之后,可以通过键入quit
命令退出GDB调试器。
清单8:GDB Debugger xterm中的断点信息
_______________________________________________________________________________
eax:00000000 ebx:00000001 ecx:00000000 edx:00000001 eflags:00000206
esi:00000006 edi:00000000 esp:BFFFF690 ebp:BFFFF6A8 eip:08048E70
cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s z a P c
[007B:BFFFF690]---------------------------------------------------------[stack]
BFFFF6C0 : 03 00 00 00 D4 86 04 08 - 00 00 00 00 F5 86 04 08 ................
BFFFF6B0 : 03 00 00 00 F4 F6 FF BF - 04 F7 FF BF 2C 58 01 40 ............,X.@
BFFFF6A0 : 60 53 01 40 24 8F 04 08 - C8 F6 FF BF 04 55 01 42 `S.@$........U.B
BFFFF690 : 14 0A 13 42 70 C6 00 40 - 10 00 00 00 14 0A 13 42 ...Bp..@.......B
[007B:BFFFF690]---------------------------------------------------------[ data]
BFFFF690 : 14 0A 13 42 70 C6 00 40 - 10 00 00 00 14 0A 13 42 ...Bp..@.......B
BFFFF6A0 : 60 53 01 40 24 8F 04 08 - C8 F6 FF BF 04 55 01 42 `S.@$........U.B
[0073:08048E70]---------------------------------------------------------[ code]
0x8048e70 <main+718>: sub esp,0x4
0x8048e73 <main+721>: lea eax,[ebp-16]
0x8048e76 <main+724>: push eax
0x8048e77 <main+725>: push 0x804c6c4
0x8048e7c <main+730>: push edi
0x8048e7d <main+731>: call 0x80485e4 <accept>
------------------------------------------------------------------------------
Breakpoint 1, main (argc=3, argv=0x1) at nweb.c:188
188 if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)
Program received signal SIGINT, Interrupt.
0xffffe410 in ?? ()
(gdb) quit
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /home/bill/src/nweb/nweb, process 4009
$
如您所见, context
函数显示的信息比使用默认GDB hook_stop
向量通常看到的信息要详细得多。 (您还将注意到,现在您也可以访问数据段。)借助这些GDB增强功能,您可以在每次到达断点时以及执行的每个步骤操作中看到CPU的确切状态。 逐步执行每个命令并观察寄存器和内存值如何受到影响,这也是学习英特尔机器语言命令的基础的好方法。
就像所有程序一样,.gdbinit文件中的代码为增强和改进提供了无穷的机会。 一切都还没有结束! 强烈建议您使用此处描述的命令,并为不断增长的.gdbinit自定义集添加更多命令。 在探索和使用这些工具时,请与更广泛的社区共享这些工具,以便每个人都掌握知识。
结论
更多的人应该深入研究编程工具的工作方式,摆脱困境,并为寻求此类知识的人们的整个社区做出贡献。 访问这些在线社区中的一些,甚至考虑组建自己的社区,以便将来的技术创新能更快地实现。
翻译自: https://www.ibm.com/developerworks/aix/library/au-gdb.html