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
次回はフロッピー読み込みをデバッグしてみます。