【アセンブリ言語】C言語コンパイルしてアセンブリ言語を解読していく の変更点
#author("2024-06-26T17:17:41+09:00","default:pitablog","pitablog")
#author("2024-06-26T17:19:52+09:00","default:pitablog","pitablog")
* 【アセンブリ言語】Cをコンパイルしてアセンブリを解読していく [#s686fa73]
//#seo(description,テンプレ)
//#seo(keywords,テンプレ)
#splitbody{{
LEFT:
&tag(情報技術,プログラミング,コンパイラ言語,アセンブリ言語,C言語);
#split
RIGHT:&size(13){投稿日: 2024-06-24 (月)};
}}
#bcontents
※この記事は素人の私がコンパイルをしてアセンブリ言語を解読するだけです。間違えてたらコメ欄で指摘してくださると助かります。
** 0. MinGW を入れる [#i8841cb4]
これは別の記事で紹介しますたぶん
** 1. Cのソースを準備する [#ke570e4d]
「test.c」として保存した
#include <stdio.h>
int main(void) {
int a = 5;
int b = 2;
a -= 1;
b *= 2;
int c = a + b;
printf("%d", c);
return 0;
}
** 2. コンパイルだけをする [#n7f3dd91]
コマンドプロンプトなどで以下のコマンドを実行した。
gcc -S test.c
-S とはアセンブラファイルを生成する(コンパイルまでのみの処理)というオプション
** 3. アセンブラファイルを開く [#tbd8a3f9]
test.c が入っているフォルダに test.s が生成されていた。
.file "test.c"
.text
.def __main; .scl 2; .type 32; .endef
.section .rdata,"dr"
.LC0:
.ascii "%d\0"
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $48, %rsp
.seh_stackalloc 48
.seh_endprologue
call __main
movl $5, -4(%rbp)
movl $2, -8(%rbp)
subl $1, -4(%rbp)
sall -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
movl %eax, -12(%rbp)
movl -12(%rbp), %eax
movl %eax, %edx
leaq .LC0(%rip), %rcx
call printf
movl $0, %eax
addq $48, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0"
.def printf; .scl 2; .type 32; .endef
これを解読していく。
** 4. アセンブラファイルを解読する [#f3145372]
*** 基本的な各命令の説明 [#t7a1b2bb]
|~命令名|~意味|~備考|h
|mov|代入|mov 値 代入先|
|add|加算|add 数値 加算先|
|sub|減算|sub 数値 減算先|
|imul|乗算|imul 数値 乗算先|
|idiv|除算|idiv 数値 除算先|
|sal|左シフト(2倍)|sal シフト先|
|call|関数呼び出し|call 関数名|
|ret|返る|呼び出された前の処理の位置に戻る|
$(数値)は10進数の数字(普段使う数字)です。
つまり、$6 は 6 ということになります。
※2倍のときはimulではなくsalが使われるので注意
*** mainの処理 [#f5756dca]
上記の各命令の説明と照らし合わせて紐解きます。
LC0はprintの引数のラベルのようです
.LC0:
.ascii "%d\0"
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main: は main関数の中身となっている
前処理は省いて実際にmainの処理だけに注目してみた
# レジスタ-4に5を代入(a = 5)
movl $5, -4(%rbp)
# レジスタ-8に2を代入(b = 2)
movl $2, -8(%rbp)
# レジスタ-4から1を減算(a -= 1)
subl $1, -4(%rbp)
# レジスタ-8の値を左にシフト(b *= 2)
sall -8(%rbp)
# edxにレジスタ-4の値を代入
movl -4(%rbp), %edx
# eaxにレジスタ-8の値を代入
movl -8(%rbp), %eax
# eaxにedxの値を加算(a + b)
addl %edx, %eax
# eaxの値をレジスタ-12に代入(c = 加算結果)
movl %eax, -12(%rbp)
# レジスタ-12の値をeaxに代入
movl -12(%rbp), %eax
# eaxの値をedxに代入
movl %eax, %edx
leaq .LC0(%rip), %rcx
call printf
movl $0, %eax
addq $48, %rsp
popq %rbp
ret
** コメント [#mf183f2a]
#pctrlcmt
&size(10){キーワード: C言語, プログラミング言語, コンパイラ言語, アセンブリ言語, コンパイル, アセンブラ, 解読, 読み方};