GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana saya bisa memantau apa yang dimasukkan ke dalam buffer keluar standar dan pecah ketika string tertentu disimpan di dalam pipa?

Pertanyaan ini mungkin merupakan titik awal yang baik:bagaimana cara memberi titik henti pada "sesuatu dicetak ke terminal" di gdb?

Jadi Anda setidaknya bisa istirahat setiap kali ada sesuatu yang ditulis ke stdout. Metode ini pada dasarnya melibatkan pengaturan breakpoint pada write syscall dengan syarat bahwa argumen pertama adalah 1 (yaitu STDOUT). Di komentar, ada juga petunjuk bagaimana Anda bisa memeriksa parameter string dari write telepon juga.

x86 mode 32-bit

Saya menemukan yang berikut dan mengujinya dengan gdb 7.0.1-debian. Tampaknya bekerja dengan cukup baik. $esp + 8 berisi penunjuk ke lokasi memori dari string yang diteruskan ke write , jadi pertama-tama Anda mentransmisikannya ke integral, lalu ke pointer ke char . $esp + 4 berisi deskriptor file untuk menulis (1 untuk STDOUT).

$ gdb break write if 1 == *(int*)($esp + 4) && strcmp((char*)*(int*)($esp + 8), "your string") == 0

mode x86 64-bit

Jika proses Anda berjalan dalam mode x86-64, maka parameter dilewatkan melalui register awal %rdi dan %rsi

$ gdb break write if 1 == $rdi && strcmp((char*)($rsi), "your string") == 0

Perhatikan bahwa satu tingkat tipuan dihapus karena kami menggunakan register awal, bukan variabel pada tumpukan.

Varian

Fungsi selain strcmp dapat digunakan dalam cuplikan di atas:

  • strncmp berguna jika Anda ingin mencocokkan n pertama jumlah karakter dari string yang sedang ditulis
  • strstr dapat digunakan untuk menemukan kecocokan dalam sebuah string, karena Anda tidak selalu dapat memastikan bahwa string yang Anda cari ada di permulaan string yang ditulis melalui write fungsi.

Edit: Saya menikmati pertanyaan ini dan menemukan jawaban selanjutnya. Saya memutuskan untuk membuat posting blog tentang itu.


catch + strstr kondisi

Hal keren tentang metode ini adalah tidak bergantung pada glibc write sedang digunakan:ini melacak panggilan sistem yang sebenarnya.

Selain itu, lebih tahan terhadap printf() buffering, karena bahkan mungkin menangkap string yang dicetak di beberapa printf() panggilan.

versi 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

Program uji:

#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;
}

Hasil:istirahat di:

  • qwer1
  • qwer2
  • fflush . printf sebelumnya tidak benar-benar mencetak apa pun, mereka di-buffer! write syacall hanya terjadi pada fflush .

Catatan:

  • $bpnum terima kasih kepada Tromey di:https://sourceware.org/bugzilla/show_bug.cgi?id=18727
  • rdi :register yang berisi nomor system call Linux di x86_64, 1 adalah untuk write
  • rsi :argumen pertama syscall, untuk write itu menunjuk ke buffer
  • strstr :pemanggilan fungsi C standar, mencari sub-pertandingan, mengembalikan NULL jika tidak ditemukan

Diuji di Ubuntu 17.10, gdb 8.0.1.

strace

Opsi lain jika Anda merasa interaktif:

setarch "$(uname -m)" -R strace -i ./stdout.out |& grep '\] write'

Keluaran sampel:

[00007ffff7b00870] write(1, "a\nb\n", 4a

Sekarang salin alamat itu dan tempelkan ke:

setarch "$(uname -m)" -R strace -i ./stdout.out |& grep -E '\] write\(1, "a'

Keuntungan dari metode ini adalah Anda dapat menggunakan alat UNIX biasa untuk memanipulasi strace keluaran, dan tidak memerlukan GDB-fu yang dalam.

Penjelasan:

  • -i membuat RIP keluaran strace
  • setarch -R menonaktifkan ASLR untuk proses dengan personality panggilan sistem:Cara men-debug dengan strace -i ketika setiap alamat berbeda GDB sudah melakukannya secara default, jadi tidak perlu melakukannya lagi.

Jawaban Anthony luar biasa. Mengikuti jawabannya, saya mencoba solusi lain di Windows (x86-64 bit Windows). Saya tahu pertanyaan ini untuk GDB di Linux, bagaimanapun, saya pikir solusi ini adalah pelengkap untuk pertanyaan semacam ini. Mungkin bermanfaat bagi orang lain.

Solusi di Windows

Di Linux panggilan ke printf akan menghasilkan panggilan ke API write . Dan karena Linux adalah OS sumber terbuka, kami dapat melakukan debug di dalam API. Namun, API berbeda pada Windows, ia menyediakan API WriteFile-nya sendiri. Karena Windows adalah OS non-open source komersial, breakpoint tidak dapat ditambahkan di API.

Namun beberapa source code VC dipublish bersamaan dengan Visual Studio, sehingga kita bisa mengetahuinya di source code mana yang akhirnya disebut WriteFile API dan atur breakpoint di sana. Setelah men-debug kode sampel, saya menemukan printf metode dapat menghasilkan panggilan ke _write_nolock di mana WriteFile disebut. Fungsi ini terletak di:

your_VS_folder\VC\crt\src\write.c

Prototipe adalah:

/* now define version that doesn't lock/unlock, validate fh */
int __cdecl _write_nolock (
        int fh,
        const void *buf,
        unsigned cnt
        )

Dibandingkan dengan write API di Linux:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count); 

Mereka benar-benar memiliki parameter yang sama. Jadi kita cukup menyetel condition breakpoint di _write_nolock lihat saja solusi di atas, dengan hanya beberapa perbedaan detail.

Solusi Portabel untuk Win32 dan x64

Sangat beruntung bahwa kami dapat menggunakan nama parameter secara langsung di Visual Studio saat menyetel kondisi untuk breakpoint pada Win32 dan x64. Jadi sangat mudah untuk menulis kondisi:

  1. Tambahkan breakpoint di _write_nolock

    PEMBERITAHUAN :Ada sedikit perbedaan pada Win32 dan x64. Kami hanya bisa menggunakan nama fungsi untuk mengatur lokasi breakpoints di Win32. Namun, itu tidak akan berfungsi pada x64 karena di pintu masuk fungsi, parameternya tidak diinisialisasi. Oleh karena itu, kami tidak dapat menggunakan nama parameter untuk menyetel kondisi breakpoint.

    Tapi untungnya kami memiliki beberapa solusi:gunakan lokasi dalam fungsi daripada nama fungsi untuk menyetel breakpoint, misalnya, baris pertama fungsi. Parameter sudah diinisialisasi di sana. (Maksud saya gunakan filename+line number untuk mengatur breakpoint, atau buka file secara langsung dan atur breakpoint dalam fungsi, bukan pintu masuk tetapi baris pertama. )

  2. Batasi ketentuan:

    fh == 1 && strstr((char *)buf, "Hello World") != 0
    

PEMBERITAHUAN :masih ada masalah di sini, saya menguji dua cara berbeda untuk menulis sesuatu ke stdout:printf dan std::cout . printf akan menulis semua string ke _write_nolock berfungsi sekaligus. Namun std::cout hanya akan meneruskan karakter demi karakter ke _write_nolock , yang berarti API akan disebut strlen("your string") waktu. Dalam hal ini, kondisi tidak dapat diaktifkan selamanya.

Solusi Win32

Tentu saja kita bisa menggunakan metode yang sama seperti Anthony disediakan:atur kondisi breakpoint oleh register.

Untuk program Win32, solusinya hampir sama dengan GDB di Linux. Anda mungkin memperhatikan bahwa ada dekorasi __cdecl dalam prototipe _write_nolock . Konvensi pemanggilan ini berarti:

  • Urutan penyampaian argumen adalah Kanan ke kiri.
  • Memanggil fungsi memunculkan argumen dari tumpukan.
  • Konvensi dekorasi nama:Karakter garis bawah (_) diawali dengan nama.
  • Terjemahan kasus tidak dilakukan.

Ada deskripsi di sini. Dan ada contoh yang digunakan untuk menampilkan register dan stack di situs web Microsoft. Hasilnya dapat ditemukan di sini.

Maka sangat mudah untuk menyetel kondisi breakpoint:

  1. Tetapkan breakpoint di _write_nolock .
  2. Batasi ketentuan:

    *(int *)($esp + 4) == 1 && strstr(*(char **)($esp + 8), "Hello") != 0
    

Ini adalah metode yang sama seperti di Linux. Kondisi pertama adalah memastikan string ditulis ke stdout . Yang kedua adalah mencocokkan string yang ditentukan.

Solusi x64

Dua modifikasi penting dari x86 ke x64 adalah kemampuan pengalamatan 64-bit dan satu set datar 16 register 64-bit untuk penggunaan umum. Seiring bertambahnya register, x64 hanya menggunakan __fastcall sebagai konvensi pemanggilan. Empat argumen bilangan bulat pertama dilewatkan dalam register. Argumen lima dan lebih tinggi diteruskan ke tumpukan.

Anda dapat merujuk ke halaman Parameter Passing di situs web Microsoft. Keempat register (dalam urutan dari kiri ke kanan) adalah RCX , RDX , R8 dan R9 . Jadi sangat mudah untuk membatasi kondisi:

  1. Tetapkan breakpoint di _write_nolock .

    PEMBERITAHUAN :ini berbeda dengan solusi portabel di atas, kita bisa mengatur lokasi breakpoint ke fungsi daripada baris pertama dari fungsi tersebut. Pasalnya, semua register sudah diinisialisasi di pintu masuk.

  2. Batasi ketentuan:

    $rcx == 1 && strstr((char *)$rdx, "Hello") != 0
    

Alasan mengapa kita membutuhkan cast dan dereference pada esp apakah itu $esp mengakses ESP mendaftar, dan untuk semua maksud dan tujuan adalah void* . Sedangkan register disini menyimpan secara langsung nilai-nilai parameter. Jadi tingkat tipuan lain tidak diperlukan lagi.

Poskan

Saya juga sangat menikmati pertanyaan ini, jadi saya menerjemahkan posting Anthony ke dalam bahasa Mandarin dan memasukkan jawaban saya sebagai pelengkap. Pos tersebut dapat ditemukan di sini. Terima kasih atas izin @anthony-arnold.


Linux
  1. Seberapa Besar Buffer Pipa?

  2. Bagaimana Memantau Lalu Lintas TCP Antara Localhost Dan Alamat Ip?

  3. Bagaimana Cara Mengetahui Beban Eksekusi Perpustakaan Dinamis Saat Dijalankan?

  1. Bagaimana saya bisa menambahkan string ke awal setiap baris dalam sebuah file?

  2. Bagaimana cara memantau penggunaan memori yang didedikasikan untuk kernel?

  3. Bagaimana saya bisa mengubah alamat IP dan gateway secara permanen?

  1. Bagaimana saya bisa memantau panjang antrian penerimaan?

  2. Bagaimana saya bisa menyembunyikan output dari aplikasi shell di Linux?

  3. Menggunakan bash, bagaimana saya bisa mengetahui rata-rata, maks, dan min dari daftar angka?