当前位置: 首页 > 科技观察

5个鲜为人知的GNU调试器(GDB)技巧

时间:2023-03-13 13:21:42 科技观察

了解如何使用gdb的一些鲜为人知的功能来检查和修复您的代码。GNU调试器(gdb)是一个非常有用的工具,用于在开发程序时检查正在运行的进程和解决问题。您可以在特定位置(按函数名称、行号等)设置断点,启用和禁用这些断点,显示和更改变量值,以及执行调试器期望执行的所有标准操作。但它还有许多您可能没有尝试过的其他功能。这里有五个你可以试试。条件断点设置断点是学习使用GNU调试器的第一步。程序在遇到断点时停止,您可以运行gdb命令来检查它或更改变量,然后再允许程序继续。例如,您可能知道经常调用的函数有时会崩溃,但只有在它获得某个参数值时才会崩溃。您可以在函数的开头设置断点并运行程序。每次下断点都会显示函数参数,如果没有提供触发崩溃的参数值,则可以继续,直到再次调用该函数。当这个麻烦的参数触发崩溃时,您可以单步执行代码以查看哪里出了问题。(gdb)breaksometimes_crashesBreakpoint1at0x40110e:fileprog.c,line5.(gdb)run[...]Breakpoint1,sometimes_crashes(f=0x7fffffffd1bc)atprog.c:55fprintf(stderr,(gdb)continueBreakpoint1,sometimes_crashes(f=0x7fffffffd1bc)atprog.c:55fprintf(stderr,(gdb)continue为了使这个更可重现,你可以计算函数在你感兴趣的特定调用之前被调用了多少次,并设置一个计数器在那个断点处(例如,继续30让它在接下来的29次到达断点时忽略它)。但是断点的真正力量在于它们在运行时评估表达式的能力,它允许您自动化这种测试。break[LOCATION]ifCONDITION(gdb)breaksometimes_crashesif!fBreakpoint1at0x401132:文件prog.c,line5.(gdb)run[...]Breakpoint1,sometimes_crashes(f=0x0)atprog.c:55fprintf(stderr,(gdb)条件断点让你避免每次调用函数时gdb都问你要做什么,而是让条件断点让gdb停在该位置。如果执行到条件断点的位置,但是表达式计算结果为false,调试器会自动使程序继续运行而不询问用户要做什么。断点命令更加复杂。复杂的是能够编写脚本以响应遇到断点。断点命令让您可以编写一系列到达该断点时运行的GNU调试器命令的数量。我们可以使用它来规避已知错误,并使它在提供空指针时从该函数返回无害。我们可以使用silent作为第一行以获得更多控制在你之上输出。否则,每次命中断点,即使在断点运行点命令之前,也会显示栈帧。(gdb)breaksometimes_crashesBreakpoint1at0x401132:fileprog.c,line5.(gdb)commands1Typecommandsforbreakpoint(s)1,oneline.Endwithalinesayingjust"end".>silent>if!f>frame>printf"Skippingcall\n">return0>continue>end>printf"Continuing\n">continue>end(gdb)runStarting程序:/home/twaugh/Documents/GDB/progwarning:Loadablesection".note.gnu.property"在ELF段之外ContinuingContinuingContinuing#0sometimes_crashes(f=0x0)atprog.c:55fprintf(stderr,Skippingcall[Inferior1(process9373)exitednormally](gdb)dumpbinarymemoryGNUdebuggerbuiltinThex命令支持检查各种格式的内存,包括八进制、十六进制等。但我喜欢并排查看两种格式:左边是十六进制字节,右边是相同字节表示的ASCII字符……我经常使用hexdump-C(hexdump来自util-linux包)当我想逐字节查看文件的内容时。ThisisthehexadecimalbytedisplayedbytheXcommandofGDB:(GDB)X/33XBmydata0x404040:0x020x000x000x000x000x000x010x404048:0x474000x40x740x740x740x40x44440x740x40x40x40x40x440x40x440x40x440x40x40x440x40x440x40x440x40x440x40x440x440x40x4440x40x40x40x40x440x70x0x0x0x0x0x0x0x0x0x0x-.+16>:0x690x620x750x740x650x730x2d0x630x404058:0x680x610x720x730x650x750x000x050x404060:0xddb00如果你想要像hextoug00这样的内存怎么办?这没关系,事实上,您可以将此方法用于您喜欢的任何格式。通过使用dump命令在文件中存储字节,结合shell命令在文件上运行hexdump,以及define命令,我们可以创建自己的新hexdump命令来使用hexdump显示内存内容。(gdb)定义用于定义“hexdump”的hexdumpType命令。以仅表示“end”的行结尾。>dumpbinarymemory/tmp/dump.bin$arg0$arg0+$arg1>shellhexdump-C/tmp/dump.bin>end这些命令甚至可以放在~/.gdbinit文件中以永久定义hexdump命令。这是它如何工作的一个例子:(gdb)hexdumpmydatasizeof(mydata)0000000002010002000000010147001261747472|.........G..attr|000000106962757465732d636861727365750005|ibutes-charseu..|0000002000|.|00000021内联反汇编有时候你想知道更多关于导致崩溃的原因比源代码就足够了。您想要查看在CPU指令级别发生了什么。反汇编命令可让您查看实现功能的CPU指令。但有时很难跟踪输出。通常,我想查看与函数源代码的特定部分相对应的指令。为此,请使用/s修饰符在反汇编中包含源代码行。(gdb)disassemble/smainDump函数main:prog.c:11{0x00000000000401158<+0>:push%rbp0x0000000000401159<+1>:mov%rsp,%rbp0x0000000000404>1015c<+,%rsp12intn=0;0x0000000000401160<+8>:movl$0x0,-0x4(%rbp)13sometimes_crashes(&n);0x0000000000401167<+15>:lea-0x4(%rbp),%rax+0x0000101000>:mov%rax,%rdi0x000000000040116e<+22>:callq0x401126[...snipped...]这里,使用info寄存器查看所有CPU寄存器的当前值,使用stepi等命令执行一条指令可以让你对程序有更详细的了解。逆向调试有时您希望时光倒流。想象一下,您已经到达变量的观察点。观察点类似于断点,但不是设置在程序中的某处,而是设置在表达式上(使用watch命令)。每当表达式的值发生变化时,执行将停止并且调试器将获得控制权。假设您已到达此观察点,并且该变量使用的内存已更改值。事实证明,这可能是由之前发生的事情引起的。例如,内存被释放,现在被重新使用。但它是何时何地发布的?GNU调试器甚至可以解决这个问题,因为你可以反向运行程序!它通过仔细记录程序在每个步骤的状态来实现这一点,以便可以恢复以前记录的状态,从而产生时光倒流的错觉。要启用此状态记录,请使用targetrecord-full命令。然后,你可以使用一些听起来不太可行的命令,例如:reverse-step,倒退到上一个源代码行*reverse-next,倒退到上一个源代码行,并向后跳过函数调用reverse-finish,倒退到当前函数即将??被调用的点reverse-continue,它返回到程序中的前一个状态,该状态将(现在)触发断点(或断点停止的任何状态)这是反向调试动作示例:(gdb)bmainBreakpoint1at0x401160:文件prog.c,第12行。(gdb)rStarting程序:/home/twaugh/Documents/GDB/prog[...]Breakpoint1,main()atprog。c:1212intn=0;(gdb)targetrecord-full(gdb)cContinuing.ProgramreceivedsignalSIGSEGV,Segmentationfault.0x00000000000401154insometimes_crashes(f=0x0)atprog.c:77return*f;(gdb)反转-finishRun返回sometimes_crashes(f=0x0)atprog.c:70x0000000000401190inmain()atprog.c:1616sometimes_crashes(0)中的#00x0000000000401154调用;这些只是GNU调试器可以做的一些有用的事情。还有更多有待发现。