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 mencocokkann
pertama jumlah karakter dari string yang sedang ditulisstrstr
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 melaluiwrite
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 padafflush
.
Catatan:
$bpnum
terima kasih kepada Tromey di:https://sourceware.org/bugzilla/show_bug.cgi?id=18727rdi
:register yang berisi nomor system call Linux di x86_64,1
adalah untukwrite
rsi
:argumen pertama syscall, untukwrite
itu menunjuk ke bufferstrstr
: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 stracesetarch -R
menonaktifkan ASLR untuk proses denganpersonality
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:
-
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. ) -
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:
- Tetapkan breakpoint di
_write_nolock
. -
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:
-
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.
-
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.