我目前有一个棘手的错误,发生在我无法访问源或符号的地方,即我可以看到发生崩溃的指令及其地址,但这就是它。 我想做的是gdb
运行而不需要交互并显示每条指令,但我还没有找到办法。
我希望实现的目标是:
(gdb) /* some command */
0x2818a7c0: push %ebp
0x2818a7c1: mov %esp,%ebp
0x2818a7c3: push %ebx
0x2818a7c4: sub $0x4,%esp
...
0x28563622: mov %esi,0x0(%eax)
Program received signal SIGSEGV, Segmentation fault.
我一直在做的是为程序计数器设置一个显示器,如下所示:
(gdb) display/i $pc
然后使用stepi
:
(gdb) stepi
1: x/i $pc 0x2818a7c0: push %ebp
然而,崩溃是数百或数千条指令,我想要一种方法来查看每一个(一起,如果可取的话),而不必多次点击“输入”。另外,如果我是手动完成的,我会在每条指令之间看到(gdb)
提示符,这是不太理想的。
我简要介绍的一条路线是scripting,但我唯一的想法是设置在main()
,让它显示并再次中断(对于下一条指令),然后继续,但是那么我不能在commands
块中使用commands
,所以它不会像我想象的那样工作。
如果重要,我正在使用FreeBSD。
答案 0 :(得分:14)
以下内容应该按照您的要求进行:
# not strictly required, but you'll likely want the log anyway
(gdb) set logging on
# ask gdb to not stop every screen-full
(gdb) set height 0
(gdb) while 1
> x/i $pc
> stepi
> end
但是,这种调试方法可能会徒劳无功:即使在大多数琐碎的程序中,也只会执行太多的指令。
更好的方法可能是运行程序直到崩溃,尝试了解当前函数正在做什么以及谁调用它,并适当地设置断点。
在x86上,您甚至可以在完全剥离的可执行文件中推断出函数边界。
您要查看的另一件事是strace/truss
输出,因此您可以看到紧急情况发生在崩溃点之前的系统调用。
答案 1 :(得分:1)
Python脚本
这将提供比GDB脚本更多的灵活性,以实现您的疯狂想法。
这里的主要问题,就像GDB脚本一样,对于没有目标硬件支持的大多数应用程序来说,这可能会太慢,例如:只有18k指令,C hello world需要1分钟。
gdb.py
class TraceAsm(gdb.Command):
def __init__(self):
super().__init__(
'trace-asm',
gdb.COMMAND_BREAKPOINTS,
gdb.COMPLETE_NONE,
False
)
def invoke(self, argument, from_tty):
argv = gdb.string_to_argv(argument)
if argv:
gdb.write('Does not take any arguments.\n')
else:
done = False
thread = gdb.inferiors()[0].threads()[0]
last_path = None
last_line = None
with open('trace.tmp', 'w') as f:
while thread.is_valid():
frame = gdb.selected_frame()
sal = frame.find_sal()
symtab = sal.symtab
if symtab:
path = symtab.fullname()
line = sal.line
else:
path = None
line = None
if path != last_path:
f.write("path {}{}".format(path, os.linesep))
last_path = path
if line != last_line:
f.write("line {}{}".format(line, os.linesep))
last_line = line
pc = frame.pc()
f.write("{} {} {}".format(hex(pc), frame.architecture().disassemble(pc)[0]['asm'], os.linesep))
gdb.execute('si', to_string=True)
TraceAsm()
main.S
global _start
_start:
; Write.
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, hello_world_len
syscall
; Exit.
mov rax, 60
mov rdi, 0
syscall
hello_world db "hello world", 10
hello_world_len equ $ - hello_world
组装并运行:
as -o main.o main.S
ld -o main.out main.o
gdb -nh -batch -ex 'source ~/test/gdb.py' -ex 'starti' -ex 'trace-asm' ./main.out
cat trace.tmp
输出:
0x401000 mov $0x1,%rax
0x401007 mov $0x1,%rdi
0x40100e mov $0x402000,%rsi
0x401015 mov $0xc,%rdx
0x40101c syscall
0x40101e mov $0x3c,%rax
0x401025 mov $0x0,%rdi
0x40102c syscall
QEMU仿真
这比GDB python解决方案快得多,C hello工作即时运行!但是,对于同一个可执行文件,日志只有10k指令长而不是18k,因此它必须跳过通常可以运行TODO的东西。
E.g。在用户模式模拟中:
qemu-x86_64 -d in_asm ./main.out
输出:
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
----------------
IN:
0x0000000000401000: mov $0x1,%rax
0x0000000000401007: mov $0x1,%rdi
0x000000000040100e: mov $0x402000,%rsi
0x0000000000401015: mov $0xc,%rdx
0x000000000040101c: syscall
hello world
----------------
IN:
0x000000000040101e: mov $0x3c,%rax
0x0000000000401025: mov $0x0,%rdi
0x000000000040102c: syscall
在Ubuntu 18.10,GDB 8.2,QEMU 2.12.0中测试。
答案 2 :(得分:0)
(免责声明:我为Hex-Rays工作)