この質問は良い出発点かもしれません:gdb の「何かが端末に出力されます」にブレークポイントを設定するにはどうすればよいですか?
したがって、何かが stdout に書き込まれるたびに、少なくとも壊れる可能性があります。この方法では、基本的に write
にブレークポイントを設定します。 最初の引数が 1
であるという条件付きの syscall (つまり、STDOUT)。コメントには、 write
の文字列パラメーターを検査する方法に関するヒントもあります。
x86 32 ビット モード
私は次のことを思いつき、gdb 7.0.1-debian でテストしました。それはかなりうまくいくようです。 $esp + 8
write
に渡された文字列のメモリ位置へのポインタが含まれています 、最初にそれを整数にキャストし、次に char
へのポインターにキャストします . $esp + 4
書き込むファイル記述子を含みます (STDOUT の場合は 1)。
$ gdb break write if 1 == *(int*)($esp + 4) && strcmp((char*)*(int*)($esp + 8), "your string") == 0
x86 64 ビット モード
プロセスが x86-64 モードで実行されている場合、パラメーターはスクラッチ レジスタ %rdi
を介して渡されます。 と %rsi
$ gdb break write if 1 == $rdi && strcmp((char*)($rsi), "your string") == 0
スタック上の変数ではなくスクラッチ レジスタを使用しているため、1 レベルの間接性が削除されていることに注意してください。
バリアント
strcmp
以外の関数 上記のスニペットで使用できます:
strncmp
最初のn
に一致させたい場合に便利です 書き込まれている文字列の文字数strstr
探している文字列が先頭にあると常に確信できるとは限らないため、文字列内の一致を見つけるために使用できますwrite
を通じて書き込まれる文字列の 関数。
編集: 私はこの質問を楽しんで、その後の答えを見つけました。それについてブログ投稿をすることにしました。
catch
+ strstr
状態
この方法の優れた点は、glibc write
に依存しないことです。 使用中:実際のシステム コールをトレースします。
さらに、printf()
に対してより回復力があります。 複数の printf()
に渡って出力される文字列をキャッチする可能性があるため、バッファリング
x86_64 バージョン:
define stdout
catch syscall write
commands
printf "rsi = %s\n", $rsi
bt
end
condition $bpnum $rdi == 1 && strstr((char *)$rsi, "$arg0") != NULL
end
stdout qwer
テスト プログラム:
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
write(STDOUT_FILENO, "asdf1", 5);
write(STDOUT_FILENO, "qwer1", 5);
write(STDOUT_FILENO, "zxcv1", 5);
write(STDOUT_FILENO, "qwer2", 5);
printf("as");
printf("df");
printf("qw");
printf("er");
printf("zx");
printf("cv");
fflush(stdout);
return EXIT_SUCCESS;
}
結果:休憩:
qwer1
qwer2
fflush
.以前のprintf
実際には何も印刷されませんでした。バッファリングされていました。write
syacall はfflush
でのみ発生しました .
注:
$bpnum
Tromey に感謝:https://sourceware.org/bugzilla/show_bug.cgi?id=18727rdi
:x86_64、1
での Linux システム コールの番号を含むレジスタwrite
用ですrsi
:システムコールの最初の引数、write
の場合 バッファを指しますstrstr
:標準 C 関数呼び出し、サブマッチの検索、見つからない場合は NULL を返します
Ubuntu 17.10、gdb 8.0.1 でテスト済み。
トレース
インタラクティブな場合の別のオプション:
setarch "$(uname -m)" -R strace -i ./stdout.out |& grep '\] write'
出力例:
[00007ffff7b00870] write(1, "a\nb\n", 4a
そのアドレスをコピーして次の場所に貼り付けます:
setarch "$(uname -m)" -R strace -i ./stdout.out |& grep -E '\] write\(1, "a'
この方法の利点は、通常の UNIX ツールを使用して strace
を操作できることです。 出力し、深い GDB-fu を必要としません。
説明:
-i
strace 出力を RIP にしますsetarch -R
personality
を持つプロセスの ASLR を無効にします システム コール:アドレスが毎回異なる場合に strace -i を使用してデバッグする方法 GDB はデフォルトで既にデバッグを行っているため、再度行う必要はありません。
アンソニーの答えは素晴らしいです。彼の答えに従って、Windows で別の解決策を試しました。 (x86-64 ビット Windows)。この質問は GDB に関するものです。 ただし、Linux では、このソリューションはこの種の質問に対する補足だと思います。他の人に役立つかもしれません。
Windows での解決策
Linux では printf
への呼び出し API write
の呼び出しになります .また、Linux はオープン ソース OS であるため、API 内でデバッグできました。ただし、Windows では API が異なり、独自の API WriteFile が提供されます。 Windows は商用の非オープン ソース OS であるため、API にブレークポイントを追加できませんでした。
しかし、VC のソース コードの一部は Visual Studio と共に公開されているため、最終的に WriteFile
と呼ばれるソース コードを見つけることができました。 API を作成し、そこにブレークポイントを設定します。サンプル コードをデバッグした後、printf
を見つけました。 メソッドは _write_nolock
を呼び出す可能性があります WriteFile
と呼ばれます。関数は次の場所にあります:
your_VS_folder\VC\crt\src\write.c
プロトタイプは次のとおりです:
/* now define version that doesn't lock/unlock, validate fh */
int __cdecl _write_nolock (
int fh,
const void *buf,
unsigned cnt
)
write
との比較 Linux 上の API:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
それらはまったく同じパラメータを持っています。 condition breakpoint
を設定するだけです _write_nolock
で 上記の解決策を参照してください。詳細が若干異なるだけです。
Win32 と x64 の両方に対応するポータブル ソリューション
パラメータの名前を直接使用できることは非常に幸運です Win32 と x64 の両方でブレークポイントの条件を設定するときの Visual Studio で。したがって、条件を記述するのは非常に簡単になります:
<オール>
_write_nolock
にブレークポイントを追加します
注意 :Win32 と x64 ではほとんど違いはありません。関数名を使用して、Win32 でブレークポイントの場所を設定できます。ただし、関数の入り口でパラメーターが初期化されていないため、x64 では機能しません。したがって、パラメーター名を使用してブレークポイントの条件を設定することはできませんでした。
しかし幸いなことに、いくつかの回避策があります。関数名ではなく関数内の場所を使用して、ブレークポイントを設定します (関数の 1 行目など)。パラメータはすでにそこで初期化されています。 (つまり、filename+line number
を使用します ブレークポイントを設定するか、ファイルを直接開き、関数の入り口ではなく最初の行にブレークポイントを設定します。 )
条件を制限:
fh == 1 && strstr((char *)buf, "Hello World") != 0
注意 :ここにはまだ問題があります。標準出力に何かを書き込む 2 つの異なる方法をテストしました:printf
と std::cout
. printf
すべての文字列を _write_nolock
に書き込みます 一気に機能。ただし std::cout
_write_nolock
に 1 文字ずつ渡すだけです。 、これは、API が strlen("your string")
と呼ばれることを意味します 回。この場合、条件を永久にアクティブにすることはできません。
Win32 ソリューション
もちろん、Anthony
と同じメソッドを使用できます。 提供:ブレークポイントの条件をレジスタで設定します。
Win32 プログラムの場合、解決策は GDB
とほぼ同じです。 Linux で。装飾 __cdecl
があることに気付くかもしれません _write_nolock
のプロトタイプで .この呼び出し規約は次のことを意味します:
- 引数の受け渡し順序は右から左です。
- 関数を呼び出すと、スタックから引数がポップされます。
- 名前の装飾規則:アンダースコア文字 (_) を名前の前に付けます。
- 大文字と小文字の変換は行われません。
ここに説明があります。また、Microsoft の Web サイトにレジスタとスタックを表示するために使用される例があります。結果はここで見つけることができました。
次に、ブレークポイントの条件を設定するのは非常に簡単です:
<オール>_write_nolock
にブレークポイントを設定する .条件を制限:
*(int *)($esp + 4) == 1 && strstr(*(char **)($esp + 8), "Hello") != 0
Linux と同じ方法です。最初の条件は、文字列が stdout
に書き込まれることを確認することです . 2 つ目は、指定された文字列に一致することです。
x64 ソリューション
x86 から x64 への 2 つの重要な変更点は、64 ビット アドレッシング機能と、一般的な使用のための 16 個の 64 ビット レジスタのフラット セットです。レジスタの増加に伴い、x64 は __fastcall
しか使用しません 呼び出し規約として。最初の 4 つの整数引数はレジスタで渡されます。 5 つ以上の引数はスタックで渡されます。
Microsoft の Web サイトの Parameter Passing ページを参照できます。 4 つのレジスタ (左から右の順) は RCX
です。 、 RDX
、 R8
と R9
.したがって、条件を制限するのは非常に簡単です:
_write_nolock
にブレークポイントを設定します .
注意 :上記のポータブル ソリューションとは異なります。関数の 1 行目ではなく、ブレークポイントの場所を関数に設定するだけです。その理由は、すべてのレジスターが入り口で既に初期化されているためです。
制限条件:
$rcx == 1 && strstr((char *)$rdx, "Hello") != 0
esp
でキャストと逆参照が必要な理由 それは $esp
です ESP
にアクセスします 登録し、すべての意図と目的のために void*
.ここのレジスタはパラメータの値を直接格納します。したがって、別のレベルの間接化はもう必要ありません。
投稿
私もこの質問をとても楽しんでいるので、アンソニーの投稿を中国語に翻訳し、補足として私の回答を入れました。投稿はここで見つけることができました。 @anthony-arnold の許可に感謝します。