ARMアセンブリ言語を読む
中止になった某ミニキャンプの応募課題を大事に温めて、やっと先日解き、ご厚意にすがって出題者に不明点を解説してもらいました。
なぜ大事に温めていたかというと、怖かったからです。
文法も記法も分からず、どこが何を指しているのか、わからない言語を読むのは苦労が伴うことは目に見えているので、なかなか手が出ませんでした。
メモリを丁寧に追っていくのに、一日以上かかりました
取り組んでいる中で、ARMの記事がとても少なかったと感じたことがこれを書く一つの動機です。
いつか誰かの参考になれば嬉しいです。
ARMって何
ざっくり言うと、組み込みシステムを制御しているCPUアーキテクチャの一つ。
様々な環境に対応するように構成されたコンピュータプロセッサ用の縮小命令セットコンピューティング(RISC)アーキテクチャのファミリーである(Wikipediaより)
低レイヤのことは、豊富な文献があるのでそれを見てください
これを読んでいく
下の画像が、今回読んでいくアセンブリプログラム。
「アドレス:アドレスのバイナリ(16進数) アセンブラ言語」という順になっている。
果たしてどんな挙動を意味するのでしょうか。
最低限必要な知識
- #の後は値がくる
(ex. #4は、"4"のこと) - []で囲まれた値はアドレスを指す
- スタックは、図において下に行くほどアドレス値が大きくなる
- " ; " 以降はコメント
- sp
スタックポインタのこと。スタック領域の一番小さいアドレスを指す。 - pc
プログラムカウンタのこと。命令をメモリから読みだして演算装置に送るため、次に読み出すメモリを記憶してくれる。
このほかにもいっぱいありますが列挙していると進まないので、参照を貼ります。
こちらが便利です。
参考リンク
一行ずつ追っていく
8208: push {fp.lr}
fp(フレームポインタ)とlrがスタックに積まれる。
820c: add fp, sp, #4
fp=sp+4の演算がされる。左側の変数に、右二つの値の和が入る。
下に行くほどアドレス値が大きくなることに注意して考えると、スタックの様子は下のようになる。
8210: sub sp, sp, #8
sp=sp-8。subは引き算を表し、addと同じく、左側の変数に右二つの演算結果が入る。
この操作によって、スタック領域が確保されていく。spがメモリの図の上方向に上がっていく。
8214: mov r3, #0
movコマンドでは、メモリアクセスが行われ、メモリのアドレスではなく、中身に対する操作が行われる。
ここでは、r3に0が代入される。r3=0
8218: str r3, [fp, #-8]
strコマンドでは、左側の値を右側のアドレス値が指すところに書き込む。
r3の値がfp-8のアドレスを持つメモリに格納される。
821c: mov r3, #0 8220: str r3, [fp, #-12] 8224: mov r3, #0
r3=0
r3の値がfp-12のアドレスを持つメモリに格納された。
r3=0
8224: ldr r3, [fp, #-12]
ldrコマンドは、アドレス値でやりとりする。
r3にfp-12に格納されている値を代入する。
8228: cmp r3, #9
cmpコマンドは、左右の値を比較する。
ここでr3の値と9の大小比較をしている。
822c: bgt 8250
もし、上のcmpでr3(左の値)の方が大きければ、8250のところまでジャンプする。
つまり、ここから推測できることは、r3が9を超えない限りは、この地点(822c)から恐らくジャンプ先までをを繰り返すということである。
forループですね。
8230: ldr r2,[fp, #-8] 8234: ldr r3,[fp, #-12]
r2にfp-8に格納されている値を代入し、r3にfp-12というアドレスに格納されている値を代入する。
8238: add r3, r2, r3
r3=r2+r3の演算が行われる。
823c: str r3, [fp, #-8]
r3の値をfp-8というアドレスのところに格納する。
8240: ldr r3, [fp, #-12]
fp-12に格納されている値をr3に代入する。
8244: add r3, r3, #1
r3=r3+1
8248: str r3, [fp, #-12]
r3の値をfp-12で表されるアドレスのところに格納する
824c: 8224
これはラベルのようなものなので、8224に戻る(ループ)
8250: ldr r1, [fp, #-8]
fp-8のアドレスにある値をr1に読み出す。
8254: ldr r0, [pc, #20]; [pc, #20]=="%d"
pc+20のアドレスにある値をr0に読み出す。
;以降はコメントであるが、作問者によると、pc+20には"%d"という文字列が入った場所へのポインタが書き込まれており、r0には"%d"という文字列が入ったデータへのポインタが入るよ、というコメント。
8258: bl 9c34
blコマンドによって、問題中には見えないが9c34にあるサブルーチンの呼び出しをおこなっている。
825c: mov r3, #0 8260: mov r0, r3 8264: sub sp, fp, #4
r3=0
r0=r3
sp=fp-4、この操作によって、スタック領域が狭くなり、スタック領域の解放が行われる。
pop {fp,lr}
popコマンドで、スタック領域から、fpとlrをPOPによって取り出している。
bx lr
bxコマンドで、ステートの切り替えが行われる。
lrはサブルーチンからの復帰アドレスを保持している。
bxコマンドのステートの切り替えについては、公式のドキュメントがあります。公式ドキュメント
以上から、r3の値によって、ループの回数を管理して10回ループが回るような挙動になっていることが分かる。
for(r3=0;r3<10;r3++)
である。
そして、そのループの中でr3=r2+r3が行われ、
r3=0でループ開始;
r3=0+0
r3=1でループ開始:
r3=0+1
r3=2でループ開始:
r3=1+2
r3=3でループ開始:
r3=3+3...
r2には、前のループでの演算結果が格納されている。
つまり、r3=(前のループでr3に格納された値)+(現在のr3の値)の計算を繰り返していく。
終わり
こんな簡単な挙動を把握するのに、丸一日以上かかりました。
スタックを理解して、アセンブリを初めてまともに読むにはちょうどいい教材になったと思います。
不明点を懇切丁寧に教えて下さった出題者の方には頭が上がりません。
ARMアセンブリの文献が少ないので誰かの参考になれば幸いです。
(参考文献)
セキュリティコンテストチャレンジブック マイナビ出版
追記
コメントで教えて頂いた、コンパイラツールを使ってみました。
https://godbolt.org/z/a73vqE
色んな種類のコンパイラがオンラインエディタで使えるので環境構築も不要で最強ですね
きちんと想定したプログラムとアセンブリが一致することが確認できました!
嬉しい!教えて下さった方に感謝です~!コップンカー笑