GDB中until *<addr>报内存错误但tbreak *<addr>正常的原因排查
现象总结
调试通过gcc -nostdlib编译、以_start为入口的纯汇编x86_64二进制时,使用until *<十六进制地址>命令会触发「Cannot access memory at address 0x1」的错误,导致命令中止;但使用break或tbreak *<相同地址>却能正确设置断点并执行。而在C编译的常规二进制中,until *<addr>命令可正常工作。
使用GDB 17.2测试,根据官方手册说明,until与break命令接受相同参数,但实际行为存在明显差异。
复现示例
汇编源码
.intel_syntax noprefix .global _start .section .data message: .ascii "hello, world!\n" .section .text _start: mov rax, 1 # syscall: write mov rdi, 1 # fd: stdout lea rsi, [rip + message] mov rdx, 14 # length syscall mov rax, 60 # syscall: exit xor rdi, rdi # status: 0 syscall
GDB操作日志
❯ gdb --version GNU gdb (GDB) 17.2
(No debugging symbols found in ./hello) (gdb) b _start Breakpoint 1 at 0x401000 (gdb) r Starting program: /home/rivon0507/low-level/hello-world/hello Enable debuginfod for this session? (y or [n]) Debuginfod has been disabled. Breakpoint 1, 0x0000000000401000 in _start () (gdb) until *0x40100a ❌ Warning: Cannot insert breakpoint 0. Cannot access memory at address 0x1 ❌ Command aborted. (gdb) tbreak *0x40100a Temporary breakpoint 2 at 0x40100a (gdb) c Continuing. Temporary breakpoint 2, 0x000000000040100a in _start ()
调试日志细节(开启表达式与断点调试)
(gdb) set debug expression 1 (gdb) set debug breakpoint 1 (gdb) until *0x40100a Operation: OP_LONG Type: int Constant: 4198410 Operation: OP_LONG Type: int Constant: 4198410 [breakpoint] update_global_location_list: insert_mode = UGLL_MAY_INSERT [breakpoint] update_global_location_list: insert_mode = UGLL_MAY_INSERT [breakpoint] update_global_location_list: insert_mode = UGLL_INSERT [breakpoint] insert_bp_location: Breakpoint 0 (0x55c4b4afda30) at address 0x1 [breakpoint] insert_bp_location: Breakpoint 1 (0x55c4b4b299f0) at address 0x401000 <_start> [breakpoint] insert_bp_location: Breakpoint 0 (0x55c4b4bcaae0) at address 0x40100a <_start+10> ❌ Warning: Cannot insert breakpoint 0. Cannot access memory at address 0x1 ❌ Command aborted.
原因分析
从调试日志可以看出,until命令执行时,除了在用户指定的0x40100a地址正确设置断点外,还额外尝试在无效地址0x1插入断点,这是导致错误的直接原因。
until命令的设计逻辑包含自动辅助断点(比如函数返回处),用于实现「执行到指定位置或函数结束」的功能。但在无标准运行时库、无调试符号且入口为_start的纯汇编二进制中,GDB的函数边界检测逻辑出现错误,误将某个值识别为返回地址或其他需要断点的位置,从而生成了无效的0x1地址。
而break/tbreak仅处理用户明确指定的地址,没有额外的自动断点逻辑,因此不受此问题影响。C编译的二进制因包含标准运行时,GDB能正确识别函数结构,辅助断点逻辑不会出错。
结论
这是GDB的bug。根据官方文档,until命令应与break接受相同参数并一致处理,但实际在特定场景下,其内部的辅助断点逻辑错误生成无效地址,导致命令失败。用户明确指定地址时,until应优先尊重用户输入,而非因错误的自动断点逻辑中止命令。
内容的提问来源于stack exchange,提问作者rivon0507

