単に実行したディレクトリのファイル名を表示するだけのプログラムを asm で書いてみる。
普段全くディレクトリエントリの構造を意識しないけど、システムコールを直接呼ぼうと思うと意識せざるを得ない。
使うシステムコールは以下の通り
- open
- getdents
- close
- write (表示用)
- exit
libc レベルだと opendir/closedir というふうにディレクトリ対象の open 操作は分かれているので、システムコールもそうなのかと思っていたけど、そうではなく普通の open/close で統一されている。ディレクトリ内容を読むには readdir というシステムコールもあるが、getdents が現代版らしいので、最初からこちらを使う。
getdents
open/close はともかく、getdents の挙動を理解するのに苦労した。
以上のような構造体を渡したバッファに書きこんでくれるのだけれど、なんで構造体の後ろのほうコメントアウトになってるの?って感じ。よく定義を読んだら d_name[] は文字列へのポインタではなく文字列そのものなので、ここは可変長になっていて、コメントアウトされている分は d_reclen から実際の位置を計算する必要があることがわかる。また、このシステムコールはバッファが許す限りこのエントリを連続で書いてくるので、d_reclen をポインタに足しながら全部読み出す必要がある。
挙動さえ理解できれば難しくないので、とりあえず C レベルで1回書いたほうが早かったかもしれない。
ソートとかしていないので、表示される順番は ls -1f したときと同じになる。
errno
libc レベルだと errno というグローバル変数にエラー番号が入るが、システムコールを直で呼ぶ場合、r0 にエラー番号の符号を反転させた値が返ってくる。
つまり、libc はシステムコールから負の値が返ってくると、符号を反転して errno にセットして、C レベルの関数では -1 を返すという挙動をするみたい。
コード全文
/*#!as --gstabs+ -o ls.o ls.s && ld -o ls -e _start ls.o && objdump -d -j .text -j .data ls && ./ls */ .global _start .macro sys_exit mov r7, $0x01 /* set system call number to 1 (exit) */ svc $0x00 /* supervisor call */ .endm O_RDONLY = 0x0000 .macro sys_open mov r7, $0x05 svc $0x00 /* supervisor call */ .endm .macro sys_write mov r7, $0x04 svc $0x00 /* supervisor call */ .endm .macro sys_close mov r7, $0x06 svc $0x00 /* supervisor call */ .endm .macro sys_getdents mov r7, $0x8d svc $0x00 /* supervisor call */ .endm .section .text _start: bl main /* not reached */ mov r0, $0xff sys_exit main: /* open */ ldr r0, =current_dir mov r1, #O_RDONLY sys_open cmp r0, $0x00 rsble r0, r0, #0 blle error mov v1, r0 1: /* getdents */ mov r0, v1 ldr r1, =dentry_buffer mov r2, #dentry_buffer_len sys_getdents cmp r0, $0x00 beq 2f mov v2, r0 /* read bytes */ rsblt r0, r0, #0 bllt error ldr v3, =dentry_buffer 3: ldrh v5, [v3, #8] /* linux_dirent d_reclen */ mov r0, $0x01 add r1, v3, #10 sub r2, v5, #12 sys_write mov r0, $0x0a push {r0} mov r0, $0x01 mov r1, sp mov r2, #1 sys_write pop {r0} sub v2, v2, v5 /* len -= d_reclen */ add v3, v3, v5 /* buffer += d_reclen */ cmp v2, $0x00 bne 3b b 1b 2: /* close */ mov r0, v1 sys_close mov r0, $0x00 /* set exit status to 0 */ sys_exit current_dir: .asciz "." .align 2 error: cmp r0, $0x00 moveq r0, $0x01 sys_exit .section .bss .align 2 buffer: .skip 4096 dentry_buffer: .skip 4096 dentry_buffer_len = . - dentry_buffer