2.64 GDB启动被调试进程时如何尽早断下 https://scz.617.cn/unix/201901161404.txt Q: windbg有个"sxe cpr",其命中时ntdll.dll尚未映射,PEB已经存在,但无法以符号 形式访问PEB,只能用数字偏移。 gdb欲达类似效果,以run方式启动被调试进程时想尽早断下,如何做? A: Zach Riggle & Ruslan 2015-04-22 $ file -b /bin/true ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=702a162597a95a7f39dd315313bed8cf1a5c50d2, stripped $ readelf -h /bin/true ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) // 不是常见的"EXEC (Executable file)" Machine: Intel 80386 Version: 0x1 Entry point address: 0x13e7 // 该值不直接等于将来的虚拟地址 Start of program headers: 52 (bytes into file) Start of section headers: 33372 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 11 Size of section headers: 40 (bytes) Number of section headers: 29 Section header string table index: 28 $ gdb -q -nx -x gdbinit.txt /bin/true (gdb) source ShellPipeCommand.py (gdb) sp info files | grep "Entry point" Entry point: 0x13e7 对于这种PIE的ELF,直接"tb *0x13e7"并不会断在"Entry point"。 有个邪门办法,针对地址0设断: (gdb) tb *0 Temporary breakpoint 1 at 0x0 (gdb) run Starting program: /bin/true Warning: Cannot insert breakpoint 1. Cannot access memory at address 0x0 上述断点实际上无法设置成功,但它有个边际效应,gdb出现错误提示时,目标进程 空间已经创建: (gdb) x/5i $pc => 0xb7fd70b0: mov eax,esp 0xb7fd70b2: sub esp,0xc 0xb7fd70b5: push eax 0xb7fd70b6: call 0xb7fd7d90 0xb7fd70bb: add esp,0x10 (gdb) info sharedlibrary No shared libraries loaded at this time. 尚无动态库被加载。 (gdb) info proc mappings process 9232 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /bin/true 0x401000 0x405000 0x4000 0x1000 /bin/true 0x405000 0x408000 0x3000 0x5000 /bin/true 0x408000 0x40a000 0x2000 0x7000 /bin/true 0xb7fd1000 0xb7fd4000 0x3000 0x0 [vvar] 0xb7fd4000 0xb7fd6000 0x2000 0x0 [vdso] 0xb7fd6000 0xb7fd7000 0x1000 0x0 /lib/i386-linux-gnu/ld-2.28.so 0xb7fd7000 0xb7ff3000 0x1c000 0x1000 /lib/i386-linux-gnu/ld-2.28.so 0xb7ff3000 0xb7ffd000 0xa000 0x1d000 /lib/i386-linux-gnu/ld-2.28.so 0xb7ffe000 0xb8000000 0x2000 0x27000 /lib/i386-linux-gnu/ld-2.28.so 0xbffdf000 0xc0000000 0x21000 0x0 [stack] $ readelf -h /lib/i386-linux-gnu/ld-2.28.so | grep "Entry point" Entry point address: 0x10b0 0xb7fd6000+0x10b0=0xb7fd70b0 PC寄存器等于0xb7fd70b0,这是ld-2.28.so的"Entry point",效果类似"sxe cpr"。 此时被调试进程的加载基址已经确认,本例中是0x400000。 (gdb) source ShellPipeCommand.py (gdb) sp info proc stat | grep "Start of text" Start of text: 0x400000 0x400000+0x13e7=0x4013e7 删掉引发错误的"tb *0",对目标进程"Entry point"设断: (gdb) d 1 (gdb) tb *0x4013e7 Temporary breakpoint 2 at 0x4013e7 (gdb) c Continuing. Temporary breakpoint 2, 0x004013e7 in ?? () (gdb) x/5i $pc => 0x4013e7: xor ebp,ebp 0x4013e9: pop esi 0x4013ea: mov ecx,esp 0x4013ec: and esp,0xfffffff0 0x4013ef: push eax (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7de90e0 0xb7f35ae6 No /lib/i386-linux-gnu/libc.so.6 0xb7fd7090 0xb7ff245b No /lib/ld-linux.so.2 断在0x4013e7时libc已经加载。 即使没有在系统范围内关闭ASLR,从GDB 7开始默认会关闭被调试进程的ASLR,因此 下次可以直接"tb *0x4013e7"。 $ gdb -q -nx -x gdbinit.txt /bin/true (gdb) show disable-randomization Disabling randomization of debuggee's virtual address space is on. (gdb) tb *0x4013e7 Temporary breakpoint 1 at 0x4013e7 (gdb) run Starting program: /bin/true Temporary breakpoint 1, 0x004013e7 in ?? () (gdb) x/5i $pc => 0x4013e7: xor ebp,ebp 0x4013e9: pop esi 0x4013ea: mov ecx,esp 0x4013ec: and esp,0xfffffff0 0x4013ef: push eax 从GDB 8.1开始支持starti命令,会直接断在0xb7fd70b0: $ gdb -q -nx -x gdbinit.txt /bin/true (gdb) help starti Start the debugged program stopping at the first instruction. You may specify arguments to give it. Args may include "*", or "[...]"; they are expanded using the shell that will start the program (specified by the "$SHELL" environment variable). Input and output redirection with ">", "<", or ">>" are also allowed. With no arguments, uses arguments last specified (with "run" or "set args"). To cancel previous arguments and run with no arguments, use "set args" without arguments. To start the inferior without using a shell, use "set startup-with-shell off". (gdb) starti Starting program: /bin/true Program stopped. 0xb7fd70b0 in ?? () from /lib/ld-linux.so.2 (gdb) x/5i $pc => 0xb7fd70b0: mov eax,esp 0xb7fd70b2: sub esp,0xc 0xb7fd70b5: push eax 0xb7fd70b6: call 0xb7fd7d90 0xb7fd70bb: add esp,0x10 (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fd7090 0xb7ff245b Yes (*) /lib/ld-linux.so.2 (*): Shared library is missing debugging information. "tb *0"与starti不完全等价,前者断下时"info sharedlibrary"尚无输出,后者断 下时"info sharedlibrary"已有输出,显然前者更早期,尽管PC值一样。就原始需求 而言,starti是最佳选择。 如果目标程序使用了动态链接库(一般都会),还可以"set stop-on-solib-events 1", 成功加截.so时会断下来。 $ gdb -q -nx -x gdbinit.txt /bin/true (gdb) tb *0x4013e7 Temporary breakpoint 1 at 0x4013e7 (gdb) set stop-on-solib-events 1 (gdb) run Starting program: /bin/true Stopped due to shared library event (no libraries added or removed) (gdb) x/5i $pc => 0xb7fe6850 <_dl_debug_state>: ret 0xb7fe6851: lea esi,[esi+eiz*1+0x0] 0xb7fe6858: lea esi,[esi+eiz*1+0x0] 0xb7fe685f: nop 0xb7fe6860: call 0xb7ff2453 (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fd7090 0xb7ff245b Yes (*) /lib/ld-linux.so.2 (*): Shared library is missing debugging information. (gdb) info proc mappings process 9341 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /bin/true 0x401000 0x405000 0x4000 0x1000 /bin/true 0x405000 0x408000 0x3000 0x5000 /bin/true 0x408000 0x40a000 0x2000 0x7000 /bin/true 0xb7fcf000 0xb7fd1000 0x2000 0x0 0xb7fd1000 0xb7fd4000 0x3000 0x0 [vvar] 0xb7fd4000 0xb7fd6000 0x2000 0x0 [vdso] 0xb7fd6000 0xb7fd7000 0x1000 0x0 /lib/i386-linux-gnu/ld-2.28.so 0xb7fd7000 0xb7ff3000 0x1c000 0x1000 /lib/i386-linux-gnu/ld-2.28.so 0xb7ff3000 0xb7ffd000 0xa000 0x1d000 /lib/i386-linux-gnu/ld-2.28.so 0xb7ffe000 0xb8000000 0x2000 0x27000 /lib/i386-linux-gnu/ld-2.28.so 0xbffdf000 0xc0000000 0x21000 0x0 [stack] (gdb) c Continuing. Stopped due to shared library event: Inferior loaded /lib/i386-linux-gnu/libc.so.6 (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fd7090 0xb7ff245b Yes (*) /lib/ld-linux.so.2 0xb7de90e0 0xb7f35ae6 Yes (*) /lib/i386-linux-gnu/libc.so.6 (*): Shared library is missing debugging information. (gdb) info proc mappings process 9341 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /bin/true 0x401000 0x405000 0x4000 0x1000 /bin/true 0x405000 0x408000 0x3000 0x5000 /bin/true 0x408000 0x409000 0x1000 0x7000 /bin/true 0x409000 0x40a000 0x1000 0x8000 /bin/true 0xb7dd0000 0xb7de9000 0x19000 0x0 /lib/i386-linux-gnu/libc-2.28.so 0xb7de9000 0xb7f37000 0x14e000 0x19000 /lib/i386-linux-gnu/libc-2.28.so 0xb7f37000 0xb7fa7000 0x70000 0x167000 /lib/i386-linux-gnu/libc-2.28.so 0xb7fa7000 0xb7fa8000 0x1000 0x1d7000 /lib/i386-linux-gnu/libc-2.28.so 0xb7fa8000 0xb7faa000 0x2000 0x1d7000 /lib/i386-linux-gnu/libc-2.28.so 0xb7faa000 0xb7fab000 0x1000 0x1d9000 /lib/i386-linux-gnu/libc-2.28.so 0xb7fab000 0xb7fae000 0x3000 0x0 0xb7fae000 0xb7fcf000 0x21000 0x0 /etc/ld.so.cache 0xb7fcf000 0xb7fd1000 0x2000 0x0 0xb7fd1000 0xb7fd4000 0x3000 0x0 [vvar] 0xb7fd4000 0xb7fd6000 0x2000 0x0 [vdso] 0xb7fd6000 0xb7fd7000 0x1000 0x0 /lib/i386-linux-gnu/ld-2.28.so 0xb7fd7000 0xb7ff3000 0x1c000 0x1000 /lib/i386-linux-gnu/ld-2.28.so 0xb7ff3000 0xb7ffd000 0xa000 0x1d000 /lib/i386-linux-gnu/ld-2.28.so 0xb7ffe000 0xb7fff000 0x1000 0x27000 /lib/i386-linux-gnu/ld-2.28.so 0xb7fff000 0xb8000000 0x1000 0x28000 /lib/i386-linux-gnu/ld-2.28.so 0xbffdf000 0xc0000000 0x21000 0x0 [stack] (gdb) c Continuing. Temporary breakpoint 1, 0x004013e7 in ?? () 可以看到,目标进程的"Entry point"比"set stop-on-solib-events 1"晚命中。 (gdb) source ShellPipeCommand.py (gdb) sp info files | grep 0x4013e7 Entry point: 0x4013e7 还可以尝试"catch load",命中时尚未经过目标进程的"Entry point"。 $ gdb -q -nx -x gdbinit.txt /bin/true (gdb) tb *0x4013e7 Temporary breakpoint 1 at 0x4013e7 (gdb) catch load Catchpoint 2 (load) (gdb) run Starting program: /bin/true Catchpoint 2 Inferior loaded /lib/i386-linux-gnu/libc.so.6 0xb7fe6850 in _dl_debug_state () from /lib/ld-linux.so.2 (gdb) x/5i $pc => 0xb7fe6850 <_dl_debug_state>: ret 0xb7fe6851: lea esi,[esi+eiz*1+0x0] 0xb7fe6858: lea esi,[esi+eiz*1+0x0] 0xb7fe685f: nop 0xb7fe6860: call 0xb7ff2453 (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fd7090 0xb7ff245b Yes (*) /lib/ld-linux.so.2 0xb7de90e0 0xb7f35ae6 Yes (*) /lib/i386-linux-gnu/libc.so.6 (*): Shared library is missing debugging information. (gdb) c Continuing. Temporary breakpoint 1, 0x004013e7 in ?? () 小结一下,有4种方案: 1) tb *0 2) starti 3) set stop-on-solib-events 1 4) catch load GDB 8.1及之后版本推荐使用starti,但"tb *0"更广谱适用。 D: scz 很少需要以run方式启动被调试进程,Attach一般都能满足调试需求。但如果需要调 试较早期代码,不一定是main()之前的代码,考虑那些长期运行的服务进程的初始化 代码,此时Attach方式不满足调试需求。 顺便问一句,Linux上有办法达到"Just-In-Time Debugging"效果吗?如果可以,那 Patch目标程序插入int3也是一招。 D: bluerust JIT调试有个聊胜于无的方案: https://github.com/yugr/libdebugme