Kernel/VM探検隊.AdventCalender.47日目―見習いの記事(昼の部)

アセンブリで関数呼び出しがよう分からんっていう話をしたら、スタックに積んでcallするという話を聞いた。

ところが、僕が見たところ、そのような感じではなかった。

つまり、僕の説明不足で、アーキテクチャがARMであることをいい忘れていたのだった。

そこで、みんな知ってることだと思うのですが、ARMでの関数の呼び方をデータシートを読んだりするのは面倒くさいので修行だと思って、アセンブリを読んで推定しようと思います。

今回のターゲットはLPC2388で、FreeRTOS上でにあるプロセスのサブルーチンが孫ルーチンを呼ぶ部分をまず見てみましょう。

子ルーチン:(思ったより長くなったので抜粋)

.LVL64:
.loc 1 264 0
mov ip, #0
.loc 1 265 0
mov r0, ip
mov r1, #66
mov r2, #8
.loc 1 264 0
str ip, [r4, #0]
.loc 1 265 0
bl i2csender
i2csender:
.LFB5:
.loc 1 134 0
.cfi_startproc
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
.LVL22:
.loc 1 136 0
cmp r0, #0
.loc 1 134 0
stmfd sp!, {r3, r4, r5, r6, r7, lr}
.LCFI1:
.cfi_def_cfa_offset 24
.loc 1 134 0
mov r4, r1
.cfi_offset 14, -4
.cfi_offset 7, -8
.cfi_offset 6, -12
.cfi_offset 5, -16
.cfi_offset 4, -20
.cfi_offset 3, -24
mov r6, r2
.loc 1 136 0
beq .L58
.loc 1 148 0 discriminator 1
cmp r2, #0
.loc 1 149 0 discriminator 1
ldrne r5, .L71
.loc 1 150 0 discriminator 1
movne r7, #40
.loc 1 148 0 discriminator 1
beq .L57
.LVL23:
.L68:
.loc 1 149 0
and r1, r4, #255
str r1, [r5, #8]
.loc 1 150 0
str r7, [r5, #24]
.L63:
.loc 1 151 0 discriminator 1
ldr r3, [r5, #4]
cmp r3, #40
bne .L63
.loc 1 152 0
ldr r0, .L71+4
mov r1, r4
bl printf
(略)
ここで、孫ルーチンのC言語での記述を見てみよう。
void i2csender(int Continue, unsigned int Data, int place){
if(Continue==0){
I22DAT = Data;
I22CONSET |= 0x04;
I22CONCLR  = 0x08;
printf(“requesting%x”,Data);
while(I22STAT!=0x18 && I22STAT!=0x20)printf(“.”);
if(I22STAT==0x20){
printf(“No such device%4x\n”,Data);
i2cErr = 0x22;
return;
}
}else {
for(;place != 0;place-=8){
I22DAT = (Data & 0xFF);
I22CONCLR  = 0x28;
while(I22STAT!=0x28);
printf(“Data%4x\n”,Data);
Data = Data >> 8;
}
}
}

すごい汚い&スパゲッティだが、中学だか高校入ったばっかのころ書いたコードだから仕方ない。(いままで必殺「うごくだからいーじゃん」を発動してた)

引数が3つある。i2csender(0,0×42,8);

0,0×42(0d66),8を投げている。もう一度コルーチンを見る

.LVL64:
.loc 1 264 0
mov ip, #0
.loc 1 265 0
mov r0, ip @引数1つめ、なんでうえのmov ip, #0をへるのか不明過ぎる、mov r0, #0ではだめなのか …A
mov r1, #66 @引数2つめ
mov r2, #8 @引数3つめ
.loc 1 264 0
str ip, [r4, #0] @r4に入ってるところちょうどそこに0を格納する。r4になにはいってるの?いっぱい考えたけど、いろいろ追ったけど、わかりそうでわかんなかった
.loc 1 265 0
bl i2csender

ということでどうやらARMでは3つの引数を渡すときにr0〜r2に置くということが分かった。本当だろうか。

最初にあるstmfd sp!, {r3, r4, r5, r6, r7, lr}と最後にあるldmfd sp!, {r3, r4, r5, r6, r7, lr}をみる。つまり、スタックにr3〜r7とlrを積んで、作業スペースを確保しているのだろう。

と。

あれ?終わり?

いいえ、printfとか、文字列どうやって渡してるんだろう。

神は言っている、ソース嫁

.LCFI1:
.cfi_def_cfa_offset 24
.loc 1 134 0
mov r4, r1
.cfi_offset 14, -4
.cfi_offset 7, -8
.cfi_offset 6, -12
.cfi_offset 5, -16
.cfi_offset 4, -20
.cfi_offset 3, -24
mov r6, r2 @第3引数をr6に退避(r2が0だと0フラグが立つんじゃね?)
.loc 1 136 0
beq .L58 @0フラグ立ってたら分岐
.loc 1 148 0 discriminator 1
cmp r2, #0 @第3引数は0か?
.loc 1 149 0 discriminator 1
ldrne r5, .L71 @0を第3引数にした覚えはないのでr5に.word -536346624をいれる。なんだこれ?いっぱいかんがえた結果、I22〜のベースアドレスだとわかった

.loc 1 150 0 discriminator 1
movne r7, #40 @0を第3引数にしてないので、r7に40を入れる。
.loc 1 148 0 discriminator 1
beq .L57 @0を第3引数にしてたらL57にとぶ……あれ?もっと上にあるべきじゃね?したら第3引数が0だったとき2命令サイクル早くすむし、0じゃなかったら1命令サイクルのロスで済む。この場合、ここのループは1回しか回らないことの方が多いので、そっちの方が速い。ループ回数がソースから分かるので、もうちょっと頭がよければ最適化対象だろう
.LVL23:
.L68:
.loc 1 149 0
and r1, r4, #255
str r1, [r5, #8] @I22DATにいれます
.loc 1 150 0
str r7, [r5, #24] @I22CONCLRに0x28をいれる
.L63:
.loc 1 151 0 discriminator 1
ldr r3, [r5, #4] @I22STATをロード
cmp r3, #40 @それって0x28?
bne .L63 @違う間ループ, 3命令かな?(実は.locがようわかってない)
.loc 1 152 0
ldr r0, .L71+4 ここが問題のprintf()にたいする引数渡し
mov r1, r4
bl printf

さて、L71+4が何をさすのだろうか。

それが一番の問題だ。

.L71:

.word -536346624

.word .LC3

.word .LC1

.word .LC2

.word i2cErr

.cfi_endproc

ふむ。+4ということは、きっと.LC3だろう。(4byte = 32bit,まえのが.wordだから32bitでアラインされている)

LC3ってなんだよ。たらい回しすんな

.LC3:

.ascii “Data%4x\ 012\ 000”

.space 3

これを見るとどうやら.asciiというアライン方法があるらしい。こんなのInterfaceでもみたことないよママ…..

ということで、データシートを開く。

https://dl.dropbox.com/u/15570814/051020DDI0100HJ_v6_1.pdf (公開がライセンス違反だったら教えてください、消します)

とおもったら載ってない。

あれ?とにかく、.asciiっていうアライン方法で、 “ASCIIencoded string“ってやってやれば、(多分アセンブラさんが)対応するバイナリに変換してLC3に置いてくれるのだろう。

ところで、\ 012\ 000ってなんだろう。

とりあえず、\nを\tに変えてもっかいコンパイルする。

.ascii “Data%4x\ 011\ 000”

ほう。\ 011\ 000とな?(スペースは=null回避)

よくわかんないけど、\nが12で、\tでは11っぽい。

PCではどうなるんだろう。

.string “Hello,World!”

あれ?

movl $.LC0, %edi

call puts

おい、そんな最適化いらない!

\tにしてみた。

.string “Hello,world!\t”

ほう。\tとな。ふぅむ。\ 012\ 000にあたりそうなasciiコードも\nになさそうだし、PCじゃふつうに\tって渡すし。

とにかく、これでは8byteなため、r0にわたしてprintfにリンク付き分岐するみたい。

printf(“Data%4x::::\n”,Data);

と言う風に変えてみた。

ところが、

.LC3:

.ascii “Data%4x::::1200”

.space 3

.L71:

.word -536346624

.word .LC3

.word .LC1

.word .LC2

.word i2cErr

.cfi_endproc

命令も

ldr r0, .L71+4

mov r1, r4

bl printf

変わらん……

もしや、.L71+4には.LC3のアドレスがはいってるのか?

arm-none-eabi-objdumpする

3c: e59f009c ldr r0, [pc, #156] ; e0 <i2csender+0xe0>

40: e1a01004 mov r1, r4

44: ebfffffe bl 0 <printf>

e0: 0000003c .word 0x0000003c

あれ?3cってだれ?0x3cをr0にロードしてるんだよね…..?

0x3c番地になにが入るかってリンクしてないんだからわかんねえだろとおもうんだけど。

お手上げ。

Comments are closed, but you can leave a trackback: Trackback URL.