アセンブリ言語のお勉強
間違っている場所あったら優しく教えてね
C言語を(狭義の意味で)コンパイルして得られるアセンブリを理解してみましょう.
C言語でhello world
初めにhello worldをC言語で書いてアセンブリ言語に変換してみます
// hello.c #include <stdio.h> int main() { printf("hello world!\n"); return 0; }
$ gcc -S hello.c // hello.s が出来上がる
hello.s
何も省略せずにできあがったコードを載せると以下のようになります. (実行環境でこのコードは多少変化しそう.. )
.section __TEXT,__text,regular,pure_instructions .build_version macos, 10, 14 .globl _main ## -- Begin function main .p2align 4, 0x90 _main: ## @main .cfi_startproc ## %bb.0: pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq %rsp, %rbp .cfi_def_cfa_register %rbp subq $16, %rsp leaq L_.str(%rip), %rdi movl $0, -4(%rbp) movb $0, %al callq _printf xorl %ecx, %ecx movl %eax, -8(%rbp) ## 4-byte Spill movl %ecx, %eax addq $16, %rsp popq %rbp retq .cfi_endproc ## -- End function .section __TEXT,__cstring,cstring_literals L_.str: ## @.str .asciz "hello world!\n" .subsections_via_symbols
このコードの中から必要な部分だけ抽出していきます.
.cfiは不要
.cfiは .
から始まっている文なのでアセンブリ命令です. 例外処理などを行うためのものなので今回は必要ないので.cfi
から始まる一文は全て消去してしまいます .
##
から行末まではコメントとして扱われます.
上記のバージョンもいらにゃい!
.cfiに書いてあるオフセットの16も消しちゃえ!
整理した結果
.section __TEXT,__text,regular,pure_instructions .globl _main .p2align 4, 0x90 _main: pushq %rbp movq %rsp, %rbp leaq L_.str(%rip), %rdi movl $0, -4(%rbp) movb $0, %al callq _printf xorl %ecx, %ecx movl %eax, -8(%rbp) movl %ecx, %eax popq %rbp retq .section __TEXT,__cstring,cstring_literals L_.str: .asciz "hello world!\n"
短くなりました.これより文字の解読作業です. と言いたいがもう少し必要ない部分を消していききます
セクションを整理
コンパイラさんが変なセクションを付けてくださったために.section hogehoge
となっている.
ようわからんので.text
と.data
セクションに変換してしまいましょう. こんな感じに圧縮される.
$ gcc helllo.s $ ./a.out
上記コマンドで実行できるので, 逐一出力がhello world
になっているかチェックします.
.text .globl _main .p2align 4 _main: pushq %rbp movq %rsp, %rbp leaq L_.str(%rip), %rdi movl $0, -4(%rbp) movb $0, %al callq _printf xorl %ecx, %ecx movl %eax, -8(%rbp) movl %ecx, %eax popq %rbp retq .data L_.str: .asciz "hello world!\n"
hello worldを出力したいだけなのにこんなに長くなるもんなんですかね. . .
解析タイム
.text //以下のことはtextセクションに書きます .globl _main //_mainラベルはglobalにします .p2align 4 //アライメントを設定する
.text
セクションは実際に実行される機械語を格納する場所です. そこにglobal(初期に実行される)ラベルとして _mainを登録します. ここのラベルは固定です. gccで_main
ラベルがスタートと定めているようです.
.global _main
を消しても動かなかったため_main
はスターとの関数として定義して, globalにしなくてはアセンブルできないようです.
.p2align 4
これがアライメント規約を定めるためのアセンブリ命令です. 24 = 16bitごとにアライメントを(メモリの開始位置を指定しろと言っています.
みなさんお待ちかねのメイン関数(ラベル)です
_main: //1 pushq %rbp movq %rsp, %rbp leaq L_.str(%rip), %rdi movl $0, -4(%rbp) movb $0, %al //3 callq _printf //2 xorl %ecx, %ecx movl %eax, -8(%rbp) movl %ecx, %eax //1 popq %rbp retq .data
1.
ベースポインタに大切な情報が入っているかもしれないので退避しておいて実行したらまた戻す処理を行います.
pushq %rbp // スタックにベースぽポインタをいれる popq %rbp // スタックをベースポインタへ取り出す.
2.
ecxというのは, %rcxレジスタ(汎用レジスタ)の下32bit分を指しています. %rcxはカウンタレジスタで繰り返し回数を指定します. 今回は繰り返し処理がないので0で初期化します. (必要ないかも... )
xorl %ecx, %ecx // 排他的論理和を用いて%ecxの値を全て0にセットします.
3.
call です. 英語と同じ意味で関数の呼び出しを行いないます.
callq _printf // _printf を呼び出す.