ぐだぐだ低レベルプログラミング(10) アセンブラ・リスティング

前回に引き続き、gasを用いてArmのアセンブラやっていこうと思ったのです。個別の命令をいくつか見てきたので、今回は、メモリアクセスに進もうかと。そこでちょっと魔が差しました。最初からアセンブラでサンプルを書くのではなく、gccがどんなコードを吐いているのか「参考」にさせてもらうのはいかがかと。Cであれば、大域変数、ローカル変数、単純変数、配列、構造体、いろいろあります。そういうモノどもをコンパイラはどんなコードで扱っているのか調べておこうと。でもね、その前に、大体逆アセンブルとか、メモリとか、セクションとかの調べ方押さえておかないと意味わかんなくない、ということに思い至りました。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

Cのコードのアセンブリ言語リスト見たいときにどうしていますか?

そんなもの見たくない

場合には、致し方ありませぬ。本稿はお役に立てません。また、普段から嫌というほど、アセンブラどころかバイナリ・ダンプを眺めている

バイナリ者

の方々にとっては、呼吸以下の命題かもしれません。まあ、Cのレベルのソースのアセンブリコードを読むことは組み込みプログラミングでは読み書きソロバン程度の技能(今後のソロバン人口はどんなもんでしょう?それに読み書きとか言っても、紙に文字を書かなくなっているし。まあ、アセンブラも似たり寄ったりかもしれませんで。そういう点では良い例えかな?)。まあ、どの処理系でも大体3レベルほどチャンスがあります。

  1. コンパイラにアセンブリソースを吐き出してもらう
  2. バイナリツールにセクションの逆アセンブルをお願いする
  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.

このみにもよりますが、一番シンプルかもしれない。それにアドレスの<+なんちゃら>というのは、相対番地を確かめるときに便利と言えば便利。ま、ラベルで見る方がナンボか楽ですが。

次回こそ、これらのアセンブリリスティングを手掛かりに、セクションとメモリアクセスのコードなど調べて行きたいと思います。

ぐだぐだ低レベルプログラミング(9) Armらしい命令2 へ戻る

ぐだぐだ低レベルプログラミング(11) オブジェクトファイル その1 へ進む