前回に引き続き、gasを用いてArmのアセンブラやっていこうと思ったのです。個別の命令をいくつか見てきたので、今回は、メモリアクセスに進もうかと。そこでちょっと魔が差しました。最初からアセンブラでサンプルを書くのではなく、gccがどんなコードを吐いているのか「参考」にさせてもらうのはいかがかと。Cであれば、大域変数、ローカル変数、単純変数、配列、構造体、いろいろあります。そういうモノどもをコンパイラはどんなコードで扱っているのか調べておこうと。でもね、その前に、大体逆アセンブルとか、メモリとか、セクションとかの調べ方押さえておかないと意味わかんなくない、ということに思い至りました。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
Cのコードのアセンブリ言語リスト見たいときにどうしていますか?
そんなもの見たくない
場合には、致し方ありませぬ。本稿はお役に立てません。また、普段から嫌というほど、アセンブラどころかバイナリ・ダンプを眺めている
バイナリ者
の方々にとっては、呼吸以下の命題かもしれません。まあ、Cのレベルのソースのアセンブリコードを読むことは組み込みプログラミングでは読み書きソロバン程度の技能(今後のソロバン人口はどんなもんでしょう?それに読み書きとか言っても、紙に文字を書かなくなっているし。まあ、アセンブラも似たり寄ったりかもしれませんで。そういう点では良い例えかな?)。まあ、どの処理系でも大体3レベルほどチャンスがあります。
- コンパイラにアセンブリソースを吐き出してもらう
- バイナリツールにセクションの逆アセンブルをお願いする
- デバッガに逆アセンブルしてもらう
まず、元のCのコードの対応部分を掲げておきます。何も意味はありません。関数引数とローカル変数の「配置」を確かめるだけのもの。
int sub(int a, int b) { int c; c = a * 3 + b; c >>= 1; return c; }
1ですが、今回は gas でやっているので、当然 コンパイラは gccとなります。ラズパイのRasbian上で作業しています。
$ gcc -g -O0 -S コンパイラソース名.c
という感じ。とりあえず見やすくするために、最適化無しの-O0で、デバッガにもシンボルが渡るように -g です。-Sが、コンパイル後、アセンブラソースを出力しておしまいにするオプション。成功すれば、.cでなく、.sなるファイルが出来ている筈。ファイルの末尾付近を見れば、ちゃんとコンパイルしたコンパイラ・バージョンも出力されており。
.ident "GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
該当のサブルーチン部分を見れば、当然ながらアセンブラ(gas)に食わせるための型式です。コメントには@つかっているし、即値にはちゃんと#つけている(.syntax unifiedしているのですが)。
.text .align 2 .global sub .arch armv6 .syntax unified .arm .fpu vfp .type sub, %function sub: @ args = 0, pretend = 0, frame = 16 @ frame_needed = 1, uses_anonymous_args = 0 @ link register save eliminated. str fp, [sp, #-4]! add fp, sp, #0 sub sp, sp, #20 str r0, [fp, #-16] str r1, [fp, #-20] ldr r2, [fp, #-16] mov r3, r2 lsl r3, r3, #1 add r3, r3, r2 ldr r2, [fp, #-20] add r3, r2, r3 str r3, [fp, #-8] ldr r3, [fp, #-8] asr r3, r3, #1 str r3, [fp, #-8] ldr r3, [fp, #-8] mov r0, r3 add sp, fp, #0 @ sp needed ldr fp, [sp], #4 bx lr .size sub, .-sub
続いて2番目、オブジェクトコードからバイナリツールにディスアセンブルしてもらう方法です。gccのツールチェーンの場合、バイナリツール群は
binutil
というお名前で呼ばれており、その中のコマンド
objdump
が、今回使わせてもらうプログラムです。コンパイルするときに -g オプションつけてコンパイルしたので、オブジェクトコード内にはデバッグ情報が含まれています。そこで、便利なオプションが -x です。
$ objdump -x オブジェクトファイル
このオプションで逆アセンブルすると、逆アセンブル結果の「だいたい」の対応位置に対応するソースコードを挿入してくれます。こんな感じ。
int sub(int a, int b) { 10434: e52db004 push {fp} ; (str fp, [sp, #-4]!) 10438: e28db000 add fp, sp, #0 1043c: e24dd014 sub sp, sp, #20 10440: e50b0010 str r0, [fp, #-16] 10444: e50b1014 str r1, [fp, #-20] ; 0xffffffec int c; c = a * 3 + b; 10448: e51b2010 ldr r2, [fp, #-16] 1044c: e1a03002 mov r3, r2 10450: e1a03083 lsl r3, r3, #1 10454: e0833002 add r3, r3, r2 10458: e51b2014 ldr r2, [fp, #-20] ; 0xffffffec 1045c: e0823003 add r3, r2, r3 10460: e50b3008 str r3, [fp, #-8] c >>= 1; 10464: e51b3008 ldr r3, [fp, #-8] 10468: e1a030c3 asr r3, r3, #1 1046c: e50b3008 str r3, [fp, #-8] return c; 10470: e51b3008 ldr r3, [fp, #-8] } 10474: e1a00003 mov r0, r3 10478: e28bd000 add sp, fp, #0 1047c: e49db004 pop {fp} ; (ldr fp, [sp], #4) 10480: e12fff1e bx lr
先ほどのソースがアセンブル前ならば、こちらはアセンブル後なので、情報としては増えておるわけですが、すこし、いろいろありすぎて目に優しくないかもしれまへん。なお、Cのソースをミックスしない(したくても落としてしまってできないこともあり)ようなときは -x ではなく、-d オプションですね。
さて、3番目がデバッガに逆アセンブルしてもらう方法です。gccのツールチェーンなので、当然 gdb です。こんな感じ。
(gdb) disassem sub Dump of assembler code for function sub: 0x00010434 <+0>: push {r11} ; (str r11, [sp, #-4]!) 0x00010438 <+4>: add r11, sp, #0 0x0001043c <+8>: sub sp, sp, #20 0x00010440 <+12>: str r0, [r11, #-16] 0x00010444 <+16>: str r1, [r11, #-20] ; 0xffffffec 0x00010448 <+20>: ldr r2, [r11, #-16] 0x0001044c <+24>: mov r3, r2 0x00010450 <+28>: lsl r3, r3, #1 0x00010454 <+32>: add r3, r3, r2 0x00010458 <+36>: ldr r2, [r11, #-20] ; 0xffffffec 0x0001045c <+40>: add r3, r2, r3 0x00010460 <+44>: str r3, [r11, #-8] 0x00010464 <+48>: ldr r3, [r11, #-8] 0x00010468 <+52>: asr r3, r3, #1 0x0001046c <+56>: str r3, [r11, #-8] 0x00010470 <+60>: ldr r3, [r11, #-8] 0x00010474 <+64>: mov r0, r3 0x00010478 <+68>: add sp, r11, #0 0x0001047c <+72>: pop {r11} ; (ldr r11, [sp], #4) 0x00010480 <+76>: bx lr End of assembler dump.
このみにもよりますが、一番シンプルかもしれない。それにアドレスの<+なんちゃら>というのは、相対番地を確かめるときに便利と言えば便利。ま、ラベルで見る方がナンボか楽ですが。
次回こそ、これらのアセンブリリスティングを手掛かりに、セクションとメモリアクセスのコードなど調べて行きたいと思います。