単に実行したディレクトリのファイル名を表示するだけのプログラムを asm で書いてみる。
普段全くディレクトリエントリの構造を意識しないけど、システムコールを直接呼ぼうと思うと意識せざるを得ない。
使うシステムコールは以下の通り
- open
- getdents
- close
- write (表示用)
- exit
libc レベルだと opendir/closedir というふうにディレクトリ対象の open 操作は分かれているので、システムコールもそうなのかと思っていたけど、そうではなく普通の open/close で統一されている。ディレクトリ内容を読むには readdir というシステムコールもあるが、getdents が現代版らしいので、最初からこちらを使う。
getdents
open/close はともかく、getdents の挙動を理解するのに苦労した。
struct linux_dirent {
unsigned long d_ino; /* Inode number */
unsigned long d_off; /* Offset to next linux_dirent */
unsigned short d_reclen; /* Length of this linux_dirent */
char d_name[]; /* Filename (null-terminated) */
/* length is actually (d_reclen - 2 -
offsetof(struct linux_dirent, d_name)) */
/*
char pad; // Zero padding byte
char d_type; // File type (only since Linux
// 2.6.4); offset is (d_reclen - 1)
*/
} 以上のような構造体を渡したバッファに書きこんでくれるのだけれど、なんで構造体の後ろのほうコメントアウトになってるの?って感じ。よく定義を読んだら 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