プログラミング学習日記

プログラミング学習時のメモ帳。

アセンブリ言語のお勉強

間違っている場所あったら優しく教えてね

C言語を(狭義の意味で)コンパイルして得られるアセンブリを理解してみましょう.

C言語hello world

初めにhello worldC言語で書いてアセンブリ言語に変換してみます

// 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 を呼び出す.

参考

Linuxでx86アセンブラ(論理演算編) - Qiita

Linuxでx86アセンブラ(論理演算編) - Qiita

ブログを見ていただきありがとうございました