GDB デバッグ ツールをマスターして、バグのトラブルシューティングを簡単にしましょう。

1.GDBとは

gdb は GNU debugger の略で、プログラミングのデバッグツールです。

  • GDB 公式ウェブサイト: https://www.gnu.org/software/gdb/
  • GDB に適用可能なプログラミング言語: Ada / C / C++ / object-c / Pascal など。
  • GDB の仕組み: ローカル デバッグとリモート デバッグ。

現在のリリースの最新バージョンは 8.0 で、GDB は Linux および Windows オペレーティング システム上で実行できます。

1.1 GDBのインストールと起動

  1. gdb -v はインストールが成功したかどうかを確認し、失敗した場合はインストールします (gcc などのコンパイラーがインストールされていることを確認する必要があります)。

  2. GDBを起動する

    1. gdb test_file.exe は gdb デバッグを開始します。つまり、デバッグする実行可能ファイルの名前を直接指定します。

    2. gdb を直接入力して起動し、コマンド ファイル test_file.exe を使用して gdb を入力した後にファイル名を指定します。

    3. ターゲット実行ファイルが入出力パラメータ (argv[] 受信パラメータなど) を必要とする場合、パラメータは次の 3 つの方法で指定できます。

      1. gdb を起動するとき、gdb --args text_file.exe
      2. gdb に入ったら、set args param_1 を実行します。
      3. gdb デバッグに入った後、param_1 を実行するか、para_1 を起動します。

1.2 gdbの機能

  • プログラムを開始すると、ユーザー定義の要件に従って好きなようにプログラムを実行できます。
  • デバッグ中のプログラムを、ユーザーが指定したデバッグ ブレークポイントで停止できるようにします (ブレークポイントは条件式にすることができます)。
  • プログラムが停止すると、その時点でプログラム内で何が起こったかを確認できます。たとえば、変数の値を出力できます。
  • 可変プログラムの実行環境を動的に変更します。

1.3 gdb の使用

プログラムを実行する

run(r)运行程序,如果要加参数,则是run arg1 arg2 ... 

ソースコードを表示する

list(l):查看最近十行源码
list fun:查看fun函数源代码
list file:fun:查看flie文件中的fun函数源代码

ブレークポイントの設定とブレークポイントの監視

break 行号/fun设置断点。
break file:行号/fun设置断点。
break if<condition>:条件成立时程序停住。
info break(缩写:i b):查看断点。
watch expr:一旦expr值发生改变,程序停住。
delete n:删除断点。

シングルステップデバッグ

continue(c):运行至下一个断点。
step(s):单步跟踪,进入函数,类似于VC中的step in。
next(n):单步跟踪,不进入函数,类似于VC中的step out。
finish:运行程序,知道当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
until:当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序知道退出循环体。

ランタイムデータの表示

print(p):查看运行时的变量以及表达式。
ptype:查看类型。
print array:打印数组所有元素。
print *array@len:查看动态内存。len是查看数组array的元素个数。
print x=5:改变运行时数据。

1.4 プログラムエラー

  • コンパイルエラー:プログラムを作成する際に、言語仕様に準拠していないため、コンパイルエラーが発生します。例: 文法上の誤り。
  • 実行時エラー: コンパイラーはこの種のエラーを検出できませんが、実行時にプログラムがクラッシュする可能性があります。例: メモリアドレス不正アクセス。
  • 論理エラー: コンパイルと実行は問題ありませんが、プログラムは期待どおりの動作をしません。

1.5gdb デバッグセグメント障害

セグメンテーション違反とは何ですか? セグメンテーション違反は、不正なアドレスへのアクセスによって発生するエラーです。

  • システムデータ領域にアクセスし、特にシステムによって保護されているメモリアドレスにデータを書き込みます。例: アドレスが 0 のアドレスにアクセスします。
  • 境界外のメモリ (境界外の配列、変数の型の不一致など) は、現在のプログラムに属さないメモリ領域にアクセスします。

Gdb はセグメント フォールトをデバッグします。プログラムを直接実行できます。プログラムがクラッシュすると、gdb は実行中の情報を出力します。例: SIGSEGV シグナルを受信した後、コマンドを使用してスタック バックトレース情報を出力し、その後プログラムを変更できます。プログラムのエラーコードに応じてbt

1.6.コアファイルのデバッグ

6.1コアファイル

プログラムがクラッシュすると、通常、coreファイルと呼ばれるファイルが生成されます。コア ファイルは、プログラムがクラッシュしたときのメモリ イメージを記録し、デバッグ情報を追加するものであり、コア ファイル生成プロセスは と呼ばれますcore dump(核心已转储)システムはデフォルトではこのファイルを生成しません。

6.2 コアファイル生成の設定

  • ulimit -c: コアダンプのステータスを確認します。
  • ulimit -c xxxx: コアファイルのサイズを設定します。
  • ulimit -c unlimited: コア ファイルのサイズは無制限です。

6.3 gdb デバッグコアファイル

を設定したulimit -c xxxx後、プログラムを再度実行するとセグメンテーション違反が発生しますが、このときcoreファイルが生成されるので、gdb coreデバッグコアファイルを使用し、btコマンドを使用してスタックトレースバック情報を出力します。

2、GDB共通コマンド

  • 以下では、ソースプログラム例の名前として test_file.c 、実行ファイル例の名前として test_file.exe 、パラメータ例の名前として param_1 を使用しています。
  • (gdb) は gdb デバッグ モードで実行することを意味します
  • 一般に、よく使用される方法は、ブレークポイント デバッグシングルステップ デバッグの2 つです
  • list(l):ソースコードのリスト
  • quit(q): GDB デバッグ モードを終了します。
  • gdb と入力した後、help と入力してすべてのコマンドの手順を表示します。

2.1 ソースコードを表示する

list [関数名] [行数]

2.2 ブレークポイントのデバッグ

(1) ブレークポイントを設定します。

  • a. ブレーク + [ソースコードの行番号][ソースコードの関数名][メモリアドレス]
  • b. ブレーク ... if 条件 ... は上記のパラメータのいずれかであり、条件は条件です。たとえば、ループ本体で、break ... if i = 100 を設定してループの数を設定できます。

ブレークポイントの削除

(gdb) 位置のクリア: パラメーターの位置は通常、コードの特定の行の行番号または特定の関数名です。location パラメーターが関数の関数名である場合、関数の入り口にあるすべてのブレークポイントを削除することを意味します。

(gdb) delete [breakpoints] [num] : ブレークポイント パラメーターはオプションで、 num パラメーターは指定されたブレークポイントの番号です。これを削除すると、すべてではなく特定のブレークポイントを削除できます。

ブレークポイントを無効にする

**disable [breakpoints] [num…]: **breakpoints パラメータはオプションです。num… は複数のパラメータが存在できることを意味し、各パラメータは無効にするブレークポイントの番号です。num... を指定すると、disable コマンドは指定された番号のブレークポイントを無効にします。それ以外の場合、num... を設定しないと、disable は現在のプログラム内のすべてのブレークポイントを無効にします。

ブレークポイントをアクティブにする

  1. Enable [breakpoints] [num…] num…パラメータで指定された複数のブレークポイントをアクティブにします。num…が設定されていない場合は、無効になっているすべてのブレークポイントをアクティブにすることを意味します
  2. Enable [breakpoints] Once num… num… で番号付けされた複数のブレークポイントを一時的にアクティブにします。ただし、ブレークポイントは 1 回しか使用できず、その後は自動的に無効な状態に戻ります。
  3. Enable [breakpoints] count num… num…で番号付けされた複数のブレークポイントを一時的に有効にし、ブレークポイントは count 回使用でき、その後無効状態になります
  4. Enable [breakpoints] delete num... num... という番号の複数のブレークポイントをアクティブにします。ただし、ブレークポイントは 1 回しか使用できず、その後は完全に削除されます。

Break(b):これは一般的なブレークポイントであり、ブレークポイントには 2 つの形式があります。

(gdb) ブレーク位置 // b location、location はブレークポイントの位置を表します

画像

(gdb) Break … if cond // b … if cond、つまり、cond の条件が true の場合、ブレーク ポイントは「…」になります。

条件コマンドを使用してさまざまな種類のブレークポイントに条件式を設定すると、条件式が true (値が True) の場合にのみ、対応するブレークポイントがトリガーされ、プログラムが一時停止されます。

tbreak : tbreak コマンドは、break コマンドの別のバージョンと見なすことができます。tbreak と Break コマンドの使用法と機能は非常に似ています。唯一の違いは、tbreak コマンドによってヒットしたブレークポイントは、その後であっても 1 回しか使用されないことです。プログラムは一時停止され、ブレークポイントは自動的に消えます。

rbreak : Break および tbreak コマンドとは異なり、rbreak コマンドは C および C++ プログラムの関数に作用し、指定された関数の先頭で中断します。

  • (gdb) tbreak 正規表現

    • regex は、一致した関数の先頭で中断される正規表現を表します。
  • tbreak コマンドによって設定されたブレークポイントは、break コマンドの場合と同じ効果があり、常に存在し、自動的には消えません。

watch : このコマンドは、変数または式の値を監視できる監視ブレークポイントにヒットします。監視対象の変数(式)の値が変化した場合にのみ、プログラムの実行が停止します。

  • (gdb) 監視条件

    • cond は監視する変数または式を表します

rwatch コマンド: プログラム内でターゲット変数 (式) の値を読み取る操作がある限り、プログラムは実行を停止します。

awatchコマンド:プログラム内で対象変数(式)の値を読み取る、または値を変更する操作がある限り、プログラムの実行は停止します。

catch : ブレークポイントをキャッチする機能は、プログラム内で特定の例外が発生したとき、ダイナミック ライブラリがロードされたときなど、プログラム内で特定のイベントの発生を監視することです。ターゲット時間が発生すると、プログラムは停止します。実行中。

(2) ブレークポイントを観察します。

  • a. watch + [変数] [式] 変数または式の値が変化したときにプログラムを停止します。
  • b. rwatch + [変数] [式] 変数または式が読み込まれたら、プログラムを停止します。
  • c, awatch + [変数] [式] 変数または式を読み書きするときは、プログラムを停止します。

(3) キャプチャポイントを設定します。

catch + events イベントが発生したらプログラムを停止します。

イベントは次のとおりです。

  • a, throw C++ によってスローされる例外。(投げがキーワード)
  • b. catch C++ によってキャッチされた例外。(キャッチはキーワードです)
  • c. exec がシステムコール exec を呼び出すとき。(exec はキーワードです。現在、この機能は HP-UX でのみ使用可能です)
  • d. fork が fork を呼び出すシステムを呼び出すとき。(fork はキーワードです。現在、この機能は HP-UX でのみ使用可能です)
  • e. vfork がシステムを呼び出して vfork を呼び出すとき。(vfork はキーワードです。現在、この機能は HP-UX でのみ使用できます)
  • f、load、またはload 共有ライブラリ(ダイナミックリンクライブラリ)をロードする場合。(load はキーワードです。現在、この機能は HP-UX でのみ使用可能です)
  • g、共有ライブラリ(ダイナミックリンクライブラリ)をアンロードする場合、アンロードまたはアンロードします。(unload はキーワードです。現在、この機能は HP-UX でのみ使用可能です)

(4) キャプチャ信号:

ハンドル + [引数] + シグナル

シグナル: Linux/Unix で定義されたシグナルで、SIGINT は割り込み文字シグナル、つまり Ctrl+C のシグナル、SIGBUS はハードウェア障害のシグナル、SIGCHLD は子プロセスの状態を変更するシグナル、SIGCHLD は子プロセスの状態を変更するシグナルを意味します。 SIGKILL はプログラム動作を終了する信号などを意味します。

議論:

  • nostop デバッグ中のプログラムがシグナルを受信すると、GDB はプログラムの実行を停止しませんが、そのようなシグナルを受信したことを通知するメッセージを出力します。
  • stop GDB は、デバッグ中のプログラムがシグナルを受信したときにプログラムを停止します。
  • print GDB は、デバッグ中のプログラムがシグナルを受信するとメッセージを表示します。
  • noprint デバッグ中のプログラムがシグナルを受信して​​も、GDB はシグナルを受信したことを通知しません。
  • pass または noignore GDB は、デバッグ中のプログラムが信号を受信したときに信号を処理しません。これは、GDB がこの信号をデバッグ中のプログラムに渡して処理することを意味します。
  • nopass またはignore デバッグされたプログラムがシグナルを受信すると、GDB はデバッグされたプログラムにシグナルを処理させません。

(5) スレッドの中断:

Break [linespec] thread [threadno] [if …]

linespec ブレークポイントが設定されているソース コードの行番号。たとえば、test.c:12 は、ファイルが test.c の 12 行目にブレークポイントを設定することを意味します。

threadno スレッドの ID。GDBによって割り当てられ、info threadを入力することで実行中のプログラムのスレッド情報を表示できます。

if … ブレーク条件を設定します。

情報を見る:

(1) データの表示:

変数を表示する 変数を表示する

print *array@len 配列を表示します (array は配列ポインター、len は必要なデータ長です)

出力形式はパラメータを追加することで設定できます。

/ 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。 
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/f 按浮点数格式显示变量。

(2) ビューメモリ

/nfu + メモリアドレス (ポインタ変数) を調べる

  • nは表示メモリ長を示します
  • f は出力形式を示します (上記を参照)
  • u は指定されたバイト数を示します (b 1 バイト、h 2 バイト、w 4 バイト、g 8 バイト、デフォルトは 4 バイト)
  如:x /10cw pFilePath  (pFilePath为一个字符串指针,指针占4字节)
     x 为examine命令的简写。

(3) スタック情報の表示

バックトレース [-n][n]

  • n は、スタックの最上位の n 層のスタック情報のみが印刷されることを示します。
  • -n は、スタックの最下位から上の n 層のスタック情報のみを出力することを意味します。
  • パラメータを指定しないと、すべてのスタック情報が出力されます。

2.3 シングルステップデバッグ

ラン®

続ける©

次へ(n)

  • コマンド形式: (gdb) next count: count は、1 つのステップで実行するコードの行数を示します。デフォルトは 1 行です。
  • 最大の特徴は、呼び出し関数を含むステートメントに遭遇すると、関数内に何行のコードが含まれていても、次の命令が 1 ステップで実行されることです。つまり、呼び出された関数は、次のコマンドではコード行としてのみ扱われます。

ステップ

  • (gdb) ステップ数: パラメータ count は一度に実行される行数を示し、デフォルトは 1 行です。
  • 通常、step コマンドと next コマンドは同じ機能を持ち、どちらもプログラムをステップごとに実行します。違いは、step コマンドによって実行されるコード行に関数が含まれている場合、その関数に入り、関数のコードの最初の行で実行が停止することです。

まで(u)

  • (gdb) until: パラメーターを指定しない until コマンドを使用すると、GDB デバッガーが現在のループ本体を迅速に実行し、ループ本体が停止するまで実行されます。until コマンドはいかなる状況でもこの役割を果たさず、ループ本体の最後 (コードの最後の行) まで実行された場合にのみ、until コマンドがこの効果を持つことに注意してください。それ以外の場合、until コマンドは同じ効果を持ちます。次のコマンドとして機能、シングルステップ実行プログラムのみ

(gdb) until location: パラメーターの場所は、コードの特定の行の行番号です。

変数の値を表示する

印刷§

  • p num_1: パラメータ num_1 は、表示または変更するターゲット変数または式を参照するために使用されます。
  • その機能は、GDB によるプログラムのデバッグの過程で、指定された変数または式の値を出力または変更することです。

プレイ

  • (gdb) 表示式
  • (gdb) 表示/fmt expr
  • expr は、表示されるターゲット変数または式を示します。パラメータ fmt は、出力変数または式の形式を指定するために使用されます。

画像

  • (gdb) 非表示番号…
  • (gdb) 表示番号を削除…
  • パラメータ num... は、ターゲット変数または式の番号を示します。数値の数は複数にすることができます。
  • (gdb) 表示番号を無効にする…
  • リスト内のアクティブな変数または式の自動表示を無効にする
  • (gdb) 表示番号を有効にする…
  • 現在無効になっている変数または式をアクティブにすることもできます
  • print コマンドと同様に、display コマンドもデバッグ段階で変数または式の値を表示するために使用されます。
  • それらの違いは、display コマンドを使用して変数または式の値を表示する場合、プログラムが一時停止するたびに (シングル ステップの実行など)、GDB デバッガーが自動的にその値を出力しますが、print コマンドは出力しないことです。

GDB ハンドル コマンド: シグナル処理

→(gdb) ハンドルシグナルモード このうち、signal パラメータは設定対象のシグナルを示します。通常は、あるシグナルの完全名 (SIGINT) または略称 (INT などの「SIG」を除いた部分) です。すべてを指定したい場合は、信号を all で表すことができます。

mode パラメーターは、GDB がターゲット情報を処理する方法を指定するために使用され、その値は次のようになります。

  • ostop: シグナルが発生しても、GDB はプログラムを一時停止せず、実行を継続できますが、シグナルが発生したことを示すプロンプト メッセージを出力します。
  • stop: シグナルが発生すると、GDB はプログラムの実行を一時停止します。
  • noprint: GDB は、シグナルの発生時にプロンプ​​ト情報を出力しません。
  • print: シグナルが発生すると、GDB は必要なプロンプト情報を出力します。
  • nopass (または無視): GDB がターゲット信号をキャプチャしている間、プログラムはそれ自体で信号を処理できません。
  • pass (または noignore): GDB デバッグはターゲット信号をキャプチャすると同時に、プログラムが信号を自動的に処理できるようにします。

gdb モードでは、情報信号または情報信号 <signal_name> (たとえば、情報信号 SIGINT) を通じてさまざまな信号の情報を表示できます。

GDB フレームおよびバックトレース コマンド: スタック情報の表示

(gdb) Frame specこのコマンドは、spec パラメータで指定されたスタック フレームを現在のスタック フレームとして選択できます。spec パラメータの値を指定するには、一般的に次の 3 つの方法が使用されます。

  1. スタックフレームの番号で指定します。0 は現在呼び出されている関数に対応するスタック フレーム番号で、最大の番号を持つスタック フレームに対応する関数は通常 main() メイン関数です。
  2. スタック フレームのアドレス指定を利用します。スタック フレームのアドレスは、info Frame コマンドによって出力される情報で確認できます (後述)。
  3. 関数の関数名で指定します。なお、複数のスタックフレームに対応する同様の再帰関数の場合、このメソッドでは最も番号の小さいスタックフレームが特定されます。

(gdb) 情報フレーム現在のスタック フレームに保存されている情報を表示できます。

このコマンドは、現在のスタック フレームの次の情報を順番に出力します。

  • 現在のスタック フレームの番号とスタック フレームのアドレス。
  • 現在のスタックフレームに対応する関数の格納先アドレスと、関数呼び出し時のコード格納先のアドレス
  • 現在の関数の呼び出し元、対応するスタック フレームのアドレス。
  • このスタック フレームの作成に使用されるプログラミング言語。
  • 関数パラメータのストレージアドレスと値。
  • 関数内のローカル変数の格納アドレス。
  • スタックフレームに格納されるレジスタ変数。命令レジスタ(64ビット環境ではrip、32ビット環境ではeipで表されます)、スタックベースポインタレジスタ(64ビット環境ではrbpで表され、 32 ビット環境では ebp) など。

さらに、info args このコマンドを使用して、現在の関数の各パラメータの値を表示することもできます。info locals このコマンドを使用して、現在の関数の各ローカル変数の値を表示することもできます。

(gdb) backtrace [-full] [n] は、現在のデバッグ環境のすべてのスタック フレームの情報を出力するために使用されます。

このうち、[]で囲まれたパラメータはオプションであり、その意味は次のとおりです。

  • n: 整数値。正の整数の場合は、最も内側の n 個のスタック フレームの情報を出力することを意味し、n が負の整数の場合は、最も外側の n 個のスタック フレームの情報を出力することを意味します。
  • -full: スタックフレーム情報を出力しながらローカル変数の値を出力します。

GDB のソース コードの編集と検索

GDB 編集コマンド: ファイルを編集する

  • (gdb) [場所] を編集します

  • (gdb) [ファイル名] : [場所] を編集します

    • location はプログラム内の位置を表します。このコマンドは、ファイルの指定された場所をアクティブにしてから編集することを示します。
    • 「bash: /bin/ex: No such file or directory」というエラーが発生した場合は、GDB のデフォルトのエディターが ex であるため、export EDITOR=/usr/bin/vim または export EDITOR などのエディターを指定する必要があります。 =/usr /bin/vi

GDB検索コマンド: ファイルの検索

  • 検索

  • 逆引き検索

    • 最初の項目のコマンド形式は、現在の行の先頭から前方検索を意味し、2 番目の項目は、現在の行の先頭から逆方向検索を意味します。このうち regexp は正規表現で、文字列に特定の部分文字列が含まれているかどうかを確認したり、一致した部分文字列を置換したり、文字列から特定の条件を抽出したりするために使用できる、文字列の一致パターンを記述します。多くのプログラミング言語は正規表現の使用をサポートしています。

3、GDB デバッガの使用方法

一般に、GDB は主に次の 4 つの機能を実行するのに役立ちます。

1. プログラムを起動すると、カスタム要件に従って自由にプログラムを実行できます。
2. デバッグ中のプログラムが指定したブレークポイントで停止できるようにします。(ブレークポイントは条件式にすることもできます)
3. プログラムが停止すると、その時点でプログラム内で何が起こったかを確認できます。
4. プログラムの実行環境を動的に変更します。

以上のように、GDB は一般的なデバッグ ツールと何ら変わりはありません。基本的にはこれらの機能を満たしています。しかし、詳細に説明すると、GDB は強力なデバッグ ツールであることがわかります。グラフィカルなデバッグ ツールに慣れているかもしれませんが、場合によっては、コマンドラインのデバッグツールには、グラフィカルツールでは完了できない機能があります。一つずつ見ていきましょう。

デバッグの例:

源程序:tst.c

1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d /n", result );
24 printf("result[1-250] = %d /n", func(250) );
25 }

実行可能ファイルをコンパイルして生成します: (Linux の場合)

hchen/test> cc -g tst.c -o tst

GDB を使用してデバッグします。

hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-SUSE-linux"...
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst

Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。

Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。

Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>

さて、上記の感覚的な知識を踏まえて、gdb を体系的に理解しましょう。

基本的な gdb コマンド:

GDB常用命令	格式	含义	简写
list	List [开始,结束]	列出文件的代码清单	l
prit	Print 变量名	打印变量内容	p
break	Break [行号或函数名]	设置断点	b
continue	Continue [开始,结束]	继续运行	c
info	Info 变量名	列出信息	i
next	Next	下一行	n
step	Step	进入函数(步入)	S
display	Display 变量名	显示参数	 
file	File 文件名(可以是绝对路径和相对路径)	加载文件	 
run	Run args	运行程序	r

4、GDB実戦

上記のコマンドを使用した実際の例を次に示します。

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://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 "x86_64-RedHat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.
(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) run -t cdai
Starting program: /root/Temp/bufbomb/bufbomb -t cdai
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686

(gdb) bt
#0  0x08048ad6 in getbuf ()
#1  0x08048db2 in test ()
#2  0x08049085 in launch ()
#3  0x08049257 in main ()
(gdb) info frame 0
Stack frame at 0xffffb540:
 eip = 0x8048ad6 in getbuf; saved eip 0x8048db2
 called by frame at 0xffffb560
 Arglist at 0xffffb538, args: 
 Locals at 0xffffb538, Previous frame's sp is 0xffffb540
 Saved registers:
  ebp at 0xffffb538, eip at 0xffffb53c
(gdb) info registers
eax            0xc      12
ecx            0xffffb548       -19128
edx            0xc8c340 13157184
ebx            0x0      0
esp            0xffffb510       0xffffb510
ebp            0xffffb538       0xffffb538
esi            0x804b018        134524952
edi            0xffffffff       -1
eip            0x8048ad6        0x8048ad6 <getbuf+6>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99
(gdb) x/10x $sp
0xffffb510:     0xf7ffc6b0      0x00000001      0x00000001      0xffffb564
0xffffb520:     0x08048448      0x0804a12c      0xffffb548      0x00c8aff4
0xffffb530:     0x0804b018      0xffffffff

(gdb) si
0x08048ad9 in getbuf ()
(gdb) si
0x08048adc in getbuf ()
(gdb) si
0x080489c0 in Gets ()
(gdb) n
Single stepping until exit from function Gets,
which has no line number information.
Type string:123
0x08048ae1 in getbuf ()
(gdb) si
0x08048ae2 in getbuf ()
(gdb) c
Continuing.
Dud: getbuf returned 0x1
Better luck next time

Program exited normally.
(gdb) quit

4.1 リバースデバッグ

GDB 7.0以降にリバーサルデバッグ機能が追加されました。具体的には、例えばgetbuf()とmain()にブレークポイントを設定し、プログラムを起動するとmain()関数のブレークポイントで停止します。このとき、record を入力した後、次のブレークポイント getbuf() に進み、GDB は main() から getbuf() までのランタイム情報を記録します。次に、rn を使用して getbuf() から main() までを逆にデバッグします。『X-MEN:デイズ・オブ・フューチャー・パスト』同様、すごいですね!

この方法は、バグから逆にバグの原因となったコードを見つけるのに適しており、実用性は状況によって異なります。もちろん、それには限界もあります。プログラム I/O 出力などの外部条件が変化した場合、GDB は「逆転」できません。

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://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 "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.

(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) b main
Breakpoint 2 at 0x80490c6

(gdb) run -t cdai
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /root/Temp/bufbomb/bufbomb -t cdai

Breakpoint 2, 0x080490c6 in main ()
(gdb) record
(gdb) c
Continuing.
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()

(gdb) rn
Single stepping until exit from function getbuf,
which has no line number information.
0x08048dad in test ()
(gdb) rn
Single stepping until exit from function test,
which has no line number information.
0x08049080 in launch ()
(gdb) rn
Single stepping until exit from function launch,
which has no line number information.
0x08049252 in main ()

4.2 VSCode+GDB+Qemu による ARM64 Linux カーネルのデバッグ

Linux カーネルは非常に複雑なシステムであり、初心者が使い始めるのは困難です。便利なデバッグ環境があれば、学習効率は少なくとも 5 ~ 10 倍向上します。

Linux カーネルを学習するには、通常、次の 2 つのニーズがあります。

  1. ハードウェアを取り除き、Linux を簡単にコンパイルして実行できる
  2. グラフィカルツールを使用して Linux をデバッグできます

著者は、VSCode+GDB+Qemu を使用して、次の 2 つの要件を満たしています。

  • qemu は Linux を起動するための仮想マシンとして使用されます。
  • VSCode+GDB は、グラフィカル DEBUG のデバッグ ツールとして使用されます。

最終的な効果はおおよそ次のとおりです。

qemu 実行インターフェイス:

画像

vscode デバッグ インターフェイス:

画像

以下に、上記の環境を構築する方法をステップごとに紹介します。この記事のすべての操作は、Vmware Ubuntu16 仮想マシン上で実行されます。

コンパイルツールチェーンをインストールする

Ubuntu は X86 アーキテクチャであるため、arm64 ファイルをコンパイルするには、クロスコンパイル ツールチェーンをインストールする必要があります。

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev  build-essential git bison flex libssl-dev

ルートファイルシステムを作成する

Linux の起動にはルート ファイル システムと連携する必要があります。ここでは、busybox を使用して単純なルート ファイル システムを作成します。

ビジーボックスをコンパイルする

wget  https://busybox.net/downloads/busybox-1.33.1.tar.bz2
tar -xjf busybox-1.33.1.tar.bz2
cd busybox-1.33.1

静的ライブラリのコンパイル オプションをオンにする

make menuconfig
Settings --->
 [*] Build static binary (no shared libs)

コンパイルツールを指定する

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

コンパイル

make
make install

コンパイルが完了し、busybox ディレクトリの下に _install ディレクトリが生成されます。

カスタムファイルシステム

init プロセスを正常に開始するには、追加の構成が必要です

etc、dev、libディレクトリをルートディレクトリに追加します。

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ mkdir etc dev lib
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ ls
bin  dev  etc  lib  linuxrc  sbin  usr

etc に個別にファイルを作成します。

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13]
$ cat profile
#!/bin/sh
export HOSTNAME=bryant
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16]
$ cat inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19]
$ cat fstab
#device  mount-point    type     options   dump   fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26]
$ ls init.d
rcS

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30]
$ cat init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

これらのファイルについて少し説明します。

  1. Busybox が linuxrc として起動されると、/etc/profile が読み込まれ、いくつかの環境変数とシェル プロパティが設定されます。
  2. /etc/fstab によって提供されるマウント情報に従ってファイル システムをマウントします。
  3. Busybox は /etc/inittab から sysinit を読み取って実行します。sysinit は /etc/init.d/rcS を指します。
  4. /etc/init.d/rcS では、mdev -s コマンドが非常に重要です。このコマンドは、/sys ディレクトリをスキャンし、キャラクター デバイスとブロック デバイスを検索し、/dev の下にある mknod を検出します。

開発ディレクトリ:

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36]
$ sudo mknod console c 5 1

この手順は非常に重要です。コンソール ファイルがないと、ユーザー モードの出力をシリアル ポートに出力できません。

lib ディレクトリ: lib ライブラリをコピーし、動的にコンパイルされたアプリケーションの実行をサポートします

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/lib [1:18:43]
$ cp /usr/aarch64-linux-gnu/lib/*.so*  -a .

カーネルをコンパイルする

カーネルを構成する

Linux カーネルのソース コードは github から直接ダウンロードできます。

Arch/arm64/configs/defconfig ファイルに従って .config を生成します。

make defconfig ARCH=arm64

次の構成を .config ファイルに追加します。

CONFIG_DEBUG_INFO=y 
CONFIG_INITRAMFS_SOURCE="./root"
CONFIG_INITRAMFS_ROOT_UID=0
CONFIG_INITRAMFS_ROOT_GID=0

CONFIG_DEBUG_INFO はデバッグ用です

CONFIG_INITRAMFS_SOURCE はカーネル RAM ディスクの場所を指定するもので、指定後に RAM ディスクがカーネル イメージに直接コンパイルされます。

以前に作成したルート ファイル システムをルート ディレクトリに cp します。

# bryant @ ubuntu in ~/Downloads/linux-arm64 on git:main x [1:26:56]
$ cp -r ../busybox-1.33.1/_install root

コンパイルを実行する

make ARCH=arm64 Image -j8  CROSS_COMPILE=aarch64-linux-gnu-

ここでターゲットをイメージとして指定すると、モジュールではなくカーネルのみがコンパイルされるため、コンパイル速度が向上します。

qemuを起動する

ダウンロード

qemu をソース コードからコンパイルするのが最善であることに注意してください。apt-get で直接インストールされた qemu のバージョンが低すぎるため、arm64 カーネルを起動できなくなる可能性があります。著者は qemu の 4.2.1 バージョンを使用しています

apt-get install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv
wget https://download.qemu.org/qemu-4.2.1.tar.xz
tar xvJf qemu-4.2.1.tar.xz
cd qemu-4.2.1
./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm
make 
sudo make install

コンパイルが完了すると、qemu は /usr/local/bin ディレクトリにあります。

$ /usr/local/bin/qemu-system-aarch64 --version
QEMU emulator version 4.2.1
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

Linuxカーネルを起動します

/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel

パラメーターの説明をいくつか示します。

  • -m 512Mメモリは512Mです
  • -smp 44 核
  • -cpu cortex-a57CPUはcortex-a57です
  • -kernelカーネルイメージファイル
  • -appendカーネルに渡される cmdline パラメーター。その中で、rdinit は init プロセスを指定します; nokaslr はカーネル開始アドレスのランダム化を禁止しますが、これは非常に重要であり、そうしないと GDB デバッグに問題が発生する可能性があります; console=ttyAMA0 はシリアル ポートを指定します、この手順を実行しないと Linux の出力を見ることができません;
  • -nographicグラフィック出力を無効にする
  • -sgdb ポートをリッスンすると、gdb プログラムはポート 1234 経由で接続できます。

ここでは、console=ttyAMA0 がどのように機能するかを説明します。

Linux ソース コードを表示すると、ttyAMA0 がAMBA_PL011このドライバーに対応していることがわかります。

config SERIAL_AMBA_PL011_CONSOLE
    bool "Support for console on AMBA serial port"
    depends on SERIAL_AMBA_PL011=y
    select SERIAL_CORE_CONSOLE
    select SERIAL_EARLYCON
    help
      Say Y here if you wish to use an AMBA PrimeCell UART as the system
      console (the system console is the device which receives all kernel
      messages and warnings and which allows logins in single user mode).

      Even if you say Y here, the currently visible framebuffer console
      (/dev/tty0) will still be used as the system console by default, but
      you can alter that using a kernel command line option such as
      "console=ttyAMA0". (Try "man bootparam" or see the documentation of
      your boot loader (lilo or loadlin) about how to pass options to the
      kernel at boot time.)

AMBA_PL011 は arm の標準シリアル ポート デバイスであり、qemu の出力はシミュレートされたシリアル ポートです。

qemu のソース コード ファイルには、PL011 の関連ファイルも表示されます。

# bryant @ ubuntu in ~/Downloads/qemu-4.2.1 [1:46:54]
$ find . -name "*pl011*"
./hw/char/pl011.c

Linux が正常に起動すると、シリアル ポートの出力は次のようになります。

[    3.401567] usbcore: registered new interface driver usbhid
[    3.404445] usbhid: USB HID core driver
[    3.425030] NET: Registered protocol family 17
[    3.429743] 9pnet: Installing 9P2000 support
[    3.435439] Key type dns_resolver registered
[    3.440299] registered taskstats version 1
[    3.443685] Loading compiled-in X.509 certificates
[    3.461041] input: gpio-keys as /devices/platform/gpio-keys/input/input0
[    3.473163] ALSA device list:
[    3.474432]   No soundcards found.
[    3.485283] uart-pl011 9000000.pl011: no DMA platform data
[    3.541376] Freeing unused kernel memory: 10752K
[    3.545897] Run /linuxrc as init process
[    3.548390]   with arguments:
[    3.550279]     /linuxrc
[    3.551073]     nokaslr
[    3.552216]   with environment:
[    3.554396]     HOME=/
[    3.555898]     TERM=linux
[    3.985835] 9pnet_virtio: no channels available for device kmod_mount
mount: mounting kmod_mount on /mnt failed: No such file or directory
/etc/init.d/rcS: line 8: can't create /proc/sys/kernel/hotplug: nonexistent directory

Please press Enter to activate this console.
[root@bryant ]#
[root@bryant ]#

VSCode+GDB

GDB 関数は vscode に統合されており、これを使用して Linux カーネルをグラフィカルにデバッグできます

まず、vscode の gdb 構成ファイル (.vscode/launch.json) を追加します。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "kernel debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/vmlinux",
            "cwd": "${workspaceFolder}",
            "MIMode": "gdb",
            "miDebuggerPath":"/usr/bin/gdb-multiarch",
            "miDebuggerServerAddress": "localhost:1234"
        }
    ]
}

ここでは、いくつかの重要なパラメータについて説明します。

  • program: デバッグ用シンボルファイル
  • miDebuggerPath: gdb のパス ここで注意すべき点は、arm64 カーネルであるため、デバッグには gdb-multiarch を使用する必要があることです。
  • miDebuggerServerAddress: ピアアドレス、qemu はデフォルトでポート 1234 を使用します

構成が完了したら、GDB を直接起動して Linux カーネルに接続できます。

画像

vscode では、シングルステップ デバッグ用のブレークポイントを設定できます

画像

画像


著作権に関する声明: この記事は、Zhihu ブロガー「Linux カーネルで遊ぶ」によって書かれたオリジナルの記事です。CC 4.0 BY-SA 著作権契約に従っています。転載する場合は、元のソースのリンクとこの声明を添付してください。
元のリンク: https://zhuanlan.zhihu.com/p/639365490

おすすめ

転載: blog.csdn.net/m0_50662680/article/details/131484284
おすすめ