1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

初心者がIntel x64アセンブリに入門する その2

Last updated at Posted at 2022-12-09

はじめに

この記事は低レイヤーについてほとんど知識がない人間がアセンブリ言語を学んでいく際のメモ書きです。学習の際には「低レベルプログラミング」という本を参考書にしています。今回は前回に学習したレジスタの理解を前提として、実際にアセンブリ言語を書いていきたいと思います。

目次

  • hello, world
  • プログラムの中身
    • コメント
    • セクション
    • ラベル
    • ディレクティブ
    • ディスクリプタ
      • 基本的な命令
      • mov命令
      • xor命令
    • システムコール
      • writeシステムコール
      • exitシステムコール

環境

  • os: Ubuntu 22.04 LTS (WSL2)
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
  • アセンブラ
$ nasm --version
NASM version 2.15.05

hello, world

とりあえず、はじめてのアセンブリプログラムということで、hello worldを出力してみる。

hello.asm
section .data
message: db 'hello, world!', 10

section .text
global _start
_start:
    mov rax, 1       ; システムコールの番号をraxに入れる
    mov rdi, 1       ; 1は書き込み先(descriptor)
    mov rsi, message ; messageは文字の先頭
    mov rdx, 14      ; 14は書き込むバイト数
    syscall          ; システムコールの呼び出し

    mov rax, 60      ; 60は'exit'のsyscall番号
    xor rdi, rdi
    syscall

実行するには、

  1. nasmによりアセンブリして
  2. ldでリンク
  3. 適切な実行権限を与える

という手順で実行できます。

$ nasm -felf64 hello.asm -o hello.o
$ ld -o hello hello.o
$ chmod u+x hello

実行結果は以下のようになるはずです。

$ ./hello
hello, world

プログラムの中身

コメント

アセンブリ言語ではセミコロンからその行の最後までがコメントとして扱われる。

セクション

ノイマン型コンピュータでは1つのメモリにコードとデータが書き込まれる。プログラムを作成する際には、この2つを別々に扱うためにプログラムを複数のセクションに分ける。

.textセクションには命令、.dataセクションにはグローバル変数(global variable)が記述される。

ラベル

可読性を上げるためにプログラマはラベルを使用することができる。ラベルによってアドレスに読みやすい名前を付けることができる。

プログラムでは

_start:

がそれにあたるが、この_startというラベルはアセンブリプログラムのエントリーポイントであり、1つのアセンブリプログラムに必ず設けられるラベルである(1つのアセンブリプログラムを複数のファイルに分けて場合には、その中の1つに_startラベルが設けられる)。

また、_startラベルはglobal宣言が必要である。プログラム内でも

global _start

として宣言されている。

ディレクティブ

global宣言、セクションなどの変数処理を制御するコマンドをディレクティブ(directive)と呼ぶ。

プログラムでは、先程のglobalsectionの他にdbというディレクティブも使用されていて、これはバイトデータを作成する際に使用される。

message: db 'hello, world!', 10

この, 10hello, worldにASCIIコードでの10の文字(改行\n)を足している。

データを作成する際は

  • db: バイト(1byte)
  • dw: ワード(2byte)
  • dd: ダブルワード(4byte)
  • dq: クアッドワード(8byte)

の4つのディレクティブが使用される。

ディスクリプタ

writeシステムコールの出力先を決めるのがディスクリプタ(descripter, 記述子)である。これはファイルを識別する番号で、中身は単なる整数である。基本的にはディスクリプタによって指定されたファイルはopenシステムコールを呼び出し明示的にファイルをオープンしなければならないが、stdinstdoutstderrの3つはプログラムが開始されると即座にオープンされ、プログラマ側が管理することはできない。それぞれのディスクリプタの値は以下の通りである。

  • stdin: 0
  • stdout: 1
  • stderr: 2

また、デフォルトではキーボード入力はstdinに、ターミナル出力はstdoutにリンクされている。

今回の場合、hello, worldという文字列は標準出力に書き込まないといけないので、ディスクリプタの値は1とする。

プログラムでいうと以下がその部分に該当する。

mov rdi, 1

基本的な命令

mov命令

mov命令はある値をレジスタやメモリに書き込むことができる。書き込む値は直接指定する以外に、他のレジスタやメモリから取ることもできる。

プログラムでは

mov rax, 1

などがそれにあたる。この場合、レジスタraxに直接1を書き込んでいる。

xor命令

xor命令は第一引数に対して第一引数と第二引数の排他的論理和をとって書き込む。

プログラムでは

xor rdi, rdi

がそれにあたる。rdi=xor(rdi, rdi)となっている。

システムコール

システムコール(system call)はsyscallにより実行することができる。その際はraxに入っているシステムコール番号のシステムコールが実行される。

システムコールの引数を設定するには順にrdirsirdxr10r9に値を格納する。

今回のプログラムでは

mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 14
syscall

という部分と

mov rax, 60
xor rdi, rdi
syscall

という部分がそれにあたる。

writeシステムコール

hello, worldという文字列を出力するにはwriteシステムコールを利用する。これは出力先が標準出力だろうが、標準エラー出力だろうが、ファイルだろうが同じである。そしてwriteシステムコールの番号は1であるためレジスタraxに格納しておく。

プログラムでいうと以下がその部分にあたる。

mov rax, 1

exitシステムコール

プログラムを正常に終了するためにexitシステムコールを実行する。

プログラムでは

mov rax, 60

がそれにあたり、raxexitシステムコール番号である60を代入することで実行している。

そして、exitシステムコールの引数は終了ステータスコードを指している。正常終了は0、一般的なエラーは1などである。

プログラムでは正常終了のステータスコードをrdiに格納するために

xor rdi, rdi

としている。排他的論理和は2つの値が同じ時、0になるので必然的にrdiには0が格納される。

つまり、

mov rdi, 0

とやっていることは同じになる。ただ、xorを使用することで命令が小さくなるため、xorを使用した書き方が一般的である。

ちなみにこのexitシステムコールが実行されないとプログラムは次の命令(writeシステムコールが呼び出された後にメモリセルに入っているランダムな値を読み込んで実行される命令)が実行され、意図していない命令が実行され、クラッシュする。

exitシステムコールを呼び出さないコードは以下のようになる。

hello_without_exit.asm
section .data
message: db 'hello, world!', 10

section .text
global _start
_start:
    mov rax, 1       ; システムコールの番号をraxに入れる
    mov rdi, 1       ; 1は書き込み先(descriptor)
    mov rsi, message ; messageは文字の先頭
    mov rdx, 14      ; 14は書き込むバイト数
    syscall          ; システムコールの呼び出し

これを以下のように実行する。

$ nasm -felf64 hello_without_exit.asm -o hello_without_exit.o
$ ld -o hello_without_exit hello_without_exit.o
$ chmod u+x hello_without_exit
$ ./hello_without_exit

すると、実行結果は

$ ./hello_without_exit
hello, world
zsh: segmentation fault  ./hello_without_exit

となって、セグフォしていることがわかる。

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?