Men-debug penerapan perangkat lunak dengan strace

Men-debug penerapan perangkat lunak dengan strace

Pekerjaan harian saya sebagian besar adalah penerapan perangkat lunak, yang berarti saya menghabiskan banyak waktu untuk mencoba menjawab pertanyaan seperti:

  • Perangkat lunak ini berfungsi untuk pengembang, tetapi tidak untuk saya. Mengapa?
  • Kemarin perangkat lunak ini berfungsi untuk saya, tetapi hari ini tidak. Mengapa?

Ini adalah jenis debugging yang sedikit berbeda dari debugging perangkat lunak biasa. Proses debug reguler adalah tentang logika kode, tetapi proses debug penerapan adalah tentang interaksi antara kode dan lingkungan. Sekalipun akar masalahnya adalah kesalahan logis, fakta bahwa semuanya berfungsi pada satu mesin dan bukan pada mesin lain berarti bahwa masalahnya ada pada lingkungan.

Jadi, bukan alat debugging biasa seperti gdb Saya memiliki seperangkat alat berbeda untuk men-debug penerapan. Dan alat favorit saya untuk mengatasi masalah seperti “Mengapa perangkat lunak ini tidak berfungsi untuk saya?” ditelepon jejak.

Apa itu strace?

jejak adalah alat untuk "pelacakan panggilan sistem". Awalnya dibuat untuk Linux, tetapi trik debugging yang sama dapat dilakukan dengan alat untuk sistem lain (DTrace или krasi).

Aplikasi dasarnya sangat sederhana. Anda hanya perlu menjalankan strace dengan perintah apa pun dan itu akan membuang semua panggilan sistem (meskipun pertama-tama Anda mungkin harus menginstalnya sendiri jejak):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Apa saja panggilan sistem ini? Ini seperti API untuk kernel sistem operasi. Dahulu kala, perangkat lunak memiliki akses langsung ke perangkat keras yang menjalankannya. Jika, misalnya, ia perlu menampilkan sesuatu di layar, ia bermain dengan port atau register yang dipetakan memori untuk perangkat video. Ketika sistem komputer multitasking menjadi populer, kekacauan terjadi ketika berbagai aplikasi berebut perangkat keras. Kesalahan dalam satu aplikasi dapat menjatuhkan aplikasi lain, atau bahkan keseluruhan sistem. Kemudian mode hak istimewa (atau “perlindungan dering”) muncul di CPU. Kernel menjadi yang paling istimewa: ia menerima akses penuh ke perangkat keras, menghasilkan aplikasi yang kurang memiliki hak istimewa yang harus meminta akses dari kernel untuk berinteraksi dengan perangkat keras melalui panggilan sistem.

Pada tingkat biner, panggilan sistem sedikit berbeda dari pemanggilan fungsi sederhana, namun sebagian besar program menggunakan wrapper di perpustakaan standar. Itu. perpustakaan standar POSIX C berisi pemanggilan fungsi menulis(), yang berisi semua kode khusus arsitektur untuk panggilan sistem menulis.

Men-debug penerapan perangkat lunak dengan strace

Singkatnya, setiap interaksi antara aplikasi dan lingkungannya (sistem komputer) dilakukan melalui panggilan sistem. Oleh karena itu, ketika perangkat lunak berfungsi pada satu mesin tetapi tidak pada mesin lain, sebaiknya lihat hasil penelusuran panggilan sistem. Lebih khusus lagi, berikut adalah daftar poin umum yang dapat dianalisis menggunakan pelacakan panggilan sistem:

  • Konsol I/O
  • I/O jaringan
  • Akses sistem file dan I/O file
  • Mengelola masa pakai thread proses
  • Manajemen memori tingkat rendah
  • Akses ke driver perangkat tertentu

Kapan menggunakan strace?

Dalam teori, jejak digunakan dengan program apa pun di ruang pengguna, karena program apa pun di ruang pengguna harus melakukan panggilan sistem. Ia bekerja lebih efisien dengan program tingkat rendah yang dikompilasi, tetapi juga bekerja dengan bahasa tingkat tinggi seperti Python jika Anda dapat menghilangkan gangguan tambahan dari runtime dan interpreter.

Dengan segala kemegahannya jejak memanifestasikan dirinya selama debugging perangkat lunak yang bekerja dengan baik pada satu mesin, tapi tiba-tiba berhenti bekerja pada mesin lain, menghasilkan pesan yang tidak jelas tentang file, izin, atau upaya yang gagal untuk menjalankan beberapa perintah atau sesuatu yang lain... Sayang sekali, tapi tidak dikombinasikan dengan baik dengan masalah tingkat tinggi seperti kesalahan verifikasi sertifikat. Biasanya ini memerlukan kombinasi jejakterkadang jejak dan alat tingkat yang lebih tinggi (seperti alat baris perintah openssl untuk men-debug sertifikat).

Kami akan menggunakan server mandiri sebagai contoh, namun pelacakan panggilan sistem sering kali dapat dilakukan pada platform penerapan yang lebih kompleks. Anda hanya perlu memilih alat yang tepat.

Contoh debugging sederhana

Katakanlah Anda ingin menjalankan aplikasi server yang luar biasa foo, dan inilah yang Anda dapatkan:

$ foo
Error opening configuration file: No such file or directory

Rupanya tidak dapat menemukan file konfigurasi yang Anda tulis. Hal ini terjadi karena terkadang ketika manajer paket mengkompilasi aplikasi, mereka mengambil alih lokasi file yang diharapkan. Dan jika Anda mengikuti panduan instalasi untuk satu distribusi, di distribusi lain Anda akan menemukan file yang sama sekali berbeda dari yang Anda harapkan. Masalahnya dapat diselesaikan dalam beberapa detik jika pesan kesalahan memberitahukan di mana mencari file konfigurasi, namun ternyata tidak. Jadi di mana mencarinya?

Jika Anda memiliki akses ke kode sumber, Anda dapat membacanya dan mengetahui semuanya. Rencana cadangan yang bagus, tapi bukan solusi tercepat. Anda dapat menggunakan debugger langkah demi langkah seperti gdb dan lihat apa yang dilakukan program, namun akan jauh lebih efektif jika menggunakan alat yang dirancang khusus untuk menunjukkan interaksi dengan lingkungan: jejak.

Keluaran jejak mungkin tampak mubazir, namun kabar baiknya adalah sebagian besar hal tersebut dapat diabaikan dengan aman. Seringkali berguna untuk menggunakan operator -o untuk menyimpan hasil penelusuran ke file terpisah:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

Kira-kira seluruh halaman pertama keluaran jejak - Ini biasanya merupakan persiapan peluncuran tingkat rendah. (Banyak panggilan mmap, melindungi, brk untuk hal-hal seperti mendeteksi memori tingkat rendah dan menampilkan perpustakaan dinamis.) Sebenarnya, selama men-debug output jejak Lebih baik membaca dari akhir. Akan ada tantangan di bawah ini menulis, yang menampilkan pesan kesalahan. Kami melihat ke atas dan melihat panggilan sistem yang salah pertama - panggilan tersebut terbuka, yang menimbulkan kesalahan ENON (“file atau direktori tidak ditemukan”) mencoba membuka /etc/foo/config.json. Di sinilah seharusnya file konfigurasi berada.

Ini hanyalah sebuah contoh, tetapi menurut saya 90% dari waktu yang saya gunakan jejak, tidak ada yang lebih sulit untuk dilakukan selain ini. Di bawah ini adalah panduan debugging langkah demi langkah yang lengkap:

  • Kesal karena pesan yang tidak jelas tentang kesalahan sistem dari suatu program
  • Mulai ulang program dengan jejak
  • Temukan pesan kesalahan di hasil penelusuran
  • Naik lebih tinggi hingga Anda mencapai panggilan sistem pertama yang gagal

Sangat mungkin bahwa panggilan sistem pada langkah 4 akan mengungkapkan apa yang salah.

Kiat

Sebelum menunjukkan contoh debugging yang lebih kompleks, saya akan menunjukkan beberapa trik untuk penggunaan yang efektif jejak:

laki-laki adalah temanmu

Pada banyak sistem *nix, daftar lengkap panggilan sistem ke kernel dapat diperoleh dengan menjalankannya pria sycalls. Anda akan melihat hal-hal seperti jalang(2), yang berarti lebih banyak informasi dapat diperoleh dengan menjalankan pria 2 kamar.

Penggaruk kecil: pria 2 garpu menunjukkan kepada saya halaman untuk shell garpu() в libc GNU, yang ternyata diimplementasikan dengan menelepon klon(). Panggil semantik garpu tetap sama jika Anda menulis program menggunakan garpu(), dan jalankan penelusuran - saya tidak akan menemukan panggilan apa pun garpu, alih-alih mereka yang akan ada klon(). Penggaruk seperti itu hanya membingungkan Anda jika Anda mulai membandingkan sumber dengan keluaran jejak.

Gunakan -o untuk menyimpan output ke file

jejak dapat menghasilkan keluaran yang luas, sehingga seringkali berguna untuk menyimpan hasil penelusuran dalam file terpisah (seperti pada contoh di atas). Hal ini juga membantu menghindari kebingungan antara keluaran program dan keluaran jejak di konsol.

Gunakan -s untuk melihat lebih banyak data argumen

Anda mungkin telah memperhatikan bahwa bagian kedua dari pesan kesalahan tidak ditampilkan pada contoh pelacakan di atas. Ini karena jejak default hanya menampilkan 32 byte pertama dari argumen string. Jika Anda ingin melihat lebih banyak, tambahkan sesuatu seperti -s 128 ke panggilan jejak.

-y memudahkan pelacakan file, soket, dll.

"Semua adalah file" berarti sistem *nix melakukan semua I/O menggunakan deskriptor file, baik itu berlaku untuk file atau jaringan atau pipa antarproses. Ini nyaman untuk pemrograman, tetapi menyulitkan untuk melacak apa yang sebenarnya terjadi ketika Anda melihat hal yang umum Baca baca и menulis dalam hasil pelacakan panggilan sistem.

Dengan menambahkan operator -y, kamu akan memaksa jejak beri anotasi pada setiap deskriptor file dalam output dengan catatan tentang apa yang ditunjuknya.

Lampirkan ke proses yang sudah berjalan dengan -p**

Seperti yang akan Anda lihat dari contoh di bawah, terkadang Anda perlu melacak program yang sudah berjalan. Jika diketahui berjalan sebagai proses 1337 (misalnya, dari output ps), maka Anda dapat melacaknya seperti ini:

$ strace -p 1337
...system call trace output...

Anda mungkin memerlukan hak root.

Gunakan -f untuk memantau proses anak

jejak Secara default, ini hanya melacak satu proses. Jika proses ini memunculkan proses anak, maka panggilan sistem untuk menelurkan proses anak tersebut dapat dilihat, namun panggilan sistem dari proses anak tersebut tidak akan ditampilkan.

Jika menurut Anda kesalahannya ada pada proses anak, gunakan pernyataan tersebut -f, ini akan mengaktifkan penelusurannya. Sisi negatifnya adalah hasilnya akan semakin membingungkan Anda. Kapan jejak menelusuri satu proses atau satu thread, ini menunjukkan satu aliran peristiwa panggilan. Saat melacak beberapa proses sekaligus, Anda mungkin melihat awal panggilan terganggu oleh sebuah pesan , lalu - sekumpulan panggilan untuk cabang eksekusi lainnya, dan baru kemudian - akhir dari cabang eksekusi pertama <…panggilan kembali dilanjutkan>. Atau bagi semua hasil penelusuran ke dalam file yang berbeda, juga menggunakan operator -ff (detail di kepemimpinan pada jejak).

Filter jejak menggunakan -e

Seperti yang Anda lihat, hasil penelusuran adalah tumpukan nyata dari semua kemungkinan panggilan sistem. Bendera -e Anda dapat memfilter jejaknya (lihat panduan pada jejak). Keuntungan utamanya adalah menjalankan penelusuran yang difilter lebih cepat daripada menjalankan penelusuran penuh kemudian grep`di. Sejujurnya, saya hampir selalu tidak peduli.

Tidak semua kesalahan itu buruk

Contoh sederhana dan umum adalah program mencari file di beberapa tempat sekaligus, seperti shell yang mencari direktori yang berisi file yang dapat dieksekusi:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Heuristik seperti “permintaan terakhir yang gagal sebelum melaporkan kesalahan” bagus dalam menemukan kesalahan yang relevan. Meskipun demikian, masuk akal untuk memulai dari awal.

Tutorial pemrograman C dapat membantu Anda memahami panggilan sistem.

Panggilan standar ke perpustakaan C bukanlah panggilan sistem, tetapi hanya lapisan permukaan tipis. Jadi, jika Anda memahami setidaknya sedikit bagaimana dan apa yang harus dilakukan di C, akan lebih mudah bagi Anda untuk memahami hasil penelusuran system call. Misalnya, Anda mengalami kesulitan men-debug panggilan ke sistem jaringan, lihat klasik yang sama Panduan Bija untuk Pemrograman Jaringan.

Contoh debugging yang lebih kompleks

Saya sudah mengatakan bahwa contoh debugging sederhana adalah contoh dari apa yang paling sering saya hadapi saat bekerja jejak. Namun, terkadang penyelidikan nyata diperlukan, jadi inilah contoh nyata dari proses debug lebih lanjut.

brron - penjadwal pemrosesan tugas, implementasi lain dari daemon *nix cron. Itu diinstal di server, tetapi ketika seseorang mencoba mengedit jadwal, inilah yang terjadi:

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

Oke, itu artinya brron mencoba menulis file tertentu, tetapi tidak berhasil, dan dia tidak mau mengakui alasannya. Mengungkap jejak:

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsaggn20 14 * * * lo"..., 8192) = 150
read(3, "", 8192)                       = 0
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs #Ansible: logsaggn20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111)                         = ?
+++ exited with 111 +++

Ada pesan kesalahan menjelang akhir menulis, tapi kali ini ada yang berbeda. Pertama, tidak ada kesalahan panggilan sistem terkait yang biasanya terjadi sebelumnya. Kedua, jelas bahwa seseorang telah membaca pesan kesalahan tersebut. Sepertinya masalah sebenarnya ada di tempat lain, dan brontab cukup putar ulang pesannya.

Jika Anda melihat pria 2 membaca, Anda dapat melihat bahwa argumen pertama (3) adalah deskriptor file, yang *nix gunakan untuk semua pemrosesan I/O. Bagaimana cara mengetahui apa yang diwakili oleh deskriptor file 3? Dalam kasus khusus ini, Anda dapat menjalankannya jejak dengan operator -y (lihat di atas) dan secara otomatis akan memberi tahu Anda, tetapi untuk mengetahui hal-hal seperti ini, ada gunanya mengetahui cara membaca dan mengurai hasil penelusuran.

Sumber deskriptor file dapat berupa salah satu dari banyak panggilan sistem (semuanya tergantung pada kegunaan deskriptor - konsol, soket jaringan, file itu sendiri, atau yang lainnya), namun demikian, kami mencari panggilan dengan mengembalikan 3 (yaitu kita mencari “= 3” di hasil penelusuran). Dalam hasil ini ada 2 di antaranya: terbuka di bagian paling atas dan stopkontak Di tengah-tengah. terbuka membuka file tetapi menutup penjualan(3) kemudian akan menunjukkan bahwa itu menutup kembali. (Rake: deskriptor file dapat digunakan kembali saat dibuka dan ditutup). Panggilan stopkontak () cocok karena ini yang terakhir sebelumnya Baca(), dan ternyata bcrontab bekerja dengan sesuatu melalui soket. Baris berikutnya menunjukkan bahwa deskriptor file dikaitkan dengan soket domain unix sepanjang jalan /var/run/bcron-spool.

Jadi, kita perlu menemukan proses yang terkait dengannya soket unix di sisi lain. Ada beberapa trik menarik untuk tujuan ini, keduanya berguna untuk men-debug penerapan server. Yang pertama adalah menggunakan netstat atau lebih baru ss (status soket). Kedua perintah menunjukkan koneksi jaringan aktif sistem dan mengambil pernyataan tersebut -l untuk menggambarkan soket pendengaran, serta operator -p untuk menampilkan program yang terhubung ke soket sebagai klien. (Masih banyak lagi opsi berguna, namun keduanya sudah cukup untuk tugas ini.)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

Hal ini menunjukkan bahwa pendengarlah yang memberi perintah server inix, berjalan dengan ID proses 20629. (Dan, secara kebetulan, ia menggunakan deskriptor file 3 sebagai soketnya.)

Alat kedua yang sangat berguna untuk menemukan informasi yang sama disebut lsof. Ini mencantumkan semua file yang terbuka (atau deskriptor file) di sistem. Atau Anda bisa mendapatkan informasi tentang satu file tertentu:

# lsof /var/run/bcron-spool
COMMAND   PID   USER  FD  TYPE  DEVICE              SIZE/OFF  NODE    NAME
unixserve 20629 cron  3u  unix  0x000000005ac4bd83  0t0       1466637 /var/run/bcron-spool type=STREAM

Proses 20629 adalah server yang berumur panjang, sehingga Anda dapat melampirkannya jejak menggunakan sesuatu seperti strace -o /tmp/jejak -p 20629. Jika Anda mengedit tugas cron di terminal lain, Anda akan menerima keluaran jejak dengan kesalahan. Dan inilah hasilnya:

accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(Terakhir menerima() tidak akan selesai saat penelusuran.) Sekali lagi, sayangnya, hasil ini tidak mengandung kesalahan yang kita cari. Kami tidak melihat pesan apa pun yang dikirim atau diterima bcrontag dari soket. Sebaliknya, kendalikan proses secara lengkap (clone, tunggu4, SIGCHLD dll.) Proses ini memunculkan proses anak, yang, seperti yang Anda duga, melakukan pekerjaan sebenarnya. Dan jika Anda perlu mengetahui jejaknya, tambahkan ke panggilannya jejak -f. Inilah yang akan kita temukan ketika kita mencari pesan kesalahan di hasil baru dengan strace -f -o /tmp/jejak -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

Nah, itu sesuatu. Proses 21470 menerima kesalahan "akses ditolak" ketika mencoba membuat file di jalur tmp/spool.21470.1573692319.854640 (berhubungan dengan direktori kerja saat ini). Jika kita hanya mengetahui direktori kerja saat ini, kita juga akan mengetahui jalur lengkapnya dan dapat mengetahui mengapa proses tidak dapat membuat file sementara di dalamnya. Sayangnya prosesnya sudah keluar, jadi tidak bisa digunakan begitu saja lsof -p 21470 untuk menemukan direktori saat ini, tetapi Anda dapat bekerja dalam arah yang berlawanan - cari panggilan sistem PID 21470 yang mengubah direktori. (Jika tidak ada, PID 21470 pasti diwarisi dari induknya, dan ini sudah lewat lsof -p tidak dapat ditemukan.) Panggilan sistem ini adalah chdir (yang mudah ditemukan dengan bantuan mesin pencari online modern). Dan berikut hasil pencarian balik berdasarkan hasil tracing hingga ke server PID 20629:

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron")          = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(Jika Anda tersesat, Anda mungkin ingin membaca posting saya sebelumnya tentang *manajemen proses nix dan shell.) Jadi, server PID 20629 tidak mendapat izin untuk membuat file di jalur tersebut /var/spool/cron/tmp/spool.21470.1573692319.854640. Kemungkinan besar alasannya adalah pengaturan izin sistem file klasik. Mari kita periksa:

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov  6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cron     20629  0.0  0.0   2276   752 ?        Ss   Nov14   0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

Di situlah anjing itu dikuburkan! Server berjalan sebagai pengguna cron, tetapi hanya root yang memiliki izin untuk menulis ke direktori /var/spool/cron/tmp/. Perintah sederhana chown cron /var/spool/cron/tmp/ akan memaksa brron bekerja dengan benar. (Jika bukan itu masalahnya, kemungkinan besar tersangka berikutnya adalah modul keamanan kernel seperti SELinux atau AppArmor, jadi saya akan memeriksa log pesan kernel dengan dmesg.)

Total

Pelacakan panggilan sistem bisa sangat melelahkan bagi pemula, tapi saya harap saya telah menunjukkan bahwa ini adalah cara cepat untuk men-debug seluruh masalah penerapan umum. Bayangkan mencoba men-debug multiproses brronmenggunakan debugger langkah demi langkah.

Mengurai hasil penelusuran secara mundur sepanjang rantai panggilan sistem memerlukan keterampilan, tetapi seperti yang saya katakan, hampir selalu, menggunakan jejak, saya hanya mendapatkan hasil penelusuran dan mencari kesalahan mulai dari akhir. Bagaimanapun, jejak membantu saya menghemat banyak waktu dalam debugging. Saya harap ini bermanfaat bagi Anda juga.

Sumber: www.habr.com

Tambah komentar