一、問題
在使用寄存器調試一些堆棧破壞的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.1\sysdeps\generic\sysdep.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.7\gdb\i386-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保存,所以恢復比較簡單。
