前文
たとえ全く使ったことがない言語であっても、それがスクリプト言語であれば1日もかからない内容をアセンブリ言語で実装するということは、時間リソースを大変富豪的に使うプログラミングである。
レポジトリ
仕様
あまり仕様が多いとつらいので絞りに絞り以下だけ実装することにした
- インデックスでのリスト表示
- パーマリンクページ表示
制限事項など
- たどるディレクトリは1階層だけに制限
- エントリファイルサイズには制限はない
- テンプレートファイルサイズは4KBまで
- ファイル数に制限はない
- テンプレートはhtml 1種類のみ (flavour 機能はない)
- プラグイン機能もない
設計
せっかくなので覚えたことを使おうと以下のようにしてある。大変メモリとシステムコールを富豪的に使う
- エントリのファイル名などを持つ構造体の配列は brk で段々広げてヒープ領域に入れる
- ファイル本文・タイトルは mmap で確保した領域に入れる
- すなわちどんなに小さいファイルでも 4KB 確保される
- 直接ファイルを mmap してないのは後述
コード全文
/*#!as --gstabs+ -o blosxasm.o blosxasm.s && ld -o blosxasm -e _start blosxasm.o && objdump -d -j .text -j .data blosxasm && ./blosxasm */ .global _start .macro sys_exit mov r7, $0x01 /* set system call number to 1 (exit) */ svc $0x00 /* supervisor call */ .endm .macro sys_read mov r7, $0x03 svc $0x00 /* supervisor call */ .endm .macro sys_write mov r7, $0x04 svc $0x00 /* supervisor call */ .endm O_RDONLY = 0x0000 .macro sys_open mov r7, $0x05 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 .macro sys_stat mov r7, $0x6a svc $0x00 /* supervisor call */ .endm .macro sys_brk mov r7, $0x2d svc $0x00 /* supervisor call */ .endm PROT_NONE = 0x00 PROT_READ = 0x01 PROT_WRITE = 0x02 PROT_EXEC = 0x04 MAP_ANONYMOUS = 0x20 MAP_PRIVATE = 0x02 .macro sys_mmap mov r7, $0xc0 /* sys_mmap_pgoff */ svc $0x00 /* supervisor call */ .endm .macro sys_munmap mov r7, $0x5b svc $0x00 /* supervisor call */ .endm .macro sys_mremap mov r7, $0xa3 svc $0x00 /* supervisor call */ .endm .section .data brk: .word 0 config: var_title: .ascii "blosxasm-arm-linux-eabi" var_title_len = . - var_title .align 2 var_home: .ascii "/blosxasm.cgi/" var_home_len = . - var_home .align 2 var_data_dir: .asciz "data/" .align 2 var_head_path: .asciz "head.html" .align 2 var_story_path: .asciz "story.html" .align 2 var_foot_path: .asciz "foot.html" .align 2 .section .text _start: mov lr, #0 /* r0 = argc (not used) */ ldr r0, [sp] /* r1 = argv (not used) */ add r1, sp, $0x04 /* r2 = argc * 4 (skip argv) */ mov r3, $0x04 mul r2, r0, r3 /* skip null word */ add r2, r2, $0x04 add r2, r1 /* save char** environ */ ldr r0, =environ str r2, [r0] bl main mov r0, $0xff sys_exit main: adr r0, PATH_INFO bl getenv cmp r0, $0x00 /* if env is not set */ adreq r0, PATH_INFO_default mov v5, r0 /* v5 = PATH_INFO */ bl strlen cmp r0, $0x00 /* if env is set but empty */ adreq v5, PATH_INFO_default ldr r0, =var_head_path bl template /** * append entry to brk (like dynamic array) */ mov r0, $0x00 bl sbrk mov v1, r0 /* v1 = first brk */ ldr r0, =var_data_dir ldr r1, =file_callback bl dentries /** * loop each entry */ ldr v2, =entries_count ldr v2, [v2] /* v2 = entry count */ ldr v3, =current_entry /* v3 = pointer to current entry address */ main_entry_loop: str v1, [v3] /* findstr(entry.name, path_info+1, strlen(path_info)-1) */ mov r0, v5 bl strlen sub r2, r0, #1 add r0, v1, #entry_path add r1, v5, #1 bl findstr cmp r0, $0x00 ldreq r0, =var_story_path bleq template sub v2, v2, $0x01 cmp v2, $0x00 add v1, v1, #entry_buffer_len bne main_entry_loop ldr r0, =var_foot_path bl template mov r0, $0x00 /* set exit status to 0 */ ldr r1, =buffer sys_exit file_callback: stmfd sp!, {r1-r3, v1-v5, lr} mov v1, r0 /* v1 = name */ mov v2, r1 /* v2 = name_len */ /* skip . files */ ldrb r0, [v1] cmp r0, #'. bleq 1f mov r0, v1 bl read_entry 1: ldmfd sp!, {r1-r3, v1-v5, pc} USER: .asciz "USER" .align 2 PATH_INFO: .asciz "PATH_INFO" .align 2 PATH_INFO_default: .asciz "/" .align 2 error: cmp r0, $0x00 moveq r0, $0x01 sys_exit divmod: /* uint numerator, uint devider -> quo, rem */ stmfd sp!, {v1-v5, lr} mov v1, r0 /* num */ mov v2, r1 /* div */ mov r0, $0x00 /* quo */ mov r1, $0x00 /* rem */ mov r2, #32 /* i */ 1: sub r2, r2, $0x01 /* rem = rem << 1*/ mov r1, r1, LSL #1 /* num >> i */ mov r3, v1, LSR r2 /* num & 1 */ and r3, r3, #1 /* rem[0] = num[i] */ orr r1, r1, r3 /* rem >= div */ cmp r1, v2 subge r1, r1, v2 movge r3, #1 orrge r0, r0, r3, LSL r2 cmp r2, $0x00 bne 1b ldmfd sp!, {v1-v5, pc} base10: /* int numerator, char* buffer -> int length */ stmfd sp!, {v1-v5, lr} mov v1, r1 mov v2, $0x00 /* length */ 1: mov r1, #10 bl divmod push {r1} /* for getting digit from top */ add v2, v2, $0x01 cmp r0, $0x00 bne 1b mov r2, v2 2: sub r2, r2, $0x01 pop {r0} add r0, r0, $0x30 strb r0, [v1], $0x01 cmp r2, $0x00 bne 2b mov r0, $0x00 strb r0, [v1] mov r0, v2 ldmfd sp!, {v1-v5, pc} base16: /* int numerator, char* buffer -> int length */ stmfd sp!, {v1-v5, lr} mov v1, r1 mov v2, $0x00 /* length */ 1: and r1, r0, $0x0f cmp r1, $0x09 addle r1, r1, $0x30 addgt r1, r1, $0x57 push {r1} add v2, v2, $0x01 mov r0, r0, LSR #4 cmp r0, $0x00 bne 1b mov r2, v2 2: sub r2, r2, $0x01 pop {r0} strb r0, [v1], $0x01 cmp r2, $0x00 bne 2b mov r0, $0x00 strb r0, [v1] mov r0, v2 ldmfd sp!, {v1-v5, pc} strncmp: /* char* s1, char* s2, size_t len -> 1|0 */ stmfd sp!, {v1-v5, lr} mov r3, $0x00 /* result */ 1: cmp r2, $0x00 beq 2f /* if (r2 == 0) goto 2 */ sub r2, r2, $0x01 /* len-- */ ldrb r4, [r0], $0x01 ldrb r5, [r1], $0x01 cmp r4, r5 addne r3, $0x01 beq 1b 2: mov r0, r3 ldmfd sp!, {v1-v5, pc} strlen: /* char* str -> uint */ stmfd sp!, {r1-r2, lr} mov r1, $0x00 /* r2 = *str++ (ldrb = load byte, and r0 increment after) */ 1: ldrb r2, [r0], $0x01 cmp r2, $0x00 addne r1, r1, $0x01 /* if (r2 != 0) r1++ */ bne 1b /* if (r2 != 0) goto 1; */ mov r0, r1 ldmfd sp!, {r1-r2, pc} strcpy: /* char* dest, char* src -> dest */ stmfd sp!, {r0-r2, lr} 1: ldrb r2, [r1], $0x01 strb r2, [r0], $0x01 cmp r2, $0x00 bne 1b ldmfd sp!, {r0-r2, pc} strcat: /* char* dest, char* src -> dest */ stmfd sp!, {r0-r2, v1-v5, lr} 1: ldrb r2, [r0], $0x01 cmp r2, $0x00 bne 1b sub r0, r0, $0x01 2: ldrb r2, [r1], $0x01 strb r2, [r0], $0x01 cmp r2, $0x00 bne 2b ldmfd sp!, {r0-r2, v1-v5, pc} findstr: /* char* str, char* search, int len_of_search -> uint */ stmfd sp!, {v1-v5, lr} mov v1, r0 mov v2, r1 mov v3, r2 mov v4, v1 /* save original address */ cmp v3, $0x00 moveq r0, $0x00 ldmeqfd sp!, {v1-v5, pc} 1: ldrb r0, [v1] cmp r0, $0x00 beq 2f mov r0, v1 mov r1, v2 mov r2, v3 bl strncmp cmp r0, $0x00 addne v1, $0x01 bne 1b sub r0, v1, v4 ldmfd sp!, {v1-v5, pc} 2: mov r0, #-1 ldmfd sp!, {v1-v5, pc} sbrk: /* uint size -> void* */ push {lr} ldr r3, =brk /* r3 = prev_brk */ ldr r1, [r3] cmp r1, $0x00 /* if prev_brk == 0 */ bleq sbrk_init add r0, r0, r1 sys_brk cmp r0, r1 blt sbrk_nomem /* curr_brk == prev_brk */ str r0, [r3] /* update heap_start */ mov r0, r1 pop {pc} sbrk_init: push {r0} mov r0, $0x00 sys_brk mov r1, r0 pop {r0} mov pc, lr sbrk_nomem: mov r0, $0x00 pop {pc} getenv: /* char* name -> char* */ stmfd sp!, {v1-v5, lr} /* v1 = name */ mov v1, r0 /* v2 = strlen(r0) */ bl strlen mov v2, r0 /* v3 = environ char** */ ldr v3, =environ ldr v3, [v3] 1: /* if (strncmp(name, *environ, len) == 0) { */ mov r0, v1 ldr r1, [v3] mov r2, v2 bl strncmp cmp r0, $0x00 /* if (*environ)[len] == '=') { */ ldreq r0, [v3] ldreqb r0, [r0, v2] cmpeq r0, #'= beq 2f /* } */ /* environ++ */ add v3, $0x04 /* *environ != NULL */ ldr r1, [v3] cmp r1, $0x00 bne 1b /* not found return NULL */ mov r0, $0x00 ldmfd sp!, {v1-v5, pc} 2: /* found and return address */ ldreq r0, [v3] add r0, r0, v2 add r0, r0, $0x01 /* skip '=' */ ldmfd sp!, {v1-v5, pc} dentries: /* char* path, (void)(callback(name, name_len)) */ stmfd sp!, {r1-r3, v1-v5, lr} mov ip, r1 /* open */ 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 */ add r0, v3, #10 mov r1, v5 sub r1, r1, #12 blx ip /* callback */ 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 ldmfd sp!, {r1-r3, v1-v5, pc} read_stat: /* char* name */ stmfd sp!, {r0-r3, v1-v5, lr} ldr r1, =stat_buffer sys_stat cmp r0, $0x00 rsblt r0, r0, #0 bllt error ldmfd sp!, {r0-r3, v1-v5, pc} read_entry: /* char* name, int name_len */ stmfd sp!, {r1-r3, v1-v5, lr} mov v1, r0 /* expand heap */ mov r0, #entry_buffer_len bl sbrk mov v3, r0 /* v3 = entry_address */ /* copy path */ add r0, v3, #entry_path mov r1, v1 bl strcpy /* r0 = ["data/" + name] */ ldr r0, =buffer ldr r1, =var_data_dir bl strcpy mov r1, v1 bl strcat mov v1, r0 /* v1 = path adr */ mov r0, v1 bl read_stat /* copy mtime */ ldr r0, =st_mtime ldr r0, [r0] str r0, [v3, #entry_mtime] ldr v2, =st_size ldr v2, [v2] /* v2 = file size */ push {r4, r5} /* mmap for file contents */ mov r0, #0 mov r1, v2 mov r2, #PROT_READ orr r2, r2, #PROT_WRITE mov r3, #MAP_ANONYMOUS orr r3, r3, #MAP_PRIVATE mov r4, #-1 mov r5, #0 sys_mmap cmn r0, #4096 rsbhs r0, r0, #0 blhs error mov r3, r0 /* r3 = mmapped address */ pop {r4, r5} str r3, [v3, #entry_title] /* open */ mov r0, v1 mov r1, #O_RDONLY sys_open cmp r0, $0x00 rsble r0, r0, #0 blle error mov v5, r0 /* v5 = fd */ /* read */ 1: mov r0, v5 mov r1, r3 mov r2, #4096 sys_read cmp r0, $0x00 rsblt r0, r0, #0 bllt error add r1, r0 bne 1b /* close */ mov r0, v5 sys_close /* find first \n */ mov r0, r3 2: ldrb r1, [r0], $0x01 cmp r1, $0x0a cmpne r1, $0x00 bne 2b /* replace first \n to \0 to splitting title and body */ mov r1, $0x00 strb r1, [r0, #-1] str r0, [v3, #entry_body] ldr r0, =entries_count ldr r1, [r0] add r1, r1, $0x01 str r1, [r0] ldmfd sp!, {r1-r3, v1-v5, pc} template: /* char* name */ stmfd sp!, {r0-r3, v1-v5, lr} /* open() */ mov r1, #O_RDONLY sys_open cmp r0, $0x00 beq error mov v1, r0 /* v1 = fd */ ldr v2, =buffer /* v2 = buffer */ mov r1, v2 mov r2, #4096 sys_read mov v3, r0 /* v3 = length */ mov r0, $0x00 strb r0, [v2, v3] /* null terminate */ 1: mov r0, v2 adr r1, open_variable mov r2, #open_variable_len bl findstr /* check found */ cmp r0, #-1 beq 2f /* output prev chars */ mov r2, r0 /* set output length */ mov r0, $0x01 mov r1, v2 sys_write /* increment buffer */ add v2, v2, r2 add v2, v2, #open_variable_len /* find close */ mov r0, v2 adr r1, close_variable mov r2, #close_variable_len bl findstr /* check found */ cmp r0, #-1 beq 2f mov v5, r0 /* variable name length */ /* replaces */ /* replace title */ mov r0, v2 adr r1, variable_name_blogtitle mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_blogtitle beq 3f /* replace home */ mov r0, v2 adr r1, variable_name_home mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_home beq 3f /* replace path */ mov r0, v2 adr r1, variable_name_path mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_path beq 3f /* replace title */ mov r0, v2 adr r1, variable_name_title mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_title beq 3f /* replace body */ mov r0, v2 adr r1, variable_name_body mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_body beq 3f /* replace time */ mov r0, v2 adr r1, variable_name_time mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_time beq 3f /* replace environ */ mov r0, v2 adr r1, variable_name_environ mov r2, v5 bl strncmp cmp r0, $0x00 bleq variable_environ beq 3f 3: /* increment buffer */ add v2, v2, v5 add v2, v2, #close_variable_len ldrb r0, [v2] cmp r0, $0x00 bne 1b 2: /* output rest */ ldr r2, =buffer sub r2, v2, r2 sub r2, v3, r2 mov r0, $0x01 mov r1, v2 sys_write /* close() */ mov r0, v1 sys_close ldmfd sp!, {r0-r3, v1-v5, pc} open_variable: .ascii "#{" open_variable_len = . - open_variable .align 2 close_variable: .ascii "}" close_variable_len = . - close_variable .align 2 variable_name_blogtitle: .asciz "blogtitle" .align 2 variable_name_home: .asciz "home" .align 2 variable_name_title: .asciz "title" .align 2 variable_name_body: .asciz "body" .align 2 variable_name_path: .asciz "path" .align 2 variable_name_time: .asciz "time" .align 2 variable_name_environ: .asciz "environ" .align 2 variable_blogtitle: stmfd sp!, {r0-r3, lr} mov r0, $0x01 ldr r1, =var_title mov r2, #var_title_len sys_write ldmfd sp!, {r0-r3, pc} variable_home: stmfd sp!, {r0-r3, lr} mov r0, $0x01 ldr r1, =var_home mov r2, #var_home_len sys_write ldmfd sp!, {r0-r3, pc} variable_path: stmfd sp!, {r0-r3, v1-v5, lr} ldr r0, =current_entry ldr r0, [r0] add r1, r0, #entry_path bl strlen mov r2, r0 mov r0, $0x01 sys_write ldmfd sp!, {r0-r3, v1-v5, pc} variable_title: stmfd sp!, {r0-r3, v1-v5, lr} ldr r0, =current_entry ldr r0, [r0] add r0, r0, #entry_title ldr r0, [r0] mov r1, r0 bl strlen mov r2, r0 mov r0, $0x01 sys_write ldmfd sp!, {r0-r3, v1-v5, pc} variable_body: stmfd sp!, {r0-r3, v1-v5, lr} ldr r0, =current_entry ldr r0, [r0] add r0, r0, #entry_body ldr r0, [r0] mov r1, r0 bl strlen mov r2, r0 mov r0, $0x01 sys_write ldmfd sp!, {r0-r3, v1-v5, pc} variable_time: stmfd sp!, {r0-r3, v1-v5, lr} ldr r0, =current_entry ldr r0, [r0] add r0, r0, #entry_mtime ldr r0, [r0] ldr r1, =buffer bl base10 mov r2, r0 mov r0, $0x01 ldr r1, =buffer sys_write ldmfd sp!, {r0-r3, v1-v5, pc} variable_environ: stmfd sp!, {r0-r3, v1-v5, lr} ldr r0, =environ ldr r0, [r0] ldr r1, =buffer bl base16 mov r2, r0 mov r0, $0x01 ldr r1, =buffer sys_write ldmfd sp!, {r0-r3, v1-v5, pc} .section .bss .align 2 environ: .word 0 buffer: .skip 4096 dentry_buffer: .skip 4096 dentry_buffer_len = . - dentry_buffer stat_buffer: /* /usr/include/arm-linux-gnueabihf/asm/stat.h */ st_dev: .skip 4 st_ino: .skip 4 st_mode: .skip 2 st_nlink: .skip 2 st_uid: .skip 2 st_gid: .skip 2 st_rdev: .skip 4 st_size: .skip 4 st_blksize: .skip 4 st_blocks: .skip 4 st_atime: .skip 4 st_atime_nsec: .skip 4 st_mtime: .skip 4 st_mtime_nsec: .skip 4 st_ctime: .skip 4 st_ctime_nsec: .skip 4 .skip 4 .skip 4 .align 2 stat_buffer_len = . - stat_buffer entry_buffer: /* not used: just calculate offsets */ entry_buffer_path: .skip 256 entry_buffer_mtime: .skip 4 entry_buffer_title: .skip 4 entry_buffer_body: .skip 4 .align 2 entry_buffer_len = . - entry_buffer entry_path = entry_buffer_path - entry_buffer entry_mtime = entry_buffer_mtime - entry_buffer entry_title = entry_buffer_title - entry_buffer entry_body = entry_buffer_body - entry_buffer entries_count: .word 0 current_entry: .word 0
全体の流れ
_start
エントリポイント、スタックポインタから環境変数のアドレスを計算して初期化する処理だけして main を呼ぶ
main
全体の流れが書いてあるサブルーチン
- PATH_INFO の初期化
- ヘッダの出力 (template サブルーチンを呼ぶ)
- brk を準備してエントリリストを構築 (file_callback を渡して dentries サブルーチンを呼ぶ)
- (ソートをやるつもりで配列にしてあるけどめんどくて実装してない)
- エントリの出力 (template サブルーチンを呼ぶ)
- フッタの出力
dentries
設定したディレクトリ以下のファイルを列挙してコールバックを呼ぶサブルーチンになっている。getdents してるだけ
file_callback
dentries から呼ばれる。ファイル名をうけとって、. から始まるファイルを無視しつつ read_entry サブルーチンを呼ぶ。
read_entry サブルーチン
構造体を構築してもろもろ書きこむ。
- ファイル名 (パスではなく)
- 更新日時 (ソート用・表示用)
- タイトル文字列の先頭アドレス
- 本文文字列への先頭アドレス
更新日時は stat した結果をとって書きこんでいるだけ
タイトル文字列・本文文字列はちょっとめんどうくさい。stat した結果でファイルサイズはわかっているので、必要な分 mmap して確保して、sys_read で全部読みこんでいる。
blosxom は1行目をタイトルとして扱うという仕様になっているけど、最初の \n を \0 に変えるだけで null ターミネートの文字列にわけられ、あとはコピーする必要がないので、そのままアドレスを計算して構造体に入れている。元のファイルは書き換えたくないので直接マッピングはしてない。
template サブルーチン
単に #{varname} みたいな形式を置換している。
その他
依存なしで書いているので、基本的なサブルーチンも自分で書く必要があり、かなり面倒だった。
- base10
- 数値を文字列にする
- 割り算が必要
- divmod
- ARM は割り算がないのでソフトウェアで書く必要がある
- Wikipedia にも書いてあるアルゴリズム で実装した。
- base16
- ビットシフトだけでいけるので簡単
- strncmp
- 文字列が等しいかを判定する
- libc の実装では 1, 0, -1 で出力されるが必要ないので 0/1 だけ返す
- strcpy
- \0 を考慮しながらメモリコピーをする
- strcat
- 文字列連結
- findstr
- 指定した文字列が見つかった場所を返す
- 見つからなかったら -1
ほかに書いたもの
- ARM Linux EABI の asm で動的メモリ確保 | tech - 氾濫原 動的メモリ確保
- ARM Linux EABI の asm で簡単な ls を作る | tech - 氾濫原 ファイルリストの取得 getdents
- ARM Linux EABI の asm で getenv する | tech - 氾濫原 getenv
デモ
arm で動いてて外部公開できるサーバがないのでない。VPS で qemu で立ちあげればできるけどどこまでやることでもない。
インデックス
パーマリンク
単に1つになるだけ
まとめ
低レベルのコードを書く意義は、低レベルのデバッグ方法に慣れることができる点だと思う。コードリーディングするだけではデバッグ能力はつかない。
参考文献
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/linux/syscalls.h?id=HEAD
http://strace.git.sourceforge.net/git/gitweb.cgi?p=strace/strace;a=blob;f=linux/arm/syscallent.h;h=132b22ad8aea66c434983e99426c8f6eaed5d114;hb=HEAD
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/readdir.2.html