アセンブラで遊ぶ時に便利なgdb設定

アセンブラで遊ぶ時に便利な ~/.gdbinit を紹介します。まず ~/.gdbinit を次のように記述してください。

#
# ~/.gdbinit
#
# .so を shlib コマンドで手動で読み込む
# set auto-solib-add 0
# スレッド生成時のSIG32でブレークしない
handle SIG32 nostop
# ニモニック構文の選択
# set disassembly-flavor intel
set disassembly-flavor att
# フラグレジスタの可読化関数
define pf
        printf "eflags: %s%s%s%s%s%s%s%s%s (= 0x%08u)\n",\
                $eflags & 2048 ?  "O":"-",\
                $eflags & 1024 ?  "D":"-",\
                $eflags & 512  ?  "I":"-",\
                $eflags & 256  ?  "T":"-",\
                $eflags & 128  ?  "S":"-",\
                $eflags & 64   ?  "Z":"-",\
                $eflags & 16   ?  "A":"-",\
                $eflags & 4    ?  "P":"-",\
                $eflags & 1    ?  "C":"-",\
                $eflags
end
# stepiしてフラグを表示
define sip
        si
        pf
end
# stepiしてフラグ+レジスタを表示
define sips
        si
        pf
        reg
end
# 次に実行する命令(とその先10命令)のアセンブリリストを出力する
define al
        x/11i $pc
end
# レジスタを表示
define reg
        printf "eax:%08x ebx:%08x ecx:%08x edx:%08x\nedi:%08x esi:%08x ebp:%08x esp:%08x\n", \
                $eax, $ebx, $ecx, $edx, $edi, $esi, $ebp, $esp
end
# すべてのレジスタを表示
define allreg
        reg
        printf  "CS:%04x DS:%04x SS:%04x ES:%04x FS:%04x GS:%04x EIP:%08x", \
                $cs, $ds, $ss, $es, $fs, $gs, $eip
        printf "\n"
        pf
        printf "\ncurrent instruction:\n "
        x/1i $pc
end
define myinit
        source ~/gdbcom
end

さらに、~/gdbcom というファイルを次のように記述します。

break main
# 停止時にPCの指している命令を表示
display/i $pc

実行バイナリをロードしたら (gdb) myinit を実行し、あとは適宜 disas して結果リストを見たりしながら、自分で定義したコマンド sip で読み進めていきます。

(gdb)  myinit
(gdb)  r
(gdb)  sip
1: x/i $pc  0x8048301 <Strcmp+13>:      scas   %es:(%edi),%al 
%eflags = --ITS-AP- (918) 
(gdb)  sip
1: x/i $pc  0x8048302 <Strcmp+14>:      jne    0x804830c <Strcmp+24> 
%eflags = --IT-Z-P- (838) 

如何でしょう?

混合モード表示

VC++などの高機能なデバッガでは、一般に「混合モード」などと呼ばれる、高水準言語のソースコードと対応するアセンブリリストを混ぜて表示するモードがあります。しかし、我らがgdbにはもちろん!そんな便利な機能はないんですよね〜。


なんとかそれを模倣する方法はないかと考えてみました。結果、どうにかなりました。以下の方法です。


バイナリとソースコードを同じ場所に置き、

$ objdump -CxS --prefix-addresses a.out

としましょう。-CxS は --demangle --all-headers --source と同じ意味です。-x をつけないと、-d つまり --disassemble を書いたのと同じ意味になりますが、その場合少し出力が減るので、場合によっては見やすいかもしれません。


たとえば次のような出力になります。

int foo(C* c) {
080483d0 <foo(C*)> push   %ebp
080483d1 <foo(C*)+0x1> mov    %esp,%ebp
080483d3 <foo(C*)+0x3> sub    $0x8,%esp
    int ret = 0;
080483d6 <foo(C*)+0x6> movl   $0x0,0xfffffffc(%ebp)
    for(int j=0; j<5; ++j) {
080483dd <foo(C*)+0xd> movl   $0x0,0xfffffff8(%ebp)
080483e4 <foo(C*)+0x14> cmpl   $0x4,0xfffffff8(%ebp)
080483e8 <foo(C*)+0x18> jle    080483ec <foo(C*)+0x1c>
080483ea <foo(C*)+0x1a> jmp    0804840c <foo(C*)+0x3c>
        ret += c->b->a->i();
080483ec <foo(C*)+0x1c> sub    $0xc,%esp
080483ef <foo(C*)+0x1f> mov    0x8(%ebp),%eax
080483f2 <foo(C*)+0x22> mov    (%eax),%eax
080483f4 <foo(C*)+0x24> pushl  (%eax)
080483f6 <foo(C*)+0x26> call   08048470 <A::i() const>
080483fb <foo(C*)+0x2b> add    $0x10,%esp
080483fe <foo(C*)+0x2e> mov    %eax,%edx
08048400 <foo(C*)+0x30> lea    0xfffffffc(%ebp),%eax
08048403 <foo(C*)+0x33> add    %edx,(%eax)
08048405 <foo(C*)+0x35> lea    0xfffffff8(%ebp),%eax
08048408 <foo(C*)+0x38> incl   (%eax)
0804840a <foo(C*)+0x3a> jmp    080483e4 <foo(C*)+0x14>
    }
    return ret;
0804840c <foo(C*)+0x3c> mov    0xfffffffc(%ebp),%eax
}
0804840f <foo(C*)+0x3f> leave  
08048410 <foo(C*)+0x40> ret    
08048411 <foo(C*)+0x41> nop  

如何でしょう?


最適化をかけたバイナリでもそれなりに閲覧可能です。このdisassemble結果を表示しているターミナルを、gdbの脇にでも置いておけばそれなりに快適なデバッグができます :-)