我试图在我的C程序中获得FP,我尝试了两种不同的方式,但它们都与我运行GDB时的方式不同。
我尝试的第一种方法,我在C中为Assembly函数创建了一个协议函数:
int* getEbp();
我的代码看起来像这样:
int* ebp = getEbp(); printf("ebp: %08xn", ebp); // value i get here is 0xbfe2db58 while( esp <= ebp ) esp -= 4; printf( "ebp: %08x, esp" ); //value i get here is 0xbfe2daec
我的汇编代码
getEbp: movl %ebp, %eax ret
我尝试使原型函数只返回一个int,但这也与我的GDB输出不匹配。 我们正在使用x86程序集。
编辑:拼写错误,我的getEsp函数看起来与另一个完全相同:
getEsp: movl %esp, %eax ret
这是一个代码片段,通过扩展的内联asm获取ebp
,并通过追逐帧指针进行堆栈展开:
struct stack_frame { struct stack_frame *prev; void *return_addr; } __attribute__((packed)); typedef struct stack_frame stack_frame; void backtrace_from_fp(void **buf, int size) { int i; stack_frame *fp; __asm__("movl %%ebp, %[fp]" : /* output */ [fp] "=r" (fp)); for(i = 0; i < size && fp != NULL; fp = fp->prev, i++) buf[i] = fp->return_addr; }
我将展示两种读取下面寄存器的工作实现。 纯asm函数是get_ebp()
和get_esp()
。 另一个实现为内联函数的get_esp_inline()
是test-getbp.c
顶部的get_esp_inline()
和get_ebp_inline()
。
在getbp.S
.section .text /* obviously incurring the cost of a function call to read a register is inefficient */ .global get_ebp get_ebp: movl %ebp, %eax ret .global get_esp get_esp: /* 4: return address pushed by caller */ lea 4(%esp), %eax ret
在test-getbp.c
#include #include /* see https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation */ #include int32_t *get_ebp(void); int32_t *get_esp(void); __attribute__((always_inline)) uintptr_t *get_ebp_inline(void) { uintptr_t *r; __asm__ volatile ("movl %%ebp, %[r]" : /* output */ [r] "=r" (r)); return r; } __attribute__((always_inline)) uintptr_t *get_esp_inline(void) { uintptr_t *r; __asm__ volatile ("movl %%esp, %[r]" : /* output */ [r] "=r" (r)); return r; } int main(int argc, char **argv) { uintptr_t *bp, *sp; /* allocate some random data on the stack just for fun */ int a[10] = { 1, 3, 4, 9 }; fprintf(fopen("/dev/null", "r"), "%dn", a[3]); STAP_PROBE(getbp, getbp); /* a static probe is like a named breakpoint */ bp = get_ebp(); sp = get_esp(); printf("asm: %p, %pn", (void*)bp, (void*)sp); bp = get_ebp_inline(); sp = get_esp_inline(); printf("inline: %p, %pn", (void*)bp, (void*)sp); return 0; }
我们现在可以编写一个GDB脚本来转储ebp
和esp
同时使用上面test-getbp.c
定义的getbp
静态探测器。
在test-getbp.gdb
file test-getbp set breakpoint pending on break -p getbp commands silent printf "gdb: 0x%04x, 0x%04xn", $ebp, $esp continue end run quit
要validation函数是否返回与GDB相同的数据:
$ gdb -x test-getbp.gdb < ... > gdb: 0xffffc938, 0xffffc920 asm: 0xffffc938, 0xffffc920 inline: 0xffffc938, 0xffffc920 < ... >
反汇编test-getbp
main()
会产生:
0x08048370 <+0>: push %ebp 0x08048371 <+1>: mov %esp,%ebp 0x08048373 <+3>: push %ebx 0x08048374 <+4>: and $0xfffffff0,%esp 0x08048377 <+7>: sub $0x10,%esp 0x0804837a <+10>: movl $0x8048584,0x4(%esp) 0x08048382 <+18>: movl $0x8048586,(%esp) 0x08048389 <+25>: call 0x8048360 0x0804838e <+30>: movl $0x9,0x8(%esp) 0x08048396 <+38>: movl $0x8048590,0x4(%esp) 0x0804839e <+46>: mov %eax,(%esp) 0x080483a1 <+49>: call 0x8048350 0x080483a6 <+54>: nop 0x080483a7 <+55>: call 0x80484e4 0x080483ac <+60>: mov %eax,%ebx 0x080483ae <+62>: call 0x80484e7 0x080483b3 <+67>: mov %ebx,0x4(%esp) 0x080483b7 <+71>: movl $0x8048594,(%esp) 0x080483be <+78>: mov %eax,0x8(%esp) 0x080483c2 <+82>: call 0x8048320 0x080483c7 <+87>: mov %ebp,%eax 0x080483c9 <+89>: mov %esp,%edx 0x080483cb <+91>: mov %edx,0x8(%esp) 0x080483cf <+95>: mov %eax,0x4(%esp) 0x080483d3 <+99>: movl $0x80485a1,(%esp) 0x080483da <+106>: call 0x8048320 0x080483df <+111>: xor %eax,%eax 0x080483e1 <+113>: mov -0x4(%ebp),%ebx 0x080483e4 <+116>: leave 0x080483e5 <+117>: ret
处的
nop
是静态探针。 有关如何读取寄存器的信息,请参阅两个printf
调用的代码。
顺便说一句,你的代码中的这个循环对我来说很奇怪:
while( esp <= ebp ) esp -= 4;
不是吗?
while (esp < ebp) esp +=4
?
由于您依赖于特定于实现的详细信息,因此您需要提供有关目标的更多信息以获得准确的答案。 您没有指定回答问题的架构,编译器或操作系统。
根据您引用的寄存器名称以及您在&t语法中使用的事实进行有根据的猜测,我将假设这是i386并且您正在使用gcc。
实现这一点的最简单方法是使用gcc变量属性,您可以尝试这个,这是一个gcc特定的语法来请求特定的寄存器。
#include #include int main(int argc, char **argv) { const uintptr_t register framep asm("ebp"); fprintf(stderr, "val: %#xn", framep); return 0; }
另一种方法是使用内联汇编来加载值,如下所示:
#include #include int main(int argc, char **argv) { uintptr_t framep; asm("movl %%ebp, %0" : "=r" (framep)); fprintf(stderr, "val: %#xn", framep); return 0; }
这要求32位寄存器进行写操作(=修饰符),并将其加载到framep上。 编译器负责提取您声明的值。
在gdb中,您可以打印该值并validation它是否与输出匹配。
(gdb) b main Breakpoint 1 at 0x40117f: file ebp2.c, line 8. (gdb) r Starting program: /home/zero/a.exe [New Thread 4664.0x1290] [New Thread 4664.0x13c4] Breakpoint 1, main (argc=1, argv=0x28ac50) at ebp2.c:8 8 asm("movl %%ebp, %0" : "=r" (framep)); (gdb) n 10 fprintf(stderr, "val: %#xn", framep); (gdb) p/x framep $1 = 0x28ac28 (gdb) p/x $ebp $2 = 0x28ac28 (gdb) c Continuing. val: 0x28ac28 [Inferior 1 (process 4664) exited normally] (gdb) q
请记住,您不能依赖此行为,即使在x86 gcc上也可以配置为不使用帧指针并手动跟踪堆栈使用情况。 这通常被Microsoft称为FPO,或者在其他平台上称为omit-frame-pointer。 这个技巧释放了另一个用于通用目的的寄存器,但使调试变得更加复杂。
你是正确的,eax通常用于x86调用约定中的返回值,我不知道为什么你post上的评论声称使用了堆栈。
以上就是c/c++开发分享在C中获取帧指针相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/560823.html