息を吸うように objdump

このへんで書いた、どうなってるか自信が無いことがあったらとりあえず objdump しちゃえという話…とか言うと「アセンブリは…」みたいな雰囲気になることが多い気がするんですが、正直アセンブリなんか読めなくてもコードがどうなってるかくらいはわかるよん、という当たり前の話。

 #include <stdio.h>
 struct S {
     explicit S(int x) : x_(x) {}
     int x() const { return x_; }
     int x_;
 };
 int main() {
     S s(3);
     printf("%d\n", s.x());
 }

とりあえずこんなコードをインライン化されるか知りたいとする。とりあえず -g つきでコンパイルして objdump 。

 % g++ -g inline.cc -o inline
 % objdump -S -C inline | lv

コツはとりあえずコンパイルする時に -g つけとくのと objdump する時に -S にしておくことと出力はとりあえず less なり lv に喰わせること。 objdump -C は C++ のデマングル。個人的には gcc -S より objdump の方が見やすいかなぁと思う。

で、たぶんドバーと色々出てきてわけわかんね、と思うんだけど、内容は全く読まずに <main とかで検索する。するとなんか出てくる。

08048454 <main>:
struct S {
    explicit S(int x) : x_(x) {}
    int x() const { return x_; }
    int x_;
};
int main() {
 8048454:       8d 4c 24 04             lea    0x4(%esp),%ecx
 8048458:       83 e4 f0                and    $0xfffffff0,%esp
 804845b:       ff 71 fc                pushl  -0x4(%ecx)
 804845e:       55                      push   %ebp
 804845f:       89 e5                   mov    %esp,%ebp
 8048461:       51                      push   %ecx
 8048462:       83 ec 24                sub    $0x24,%esp
    S s(3);
 8048465:       c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
 804846c:       00
 804846d:       8d 45 f8                lea    -0x8(%ebp),%eax
 8048470:       89 04 24                mov    %eax,(%esp)
 8048473:       e8 2a 00 00 00          call   80484a2 <S::S(int)>
    printf("%d\n", s.x());
 8048478:       8d 45 f8                lea    -0x8(%ebp),%eax
 804847b:       89 04 24                mov    %eax,(%esp)
 804847e:       e8 2d 00 00 00          call   80484b0 <S::x() const>
 8048483:       89 44 24 04             mov    %eax,0x4(%esp)
 8048487:       c7 04 24 7c 85 04 08    movl   $0x804857c,(%esp)
 804848e:       e8 19 ff ff ff          call   80483ac <printf@plt>
 8048493:       b8 00 00 00 00          mov    $0x0,%eax
}
 8048498:       83 c4 24                add    $0x24,%esp
 804849b:       59                      pop    %ecx
 804849c:       5d                      pop    %ebp
 804849d:       8d 61 fc                lea    -0x4(%ecx),%esp
 80484a0:       c3                      ret

ちょっとひくけど、あまり深く考えることはなくて、要は printf の行で S::x() が call されてるかどうかが見たい。つまるところ call って機械語だけ知ってりゃ OK で、この場合 printf("%d\n", s.x()); って書いてある行の後で、

 804847e:       e8 2d 00 00 00          call   80484b0 <S::x() const>

って書いてあるからインライン化されてない。で次は g++ に -O をつけて同じように objdump する。

08048454 <main>:
struct S {
    explicit S(int x) : x_(x) {}
    int x() const { return x_; }
    int x_;
};
int main() {
 8048454:       8d 4c 24 04             lea    0x4(%esp),%ecx
 8048458:       83 e4 f0                and    $0xfffffff0,%esp
 804845b:       ff 71 fc                pushl  -0x4(%ecx)
 804845e:       55                      push   %ebp
 804845f:       89 e5                   mov    %esp,%ebp
 8048461:       51                      push   %ecx
 8048462:       83 ec 14                sub    $0x14,%esp
    S s(3);
    printf("%d\n", s.x());
 8048465:       c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
 804846c:       00
 804846d:       c7 04 24 4c 85 04 08    movl   $0x804854c,(%esp)
 8048474:       e8 33 ff ff ff          call   80483ac <printf@plt>
}
 8048479:       b8 00 00 00 00          mov    $0x0,%eax
 804847e:       83 c4 14                add    $0x14,%esp
 8048481:       59                      pop    %ecx
 8048482:       5d                      pop    %ebp
 8048483:       8d 61 fc                lea    -0x4(%ecx),%esp
 8048486:       c3                      ret

またどばーと書いてあるけど、 call 80484b0 みたいな行は消えてることがわかる。よってインライン化されてる。そんだけ。

次は定数がどうとか。

 int main() {
     int sum = 0;
     int i;
     for (i = 0; i < 10; i++) {
         sum += i;
     }
     return sum;
 }

こんな 0 から 9 まで足し算するコード。結果は 45 == 0x2d になるはず。今度はとりあえず gcc -O -g とかでコンパイルして objdump してみると、

08048344 <main>:
int main() {
 8048344:       8d 4c 24 04             lea    0x4(%esp),%ecx
 8048348:       83 e4 f0                and    $0xfffffff0,%esp
 804834b:       ff 71 fc                pushl  -0x4(%ecx)
 804834e:       55                      push   %ebp
 804834f:       89 e5                   mov    %esp,%ebp
 8048351:       51                      push   %ecx
    int i;
    for (i = 0; i < 10; i++) {
        sum += i;
    }
    return sum;
}
 8048352:       b8 2d 00 00 00          mov    $0x2d,%eax
 8048357:       59                      pop    %ecx
 8048358:       5d                      pop    %ebp
 8048359:       8d 61 fc                lea    -0x4(%ecx),%esp
 804835c:       c3                      ret

またよくわからんけど、なんとなく一見してループとか無さそう。なんか短い。ループとかあると jmp とか j で始まる命令があるはず (x86 なら。他だと b (branch) とかが多いのかな) 。そんなことより決定的なのは

 8048352:       b8 2d 00 00 00          mov    $0x2d,%eax

という行で、 $0x2d ってのはどう考えても計算結果が既に入ってる感じ。最適化されて return 45; みたいなコードになってることが想像できる。

一方 -O 消してみると、

08048344 <main>:
int main() {
 8048344:       8d 4c 24 04             lea    0x4(%esp),%ecx
 8048348:       83 e4 f0                and    $0xfffffff0,%esp
 804834b:       ff 71 fc                pushl  -0x4(%ecx)
 804834e:       55                      push   %ebp
 804834f:       89 e5                   mov    %esp,%ebp
 8048351:       51                      push   %ecx
 8048352:       83 ec 10                sub    $0x10,%esp
    int sum = 0;
 8048355:       c7 45 f4 00 00 00 00    movl   $0x0,-0xc(%ebp)
    int i;
    for (i = 0; i < 10; i++) {
 804835c:       c7 45 f8 00 00 00 00    movl   $0x0,-0x8(%ebp)
 8048363:       eb 0a                   jmp    804836f <main+0x2b>
        sum += i;
 8048365:       8b 45 f8                mov    -0x8(%ebp),%eax
 8048368:       01 45 f4                add    %eax,-0xc(%ebp)
int main() {
    int sum = 0;
    int i;
    for (i = 0; i < 10; i++) {
 804836b:       83 45 f8 01             addl   $0x1,-0x8(%ebp)
 804836f:       83 7d f8 09             cmpl   $0x9,-0x8(%ebp)
 8048373:       7e f0                   jle    8048365 <main+0x21>
        sum += i;
    }
    return sum;
 8048375:       8b 45 f4                mov    -0xc(%ebp),%eax
}
 8048378:       83 c4 10                add    $0x10,%esp
 804837b:       59                      pop    %ecx
 804837c:       5d                      pop    %ebp
 804837d:       8d 61 fc                lea    -0x4(%ecx),%esp
 8048380:       c3                      ret

なんかすんごく長い。どこにも 0x2d という文字列が見当たらない。いかにもループしてそう。よって最適化されてない。そんだけ。

というわけでベンチマークとかするより機械語見る(というより眺める)方がはるかに速いことが多いよなぁというような話。 objdump はコワクナイヨ。

あとはまぁ、機械語生成するようなコード書いてる場合は、とりあえず生成したコードをファイルに書き出して、 objdump -b binary -m i386 -D とかするのがいい感じかなぁと思います。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h