0

CプログラムでFPを取得しようとしています。2つの異なる方法を試しましたが、どちらもGDBを実行したときに取得する方法とは異なります。

私が最初に試した方法は、アセンブリ関数用にCでプロトコル関数を作成したことです。

int* getEbp();

私のコードは次のようになります。

int* ebp = getEbp(); 
printf("ebp: %08x\n", 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
4

2 に答える 2

6
  1. レジスタを読み取るには、 GCC拡張インラインアセンブリ構文を使用するのが実際に最善です。
  2. 別のアセンブラgetEbp()ファイルにコンパイルすると、動作するように見えます。
  3. getEsp()発信者によってプッシュされた差出人アドレスが考慮されていないため、あなたは明らかに間違っています。

ebpこれは、拡張インラインasmを通過し、フレームポインターを追跡することでスタックの巻き戻しを行うコードスニペットです。

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;
}

以下に、レジスタを読み取る2つの実用的な実装を示します。純粋なasm関数はとにget_ebp()あります。インライン関数として実装されている他のセットは、の先頭にあります。get_esp()getbp.Sget_esp_inline()get_ebp_inline()test-getbp.c

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 <stdio.h>
#include <stdint.h>

/* see http://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation */
#include <sys/sdt.h>

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"), "%d\n", a[3]);

    STAP_PROBE(getbp, getbp); /* a static probe is like a named breakpoint */
    bp = get_ebp();
    sp = get_esp();
    printf("asm: %p, %p\n", (void*)bp, (void*)sp);
    bp = get_ebp_inline();
    sp = get_esp_inline();
    printf("inline: %p, %p\n", (void*)bp, (void*)sp);
    return 0;
}

これで、上記で定義した静的プローブを使用ebpespながら、ダンプするGDBスクリプトを記述できます。getbptest-getbp.c

test-getbp.gdb

file test-getbp

set breakpoint pending on
break -p getbp
commands
silent
printf "gdb: 0x%04x, 0x%04x\n", $ebp, $esp
continue
end

run
quit

関数が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 <fopen@plt>
   0x0804838e <+30>:    movl   $0x9,0x8(%esp)
   0x08048396 <+38>:    movl   $0x8048590,0x4(%esp)
   0x0804839e <+46>:    mov    %eax,(%esp)
   0x080483a1 <+49>:    call   0x8048350 <fprintf@plt>
   0x080483a6 <+54>:    nop
   0x080483a7 <+55>:    call   0x80484e4 <get_ebp>
   0x080483ac <+60>:    mov    %eax,%ebx
   0x080483ae <+62>:    call   0x80484e7 <get_esp>
   0x080483b3 <+67>:    mov    %ebx,0x4(%esp)
   0x080483b7 <+71>:    movl   $0x8048594,(%esp)
   0x080483be <+78>:    mov    %eax,0x8(%esp)
   0x080483c2 <+82>:    call   0x8048320 <printf@plt>
   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 <printf@plt>
   0x080483df <+111>:   xor    %eax,%eax
   0x080483e1 <+113>:   mov    -0x4(%ebp),%ebx
   0x080483e4 <+116>:   leave  
   0x080483e5 <+117>:   ret 

nopatは<main+54>静的プローブです。printfレジスタの読み取り方法については、2つの呼び出しの周りのコードを参照してください。

ところで、あなたのコードのこのループは私には奇妙に思えます:

while( esp <= ebp )       
    esp -= 4;

意味ない

while (esp < ebp)
    esp +=4 

于 2012-11-18T08:25:01.277 に答える
4

実装固有の詳細に依存しているため、正確な回答を得るには、ターゲットに関する詳細情報を提供する必要があります。質問に答えるために本当に必要なアーキテクチャ、コンパイラ、またはオペレーティングシステムを指定していません。

参照したレジスタ名とat&t構文を使用しているという事実に基づいて知識に基づいた推測を行うと、これはi386であり、gccを使用していると想定します。

これを実現する最も簡単な方法は、gcc変数属性を使用することです。これは、特定のレジスタを要求するためのgcc固有の構文です。

#include <stdint.h>
#include <stdio.h>

int main(int argc, char **argv)
{
     const uintptr_t register framep asm("ebp");

     fprintf(stderr, "val: %#x\n", framep);

     return 0;
 }

別の方法は、インラインアセンブリを使用して、次のように値をロードすることです。

#include <stdint.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    uintptr_t framep;

    asm("movl %%ebp, %0" : "=r" (framep));

    fprintf(stderr, "val: %#x\n", framep);

    return 0;
}

これにより、書き込み操作(=修飾子)用の32ビットレジスタが要求され、framepにロードされます。コンパイラは、宣言した値の抽出を処理します。

gdbでは、値を出力して、出力と一致することを確認できます。

(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: %#x\n", 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と呼ばれ、他のプラットフォームでは省略フレームポインタと呼ばれます。このトリックにより、別のレジスタが汎用用に解放されますが、デバッグが少し複雑になります。

x86の呼び出し規約で可能な場合はeaxが戻り値に一般的に使用されているのは正しいですが、投稿のコメントでスタックが使用されていると主張している理由がわかりません。

于 2012-11-18T04:53:38.170 に答える