元となるソース
demo.c
//#include<stdio.h> /* debug */
// demo.c
int add(int a, int b, int c, int d) {
return a + b + c + d;
}
int main() {
int result = add(1, 2, 3, 4);
// printf("result = %d",result); /* debug */
return result;
}
コンパイル方法
gcc -S -masm=intel -o demo.s demo.c
オプション | 意味 |
---|---|
S | アセンブリ言語へ変換 |
masm=intel | 初期設定のAT&T記法ではなくintel記法 |
o | 出力ファイル名を指定 |
※AT&T記法で書かれたアセンブリ言語は個人的に読みづらいのでintel記法を設定しています。
生成されたアセンブリのコード
※注釈を付けています
demo.s
.file "demo.c"
.intel_syntax noprefix
.text
.globl add
.def add; .scl 2; .type 32; .endef
.seh_proc add
add:
push rbp
.seh_pushreg rbp
mov rbp, rsp
.seh_setframe rbp, 0
.seh_endprologue
mov DWORD PTR 16[rbp], ecx ;第1引数 int a
mov DWORD PTR 24[rbp], edx ;第2引数 int b
mov DWORD PTR 32[rbp], r8d ;第3引数 int c
mov DWORD PTR 40[rbp], r9d ;第4引数 int d
mov edx, DWORD PTR 16[rbp] ;edx = a;
mov eax, DWORD PTR 24[rbp] ;eax = b;
add edx, eax ;edx = edx + eax
mov eax, DWORD PTR 32[rbp] ;eax = c
add edx, eax ;edx = edx + eax
mov eax, DWORD PTR 40[rbp] ;eax = d
add eax, edx ;eax = eax + edx
;↑mainでは返り値をeaxから取り出す。明示的にreturnしている訳ではない。
pop rbp
ret
.seh_endproc
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
push rbp
.seh_pushreg rbp
mov rbp, rsp
.seh_setframe rbp, 0
sub rsp, 48
.seh_stackalloc 48
.seh_endprologue
call __main
mov r9d, 4 ;引数格納
mov r8d, 3 ;引数格納
mov edx, 2 ;引数格納
mov ecx, 1 ;引数格納
call add ;add関数呼び出し
mov DWORD PTR -4[rbp], eax
mov eax, DWORD PTR -4[rbp]
add rsp, 48
pop rbp
ret
.seh_endproc
.def __main; .scl 2; .type 32; .endef
.ident "GCC: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r3) 14.2.0"
コンパイル後のアセンブリ分析
mainからaddへの変数引き渡しはレジスタr9d,r8d,edx,ecxを通じて行われていることが分かります。
mov r9d, 4 ;
mov r8d, 3 ;
mov edx, 2 ;
mov ecx, 1 ;
add側ではそれぞれのレジスタから値を取りだしスタックへ格納しています。
mov DWORD PTR 16[rbp], ecx
mov DWORD PTR 24[rbp], edx
mov DWORD PTR 32[rbp], r8d
mov DWORD PTR 40[rbp], r9d
GDBを用いてスタックの中身を確認
C:\Users\nanasi\kansuasm>gcc -g -O0 -Wl,--disable-dynamicbase demo.c -o demo_debug.exe
C:\Users\nanasi\kansuasm>gdb demo_debug.exe
オプション | 意味 |
---|---|
O0 | 最適化レベル0。C言語の順序通りにコンパイル。未使用変数の削除無し |
masm=intel | メモリアドレスをランダムにする機能を停止する |
Wl | つけた方がよい???(理解してません) |
o | 出力ファイル名を指定 |
GDB上での操作
(gdb) set disassembly-flavor intel 初期設定のAT&T記法ではなくintel記法
(gdb) disassemble add add関数に対応するコードのアドレス及びアセンブリのコードが見られる
add関数の処理が終わった後のスタックの中身を確認したいので
0x00000001400016f4 <+36>: pop rbp
の位置にブレイクポイントを設置しプログラムを実行。
gdbコマンド.bash
(gdb) break *0x00000001400016f4 ブレイクポイントを設置
(gdb) run ブレークポイントまで実行
(gdb) x/dw $rbp+16 値を確認する
(gdb) x/dw $rbp+24
(gdb) x/dw $rbp+32
(gdb) x/dw $rbp+40
するとmain関数から渡した1,2,3,4がきちんと代入されていることが分かります。
動作環境
Windows10 Version 10.0.19045.5737
gcc version 14.2.0
GNU gdb (GDB for MinGW-W64 x86_64, built by Brecht Sanders, r3) 16.2
スタックの操作や構造は後日研究して又記事を上げます。
今回は観察なのでまとまりが無いかもしれませんがご容赦下さい。