単に実行したディレクトリのファイル名を表示するだけのプログラムを 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)
    */

}
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/getdents.2.html

以上のような構造体を渡したバッファに書きこんでくれるのだけれど、なんで構造体の後ろのほうコメントアウトになってるの?って感じ。よく定義を読んだら 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
  1. トップ
  2. tech
  3. ARM Linux EABI の asm で簡単な ls を作る