asm だけで getenv をしてみようとしたらかなり大変で、本当はもっと先の目標があったけど、遥か遠いので、とりあえず getenv しただけで一旦まとめる。
getenv は何をするか?
そもそも、Linux のプロセスイメージをちゃんと知っていないといけない。環境変数ってどこからくるんだ? というところからして、全く知らなかった。getenv とかシステムコールになってて呼んだら出てくるのかと思っていた。
このページがわかりやすかった。
結局以下のようにすれば r0, r1, r2 それぞれに argc argv environ が入るようになる。sp にスタック位置が起動時から入っているので、そこを基準にオフセットを計算するみたい。スタックはアドレスが小さくなるほうに伸びるけど、それの逆方向に argc やら何やらが入っている。
_start: mov lr, #0 /* r0 = argc */ ldr r0, [sp] /* r1 = argv */ add r1, sp, $0x04 /* r2 = argc * 4 (skip argv) */ mov r3, $0x04 mul r2, r0, r3 /* r2 += 4 (skip null word) */ add r2, r2, $0x04 /* r2 += offset (ok this is environ) */ add r2, r1
gcc で普通にコンパイルすると libc がこのへんうまいことやってくれているんだなあと思って libc の大事さを感じる。
そして getenv を実装するために最低でも strncmp 的なものが必要だし、strlen もないと出力するとき困る。
environ は文字列の配列なので、レジスタに今何がロードされているのか意識するのがこんがらがってつらい。LL とか触っていると、文字列の配列は文字列のリストにしか見えないので、しばしば実際は文字列のアドレスの配列になっていることを忘れてしまう。
ARM の場合さらに、遠いアドレスにあるメモリを直接参照できないので、さらに1段参照が増えていたりしてややこしい。オペランドの = を使うとそのへんあまり意識せずにすむようになって便利 (遠い場合は自動的に近くに値プールをつくってくれるらしい)
デバッグ方法
ちょっと複雑になってくるともはやデバッガのステップ実行なしではつらい。gdb が使えるのでつかう。as に --gstabs+ オプションをつけてコンパイルしたバイナリを gdb sketch とかで普通に起動してやればよい。レジスタの値は info registers で見ることができる。
$ as --gstabs+ -o sketch.o sketch.s && ld -o sketch -e _start sketch.o $ gdb sketch (gdb) b main Breakpoint 1 at 0x80ac: file sketch.s, line 46. (gdb) r Starting program: /home/pi/sketch/sketch Breakpoint 1, main () at sketch.s:46 46 bl getenv (gdb) info registers r0 0x80c8 32968 r1 0xbefff734 3204446004 r2 0xbefff73c 3204446012 r3 0x101cc 65996 r4 0x0 0 r5 0x0 0 r6 0x0 0 r7 0x0 0 r8 0x0 0 r9 0x0 0 r10 0x0 0 r11 0x0 0 r12 0x0 0 sp 0xbefff730 0xbefff730 lr 0x809c 32924 pc 0x80ac 0x80ac <main+4> cpsr 0x10 16
コード全文
/*#!as --gstabs+ -o sketch.o sketch.s && ld -o sketch -e _start sketch.o && objdump -d -j .text -j .data sketch && ./sketch */ .global _start .macro sys_exit mov r7, $0x01 /* set system call number to 1 (exit) */ svc $0x00 /* supervisor call */ .endm .macro sys_write mov r7, $0x04 svc $0x00 /* supervisor call */ .endm _start: mov lr, #0 /* r0 = argc */ ldr r0, [sp] /* r1 = argv */ add r1, sp, $0x04 /* r2 = argc * 4 (skip argv) */ mov r3, $0x04 mul r2, r0, r3 /* r2 += 4 (skip null word) */ add r2, r2, $0x04 /* r2 += offset (ok this is environ) */ add r2, r1 /* save char** environ to global variable */ ldr r3, =environ str r2, [r3] /* r0 = argc, r1 = argv, r2 = environ */ bl main /* not reached */ mov r0, $0xff sys_exit main: /* getenv("USER") */ adr r0, USER bl getenv /* if "USER" is not in ENV */ cmp r0, $0x00 bleq error bl puts mov r0, $0x00 /* set exit status to 0 */ sys_exit USER: .asciz "USER" .align 2 error: mov r0, $0x01 sys_exit strncmp: /* char* s1, char* s2, size_t len -> 1|0 */ stmfd sp!, {v1-v5, lr} /* save variable resistors and returning address */ mov r3, $0x00 /* result */ 1: cmp r2, $0x00 beq 2f /* if (r2 == 0) goto 2 */ sub r2, r2, $0x01 /* len-- */ ldrb r4, [r0], $0x01 /* r4 = *s1++ */ ldrb r5, [r1], $0x01 /* r5 = *s2++ */ cmp r4, r5 beq 1b /* if (r4 == r5) goto 1 */ add r3, $0x01 /* r3++ (this function always returns 1 when the comparing fails) */ 2: mov r0, r3 ldmfd sp!, {v1-v5, pc} /* restore variable resistors and set pc to returning address */ strlen: /* char* str* -> uint */ mov r1, $0x00 /* r1 = result */ /* 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 mov pc, lr 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] /* *environ != NULL */ cmp r1, $0x00 beq 2f mov r2, v2 bl strncmp cmp r0, $0x00 /* if (*environ)[len] == '=') { */ ldreq r0, [v3] ldreqb r0, [r0, v2] cmpeq r0, #'= beq 3f /* } */ /* environ++ */ add v3, $0x04 b 1b 2: /* not found return NULL */ mov r0, $0x00 ldmfd sp!, {v1-v5, pc} 3: /* found and return address */ ldreq r0, [v3] add r0, r0, v2 add r0, r0, $0x01 /* skip '=' */ ldmfd sp!, {v1-v5, pc} puts: stmfd sp!, {v1-v5, lr} mov v1, r0 bl strlen mov r2, r0 mov r1, v1 mov r0, $0x01 sys_write mov r0, $0x01 adr r1, linefeed mov r2, $0x01 sys_write ldmfd sp!, {v1-v5, pc} linefeed: .byte '\n .align 2 .section .bss .align 2 environ: .word 0