[[katsuhiro]] -> [[katsuhiro/refmon]] -> katsuhiro/refmon/stack_dump

*スタックのイメージについて [#n4a2eceb]

**fp を使う場合 [#z98c17d4]
ローカル変数を用いる場合、関数開始時の sp が ip に保存されてスタックに積まれる。

関数のプロローグ、およびエピローグコード(g++ 3.3.5 最適化なし)
 mov   ip, sp
 stmdb sp!, {fp, ip, lr, pc} ; これが終わったあとを sp_new とする
 sub   fp, ip, #4
 sub   sp, sp, #8 ; これが終わったあとを sp_after_sub とする
 ...
 ldmia fp, {fp, sp, pc}

stmdb では DB が指定されているので、sp を sp-16 の値まで減らしてからリストの左側にあるレジスタから fp, ip, lr, pc の順にプッシュする。プッシュの方向はアドレスが増加する(絵で言うと下向き)方向である。~
出口には ldmia がある。ここでは IA が指定されているので、fp <- 積んである元の fp、sp <- 元の ip、pc <- 元の lr と読み込んでから(「元の〜」はスタックに積んだ値です)、sp を sp+12 する。

| 接尾辞 | 意味 |
| DA | Decrement After(メモリアクセス後にポインタを減らす) |
| IA | Increment After(メモリアクセス後にポインタを増やす) |
| DB | Decrement Before(メモリアクセス前にポインタを減らす) |
| IB | Increment Before(メモリアクセス前にポインタを増やす) |

スタックの様子
 0x00000000
 |
 |______| __ sp_after_sub
 |_local|
 |_local| __ sp_new
 |_float|(浮動小数点のバックアップが
 |_float| 来るとしたら、この位置)
 |__fp__|
 |__ip__|
 |__lr__| __ fp
 |__pc__| __ sp, ip
 |
 0xffffffff

ネストした呼び出しを行ったときの様子
 0x00000000
 |
 |______| __ sp_after_sub2
 |_local|
 |_local| __ sp_new2
 |__fp1_|
 |__ip2_|
 |__lr2_| __ fp2
 |__pc2_| __ sp_after_sub1, ip2
 |_local|
 |_local| __ sp_new1
 |__fp0_|
 |__ip1_|
 |__lr1_| __ fp1
 |__pc1_| __ sp, ip1
 |
 0xffffffff


**fp を使わない場合 [#w5d51a69]
関数の開始から終了まで sp は不変であるとみなす。~
fp を使う関数との差はあまりないように思える。
ローカル変数も浮動小数点レジスタのバックアップも行われることがある。

-一般的なスタックのイメージ
 0x00000000
 |
 |______| __ sp
 |_local|
 |_local|
 |_float|
 |__r4__|
 |__r5__|
 |__fp__|
 |__lr__| __ sp_orig
 |
 0xffffffff
ただし、ローカル変数領域を使わない、浮動小数点レジスタを積まない、整数レジスタ(fp, lr 以外のレジスタ)を積まない、fp を積まない、lr を積まない、といったさまざまなバリエーションがある。

レジスタを変更する場合、保存すべきレジスタは全て積まれる。このとき stmdb 命令が使われることが多い。
 stmdb sp!, {r0, r1, ..., lr}
 (関数本体)
 ldmia sp!, {r0, r1, ..., lr}
大切なのは「lr や fp がどこにあるのか」だが、これは命令を解析して得るほかない。~
stm 命令の下位 15ビットがストアするレジスタのリストになっている。リファレンス参照のこと。

***コード例 [#q3c7d1c8]
-積む:lr
-積まない:ローカル変数、整数レジスタ、浮動小数点レジスタ、fp

 str lr, [sp, #-4]!
 (関数本体)
 ldr pc, [sp], #4
-解説
--str lr, [sp, #-4]!~
この命令は sp-4 に lr をストアして、sp = sp-4 にする。
--リターンのときは~
ldr pc, [sp], #4~
この命令で sp の指すメモリを pc にロードして、sp = sp+4 にする。

----

-積む:ローカル変数、lr
-積まない:整数レジスタ、浮動小数点レジスタ、fp

 //date(coreutils-5.2.1) の __dcgettext
 str lr, [sp, #-4]!
 mov ip, #0
 sub sp, sp, #8
 (関数本体)
 add sp, sp, #8
 ldr pc, [sp], #4
-解説
--ローカル変数領域を使うが、レジスタの保存はしない。
--ちなみに中で r1 r2 ip lr を破壊しているが、確か保存しなくて良いレジスタという規定があったような気がするのでそのパターンかなあ?

----

-積む:ローカル変数、整数レジスタ、lr
-積まない:浮動小数点レジスタ、fp

 //date(coreutils-5.2.1) の _IO_file_doallocate
 stmdb sp!, {r4, r5, r6, lr}
 ldr r3, [r0, #56]
 sub sp, sp, #104
 (関数本体)
 add sp, sp, #104
 ldmia sp!, {r4, r5, r6, pc}
-解説
--ローカル変数領域も使うし、整数レジスタも破壊するから退避している。

----

-積む:整数レジスタ
-積まない:ローカル変数、浮動小数点レジスタ、lr、fp

 //date(coreutils-5.2.1) の __mmap
 stmdb   sp!, {r0, r1, r2, r3}
 mov     r0, sp
 swi     0x0090005a
 add     sp, sp, #16     ; 0x10
 cmn     r0, #4096       ; 0x1000
 movcc   pc, lr
 b       35c60 <__syscall_error>
-解説
--整数レジスタが積まれるが、lr が積まれないタイプである。movcc pc, lr 命令でリターンする。


**浮動小数点レジスタのバックアップについて [#v7c724c5]
以下の関数で使用されていた
 //date(coreutils-5.2.1, static linked) の _dl_catch_error
 stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
 sfm f4, 4, [sp, #-48]!
 (...)
 sub sp, sp, #252
 (関数本体)
 add sp, sp, #252
 lfm f4, 4, [sp], #48
 ldmia sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

***sfm 命令とは? [#uc52744e]
ARM の命令セットリファレンスには、sfm という命令はない。
-機械語を見る限り stc(コプロセッサストア命令)の P=1 W=1(プリインデクスアドレシング)の場合だろう。
--オフセットアドレシング(W=0):ベースレジスタを更新しない。
--プリインデクスアドレシング(W=1):ベースレジスタ、この場合は sp(= r13) を更新するということ。
-sfm とか lfm で浮動小数点を扱う場合を、FPAモデルという。
--レジスタは f0 から f7 まであり、サイズは 3 words(12バイト)。f4-f7 を壊す場合は、関数終了時に元の値に戻さなければならない。

objdump では特殊な stc 命令を sfm という名前で表示するようだ。
 objdump によって sfm f4, 4, [sp, #-48]! と翻訳される機械語
 16進 e    d    2    d    4    2    0c
 2進  1110 1101 0010 1101 0100 0010 00001100
 これを stc 命令のフィールドに当てはめると
      cond 110P UNWL Rn   CRd  cp#  offset_8
                     =13  =4   =2   =12

-以上の命令を、stc命令だとみなして解釈すると
--cond は条件なし(0x0e) である
--P=1, W=1 なので更新あり [r, offset]! のアドレシング
--W=0 なら更新なし [r, offset] のアドレシング
--コプロセッサ番号は 2 で、レジスタは 4 である
--U=0 なのでオフセットは負の方向(U=1 なら正の方向)
--L=0 なのでストア命令(L=1 ならロード)
--N はコプロセッサ依存、おそらくロード/ストアのサイズ(一般的には N=0 でショート、N=1 でロングを意味するらしい)
---よって命令は stc となる。N=1 なら stcl となる。
---コプロセッサが一度に転送できるのは 16ワードまでだそうだ。
-stc p2, cr4, [r13, #-48]!
--(#-48) は (#-12 * 4) と解釈される。
-ちなみに stcl p2, cr4, [r13, #-48]! を objdump にかけると、~
ed6d420c    sfm f4, 2, [sp, #-48]! という命令になる。




*今のところだめなやつ [#o86d6492]
 00342f90 <__select>:
   342f90:       e59fc068        ldr     ip, [pc, #104]  ; 343000 <.text+0x33af30>
   342f94:       e59cc000        ldr     ip, [ip]
   342f98:       e33c0000        teq     ip, #0  ; 0x0
   342f9c:       1a000006        bne     342fbc <__select+0x2c>
   342fa0:       e52d4004        str     r4, [sp, #-4]!
   342fa4:       e59d4004        ldr     r4, [sp, #4]
   342fa8:       ef90008e        swi     0x0090008e
-解説
--str で sp を 4 減らす処理の前に、変な bne が居るせいで検知できない。
---この命令を理解するには、ジャンプ命令が成立するかどうかを判断せねばならない。一筋縄では読めないかも?
--この変な bne は非同期キャンセル用の処理に使っているようだ。




トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS