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.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到顶层查看寄存器
[email protected]: 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 }
[email protected]: g++ -g gdbframe.cpp
[email protected]: 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的值都没有正确恢复
[email protected]: 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>
[email protected]:

六、为什么不能恢复

if (……)

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

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

时间: 2024-10-13 22:34:39

gdb通过frame切换栈帧之后寄存器是否准确的相关文章

How a stack frame works 栈帧

http://en.citizendium.org/wiki/Stack_frame To use a stack frame, a thread keeps two pointers, often called the Stack Pointer (SP), and the Frame (FP) or Base Pointer (BP). SP always points to the "top" of the stack, and FP always points to the &

使用gdb查看栈帧的情况, 没有ebp

0x7fffffffdb58: 0x004005ba  0x00000000  0x00000000  0x00000000 <-----funcb的栈帧 [0x7fffffffdb60, 0x7fffffffdb80],其中a=0x1a,其中这个栈的栈底是返回地址4005d0x7fffffffdb68: 0x00000000  0x0000001a  0x00000000  0x00000000         0x4005d8,是函数funca的返回地址,然后往上就逐渐是各种局部变量0x7f

Linux 下函数栈帧分析

1.关于栈 对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈 代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写 数据段:保存初始化的全局变量和静态变量,可读可写不可执行 BSS:未初始化的全局变量和静态变量 堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行 栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行.如下图所示: 首先必须明确一点也是非常重要的一点,栈是向下

llvm JIT强制保留frame pointer(栈帧)

 llvm JIT强制保留frame pointer(栈帧) 搬运自我的百度空间 不优化时,在每个函数开头都会把ebp入栈,这样可以方便调试时栈回溯(Stack Trace)等.push ebp的这个动作称为创建栈桢 但是llvm默认情况下,如果函数中没有alloca等栈操作,就会把栈桢动作优化掉(因为没有用到esp和ebp),这样调试时无法回溯了. 解决办法: 在创建execution engine 时 EngineBuilder&eb= EngineBuilder(m); TargetM

C函数调用机制及栈帧指针

转载: http://bbs.csdn.net/topics/90317145 http://blog.chinaunix.net/uid-26817832-id-3347227.html 帧指针 和栈指针到底是什么,有什么联系吗 FP帧指针指向帧头 SP栈指针指向栈顶 大部分现代计算机系统使用栈来给进程传递参数并且存储局部变量.栈是一种在进程映象内存的高地址内的后进先出(LIFO)的缓冲区.当程序调用一个函数时 一个新的"栈帧"会被创建.这个栈帧包含着传递给函数的各种参数和一些动态的

栈帧啊栈帧

栈帧!栈帧!今天就把栈帧给弄清楚!有一个函数调用关系-->main  -->print    -->add      -->funca        -->funcb          -->funcc在函数funcc函数处设置断点,由于用户态栈是由高到低扩展:当函数执行到(gdb) print $sp$1 = (void *) 0x7fffffffdb78然后打印出内存,0x7fffffffdb78:*0xffffdb90  0x00007fff  0x0040056

函数调用过程栈帧变化详解

http://www.cnblogs.com/fxplove/articles/2574451.html 数调用另一个词语表示叫作 过程.一个过程调用包括将数据和控制从代码的一部分传递到另一部分.另外,它还必须在进入时为过程的局部变量分配空间,并在推出时释放这些空间.而数据传递,局部变量的分配和释放通过操纵程序栈来实现.在了解本文章之前,您需要先对程序的进程空间有所了解,即对进程如何使用内存?如果你知道这些,下面的内容将是很easy的事情了.为了您的回顾还是将简单的分布图贴出来,便于您的回顾.

c函数调用过程原理及函数栈帧分析

转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707       今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比较清晰的思路把这一过程描述出来,关于c函数调用原理的理解是很重要的. 1.关于栈 首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低.对x86体系的CPU而言,其中 ---> 寄存器ebp(

Linux - 函数的栈帧

栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储.为单个过程(函数调用)分配的那部分栈称为栈帧.栈帧其实是两个指针寄存器, 寄存器%ebp为帧指针,而寄存器%esp为栈指针,当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的).总之简单一句话,栈帧的主要作用是用来控制和保存一个过程的 所有信息的.栈帧结构如下所示: 下面,我们通过一个简单的程序来了解栈帧: 简单的函数分析,如下图: 该函数的栈帧情况: 当*p=bug,修改栈