アセンブリ言語で様々なアーキテクチャ向けのHello,Worldプログラムを書こう！という話。

[NetBSD Advent Calendar 2017](https://qiita.com/advent-calendar/2017/netbsd) 1日目の記事です。今日はアセンブリ言語でのHello,World.プログラムについて書こうと思います。

### アセンブリ言語でのHello,World.プログラム

[NetBSD](https://www.netbsd.org)をインストールすると、 `/usr/share/examples/asm` というディレクトリがあるということに気がつきます。
中身は何やらアセンブリ言語のプログラムのようです。

```sh
$ /usr/share/examples/asm
$ ls -R
Makefile.inc  README        hello/

./hello:
Makefile   powerpc.s
```

READMEには「ハハッ(甲高い声) このディレクトリには様々なプラットフォーム向けのアセンブリ言語サンプルが入っているよ！」(意訳)と書かれているようですが、どうみてもPowerPCのサンプルしか存在していない状態です...。

```
$ cat README
$NetBSD: README,v 1.2 2011/11/27 09:07:11 skrll Exp $

This directory contains example programs written in assembly language
for a variety of platforms.  They are intended to illustrate the
specific details of how to write assembly code on a given platform;
they are not supposed to teach assembly (although they might have this
side-effect).

If you want to build one of these example programs, you can "cp -rf"
the corresponding directory anywhere else where you have write
permissions and then issue a "make" within the directory.
```

`hello/powerpc.s` の中身はこんな感じ。write(2)システムコールを直接呼び出す形で"Hello, world!"を表示するという動作になっています。
とはいえ、Hello,World.を様々なプラットフォーム向けに最小限のアセンブリ言語で書くという、まさにNetBSD向けの題材にも思えます。

```asm
.section ".note.netbsd.ident", "a"
        # This ELF section is used by the kernel to determine, among other
        # things, the system call interface used by the binary.
        #
        # See http://www.netbsd.org/docs/kernel/elf-notes.html for more
        # details.

        .int 7                  # Length of the OS name field below.
        .int 4                  # Length of the description field below.
        .int 0x01               # The type of the note: NetBSD OS Version.
        .ascii  "NetBSD\0\0"    # The OS name, padded to 8 bytes.
        .int 0x23b419a0         # The description value; 5.99.56.

# ------------------------------------------------------------------------

.section ".data"

message:
        .ascii "Hello, world!\n"
        .set MESSAGE_SIZE, . - message

# ------------------------------------------------------------------------

.section ".text"

        .balign 4

        .globl _start
        .type _start, @function
_start:
        # write(STDOUT_FILENO, message, MESSAGE_SIZE)
        li      %r0, 4                  # r0: write(2) syscall number.
        li      %r3, 1                  # r3: first argument.
        addis   %r4, %r0, message@h     # r4: second argument.
        ori     %r4, %r4, message@l
        li      %r5, MESSAGE_SIZE       # r5: third argument.
        sc

        # exit(EXIT_SUCCESS)
        li      %r0, 1                  # r0: exit(2) syscall number.
        li      %r3, 0                  # r3: first argument.
        sc

    .size _start, . - _start
```

### i386アーキテクチャでのアセンブリ言語によるHello,World.

某所で「[大熱血！アセンブラ入門 読書会](https://hotasm.connpass.com)」のお手伝いをさせていだいているという経緯もあり、自分の分かる範囲でサンプルを増やしたいところです。
と思っていたら、かなり前にi386アーキテクチャ向けに上記のプログラムを移植したのを忘れていました...。

 * [furandon-pig/fpig_sample/hello/nbsd](https://github.com/furandon-pig/fpig_sample/tree/master/hello/nbsd)

i386アーキテクチャ向けのHello,World.は以下になります。やっていることは `write(2)` で"Hello,World."を出力し、 `exit(3)` で終了しているだけです。

```
 .section ".note.netbsd.ident", "a"
        # This ELF section is used by the kernel to determine, among other
        # things, the system call interface used by the binary.
        #
        # See http://www.netbsd.org/docs/kernel/elf-notes.html for more
        # details.

        .int 7                  # Length of the OS name field below.
        .int 4                  # Length of the description field below.
        .int 0x01               # The type of the note: NetBSD OS Version.
        .ascii  "NetBSD\0\0"    # The OS name, padded to 8 bytes.
        .int 0x23b419a0         # The description value; 5.99.56.

# ------------------------------------------------------------------------

.section ".data"

message:
        .ascii "Hello, world!\n"
        .set MESSAGE_SIZE, . - message

# ------------------------------------------------------------------------

.section ".text"

        .balign 4

        .globl _start
        .type _start, @function
_start:
        # write
        movl    $MESSAGE_SIZE, 0xc(%esp)
        movl    $message, 0x8(%esp)
        movl    $0x1, 0x4(%esp)
        mov     $0x4, %eax
        int     $0x80
        # exit(EXIT_SUCCESS)
        movl    $0x0,(%esp)
        mov     $0x1,%eax
        int     $0x80
        ret

    .size _start, . - _start
```

実行例は以下になります。

```
$ uname -a 
NetBSD vmnbsd71 7.1 NetBSD 7.1 (GENERIC.201703111743Z) amd64
$ gcc -m32 -nostdlib i386.s 
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for NetBSD 5.99.56, not stripped
$ ./a.out 
Hello, world!
```

### amd64アーキテクチャでも動かしたい！

これで今日のAdvent Calendarに間にあわせることができましたぞ、と言いたいところですが、64bit環境(amd64)でも動かしたいのが人情というものです。

(なにも考えずに)そのままアセンブルして動かすと、もちろんクラッシュします。`rip` レジスタの指しているアドレスは `0x400100` で `_start` の開始アドレスなので、実行即クラッシュという状態ですね...。
なんでクラッシュするの→64bitでの引数渡しはスタックじゃなくてレジスタを使うから、この呼び出し方だと変なレジスタに変な値が入った状態でシステムコールが呼ばれているからだよね...という話なので、64bit環境向けにも別途Hello,World.プログラムを作成したいものです。

```
$ gcc -nostdlib i386.s
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for NetBSD 5.99.56, not stripped
$ gdb a.out
GNU gdb (GDB) 7.7.1
...
(gdb) run
Starting program: /home/fpig/work/advcal/2017/1201/fpig_sample/hello/nbsd/a.out 

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400100 in _start ()
(gdb) bt
#0  0x0000000000400100 in _start ()
(gdb) info register
rax            0x0      0
rbx            0x7f7fffffffe0   140187732541408
rcx            0x400100 4194560
rdx            0x0      0
rsi            0x0      0
rdi            0x0      0
rbp            0x0      0x0
rsp            0x7f7fffffdb88   0x7f7fffffdb88
r8             0x0      0
r9             0x0      0
r10            0x7f7ff6c83a0a   140187577891338
r11            0x202    514
r12            0x74c968 7653736
r13            0x74a008 7643144
r14            0x74d188 7655816
r15            0x74d208 7655944
rip            0x400100 0x400100 <_start>
eflags         0x10202  [ IF RF ]
cs             0x47     71
ss             0x3f     63
ds             0x3f     63
es             0x3f     63
fs             0x0      0
gs             0x0      0
(gdb) 
```

## フィーリングでシステムコールの呼び出し方を調べる

[大熱血！アセンブラ入門](http://www.shuwasystem.co.jp/products/7980html/5154.html)にある熱血バイナリアン十訓の中に、「わからなくても気にせず読め！」という項目があります。
12月1日も終わりつつあるなか、NetBSD-amd64環境でアセンブリ言語からシステムコールを呼び出す方法を今から(！)調べるのはなかなか無茶がありますが、熱血バイナリアン十訓の教えに従い、がんばって調べてみます。

### exit(3)で試してみる。

話を簡単にするため、引数を持たない `exit(3)` を64bit環境で呼び出すという例で考えてみます。
まずはi386向けのアセンブリで `exit(3)` を呼ぶだけのプログラムにしてみます。

```
.section ".text"

        .balign 4

        .globl _start
        .type _start, @function
_start:
        # exit(EXIT_SUCCESS)
        movl    $0x0,(%esp)
        mov     $0x1,%eax
        int     $0x80
        ret
```

もちろんクラッシュします。

```
$ gcc -nostdlib i386_exit.s 
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for NetBSD 5.99.56, not stripped
$ gdb ./a.out
GNU gdb (GDB) 7.7.1
...
(gdb) rn
Target exec does not support this command.
(gdb) run
Starting program: /home/fpig/work/advcal/2017/1201/fpig_sample/hello/nbsd/a.out 

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400100 in _start ()
```

ではどうやって `exit(3)` を呼び出すのだろうという話ですが、ここは一つ、実際に `exit(3)` を読んでいる箇所から調べてみましょう。
libcのライブラリであれば必ず `exit(3)` を呼び出している箇所があるはずなので、それを手がかりにしてみます。

```
$ objdump -d /lib/libc.so
...
000000000010e6c0 <_Exit>:
  10e6c0:       b8 01 00 00 00          mov    $0x1,%eax
  10e6c5:       49 89 ca                mov    %rcx,%r10
  10e6c8:       0f 05                   syscall 
  10e6ca:       c3                      retq   
  10e6cb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

000000000010e6d0 <_lwp_self>:
```

なにやらそれっぽい箇所があります。 `eax` レジスタに入れるのはシステムコール種別のようなので、 `esp` レジスタが指している(スタック領域として使っているメモリ)に入れていた値を `rcx` `r10` で渡せば良さそうです。
さっそく以下のように書き換えて試してみます。

```
_start:
        # exit(EXIT_SUCCESS)
        mov     $0x1,%eax
        mov     $0x7,%rcx
        mov     %rcx,%r10
        syscall
        retq
```

今度はクラッシュせずに動作しました！ ただ、確認のため終了ステータスを `7` としたつもりなのですが、 "exited normally"と言われているので、引数の渡し方がまだちょっと間違っているようです。
でもとりあえずは64bit環境でシステムコールが呼べるようになったので、引数の渡し方についてはおいおい調べて行こうと思います。

```
$ gcc -nostdlib i386_exit.s 
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for NetBSD 5.99.56, not stripped
$ gdb a.out
GNU gdb (GDB) 7.7.1
...
(gdb) run
Starting program: /home/fpig/work/advcal/2017/1201/fpig_sample/hello/nbsd/a.out 
[Inferior 1 (process 25952) exited normally]
```

### まとめ

NetBSDの `/usr/share/examples/asm` ディレクトリに置かれている、アセンブリ言語でのHello,World.プログラムについて紹介しました。
様々なアーキテクチャ向けのHello,World.プログラムはNetBSDに向いている題材だと思うので、ぜひサンプルを増やして行きたいところです。