
 Pada Habré sudah 
menulis tentang intersepsi panggilan sistem melalui 
ptrace ; Alexa menulis tentang posting yang jauh lebih terperinci ini, yang saya putuskan untuk diterjemahkan.
Mulai dari mana
Komunikasi antara program yang di-debug dan debugger terjadi menggunakan sinyal. Ini sangat menyulitkan hal-hal yang sudah sulit; untuk bersenang-senang, Anda dapat membaca 
bagian BUGS dari man ptrace .
Setidaknya ada dua cara berbeda untuk memulai debugging:
- ptrace(PTRACE_TRACEME, 0, NULL, NULL)akan membuat induk dari proses saat ini menjadi debugger untuknya. Tidak diperlukan bantuan dari orang tua;- mandengan lembut menyarankan: "Suatu proses mungkin seharusnya tidak membuat permintaan ini jika orang tuanya tidak berharap untuk melacaknya." (Di tempat lain di mans Anda melihat frasa "mungkin tidak boleh" ?) Jika proses saat ini sudah memiliki debugger, maka panggilan akan gagal.
- ptrace(PTRACE_ATTACH, pid, NULL, NULL)akan membuat proses saat ini menjadi debugger untuk- pid. Jika- pidsudah memiliki debugger, maka panggilan akan gagal.- SIGSTOPdikirim ke proses debug, dan tidak akan terus bekerja sampai debugger menghapusnya.
Kedua metode ini sepenuhnya independen; Anda dapat menggunakan salah satu atau yang lain, tetapi tidak ada gunanya menggabungkan mereka. 
Penting untuk dicatat bahwa 
PTRACE_ATTACH tidak instan: setelah 
ptrace(PTRACE_ATTACH) dipanggil, biasanya 
waitpid(2) PTRACE_ATTACH untuk menunggu sampai 
PTRACE_ATTACH "bekerja".
Anda dapat memulai proses anak di bawah debugging menggunakan 
PTRACE_TRACEME sebagai berikut:
 static void tracee(int argc, char **argv) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) die("child: ptrace(traceme) failed: %m");  if (raise(SIGSTOP)) die("child: raise(SIGSTOP) failed: %m");  execvp(argv[0], argv);  die("tracee start failed: %m"); } static void tracer(pid_t pid) { int status = 0;  if (waitpid(pid, &status, 0) < 0) die("waitpid failed: %m"); if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { kill(pid, SIGKILL); die("tracer: unexpected wait status: %x", status); }    }  void shim_ptrace(int argc, char **argv) { pid_t pid = fork(); if (pid < 0) die("couldn't fork: %m"); else if (pid == 0) tracee(argc, argv); else tracer(pid); die("should never be reached"); } 
Tanpa panggilan 
raise(SIGSTOP) , mungkin ternyata 
execvp(3) akan dieksekusi sebelum proses induk siap untuk ini; dan kemudian tindakan debugger (misalnya, mencegat panggilan sistem) tidak akan mulai dari awal proses.
Ketika debugging dimulai, setiap 
ptrace(PTRACE_SYSCALL, pid, NULL, NULL) akan "mencairkan" proses debug sampai entri pertama ke dalam panggilan sistem, dan kemudian sampai panggilan sistem pergi.
Assembler telekinetik
ptrace(PTRACE_SYSCALL) tidak mengembalikan informasi 
apa pun ke debugger; ia hanya berjanji bahwa proses yang sedang di-debug akan berhenti dua kali pada setiap panggilan sistem. Untuk mendapatkan informasi tentang apa yang terjadi dengan proses debug - misalnya, yang dihentikannya oleh sistem - Anda perlu naik ke salinan register yang disimpan oleh kernel dalam 
struct user dalam format yang tergantung pada arsitektur spesifik. (Misalnya, pada x86_64, nomor panggilan akan berada di bidang 
regs.orig_rax , parameter pertama yang dilewati adalah di 
regs.rdi , dll.) Alexa berkomentar: “rasanya seperti Anda menulis kode rakitan di C yang bekerja dengan register prosesor jarak jauh.”
Alih-alih struktur yang dijelaskan dalam 
sys/user.h , mungkin lebih nyaman menggunakan konstanta indeks yang didefinisikan dalam 
sys/reg.h :
 #include <sys/reg.h> /*    . */ long ptrace_syscall(pid_t pid) { #ifdef __x86_64__ return ptrace(PTRACE_PEEKUSER, pid, sizeof(long)*ORIG_RAX); #else // ... #endif } /*      . */ uintptr_t ptrace_argument(pid_t pid, int arg) { #ifdef __x86_64__ int reg = 0; switch (arg) { case 0: reg = RDI; break; case 1: reg = RSI; break; case 2: reg = RDX; break; case 3: reg = R10; break; case 4: reg = R8; break; case 5: reg = R9; break; } return ptrace(PTRACE_PEEKUSER, pid, sizeof(long) * reg, NULL); #else // ... #endif } 
Dalam hal ini, dua perhentian proses debug - di pintu masuk ke panggilan sistem dan di pintu keluar dari itu - tidak berbeda dengan cara apa pun dari sudut pandang debugger; sehingga debugger itu sendiri harus mengingat keadaan masing-masing proses yang di-debug: jika ada beberapa, maka tidak ada yang menjamin bahwa sepasang sinyal dari satu proses akan muncul secara berurutan.
Keturunan
Salah satu opsi 
ptrace , yaitu 
PTRACE_O_TRACECLONE , memastikan bahwa semua anak dari proses debugged akan secara otomatis didebug ketika mereka 
keluar dari fork(2) . Poin halus tambahan di sini adalah bahwa keturunan yang diambil untuk debugging menjadi "pseudo-children" dari debugger, dan 
waitpid akan merespons tidak hanya untuk menghentikan "anak-anak langsung", tetapi juga untuk menghentikan debugging "pseudo-anak". Man memperingatkan tentang ini: 
"Mengatur bendera WCONTINUED saat memanggil waitpid (2) tidak dianjurkan: keadaan" lanjutan "adalah per-proses dan mengkonsumsinya dapat membingungkan orang tua asli jejak." - yaitu "Pseudo-children" memiliki dua orang tua yang dapat menunggu mereka untuk berhenti. Untuk programmer debugger, ini berarti bahwa 
waitpid(-1) akan menunggu tidak hanya untuk anak-anak terdekat untuk berhenti, tetapi untuk semua proses yang di-debug.
Sinyal
(Konten bonus dari penerjemah: informasi ini tidak ada dalam artikel berbahasa Inggris)Seperti yang telah disebutkan di awal, komunikasi antara program yang di-debug dan debugger terjadi menggunakan sinyal. Suatu proses menerima 
SIGSTOP ketika debugger terhubung, dan kemudian 
SIGTRAP setiap kali sesuatu yang menarik terjadi dalam proses yang sedang di-debug - misalnya, panggilan sistem atau menerima sinyal eksternal. Debugger, pada gilirannya, menerima 
SIGCHLD setiap kali salah satu proses yang di-debug (tidak harus anak langsung) "membeku" atau "membeku".
ptrace(PTRACE_SYSCALL) proses debugged dilakukan dengan memanggil 
ptrace(PTRACE_SYSCALL) (sebelum sinyal pertama atau system call) atau 
ptrace(PTRACE_CONT) (sebelum sinyal pertama). Ketika sinyal 
SIGSTOP/SIGCONT juga digunakan untuk tujuan yang tidak terkait dengan debugging, masalah dengan 
ptrace dapat muncul: jika debugger "tidak meringkas" proses debugged yang menerima 
SIGSTOP , maka dari luar akan terlihat seolah-olah sinyal diabaikan; jika debugger tidak "mencairkan" proses yang sedang di-debug, maka 
SIGCONT eksternal tidak bisa "mencairkannya".
Sekarang bagian yang menyenangkan: Linux melarang proses 
debugging sendiri , tetapi tidak mencegah penciptaan loop ketika orang tua dan anak saling men-debug. Dalam hal ini, ketika salah satu proses menerima sinyal eksternal, itu "membeku" melalui 
SIGTRAP - maka 
SIGCHLD dikirim ke proses kedua, dan juga "membeku" melalui 
SIGTRAP . Tidak mungkin untuk mendapatkan "co-debuggers" seperti itu dari jalan buntu dengan mengirimkan 
SIGCONT dari luar; satu-satunya cara adalah membunuh ( 
SIGKILL ) anak, maka orang tua akan keluar dari debugging dan "membekukan". (Jika Anda membunuh orang tua, anak itu akan mati bersamanya.) Jika anak itu mengaktifkan opsi 
PTRACE_O_EXITKILL , maka orang tua yang 
PTRACE_O_EXITKILL akan mati bersama kematiannya.
Sekarang Anda tahu bagaimana menerapkan sepasang proses yang, ketika menerima sinyal apa pun, keduanya membeku selamanya dan mati hanya bersama. Mengapa ini mungkin perlu dalam praktek, saya tidak akan menjelaskan :-)