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 
