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
  1. トップ
  2. tech
  3. ARM Linux EABI の asm で getenv する

いい時代なので、実機がなくても qemu で環境をつくることができる。

qemu を入れる

brew install qemu

で入る

イメージを用意する

ここにある debian のイメージを例にすると、適当に必要なファイルをダウンロードするだけ

  • vmlinuz-3.2.0-4-versatile
  • initrd.img-3.2.0-4-versatile
  • debian_wheezy_armel_standard.qcow2

起動

qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1" -m 256 -redir tcp:2200::22

で起動させる。中から外のネットワークには出られるが中に繋ぐ方法がないっぽい?ので -redir tcp:2200::22 でポートフォワード的なことをしている。

ssh

ssh -p2200 root@localhost
password: root

Debian だと開発ツールがデフォルトで入っていないのがうざいけど、しかたない


ref.

  1. トップ
  2. tech
  3. ARM Linux EABI on QEMU

ngResource は単にAPIのラッパーという感じではなくて、JS でサーバ側のモデルとうまく同期するように作られている。

最も簡単な例だと以下のように使うが、Entry.get は XHR が完了する前に、とりあえず空のオブジェクトが返るようになっており、XHR の完了とともに破壊的に書きかえられる。これにより、entry の変更がすぐ全体に伝わるようになっている。

var Entry = $resource('/entry/:id');
$scope.entry = Entry.get({ id : 0 });

デフォルトで定義されている query/get/save/delete だけを見ると単に REST API のラッパーのように見えるが、独自のメソッドを追加するとより理解しやすいコードを書ける。

以下のコードは、デフォルトで下書き状態で生成される Entry オブジェクトを、後から publish 状態に変えるような挙動を想定している。単に $save() とかを使ってもいいが、専用のメソッドを生やすことでやりたいことを明確にできる。

var Entry = $resource('/entry/:id', {}, {
	'publish': { method: 'PUT', params : { publish : 1 } }
});
$scope.entry = Entry.get({ id : 0 });

....
$scope.doPublish = function () {
	$scope.entry.$publish(function () {
		alert('entry published!');
	});
}

この独自に定義したメソッドの場合も XHR が完了すると、API のレスポンスで元の entry インスタンスは破壊的に変更がかかる。すなわち $scope.entry を改めて自分で更新する必要はない。Angular の場合、オブジェクトの変更がうまいことビューに反映されるようになっているので、これだけでビューの更新までかかるコードになっている。

ngResource とサーバ側 API とうまく協調させることで、自動的にビューの更新までできるようになる。

ngResource の挙動とサーバサイドのAPIのインターフェイスをあわせる方法は前に書いた。ngResource は定義方法がいまいちわかりにくいし、挙動も若干マジカルだが、うまく使えば余計なことを気にせずにかっこよくビューまで一体したコードが書ける。

  1. トップ
  2. tech
  3. ngResource は何が便利なのか?