FromNandの日記

自分的備忘録

【x86アセンブラ】レジスタの内容を表示するプログラムを考えてみる

低レイヤの作業をしているとこういった関数が欲しくなる時があるので実装してみた。


まず、これがC言語側から呼び出す際に必要とする構造体と使用する関数のプロトタイプ宣言。

// search_regs.h

#ifndef _SEARCH_REGS_H
#define _SEARCH_REGS_H

typedef struct REGS {
    unsigned int eax, ecx, edx, ebx, esp, ebp, esi, edi, eip;
} REGS;

unsigned int search_eax(void);
unsigned int search_ecx(void);
unsigned int search_edx(void);
unsigned int search_ebx(void);
unsigned int search_esp(void);
unsigned int search_ebp(void);
unsigned int search_esi(void);
unsigned int search_edi(void);
unsigned int search_eip(void);
REGS* search_regs(REGS*);
void print_regs(REGS*);

#endif


次にこれが実際のコード。
search_eax, search_ecx, search_edx, search_ebx, search_esp, search_ebp, search_esi, search_edi, search_eipはそれぞれ戻り値に符号なし整数でレジスタの値を返す。
espとeipは関数を呼び出す直前のものを表示するようにしている。
search_regsは引数に上の構造体へのアドレスを渡し、それに内容を詰めていく感じだ。
espはそんなに参照する機会はないだろし、実装も少しめんどくさかったので、ちょっと妥協した実装になっている。

# espの場合は関数を呼び出した後の値を返す(引数、戻り地などがespを変化させて、厳密にやるのがめんどい)
# eipの場合は関数を呼び出す命令のアドレスを返す

.code32

.globl search_eax, search_ecx, search_edx, search_ebx, search_esp, search_ebp, search_esi, search_edi, search_eip
.globl search_regs

.text

# こいつらは単発でレジスタを調べたいときに使うといい
# 呼び出して戻り値を参照するだけ

search_eax:
    ret

search_ecx:
    movl %ecx, %eax
    ret

search_edx:
    movl %edx, %eax
    ret

search_ebx:
    movl %ebx, %eax
    ret

search_esp:
    leal -0x4(%esp), %eax
    ret

search_ebp:
    movl %ebp, %eax
    ret

search_esi:
    movl %esi, %eax
    ret

search_edi:
    movl %edi, %eax
    ret

search_eip:
    movl (%esp), %eax
    subl $5, %eax
    ret

search_regs:
    pushl %eax
    movl 0x8(%esp), %eax
saerch_regs_eax:
    popl (%eax)
    addl $4, %eax
search_regs_ecx:
    movl %ecx, (%eax)
    addl $4, %eax
search_regs_edx:
    movl %edx, (%eax)
    addl $4, %eax
search_regs_ebx:
    movl %ebx, (%eax)
    addl $4, %eax
search_regs_esp:
    movl %esp, (%eax)
    addl $4, %eax
search_regs_ebp:
    movl %ebp, (%eax)
    addl $4, %eax
search_regs_esi:
    movl %esi, (%eax)
    addl $4, %eax
search_regs_edi:
    movl %edi, (%eax)
    addl $4, %eax
search_regs_eip:
    movl (%esp), %ecx
    subl $5, %ecx
    movl %ecx, (%eax)
search_regs_end:
    movl 0x4(%esp), %eax
    ret


C言語の関数からは次のように呼び出すといい。

int main(void){
    REGS regs;
    search_regs(&regs);
    print_regs(&regs);
    return 0;
}


print_regsというのはREGS構造体を理解して、その内容を表示する関数だ。
この関数の中でprintというのを使っているのだが、これはprntfを自作したもの。

void print_regs(REGS *regs){
    print("eax = %x, ecx = %x, edx = %x, ebx = %x, esp = %x, ebp = %x, esi = %x, edi = %x, eip = %x\n", \
          regs->eax, regs->ecx, regs->edx, regs->ebx, regs->esp, regs->ebp, regs->esi, regs->edi, regs->eip);
}


僕の環境でのプログラムの実行結果は次のようになった。

eax = FFD99DFC, ecx = FFD99E38, edx = 3, ebx = F7F83000, esp = FFD99DDC, ebp = FFD99E28, esi = 1, edi = 8049000, eip = 8049071


おまけとして、アセンブラから呼び出す際のコードも紹介しておく。

# アセンブラから呼び出す場合は36byteの領域を確保して、その領域への先頭ポインタをpushしてから呼び出す。(C言語ではREGS構造体のポインタ)
# search_regsによってその領域に値が詰められて、領域へのポインタがeaxに入って帰ってくるので、push eaxしてからprint_regsを呼び出す。

test:
    pushl %ebp
    movl %esp, %ebp
    subl $36, %esp
    pushl %esp
    call search_regs
    addl $4, %esp
    pushl %eax
    call print_regs
    addl $4, %esp
    addl $36, %esp
    leave
    ret


このtestをmainから呼び出した結果は次のようになった。

eax = 8050008, ecx = FFB63468, edx = 3, ebx = F7F70000, esp = FFB6341C, ebp = FFB63448, esi = 1, edi = 8049000, eip = 80493B8