gdb通过frame切换栈帧之后寄存器是否准确

一、问题

在使用寄存器调试一些堆栈破坏的core文件时,可能需要通过反汇编来确定问题的原因,而此时确定寄存器的值就是一个必要的手段。但是,在通过frame切换栈帧之后,通过info reg看到的寄存器就是该栈帧当前的寄存器值吗?

二、gdb的文档说明

if all stack frames farther in were exited and their saved registers restored. In order to see the true contents of hardware registers, you must select the innermost frame (with ‘frame 0’).
总而言之,一些挥发性寄存器的值可能随着栈帧的展开而不断的累加错误。也就是这里所说的:如果栈帧越靠外层,这些挥发性寄存器的值就越容易不准确。
Usually ABIs reserve some registers as not needed to be saved by the callee (a.k.a.: “caller-saved”, “call-clobbered” or “volatile” registers). It may therefore not be possible for GDB to know the value a register had before the call (in other words, in the outer frame), if the register value has since been changed by the callee. GDB tries to deduce where the inner frame saved (“callee-saved”) registers, from the debug info, unwind info, or the machine code generated by your compiler. If some register is not saved, and GDB knows the register is “caller-saved” (via its own knowledge of the ABI, or because the debug/unwind info explicitly says the register’s value is undefined), GDB displays ‘<not saved>’ as the register’s value. With targets that GDB has no knowledge of the register saving convention, if a register was not saved by the callee, then its value and location in the outer frame are assumed to be the same of the inner frame. This is usually harmless, because if the register is call-clobbered, the caller either does not care what is in the register after the call, or has code to restore the value that it does care about. Note, however, that if you change such a register in the outer frame, you may also be affecting the inner frame. Also, the more “outer” the frame is you’re looking at, the more likely a call-clobbered register’s value is to be wrong, in the sense that it doesn’t actually represent the value the register had just before the call.

三、调试信息的生成

由于源文件对编译器来说是比较完整的信息,所以生成的debug信息比较完整;相对的,如果是汇编语言,那么生成的调试信息可能就比较有限。为了解决这个问题,其实汇编中也可以插入一些指示,来帮助编译器生成调试信息:
glibc-2.10.1sysdepsgenericsysdep.h
#ifdef __ASSEMBLER__
/* Mark the end of function named SYM. This is used on some platforms
to generate correct debugging information. */
#ifndef END
#define END(sym)
#endif

#ifndef JUMPTARGET
#define JUMPTARGET(sym) sym
#endif

/* Makros to generate eh_frame unwind information. */
# ifdef HAVE_ASM_CFI_DIRECTIVES
# define cfi_startproc .cfi_startproc
# define cfi_endproc .cfi_endproc
# define cfi_def_cfa(reg, off) .cfi_def_cfa reg, off
# define cfi_def_cfa_register(reg) .cfi_def_cfa_register reg
# define cfi_def_cfa_offset(off) .cfi_def_cfa_offset off
# define cfi_adjust_cfa_offset(off) .cfi_adjust_cfa_offset off
# define cfi_offset(reg, off) .cfi_offset reg, off
# define cfi_rel_offset(reg, off) .cfi_rel_offset reg, off
# define cfi_register(r1, r2) .cfi_register r1, r2
# define cfi_return_column(reg) .cfi_return_column reg
# define cfi_restore(reg) .cfi_restore reg
# define cfi_same_value(reg) .cfi_same_value reg
# define cfi_undefined(reg) .cfi_undefined reg
# define cfi_remember_state .cfi_remember_state
# define cfi_restore_state .cfi_restore_state
# define cfi_window_save .cfi_window_save

四、gdb通过扫描函数prologue来恢复栈帧

从实现上看,gdb在没有调试信息的时候,只会扫描函数开始时的push指令。
gdb-7.7gdbi386-tdep.c
static CORE_ADDR
i386_analyze_prologue (struct gdbarch *gdbarch,
CORE_ADDR pc, CORE_ADDR current_pc,
struct i386_frame_cache *cache)
{
pc = i386_skip_noop (pc);
pc = i386_follow_jump (gdbarch, pc);
pc = i386_analyze_struct_return (pc, current_pc, cache);
pc = i386_skip_probe (pc);
pc = i386_analyze_stack_align (pc, current_pc, cache);
pc = i386_analyze_frame_setup (gdbarch, pc, current_pc, cache);
return i386_analyze_register_saves (pc, current_pc, cache);
}
static CORE_ADDR
i386_analyze_register_saves (CORE_ADDR pc, CORE_ADDR current_pc,
struct i386_frame_cache *cache)
{
CORE_ADDR offset = 0;
gdb_byte op;
int i;

if (cache->locals > 0)
offset -= cache->locals;
for (i = 0; i < 8 && pc < current_pc; i++)
{
if (target_read_code (pc, &op, 1))
return pc;
if (op < 0x50 || op > 0x57)
break;

offset -= 4;
cache->saved_regs[op - 0x50] = offset;
cache->sp_offset += 4;
pc++;
}

return pc;
}

五、测试下下寄存器的恢复情况

测试方法是制造一个core,首先在顶层栈帧断点并查看寄存器,等到core发生时再frame到顶层查看寄存器
tsecer@harry: cat -n gdbframe.cpp
1 #include <stdlib.h>
2 #include <string.h>
3
4 int main(int argc, char * argv[])
5 {
6 char *pAddr = NULL;
7 if ( argc > 0)
8 {
9 pAddr = (char*)malloc(0x100);
10 }
11 else if (argc > 2)
12 {
13 pAddr = (char*)malloc(0x200);
14 }
15 for (int i = 0; i < 0x100; i++)
16 {
17 pAddr[-i] = 0;
18 }
19 free(pAddr);
20 return 0;
21 }
tsecer@harry: g++ -g gdbframe.cpp
tsecer@harry: gdb ./a.out
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/tsecer/CodeTest/gdbframe/a.out...done.
(gdb) b 19
Breakpoint 1 at 0x804850e: file gdbframe.cpp, line 19.
(gdb) r
Starting program: /home/tsecer/CodeTest/gdbframe/a.out

Breakpoint 1, main (argc=1, argv=0xbffff4c4) at gdbframe.cpp:19
19 free(pAddr);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.i686 libgcc-4.4.7-11.el6.i686 libstdc++-4.4.7-11.el6.i686
(gdb) info reg
eax 0x8049f00 134520576
ecx 0x109 265
edx 0x0 0
ebx 0x7e2ff4 8269812
esp 0xbffff3f0 0xbffff3f0
ebp 0xbffff418 0xbffff418
esi 0x0 0
edi 0x0 0
eip 0x804850e 0x804850e <main(int, char**)+106>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) c
Continuing.
*** glibc detected *** /home/tsecer/CodeTest/gdbframe/a.out: free(): invalid pointer: 0x0804a008 ***
======= Backtrace: =========
/lib/libc.so.6[0x6c1b91]
/home/tsecer/CodeTest/gdbframe/a.out[0x804851a]
/lib/libc.so.6(__libc_start_main+0xe6)[0x667d36]
/home/tsecer/CodeTest/gdbframe/a.out[0x8048411]
======= Memory map: ========
00110000-00111000 r-xp 00000000 00:00 0 [vdso]
0062b000-00649000 r-xp 00000000 08:02 135171 /lib/ld-2.12.so
00649000-0064a000 r--p 0001d000 08:02 135171 /lib/ld-2.12.so
0064a000-0064b000 rw-p 0001e000 08:02 135171 /lib/ld-2.12.so
00651000-007e1000 r-xp 00000000 08:02 135262 /lib/libc-2.12.so
007e1000-007e3000 r--p 00190000 08:02 135262 /lib/libc-2.12.so
007e3000-007e4000 rw-p 00192000 08:02 135262 /lib/libc-2.12.so
007e4000-007e7000 rw-p 00000000 00:00 0
00818000-00840000 r-xp 00000000 08:02 135278 /lib/libm-2.12.so
00840000-00841000 r--p 00027000 08:02 135278 /lib/libm-2.12.so
00841000-00842000 rw-p 00028000 08:02 135278 /lib/libm-2.12.so
059be000-059db000 r-xp 00000000 08:02 135303 /lib/libgcc_s-4.4.7-20120601.so.1
059db000-059dc000 rw-p 0001d000 08:02 135303 /lib/libgcc_s-4.4.7-20120601.so.1
059fa000-05adb000 r-xp 00000000 08:02 284438 /usr/lib/libstdc++.so.6.0.13
05adb000-05adf000 r--p 000e0000 08:02 284438 /usr/lib/libstdc++.so.6.0.13
05adf000-05ae1000 rw-p 000e4000 08:02 284438 /usr/lib/libstdc++.so.6.0.13
05ae1000-05ae7000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 08:02 302465 /home/tsecer/CodeTest/gdbframe/a.out
08049000-0804a000 rw-p 00000000 08:02 302465 /home/tsecer/CodeTest/gdbframe/a.out
0804a000-0806b000 rw-p 00000000 00:00 0 [heap]
b7fee000-b7ff1000 rw-p 00000000 00:00 0
b7ffe000-b8000000 rw-p 00000000 00:00 0
bffeb000-c0000000 rw-p 00000000 00:00 0 [stack]

Program received signal SIGABRT, Aborted.
0x00110424 in __kernel_vsyscall ()
(gdb) bt
#0 0x00110424 in __kernel_vsyscall ()
#1 0x0067b871 in raise () from /lib/libc.so.6
#2 0x0067d14a in abort () from /lib/libc.so.6
#3 0x006bb735 in __libc_message () from /lib/libc.so.6
#4 0x006c1b91 in malloc_printerr () from /lib/libc.so.6
#5 0x0804851a in main (argc=1, argv=0xbffff4c4) at gdbframe.cpp:19
(gdb) f 5
#5 0x0804851a in main (argc=1, argv=0xbffff4c4) at gdbframe.cpp:19
19 free(pAddr);
(gdb) info reg
eax 0x0 0
ecx 0x539f 21407
edx 0x6 6
ebx 0x7e2ff4 8269812
esp 0xbffff3f0 0xbffff3f0
ebp 0xbffff418 0xbffff418
esi 0x0 0
edi 0x0 0
eip 0x804851a 0x804851a <main(int, char**)+118>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)

比较寄存器的输出,可以看到,挥发性寄存器eax、ecx、edx的值都没有正确恢复
tsecer@harry: diff frame current
1,3c1,3
< eax 0x0 0
< ecx 0x539f 21407
< edx 0x6 6
---
> eax 0x8049f00 134520576
> ecx 0x109 265
> edx 0x0 0
9c9
< eip 0x804851a 0x804851a <main(int, char**)+118>
---
> eip 0x804850e 0x804850e <main(int, char**)+106>
tsecer@harry:

六、为什么不能恢复

if (……)

{
$eax = x
}
else
{
$eax = y
}
funccall();
假设通过frame返回funccall,此时根本不知道在调用前走的是哪个分支,所以无法恢复eax寄存器。而非挥发寄存器被调用函数会在prologue中通过push保存,所以恢复比较简单。

原文地址:https://www.cnblogs.com/tsecer/p/11371549.html