GDBデバッガーを最大限に使用する

私たちの日常業務では、誰もがそうであるように、多くのデバッガーを使用する必要があります。 作業の詳細:(OS開発、Intel-VTなどの仮想化技術の使用など)特定のケースで作業するために、しばしばデバッガーを使用する必要があります:カーネルローダーコードのデバッグ、仮想マシンローダーのデバッグ、および原則として、機能の提供独自のOSをデバッグします。 これらの特別なケースは、「最大限に」という見出しで哀れな名前が付けられています。



これらすべての問題(そしてもちろん、他の多くの問題)を解決するために、gdbを使用します。 DDDのようなシェルを使用することは可能ですが、個人的には、特にsshを介してデバッガーを使用する場合には、cgdbを最良の選択として使用することを好みます。

この記事では、gdbを使用してブートセクターとブートローダーのコードをデバッグする方法について説明します。



新しいOSの開発の開始時には、このOSの通常のブートローダーの存在を確認する必要があります。 OS開発者の「真実」は、主に、リアルモードでのBIOSポストの後にプロセッサがブートセクタコードの実行を開始するという事実にあります。 この時点では、OSはまだロードされていません(ブートセクターの512バイトのみ)が、デバッガーを完全にサポートするには、OSのカーネルに特別なモジュールを実装する必要があります(詳細については、パート2を参照)。 問題は、ブートセクターとブートローダーコードをデバッグする方法ですか? 実際、メインOSデバッガー(特別なモジュールを使用)で作業する前に、「ブートローダー」をRAMにロードし、カーネルとその基本モジュールをロードし、ハードウェアを初期化します(gdbを使用するには少なくともシリアルポートが必要です)フルOSデバッガでの作業が可能になります。

この問題は非常に簡単に解決できることがわかりました。組み込みのデバッグ機能をサポートする何らかの仮想マシン内でOSのロードを開始する必要があります。



例はqemuの使用です:

1.仮想マシンを実行します(たとえば、フロッピーディスクから単純なブートOSを使用します)。

qemu -fda ./boot.fdd -s -S -vnc none &





(qemu仮想マシンを起動します。イメージはファイル「./boot.fdd」にあるディスケットから起動します。「-s -S」はマシンがサスペンドモードとデバッグモードで起動することを意味し、「-vnc none」はマシンがアクティブな端末なしで起動すること(つまり、バックグラウンドでsshを使用する場合に特に便利であり、ブートローダーとブートセクターをデバッグするためにコンピューター画面を表示する必要はほとんどありません); &&-仮想マシンをバックグラウンドで実行します。

2.次に、gdbデバッガー自体を実行します。

$ gdb

GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2

Copyright (C) 2010 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law. Type "show copying"

and "show warranty" for details.

This GDB was configured as "i686-linux-gnu".

For bug reporting instructions, please see:

<www.gnu.org/software/gdb/bugs>.

(gdb)






3. gdbデバッガーで、qemuポートに接続します。

(Gdb) target remote localhost: 1234







ところで、このポートはnetstatで見ることができます:

$ netstat –tlpn

Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name

tcp 0 0 0.0.0.0: 1234 0.0.0.0:* LISTEN 4014/qemu









デバッグを開始するアドレスに注意してください。

(gdb) target remote localhost: 1234

Remote debugging using localhost: 1234

0x0000fff0 in ?? ()

(gdb)








このアドレスから、電源がオンになると、すべてのプロセッサが動作を開始します。 言い換えれば、BIOSの最初(もちろん、仮想マシン内)にいたのです。

4.次に、リアルモードコードをデバッグするためにgdbを準備する必要があります。

(gdb) set arch i8086

The target architecture is assumed to be i8086

(gdb)








5.ここで、ブートセクタの先頭に最初のブレークポイントを配置する必要があります。

(gdb) break * 0x7c00

Breakpoint 1 at 0x7c00

(gdb)








(BIOSは、指定されたデバイス(フロッピーディスクから)の最初の512バイトを0x7c00のメモリに読み込み 、制御をこのメモリ領域に転送します)。



6.ブートセクターにジャンプします。

(gdb) c

Continuing.



Breakpoint 1, 0x00007c00 in ?? ()

(gdb)








7.完了! コードをデバッグできます!

デバッガーコードをデバッグするための多くの機能があります。

最初に、RealModeモードでのデータアドレス指定の機能を考慮する必要があります。 すべての情報は、セグメント:オフセットとしてアドレス指定されますが、アドレスは、セグメント* 16 +オフセットとして計算できます。 つまり、現在の命令から始めて、コードの最初の10個の命令を読み取るには、コマンド(gdb) x/10i $cs*16+$eip



を使用する必要があります。 この場合の不便は、コードセグメントのベースを考慮せずに現在のgdb命令が表示されることです。

(gdb) x/10i $cs*16+$eip

0x80000: jmp 0x90013

0x80002: (bad)

0x80003: mov $0x8,%di

0x80006: add $0xff,%al

0x80008: aas

0x80009: add %al,(%bx,%si)

0x8000b: add %al,(%bx,%si)

0x8000d: add %al,(%bx,%si)

0x8000f: add %al,(%bx,%si)

0x80011: add %al,(%bx,%si)

(gdb)








命令のアドレスは次のとおりです。

Program received signal SIGTRAP, Trace/breakpoint trap.

0x00000000 in ?? ()

(gdb)








第二に、ブレークポイントを手動で設定および削除する必要があります。 これは、次の場所にブレークポイントを設定した場合:

(gdb) break *0x80017

Breakpoint 4 at 0x800 17







...その後、彼を打つ..:

(gdb) c

Continuing.



Program received signal SIGTRAP, Trace/breakpoint trap.

0x000000 17 in ?? ()

(gdb)








...そして、stepiを実行すると、命令自体をたどって以下に切り替えるのではなく、再びブレークポイントに移動します。

Program received signal SIGTRAP, Trace/breakpoint trap.

0x000000 17 in ?? ()

(gdb) stepi

0x000000 17 in ?? ()

(gdb)








したがって、デバッグプロセスは次のようになります。

1.ブートローダーコードのどこか:

Program received signal SIGTRAP, Trace/breakpoint trap.

0x00000023 in ?? ()

(gdb) x/10i $cs*16+$eip

0x80023: mov %ebx,0xb

0x80028: movl $0x0,0xf

0x80031: call 0x80c15

0x80034: or %ax,%ax

0x80036: je 0x80087

0x80038: call 0x809af

0x8003b: call 0x80193

0x8003e: or %ax,%ax

0x80040: je 0x80268

0x80042: mov $0x3f4,%dx

(gdb)






2.ブレークポイントを設定し、呼び出し後にカットします。

(gdb) break *0x8003e

Breakpoint 6 at 0x8003e








3.このブレークポイントの前にプログラムを実行します。

(gdb) c

Continuing.



Program received signal SIGTRAP, Trace/breakpoint trap.

0x0000003e in ?? ()

(gdb)






4.ブレークポイントを削除します。

(gdb) delete 6

(gdb)







5.戻り値を確認します。

(gdb) print /x $ax

$2 = 0x8000

(gdb)







6.さらに一歩進んでください。

(gdb) stepi

0x00000040 in ?? ()

(gdb)








そのため、関数のパラメーターのいずれかの値を読み取ることができます。

(gdb) x/1w $ss*16+0x12

0x70012: 0x00b8fa00

(gdb)








この方法をよく使用します。 確かに、他のカップルと一緒に使用する必要があり(たとえば、仮想マシンでの仮想マシンのデバッグ)、作業が少し複雑になりますが、本質は変わりません。



All Articles