GNU/Linux >> Linux の 問題 >  >> Linux

標準出力バッファに入れられているものを監視し、特定の文字列がパイプに配置されたときに中断するにはどうすればよいですか?

この質問は良い出発点かもしれません: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=18727
  • rdi :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 つの異なる方法をテストしました:printfstd::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 です。 、 RDXR8R9 .したがって、条件を制限するのは非常に簡単です:

    <オール>
  • _write_nolock にブレークポイントを設定します .

    注意 :上記のポータブル ソリューションとは異なります。関数の 1 行目ではなく、ブレークポイントの場所を関数に設定するだけです。その理由は、すべてのレジスターが入り口で既に初期化されているためです。

  • 制限条件:

    $rcx == 1 && strstr((char *)$rdx, "Hello") != 0
    
  • esp でキャストと逆参照が必要な理由 それは $esp です ESP にアクセスします 登録し、すべての意図と目的のために void* .ここのレジスタはパラメータの値を直接格納します。したがって、別のレベルの間接化はもう必要ありません。

    投稿

    私もこの質問をとても楽しんでいるので、アンソニーの投稿を中国語に翻訳し、補足として私の回答を入れました。投稿はここで見つけることができました。 @anthony-arnold の許可に感謝します。


    Linux
    1. DNSSECの初心者向けガイドと、DNSSECがインターネットを保護する方法

    2. ローカルホストとIPアドレス間のTCPトラフィックを監視する方法は?

    3. 実行時にダイナミックライブラリの実行可能ファイルの負荷を確認する方法は?

    1. caddr_t の重要性と使用時期は?

    2. ファイルの各行の先頭に文字列を追加するにはどうすればよいですか?

    3. Linux でシェル アプリケーションの出力を非表示にするにはどうすればよいですか?

    1. パイプバッファの大きさはどれくらいですか?

    2. Linux –システムに搭載されているハードディスクを確認する方法は?

    3. ロケールを設定する方法とその意味は何ですか?