调试去符号化/strip 过的二进制的调试方法
看到的现象:
(gdb) b main
Breakpoint 1 at 0x4130f4 (4 locations)
(gdb) run -D -e -ddd -f /mnt/mtd/openssh6/sshd_config
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.Breakpoint 1, 0x00000000004130f4 in main ()
(gdb) n
Single stepping until exit from function main,
which has no line number information.
[Inferior 1 (process 2492) exited with code 01]
🔍 逐步解析:
1️⃣ Breakpoint 1 at 0x4130f4 (4 locations)
说明 GDB 通过符号或猜测找到了“main”这个符号(可能是多个副本,例如静态链接时多个对象中定义了 main)。
已经正确断在 main。
2️⃣ (gdb) n
之后出现:
“Single stepping until exit from function main, which has no line number information.”
这句意思是:
“main 函数没有行号信息(即没有调试符号),GDB 将尝试单步直到函数返回。”
也就是说:
由于我的二进制是 去掉了 -g 调试信息 或被 strip 过的静态库合并产物,GDB 没法逐行执行。
它只知道 main 的入口地址(0x4130f4),不知道 main 内部每一行对应的地址。
所以
n
(next)无法逐行运行,只能整个函数执行完(即 main 返回)再停。
3️⃣ [Inferior 1 (process 2492) exited with code 01]
这表示程序正常返回 exit(1),即执行过程中发生了错误并退出(不是崩溃)。
✅ 结论:
GDB 确实已经进入了 main,但:
因为没有符号表 / 行号信息(比如缺少
-g
编译),所以n
不能逐行执行;程序执行到 main 的结尾或某个错误点后调用了
exit(1)
,所以直接退出;GDB 显示 “exited with code 01” 即表示 程序自己退出了(非崩溃)。
建议调试方法:
✅ 方法1:重新编译带符号版本
在编译 openssh 时加:
CFLAGS += -g -O0
LDFLAGS += -g
并确保不要执行 strip
。
然后重新执行:
gdb ./sshd
(gdb) break main
(gdb) run -D -e -ddd -f /mnt/mtd/openssh6/sshd_config
这样我就能看到源码级调试信息(行号、变量等)。
✅ 方法2:查看退出点
如果暂时不能重新编译,可以这样:
(gdb) run ...
(gdb) bt
或者
(gdb) catch syscall exit
(gdb) run ...
看看是哪个函数触发了退出。
✅ 方法3:设置更多断点
在没有符号的情况下可以:
(gdb) info functions | grep ssh
(gdb) break sshd_main
或直接断在 _start
、main
、sshd_main
、server_listen
等函数上。
一、调试目标与问题背景
场景:
二进制(例如
/usr/bin/dlz_sshd
)是 strip 过的无符号版本;无法直接看到源码行、变量名;
程序启动后马上退出(exit code 1);
想定位是初始化流程哪里出问题。
二、准备阶段
1️⃣ 启动 GDB:
gdb /usr/bin/dlz_sshd
2️⃣ 设置运行参数(例如在设备上的启动方式):
set args -D -e -ddd -f /mnt/mtd/openssh6/sshd_config
3️⃣ 关闭自动分页,避免输出中断:
set pagination off
三、断点与入口分析
1. 主入口断点
break main
run
即便没有符号,GDB 仍可能识别出 main(通过 ELF symbol header)。
若找不到,可尝试:
info functions | grep main
或直接断在程序入口:
break *_start
_start
是程序最早的入口,由 glibc 提供,负责调用__libc_start_main(main,...)
。
2. 运行后若立刻退出:
查看退出码:
info inferiors
若显示 [Inferior 1 exited with code 01]
,说明程序自行 exit(1)。
四、查找退出点
方法1:捕获 exit 调用
catch syscall exit
run
程序调用 exit()
时会中断,GDB 停下来,显示退出位置。
可以查看调用栈:
bt
即便无符号,也能看到地址序列,比如:
#0 0x0000007ff7a12f40 in exit () from /lib/libc.so.6 #1 0x00000000004156b0 in ?? () #2 0x00000000004130f4 in main ()
这就能定位出大致的调用层级。
方法2:跟踪函数调用(退化追踪)
如果 exit 没抓到,可以:
catch syscall write
查看程序最后输出的日志位置(常用于看 crash 前的输出)。
五、结合系统日志验证
通常 OpenSSH 的内部错误也会通过 stderr 输出。
可以在 GDB 中让输出重定向:
set logging file sshd_debug.log
set logging on
run
set logging off
然后在 log 文件中查找 fatal:
、error:
、failed
等关键字。
六、实战定位思路
目标 | GDB命令 | 作用 |
---|---|---|
看程序在哪退出 | catch syscall exit + bt | 定位 exit 调用点 |
看初始化执行到哪 | disassemble main / x/20i $pc | 对比汇编逻辑 |
推测失败函数 | info symbol 0xaddr / strings 对照 | 推断逻辑块 |
验证是否加载到依赖库 | info sharedlibrary | 检查 libssl、libcrypto 是否加载 |
检查 errno | print errno | 如果调用失败,errno 可能提示原因 |
八、总结:最小调试工作流(推荐顺序)
gdb /usr/bin/dlz_sshd
set args -D -e -ddd -f /mnt/mtd/openssh6/sshd_config
catch syscall exit
run
bt
info sharedlibrary
disassemble main
strings /usr/bin/dlz_sshd | grep fatal
这样能知道:
程序是否进入 main;
在哪个函数退出;
退出时的调用栈;
可能的出错字符串;
是否因为库缺失或初始化失败导致。