GNU Project Debugger:GDB带来更多乐趣

    技术2024-06-14  86

    关于该主题的上一篇文章“ 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

    Processed: 0.011, SQL: 9