Yabu.log

ITなどの雑記

objdumpによる逆アセンブルとgdbの命令形式表示の比較

02_day/helloos5で比較しています。 このOS(というかプログラム)はブートセクタの中でhello worldを画面に出力するだけのものになります。

元のソース(アセンブリ)

; hello-os
; TAB=4

        ORG     0x7c00          ; このプログラムがどこに読み込まれるのか

; 以下は標準的なFAT12フォーマットフロッピーディスクのための記述

        JMP     entry
        DB      0x90
        DB      "HELLOIPL"      ; ブートセクタの名前を自由に書いてよい(8バイト)
        DW      512             ; 1セクタの大きさ(512にしなければいけない)
        DB      1               ; クラスタの大きさ(1セクタにしなければいけない)
        DW      1               ; FATがどこから始まるか(普通は1セクタ目からにする)
        DB      2               ; FATの個数(2にしなければいけない)
        DW      224             ; ルートディレクトリ領域の大きさ(普通は224エントリにする)
        DW      2880            ; このドライブの大きさ(2880セクタにしなければいけない)
        DB      0xf0            ; メディアのタイプ(0xf0にしなければいけない)
        DW      9               ; FAT領域の長さ(9セクタにしなければいけない)
        DW      18              ; 1トラックにいくつのセクタがあるか(18にしなければいけない)
        DW      2               ; ヘッドの数(2にしなければいけない)
        DD      0               ; パーティションを使ってないのでここは必ず0
        DD      2880            ; このドライブ大きさをもう一度書く
        DB      0,0,0x29        ; よくわからないけどこの値にしておくといいらしい
        DD      0xffffffff      ; たぶんボリュームシリアル番号
        DB      "HELLO-OS   "   ; ディスクの名前(11バイト)
        DB      "FAT12   "      ; フォーマットの名前(8バイト)
        RESB    18              ; とりあえず18バイトあけておく

; プログラム本体

entry:
        MOV     AX,0            ; レジスタ初期化
        MOV     SS,AX
        MOV     SP,0x7c00
        MOV     DS,AX
        MOV     ES,AX

        MOV     SI,msg
putloop:
        MOV     AL,[SI]
        ADD     SI,1            ; SIに1を足す
        CMP     AL,0
        JE      fin
        MOV     AH,0x0e         ; 一文字表示ファンクション
        MOV     BX,15           ; カラーコード
        INT     0x10            ; ビデオBIOS呼び出し
        JMP     putloop
fin:
        HLT                     ; 何かあるまでCPUを停止させる
        JMP     fin             ; 無限ループ

msg:
        DB      0x0a, 0x0a      ; 改行を2つ
        DB      "hello, world"
        DB      0x0a            ; 改行
        DB      0

        RESB    0x7dfe-$        ; 0x7dfeまでを0x00で埋める命令

        DB      0x55, 0xaa

gobjdumpによる逆アセンブル

$ gobjdump -b binary -m i386 -M intel -D helloos.img

helloos.img:     ファイル形式 binary


セクション .data の逆アセンブル:

00000000 <.data>:
       0:   eb 4e                   jmp    0x50
       2:   90                      nop
       3:   48                      dec    eax
       4:   45                      inc    ebp
       5:   4c                      dec    esp
       6:   4c                      dec    esp
       7:   4f                      dec    edi
       8:   49                      dec    ecx
       9:   50                      push   eax
       a:   4c                      dec    esp
       b:   00 02                   add    BYTE PTR [edx],al
       d:   01 01                   add    DWORD PTR [ecx],eax
       f:   00 02                   add    BYTE PTR [edx],al
      11:   e0 00                   loopne 0x13
      13:   40                      inc    eax
      14:   0b f0                   or     esi,eax
      16:   09 00                   or     DWORD PTR [eax],eax
      18:   12 00                   adc    al,BYTE PTR [eax]
      1a:   02 00                   add    al,BYTE PTR [eax]
      1c:   00 00                   add    BYTE PTR [eax],al
      1e:   00 00                   add    BYTE PTR [eax],al
      20:   40                      inc    eax
      21:   0b 00                   or     eax,DWORD PTR [eax]
      23:   00 00                   add    BYTE PTR [eax],al
      25:   00 29                   add    BYTE PTR [ecx],ch
      27:   ff                      (bad)  
      28:   ff                      (bad)  
      29:   ff                      (bad)  
      2a:   ff 48 45                dec    DWORD PTR [eax+0x45]
      2d:   4c                      dec    esp
      2e:   4c                      dec    esp
      2f:   4f                      dec    edi
      30:   2d 4f 53 20 20          sub    eax,0x2020534f
      35:   20 46 41                and    BYTE PTR [esi+0x41],al
      38:   54                      push   esp
      39:   31 32                   xor    DWORD PTR [edx],esi
      3b:   20 20                   and    BYTE PTR [eax],ah
      3d:   20 00                   and    BYTE PTR [eax],al
    ...
      4f:   00 b8 00 00 8e d0       add    BYTE PTR [eax-0x2f720000],bh
      55:   bc 00 7c 8e d8          mov    esp,0xd88e7c00
      5a:   8e c0                   mov    es,eax
      5c:   be 74 7c 8a 04          mov    esi,0x48a7c74
      61:   83 c6 01                add    esi,0x1
      64:   3c 00                   cmp    al,0x0
      66:   74 09                   je     0x71
      68:   b4 0e                   mov    ah,0xe
      6a:   bb 0f 00 cd 10          mov    ebx,0x10cd000f
      6f:   eb ee                   jmp    0x5f
      71:   f4                      hlt    
      72:   eb fd                   jmp    0x71
      74:   0a 0a                   or     cl,BYTE PTR [edx]
      76:   68 65 6c 6c 6f          push   0x6f6c6c65
      7b:   2c 20                   sub    al,0x20
      7d:   77 6f                   ja     0xee
      7f:   72 6c                   jb     0xed
      81:   64 0a 00                or     al,BYTE PTR fs:[eax]
    ...
     1fc:   00 00                   add    BYTE PTR [eax],al
     1fe:   55                      push   ebp
     1ff:   aa                      stos   BYTE PTR es:[edi],al
     200:   f0 ff                   lock (bad)
     202:   ff 00                   inc    DWORD PTR [eax]
    ...
    1400:   f0 ff                   lock (bad)
    1402:   ff 00                   inc    DWORD PTR [eax]
    ...

結果としてわかることですが 0~0x3dはDB命令で書き込んでいるデータ(アセンブリで言う所のとりあえず18バイトあけておく以上の箇所) で、コードとして意味がある表示ができている箇所は0x50~0x72までですね

gdbのxコマンドによる命令表示

(gdb) x/38bi 0x7c00
   0x7c00:  jmp    0x7c50
   0x7c02:  nop
   0x7c03:  dec    eax
   0x7c04:  inc    ebp
   0x7c05:  dec    esp
   0x7c06:  dec    esp
   0x7c07:  dec    edi
   0x7c08:  dec    ecx
   0x7c09:  push   eax
   0x7c0a:  dec    esp
   0x7c0b:  add    BYTE PTR [edx],al
   0x7c0d:  add    DWORD PTR [ecx],eax
   0x7c0f:  add    BYTE PTR [edx],al
   0x7c11:  loopne 0x7c13
   0x7c13:  inc    eax
   0x7c14:  or     esi,eax
   0x7c16:  or     DWORD PTR [eax],eax
   0x7c18:  adc    al,BYTE PTR [eax]
   0x7c1a:  add    al,BYTE PTR [eax]
   0x7c1c:  add    BYTE PTR [eax],al
   0x7c1e:  add    BYTE PTR [eax],al
   0x7c20:  inc    eax
   0x7c21:  or     eax,DWORD PTR [eax]
   0x7c23:  add    BYTE PTR [eax],al
   0x7c25:  add    BYTE PTR [ecx],ch
   0x7c27:  (bad)  
   0x7c28:  (bad)  
   0x7c29:  (bad)  
   0x7c2a:  dec    DWORD PTR [eax+0x45]
   0x7c2d:  dec    esp
   0x7c2e:  dec    esp
   0x7c2f:  dec    edi
   0x7c30:  sub    eax,0x2020534f
   0x7c35:  and    BYTE PTR [esi+0x41],al
   0x7c38:  push   esp
   0x7c39:  xor    DWORD PTR [edx],esi
   0x7c3b:  and    BYTE PTR [eax],ah
   0x7c3d:  and    BYTE PTR [eax],al
(gdb) x/18bi 0x7c4f
   0x7c4f:  add    BYTE PTR [eax-0x2f720000],bh
   0x7c55:  mov    esp,0xd88e7c00
   0x7c5a:  mov    es,eax
   0x7c5c:  mov    esi,0x48a7c74
   0x7c61:  add    esi,0x1
   0x7c64:  cmp    al,0x0
   0x7c66:  je     0x7c71
   0x7c68:  mov    ah,0xe
   0x7c6a:  mov    ebx,0x10cd000f
   0x7c6f:  jmp    0x7c5f
   0x7c71:  hlt    
   0x7c72:  jmp    0x7c71
   0x7c74:  or     cl,BYTE PTR [edx]
   0x7c76:  push   0x6f6c6c65
   0x7c7b:  sub    al,0x20
   0x7c7d:  ja     0x7cee
   0x7c7f:  jb     0x7ced
   0x7c81:  or     al,BYTE PTR fs:[eax]

いくつか違う点がありますが、ほぼ逆アセンブルの結果と同じですね

個人的見所

0x76~0x83のオフセットはデータ

そこに書き込まれているのは"hello, world"という文字列です

76:  68 65 6c 6c 6f          push   0x6f6c6c65
7b: 2c 20                   sub    al,0x20
7d: 77 6f                   ja     0xee
7f: 72 6c                   jb     0xed
81: 64 0a 00                or     al,BYTE PTR fs:[eax]

証拠に"hello, world"という文字のasciiを表示しました

$ echo hello, world|hexdump -C
00000000  68 65 6c 6c 6f 2c 20 77  6f 72 6c 64 0a           |hello, world.|

アセンブリではDB命令で書き込んでいます

     DB      "hello, world"

すこし気に入らないのはこれらは固定長のデータとかではなく、 本当にバイナリの並びを命令として解釈しようとしていることですね。

0x7c76:  push   0x6f6c6c65
0x7c7b: sub    al,0x20
0x7c7d: ja     0x7cee
0x7c7f: jb     0x7ced
0x7c81: or     al,BYTE PTR fs:[eax]

DB命令はオペランドを内容をそのままメモリに書き込む内容ですが、 DB命令を多用すると、逆アセンブル時に元の命令と対応させるのが難しい実行ファイルができてしまいそうです。

JMP命令の解釈がgdbと逆アセンブラで違う

一番最初の命令ですね。

  • 逆アセ:jmp 0x50
  • GDB: jmp 0x7c50

gdbで16進数でバイナリダンプしてみましたが、逆アセンブル時と同じく0xed,0x4eです。

(gdb) x/5bx 0x7c00
0x7c00: 0xeb    0x4e    0x90    0x48    0x45

GDBではセグメントレジスタまで考慮して命令を表示しているのかと思い、qemu monitorで各レジスタを表示してみましたが、そんなことはありませんでした。(cs=0)

(qemu) info registers
EAX=0000aa55 EBX=00000000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00006ef0
EIP=00007c00 EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300
CS =0000 00000000 0000ffff 00009b00
SS =0000 00000000 0000ffff 00009300
DS =0000 00000000 0000ffff 00009300
FS =0000 00000000 0000ffff 00009300
GS =0000 00000000 0000ffff 00009300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT=     00000000 00000000
IDT=     00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000

もちろんこのベースアドレス分の違いはORG命令からきているものだと思いますが、

     ORG     0x7c00          ; このプログラムがどこに読み込まれるのか

.imgのバイナリの段階でこの情報が抜け落ちていて、gdbで実行中だけ見えるというのは納得が行きません。

オペランド長の設定が不適切なため、次の命令がオペランドに不正に塗りつぶされている

0x7c6a:  mov    ebx,0x10cd000f
0x7c6f: jmp    0x7c5f

ここですが、int命令が0x7c6dから始まるint命令がmove命令のオペランドとして吸収されています

(gdb) x/2bi 0x7c6d
   0x7c6d:  int    0x10
   0x7c6f:  jmp    0x7c5f

解決策

16bit命令として解釈させる

$ gobjdump -b binary -m i8086 -M intel -D helloos.img

- m optionをi386ではなく、i8086を指定すれば良いようだ。

00000000 <.data>:
       0:   eb 4e                   jmp    0x50
       2:   90                      nop
       3:   48                      dec    ax
       4:   45                      inc    bp
       5:   4c                      dec    sp
       6:   4c                      dec    sp
       7:   4f                      dec    di
       8:   49                      dec    cx
       9:   50                      push   ax
       a:   4c                      dec    sp
       b:   00 02                   add    BYTE PTR [bp+si],al
       d:   01 01                   add    WORD PTR [bx+di],ax
       f:   00 02                   add    BYTE PTR [bp+si],al
      11:   e0 00                   loopne 0x13
      13:   40                      inc    ax
      14:   0b f0                   or     si,ax
      16:   09 00                   or     WORD PTR [bx+si],ax
      18:   12 00                   adc    al,BYTE PTR [bx+si]
      1a:   02 00                   add    al,BYTE PTR [bx+si]
      1c:   00 00                   add    BYTE PTR [bx+si],al
      1e:   00 00                   add    BYTE PTR [bx+si],al
      20:   40                      inc    ax
      21:   0b 00                   or     ax,WORD PTR [bx+si]
      23:   00 00                   add    BYTE PTR [bx+si],al
      25:   00 29                   add    BYTE PTR [bx+di],ch
      27:   ff                      (bad)  
      28:   ff                      (bad)  
      29:   ff                      (bad)  
      2a:   ff 48 45                dec    WORD PTR [bx+si+0x45]
      2d:   4c                      dec    sp
      2e:   4c                      dec    sp
      2f:   4f                      dec    di
      30:   2d 4f 53                sub    ax,0x534f
      33:   20 20                   and    BYTE PTR [bx+si],ah
      35:   20 46 41                and    BYTE PTR [bp+0x41],al
      38:   54                      push   sp
      39:   31 32                   xor    WORD PTR [bp+si],si
      3b:   20 20                   and    BYTE PTR [bx+si],ah
      3d:   20 00                   and    BYTE PTR [bx+si],al
    ...
      4f:   00 b8 00 00             add    BYTE PTR [bx+si+0x0],bh
      53:   8e d0                   mov    ss,ax
      55:   bc 00 7c                mov    sp,0x7c00
      58:   8e d8                   mov    ds,ax
      5a:   8e c0                   mov    es,ax
      5c:   be 74 7c                mov    si,0x7c74
      5f:   8a 04                   mov    al,BYTE PTR [si]
      61:   83 c6 01                add    si,0x1
      64:   3c 00                   cmp    al,0x0
      66:   74 09                   je     0x71
      68:   b4 0e                   mov    ah,0xe
      6a:   bb 0f 00                mov    bx,0xf
      6d:   cd 10                   int    0x10
      6f:   eb ee                   jmp    0x5f
      71:   f4                      hlt    
      72:   eb fd                   jmp    0x71
      74:   0a 0a                   or     cl,BYTE PTR [bp+si]
      76:   68 65 6c                push   0x6c65
      79:   6c                      ins    BYTE PTR es:[di],dx
      7a:   6f                      outs   dx,WORD PTR ds:[si]
      7b:   2c 20                   sub    al,0x20
      7d:   77 6f                   ja     0xee
      7f:   72 6c                   jb     0xed
      81:   64 0a 00                or     al,BYTE PTR fs:[bx+si]
    ...
     1fc:   00 00                   add    BYTE PTR [bx+si],al
     1fe:   55                      push   bp
     1ff:   aa                      stos   BYTE PTR es:[di],al
     200:   f0 ff                   lock (bad)
     202:   ff 00                   inc    WORD PTR [bx+si]
    ...
    1400:   f0 ff                   lock (bad)
    1402:   ff 00                   inc    WORD PTR [bx+si]
    ...

6dからの命令が正しくint 0x10と出力されている

gdbで少し探してみたが、set architecture i8086というコマンドがあるようだ

https://stackoverflow.com/questions/28811811/how-to-use-gdb-in-16-bit-mode

GDBのxコマンドのiオプションは

アセンブラシンタックスでの (もしくは、それに近い) マシン・ インストラクションを表示します。指定されたユニットサイズは無視され; 1つのインストラクションを構成するバイト数は、そのマシンで利用されている オペコードとアドレッシングモードの種類に依存します。

なので、かなりこれが解決策っぽい!

http://flex.phys.tohoku.ac.jp/texi/gdb-j/gdb-j_41.html

が、適応してみたところ、結局32bitの機械語として解釈されてしまった。

(gdb) set architecture i8086
The target architecture is assumed to be i8086
(gdb) x/18i 0x7c50
   0x7c50:  mov    eax,0xd08e0000
   0x7c55:  mov    esp,0xd88e7c00
   0x7c5a:  mov    es,eax
   0x7c5c:  mov    esi,0x48a7c74
   0x7c61:  add    esi,0x1
   0x7c64:  cmp    al,0x0
   0x7c66:  je     0x7c71
   0x7c68:  mov    ah,0xe
   0x7c6a:  mov    ebx,0x10cd000f
   0x7c6f:  jmp    0x7c5f
   0x7c71:  hlt    
   0x7c72:  jmp    0x7c71
   0x7c74:  or     cl,BYTE PTR [edx]
   0x7c76:  push   0x6f6c6c65
   0x7c7b:  sub    al,0x20
   0x7c7d:  ja     0x7cee
   0x7c7f:  jb     0x7ced
   0x7c81:  or     al,BYTE PTR fs:[eax]

コード部分(0x50~0x7f)をステップ実行してみる

(gdb) x/18bi 0x7c50
=> 0x7c50:   mov    eax,0xd08e0000
   0x7c55:  mov    esp,0xd88e7c00
   0x7c5a:  mov    es,eax
   0x7c5c:  mov    esi,0x48a7c74
   0x7c61:  add    esi,0x1
   0x7c64:  cmp    al,0x0
   0x7c66:  je     0x7c71
   0x7c68:  mov    ah,0xe
   0x7c6a:  mov    ebx,0x10cd000f
   0x7c6f:  jmp    0x7c5f
   0x7c71:  hlt    
   0x7c72:  jmp    0x7c71
   0x7c74:  or     cl,BYTE PTR [edx]
   0x7c76:  push   0x6f6c6c65
   0x7c7b:  sub    al,0x20
   0x7c7d:  ja     0x7cee
   0x7c7f:  jb     0x7ced

画面の文字表示のAPIが0x7c6dにアロケートされるとがわかったのでそこにブレイクポイントを置いてステップ実行をしています。

(gdb) b *0x7c6d
Breakpoint 3 at 0x7c6a
(gdb) continue

www.youtube.com

次回はフロッピー読み込みをデバッグしてみます。