これは東京高専プロコンゼミ① 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
