これは東京高専プロコンゼミ① Advent Calendar 2022の2日目の記事です.
hello world
アセンブラでhello worldして,コンピュータのもっと奥深くへと続く階段を降り始めましょう!
x86 GNU Assembler (GAS)
x86 GNU Assembler
(x86 GAS)というのはGNUコンパイラ
(gccとか)で使われるx86
というCPUシリーズのアセンブリです
(この記事ではx86-64です)
code
まずはコードを見てください
.intel_syntax noprefix
.global main
main:
mov rax, 1 # write
mov rdi, 1 # std 1
lea rsi, _helloworld # string address
mov rdx, 10 # length
syscall
mov rax, 60 # exit
mov rdi, 0
syscall
.data
_helloworld:
.asciz "helloworld"
$gcc -c hello.s -o hello.o && ld -e main -o hello.bin hello.o
ディレクティブ
上から見ていきます.
.intel_syntax noprefix
.global main
この二つの行ではgccコンパイラに情報を渡しています.
一行目ではintel構文
でアセンブラを描くことをコンパイラに宣言しています.
二行目ではmainから処理が始まるということを教えています.
このようにコンパイラにいろいろやってくれるように頼む指令のことをディレクティブ
(directives)といいます.
ディレクティブは先頭に.
が付きます.
ラベル
main:
mov rax, 1 # write
mov rdi, 1 # std 1
lea rsi, _helloworld # string address
ここで新しく出てきたのがラベル
です.
main:
これがラベル
です.ここではラベル
を宣言しています.
実そしてここではもう一つラベル
が出てきています.
lea rsi, _helloworld # string address
ここです.ここではラベル
を使っています.
このラベル
の宣言はもっと後ろでされています.
.data
_helloworld:
.asciz "helloworld"
ここです.
ラベル
を宣言するときは:
をラベルの名前の後につけます.
ラベル
を使うときはラベル名
を以下のように使えばいいだけです.
lea rsi, _helloworld # string address
ラベル
というのは場所の名前みたいなもので,ラベル
が宣言されている場所のアドレス
が紐図けられています.
人間がコードを見たり書いたりするときに分かりやすくするためのもので,コンパイラがコンパイル時にラベル
をアドレス
に置き換えてくれます.
レジスタ
main:
mov rax, 1 # write
mov rdi, 1 # std 1
lea rsi, _helloworld # string address
mov rdx, 10 # length
これらの行ではレジスタ
と呼ばれるCPU内部の記憶装置に値を入れています.
レジスタの一覧
命令
このプログラムで出てくる命令は3種類しかないです.
-
mov
左辺に指定したレジスタに右辺の値を入れる命令です.
mov rax, 1
-
lea
左辺に指定したレジスタに右辺の示すアドレスを入れる命令です.
lea rsi, _helloworld #_helloworldがさすアドレスがrsiジスタに入る.
syscall
syscall
main:
mov rax, 1 # write
mov rdi, 1 # std 1
lea rsi, _helloworld # string address
mov rdx, 10 # length
syscall
ここではsyscall
と呼ばれる.OSが用意してくれているサブルーチンを使うことのできるアセンブリ命令を使っています.
syscallを使うにはraxに使いたいsyscallの番号を入れその他のレジスタに引数を渡します.
syscall番号や引数は以下のリンクを確認してください
syscall一覧
mov rax, 1 # write
mov rdi, 1 # std 1
lea rsi, _helloworld # string address
mov rdx, 10 # length
ここではraxにwrite
のsyscall番号である1
を入れて,write
を使って標準出力しています.
write
の引数は以下の通りです.
rdi | rsi | rdx |
---|---|---|
ファイルディスクリプタ(1で標準出力) | 文字列の先頭アドレス | 文字列の長さ(byte数) |
syscall
レジスタにsyscall番号,引数を入れたのちsyscall
命令を実行することでsyscall
を呼び出せます.
mov rax, 60 # exit
mov rdi, 0
syscall
ここでのsyscallはプログラムを終了するためexit
を呼んでいます.
文字列の確保
.data
_helloworld:
.asciz "helloworld"
ここではディレクティブ
を使っています.
まず.data
でこれより下がデータセクションであることをコンパイラに知らせています.
そしてバイナリ中に文字列を埋め込んでくれる.asciz
というディレクティブ
により,helloworld
という文字列をバイナリ中に埋め込んでいます.
またここではラベル
を宣言しています.
_helloworld:
このラベル
には.ascii
で確保した文字列の先頭アドレスが入っています.
このラベルはmainの
mov rax, 1 # write
mov rdi, 1 # std 1
lea rsi, _helloworld # string address
mov rdx, 10 # length
ここで使われています.
コンパイル・リンク
$gcc -c hello.s -o hello.o && ld -e main -o hello.bin hello.o
GAS
なのでgcc
でコンパイルしてください.
gcc
でコンパイルだけしているのは普通にコンパイルするとコンパイラがいろいろやってくれるので-c
でコンパイルだけしてld
でリンクしています(今回はどっちでもいいけどバイナリを覗くときに分かりやすい)
ld
の-e main
は最初に実行し始めるところ(エントリーポイント
)にmain
を指定しています.
$./hello.bin
生成された実行形式のファイルを覗いてみましょう.
$objdump -d hello.bin
Hello Computer
おわりに
ありがとうございました.誤り等はコメントで教えてください.
これは東京高専プロコンゼミ① Advent Calendar 2022の2日目の記事です.
このカレンダー,めちゃくちゃ開いていたので急遽日が変わる30分くらい前に投稿するために記事を書き始めました.よって案の定遅刻しています.ゆるして・・・
リンク集
- Syscall一覧↓
- brkの使いかた,アセンブラの入門↓
- アセンブラ入門(少し古め,けどわかりやすい)↓
- 命令一覧↓
- Directive 一覧↓
- 汎用レジスタ一覧↓
- Opcode/Instruction finder for x86_64