การดีบักการปรับใช้ซอฟต์แวร์ด้วย strace

การดีบักการปรับใช้ซอฟต์แวร์ด้วย strace

งานประจำวันของฉันส่วนใหญ่เป็นการปรับใช้ซอฟต์แวร์ ซึ่งหมายความว่าฉันใช้เวลาส่วนใหญ่ในการพยายามตอบคำถามเช่น:

  • ซอฟต์แวร์นี้ใช้งานได้สำหรับนักพัฒนา แต่ไม่ใช่สำหรับฉัน ทำไม
  • เมื่อวานซอฟต์แวร์นี้ใช้งานได้สำหรับฉัน แต่วันนี้กลับใช้ไม่ได้ ทำไม

นี่เป็นการดีบักประเภทหนึ่งที่แตกต่างจากการดีบักซอฟต์แวร์ทั่วไปเล็กน้อย การดีบักแบบปกติเป็นเรื่องเกี่ยวกับตรรกะของโค้ด แต่การดีบักการปรับใช้เป็นเรื่องเกี่ยวกับการโต้ตอบระหว่างโค้ดและสภาพแวดล้อม แม้ว่าต้นตอของปัญหาจะเป็นข้อผิดพลาดเชิงตรรกะ แต่ข้อเท็จจริงที่ว่าทุกอย่างทำงานได้บนเครื่องหนึ่งและไม่ใช่ในเครื่องอื่น หมายความว่าปัญหานั้นอยู่ในสภาพแวดล้อมทางใดทางหนึ่ง

ดังนั้นแทนที่จะเป็นเครื่องมือแก้ไขข้อบกพร่องตามปกติเช่น จีดีบี ฉันมีชุดเครื่องมือที่แตกต่างกันสำหรับการดีบักการปรับใช้ และเครื่องมือที่ฉันชื่นชอบในการจัดการกับปัญหาเช่น “ทำไมซอฟต์แวร์นี้ใช้ไม่ได้สำหรับฉัน” เรียกว่า สเตรซ.

สเตรสคืออะไร?

สเตรซ เป็นเครื่องมือสำหรับ “การติดตามการโทรของระบบ” เดิมทีมันถูกสร้างขึ้นสำหรับ Linux แต่เทคนิคการดีบักแบบเดียวกันนี้สามารถทำได้ด้วยเครื่องมือสำหรับระบบอื่น (DTrace หรือ ktration).

แอปพลิเคชันพื้นฐานนั้นง่ายมาก คุณเพียงแค่ต้องรัน strace ด้วยคำสั่งใด ๆ และมันจะถ่ายโอนข้อมูลการเรียกของระบบทั้งหมด (แม้ว่าก่อนอื่นคุณอาจต้องติดตั้งด้วยตัวเองก่อน สเตรซ):

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

การเรียกของระบบเหล่านี้คืออะไร? นี่คือสิ่งที่คล้ายกับ API สำหรับเคอร์เนลของระบบปฏิบัติการ กาลครั้งหนึ่ง ซอฟต์แวร์สามารถเข้าถึงฮาร์ดแวร์ที่ทำงานอยู่ได้โดยตรง ตัวอย่างเช่น หากจำเป็นต้องแสดงบางอย่างบนหน้าจอ อุปกรณ์นั้นจะเล่นกับพอร์ตหรือรีจิสเตอร์ที่แมปหน่วยความจำสำหรับอุปกรณ์วิดีโอ เมื่อระบบคอมพิวเตอร์แบบมัลติทาสกิ้งได้รับความนิยม ความวุ่นวายก็เข้ามาครอบงำเมื่อแอพพลิเคชั่นต่างๆ แย่งชิงฮาร์ดแวร์ ข้อผิดพลาดในแอปพลิเคชันหนึ่งอาจทำให้แอปพลิเคชันอื่นล่มได้ หากไม่ใช่ทั้งระบบ จากนั้นโหมดสิทธิพิเศษ (หรือ "การป้องกันวงแหวน") จะปรากฏขึ้นใน CPU เคอร์เนลได้รับสิทธิพิเศษสูงสุด: ได้รับสิทธิ์เข้าถึงฮาร์ดแวร์โดยสมบูรณ์ ทำให้เกิดแอปพลิเคชันที่มีสิทธิพิเศษน้อยกว่าซึ่งต้องร้องขอการเข้าถึงจากเคอร์เนลเพื่อโต้ตอบกับฮาร์ดแวร์ผ่านการเรียกของระบบ

ในระดับไบนารี การเรียกของระบบจะแตกต่างจากการเรียกฟังก์ชันธรรมดาเล็กน้อย แต่โปรแกรมส่วนใหญ่ใช้ wrapper ในไลบรารีมาตรฐาน เหล่านั้น. ไลบรารีมาตรฐาน POSIX C มีการเรียกใช้ฟังก์ชัน เขียน()ซึ่งมีโค้ดเฉพาะสถาปัตยกรรมทั้งหมดสำหรับการเรียกระบบ เขียน.

การดีบักการปรับใช้ซอฟต์แวร์ด้วย strace

กล่าวโดยสรุป การโต้ตอบใดๆ ระหว่างแอปพลิเคชันและสภาพแวดล้อม (ระบบคอมพิวเตอร์) จะดำเนินการผ่านการเรียกของระบบ ดังนั้น เมื่อซอฟต์แวร์ทำงานบนเครื่องหนึ่งแต่ไม่ทำงานบนอีกเครื่องหนึ่ง จะเป็นการดีที่จะดูผลลัพธ์การติดตามการโทรของระบบ โดยเฉพาะอย่างยิ่ง นี่คือรายการจุดทั่วไปที่สามารถวิเคราะห์ได้โดยใช้การติดตามการเรียกของระบบ:

  • คอนโซล I/O
  • เครือข่าย I/O
  • การเข้าถึงระบบไฟล์และ I/O ของไฟล์
  • การจัดการอายุการใช้งานของเธรดกระบวนการ
  • การจัดการหน่วยความจำระดับต่ำ
  • เข้าถึงไดรเวอร์อุปกรณ์เฉพาะ

ควรใช้สเตรซเมื่อใด?

ในทางทฤษฎี สเตรซ ใช้กับโปรแกรมใดๆ ในพื้นที่ผู้ใช้ เนื่องจากโปรแกรมใดๆ ในพื้นที่ผู้ใช้จะต้องทำการเรียกระบบ มันทำงานได้อย่างมีประสิทธิภาพมากขึ้นกับโปรแกรมระดับต่ำที่คอมไพล์ แต่ยังใช้งานได้กับภาษาระดับสูงเช่น Python หากคุณสามารถตัดเสียงรบกวนเพิ่มเติมจากรันไทม์และล่ามได้

ในทุกความงดงามของมัน สเตรซ ปรากฏขึ้นในระหว่างการดีบั๊กซอฟต์แวร์ที่ทำงานได้ดีบนเครื่องเครื่องหนึ่ง แต่หยุดทำงานในเครื่องอื่นกะทันหัน ทำให้เกิดข้อความคลุมเครือเกี่ยวกับไฟล์ การอนุญาต หรือการพยายามรันคำสั่งบางคำสั่งหรืออย่างอื่นไม่สำเร็จ... น่าเสียดาย แต่ก็ไม่ได้ ผสมผสานกับปัญหาระดับสูง เช่น ข้อผิดพลาดในการตรวจสอบใบรับรอง โดยปกติแล้วสิ่งนี้จะต้องมีการรวมกัน สเตรซ, บางครั้ง ลิเทรซ และเครื่องมือระดับสูงกว่า (เช่น เครื่องมือบรรทัดคำสั่ง OpenSSL เพื่อตรวจแก้จุดบกพร่องใบรับรอง)

เราจะใช้เซิร์ฟเวอร์แบบสแตนด์อโลนเป็นตัวอย่าง แต่การติดตามการเรียกของระบบมักจะทำได้บนแพลตฟอร์มการปรับใช้ที่ซับซ้อนกว่า คุณเพียงแค่ต้องเลือกเครื่องมือที่เหมาะสม

ตัวอย่างการดีบักอย่างง่าย

สมมติว่าคุณต้องการรันแอปพลิเคชันเซิร์ฟเวอร์ที่น่าทึ่ง foo และนี่คือสิ่งที่คุณจะได้:

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

เห็นได้ชัดว่าไม่พบไฟล์การกำหนดค่าที่คุณเขียน สิ่งนี้เกิดขึ้นเนื่องจากบางครั้งเมื่อผู้จัดการแพ็คเกจคอมไพล์แอปพลิเคชัน พวกเขาจะแทนที่ตำแหน่งไฟล์ที่คาดไว้ และถ้าคุณทำตามคำแนะนำในการติดตั้งสำหรับการกระจายรายการหนึ่ง ในอีกไฟล์หนึ่งคุณจะพบไฟล์ที่แตกต่างไปจากที่คุณคาดไว้อย่างสิ้นเชิง ปัญหาสามารถแก้ไขได้ภายในไม่กี่วินาทีหากข้อความแสดงข้อผิดพลาดแจ้งว่าจะค้นหาไฟล์การกำหนดค่าได้ที่ไหน แต่กลับไม่เป็นเช่นนั้น แล้วจะดูได้ที่ไหน?

หากคุณมีสิทธิ์เข้าถึงซอร์สโค้ด คุณสามารถอ่านและค้นหาทุกสิ่งได้ แผนสำรองที่ดี แต่ไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุด คุณสามารถใช้ดีบักเกอร์ทีละขั้นตอนได้ จีดีบี และดูว่าโปรแกรมทำอะไร แต่การใช้เครื่องมือที่ออกแบบมาโดยเฉพาะเพื่อแสดงปฏิสัมพันธ์กับสภาพแวดล้อมจะมีประสิทธิภาพมากกว่ามาก: สเตรซ.

เอาท์พุต สเตรซ อาจดูซ้ำซ้อน แต่ข่าวดีก็คือว่าส่วนใหญ่สามารถเพิกเฉยได้อย่างปลอดภัย มักจะมีประโยชน์ในการใช้ตัวดำเนินการ -o เพื่อบันทึกผลลัพธ์การติดตามไปยังไฟล์แยกต่างหาก:

$ 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 +++

ประมาณหน้าแรกของผลลัพธ์ทั้งหมด สเตรซ - โดยปกติจะเป็นการเตรียมการระดับต่ำสำหรับการเปิดตัว (โทรมาเยอะมาก. มม, ปกป้อง, BRK สำหรับสิ่งต่าง ๆ เช่นการตรวจจับหน่วยความจำระดับต่ำและการแสดงไลบรารีแบบไดนามิก) จริงๆ แล้วระหว่างการดีบักเอาต์พุต สเตรซ อ่านตั้งแต่ตอนจบเลยดีกว่า ด้านล่างจะมีความท้าทาย เขียนซึ่งแสดงข้อความแสดงข้อผิดพลาด เรามองไปด้านบนและเห็นการเรียกของระบบที่ผิดพลาดครั้งแรก - การโทร เปิดซึ่งทำให้เกิดข้อผิดพลาด อีน็อนต์ (“ไม่พบไฟล์หรือไดเร็กทอรี”) กำลังพยายามเปิด /etc/foo/config.json. นี่คือตำแหน่งที่ไฟล์การกำหนดค่าควรอยู่

นี่เป็นเพียงตัวอย่าง แต่ฉันจะบอกว่า 90% ของเวลาที่ฉันใช้ สเตรซไม่มีอะไรยากไปกว่านี้อีกแล้ว ด้านล่างนี้เป็นคำแนะนำในการดีบักทีละขั้นตอนโดยสมบูรณ์:

  • อารมณ์เสียเนื่องจากมีข้อความคลุมเครือเกี่ยวกับข้อผิดพลาดของระบบจากโปรแกรม
  • รีสตาร์ทโปรแกรมด้วย สเตรซ
  • ค้นหาข้อความแสดงข้อผิดพลาดในผลลัพธ์การติดตาม
  • สูงขึ้นไปจนกว่าคุณจะกดการเรียกของระบบที่ล้มเหลวครั้งแรก

มีโอกาสมากที่การเรียกของระบบในขั้นตอนที่ 4 จะเปิดเผยสิ่งที่ผิดพลาด

เคล็ดลับ

ก่อนที่จะแสดงตัวอย่างการดีบักที่ซับซ้อนกว่านี้ ฉันจะแสดงเคล็ดลับเล็กๆ น้อยๆ ให้คุณเห็นเพื่อการใช้งานอย่างมีประสิทธิภาพ สเตรซ:

ผู้ชายคือเพื่อนของคุณ

บนระบบ *nix หลายระบบ สามารถรับรายการการเรียกระบบทั้งหมดไปยังเคอร์เนลได้โดยการรัน แมน ซิสคอลส์. คุณจะเห็นสิ่งต่าง ๆ เช่น บริค(2)ซึ่งหมายความว่าสามารถรับข้อมูลเพิ่มเติมได้จากการรัน ชาย 2 บริค.

คราดขนาดเล็ก: คน 2 ส้อม แสดงหน้าสำหรับเชลล์ให้ฉันดู ส้อม() в GNU libcซึ่งปรากฎว่ามีการใช้งานโดยการโทร โคลน (). เรียกความหมาย ส้อม ยังคงเหมือนเดิมหากคุณเขียนโปรแกรมโดยใช้ ส้อม()และติดตาม - ฉันจะไม่พบสายใด ๆ ส้อมแทนที่จะมีพวกเขา โคลน (). คราดดังกล่าวจะทำให้คุณสับสนหากคุณเริ่มเปรียบเทียบแหล่งที่มากับเอาต์พุต สเตรซ.

ใช้ -o เพื่อบันทึกเอาต์พุตเป็นไฟล์

สเตรซ สามารถสร้างเอาต์พุตได้อย่างกว้างขวาง ดังนั้นจึงมักจะมีประโยชน์ในการจัดเก็บผลลัพธ์การติดตามเป็นไฟล์แยกกัน (ดังตัวอย่างด้านบน) นอกจากนี้ยังช่วยหลีกเลี่ยงไม่ให้เอาต์พุตของโปรแกรมสับสนกับเอาต์พุตอีกด้วย สเตรซ ในคอนโซล

ใช้ -s เพื่อดูข้อมูลอาร์กิวเมนต์เพิ่มเติม

คุณอาจสังเกตเห็นว่าข้อความแสดงข้อผิดพลาดครึ่งหลังไม่แสดงในตัวอย่างการติดตามด้านบน มันเป็นเพราะว่า สเตรซ ค่าเริ่มต้นจะแสดงเฉพาะ 32 ไบต์แรกของอาร์กิวเมนต์สตริง อยากดูเพิ่มเติมก็ใส่อะไรประมาณนี้ -s 128 เพื่อการโทร สเตรซ.

-y ช่วยให้ติดตามไฟล์ ซ็อกเก็ต ฯลฯ ได้ง่ายขึ้น

"All is file" หมายความว่าระบบ *nix ทำ I/O ทั้งหมดโดยใช้ตัวอธิบายไฟล์ ไม่ว่าจะใช้กับไฟล์หรือเครือข่ายหรือไปป์ระหว่างกระบวนการ วิธีนี้สะดวกสำหรับการเขียนโปรแกรม แต่ทำให้ยากต่อการติดตามสิ่งที่เกิดขึ้นเมื่อคุณเห็นเรื่องทั่วไป อ่าน и เขียน ในผลลัพธ์การติดตามการโทรของระบบ

โดยการเพิ่มตัวดำเนินการ Yคุณจะบังคับ สเตรซ ใส่คำอธิบายประกอบแต่ละไฟล์อธิบายในเอาต์พุตพร้อมบันทึกย่อว่าชี้ไปที่อะไร

แนบไปกับกระบวนการที่กำลังทำงานอยู่ด้วย -p**

ดังที่คุณจะเห็นจากตัวอย่างด้านล่าง บางครั้งคุณจำเป็นต้องติดตามโปรแกรมที่กำลังทำงานอยู่ หากทราบว่ากำลังทำงานเป็นกระบวนการ 1337 (เช่นจากเอาต์พุต ps) จากนั้นคุณสามารถติดตามได้ดังนี้:

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

คุณอาจต้องมีสิทธิ์รูท

ใช้ -f เพื่อตรวจสอบกระบวนการลูก

สเตรซ โดยค่าเริ่มต้น จะติดตามกระบวนการเดียวเท่านั้น หากกระบวนการนี้วางไข่กระบวนการลูก ดังนั้นการเรียกของระบบเพื่อวางไข่กระบวนการลูกนั้นสามารถมองเห็นได้ แต่การเรียกของระบบของกระบวนการลูกจะไม่แสดงขึ้นมา

ถ้าคุณคิดว่าข้อผิดพลาดอยู่ในกระบวนการลูก ให้ใช้คำสั่ง -fซึ่งจะทำให้สามารถติดตามได้ ข้อเสียคือผลลัพธ์จะทำให้คุณสับสนมากยิ่งขึ้น เมื่อไร สเตรซ ติดตามหนึ่งกระบวนการหรือหนึ่งเธรด โดยจะแสดงสตรีมเหตุการณ์การโทรเดียว เมื่อติดตามหลายกระบวนการพร้อมกัน คุณอาจเห็นว่าการเริ่มต้นการโทรถูกขัดจังหวะด้วยข้อความ จากนั้น - การโทรจำนวนมากสำหรับสาขาการดำเนินการอื่น ๆ และจากนั้น - จุดสิ้นสุดของสาขาแรก . หรือแยกผลลัพธ์การติดตามทั้งหมดออกเป็นไฟล์ต่างๆ โดยใช้โอเปอเรเตอร์ด้วย -ff (รายละเอียดอยู่ใน. ความเป็นผู้นำ บน สเตรซ).

กรองการติดตามโดยใช้ -e

อย่างที่คุณเห็น ผลลัพธ์ของการติดตามคือการเรียกระบบที่เป็นไปได้ทั้งหมดจำนวนมาก ธง -e คุณสามารถกรองการติดตามได้ (ดู ความเป็นผู้นำ บน สเตรซ). ข้อได้เปรียบหลักคือ การรันการติดตามที่กรองเร็วกว่าการดำเนินการติดตามแบบเต็มแล้ว grep`ที่. พูดตามตรงฉันมักจะไม่สนใจเลย

ความผิดพลาดไม่ได้เลวร้ายทั้งหมด

ตัวอย่างง่ายๆ ทั่วไปคือโปรแกรมที่ค้นหาไฟล์ในหลายตำแหน่งพร้อมกัน เช่น เชลล์ที่ค้นหาไดเร็กทอรีที่มีไฟล์ปฏิบัติการ:

$ 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
...

การวิเคราะห์พฤติกรรม เช่น “คำขอที่ล้มเหลวครั้งล่าสุดก่อนที่จะรายงานข้อผิดพลาด” สามารถค้นหาข้อผิดพลาดที่เกี่ยวข้องได้ดี อาจเป็นไปได้ว่าควรเริ่มจากจุดสิ้นสุด

บทช่วยสอนการเขียนโปรแกรม C สามารถช่วยให้คุณเข้าใจการเรียกของระบบ

การเรียกไลบรารี C แบบมาตรฐานไม่ใช่การเรียกของระบบ แต่เป็นเพียงชั้นผิวบางเท่านั้น ดังนั้นหากคุณเข้าใจอย่างน้อยว่าต้องทำอย่างไรและอย่างไรใน C คุณจะเข้าใจผลลัพธ์ของการติดตามการโทรของระบบได้ง่ายขึ้น ตัวอย่างเช่น คุณมีปัญหาในการดีบักการโทรไปยังระบบเครือข่าย ให้ดูแบบคลาสสิกเดียวกัน คู่มือการเขียนโปรแกรมเครือข่ายของ Bija.

ตัวอย่างการดีบักที่ซับซ้อนยิ่งขึ้น

ฉันได้กล่าวไปแล้วว่าตัวอย่างของการแก้ไขจุดบกพร่องอย่างง่ายคือตัวอย่างของสิ่งที่ฉันต้องจัดการเป็นส่วนใหญ่เมื่อทำงานด้วย สเตรซ. อย่างไรก็ตาม บางครั้งจำเป็นต้องมีการตรวจสอบจริง ดังนั้นนี่คือตัวอย่างในชีวิตจริงของการแก้ไขจุดบกพร่องขั้นสูง

bron - ตัวกำหนดเวลาการประมวลผลงาน การใช้งานอื่นของ *nix daemon cron. มีการติดตั้งไว้บนเซิร์ฟเวอร์ แต่เมื่อมีคนพยายามแก้ไขกำหนดการ นี่คือสิ่งที่เกิดขึ้น:

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

โอเค นั่นหมายความว่า bron พยายามเขียนไฟล์บางไฟล์ แต่ก็ไม่ได้ผล และเขาก็ไม่ยอมรับสาเหตุ การเปิดเผย สเตรซ:

# 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 +++

มีข้อความแสดงข้อผิดพลาดใกล้ถึงจุดสิ้นสุด เขียนแต่คราวนี้มีบางอย่างแตกต่างออกไป ประการแรก ไม่มีข้อผิดพลาดในการเรียกของระบบที่เกี่ยวข้อง ซึ่งมักจะเกิดขึ้นก่อนหน้านี้ ประการที่สอง เป็นที่ชัดเจนว่ามีคนอ่านข้อความแสดงข้อผิดพลาดแล้ว ดูเหมือนว่าปัญหาที่แท้จริงจะอยู่ที่อื่นและ brontab เพียงแค่เล่นข้อความ

หากมองดู ผู้ชาย 2 อ่านคุณจะเห็นว่าอาร์กิวเมนต์แรก (3) เป็นตัวอธิบายไฟล์ ซึ่ง *nix ใช้สำหรับการประมวลผล I/O ทั้งหมด ฉันจะทราบได้อย่างไรว่า file descriptor 3 แสดงถึงอะไร ในกรณีนี้ คุณสามารถเรียกใช้ได้ สเตรซ พร้อมด้วยผู้ปฏิบัติงาน Y (ดูด้านบน) และมันจะแจ้งให้คุณทราบโดยอัตโนมัติ แต่หากต้องการทราบสิ่งนี้ การรู้วิธีอ่านและแยกวิเคราะห์ผลลัพธ์การติดตามจะเป็นประโยชน์

แหล่งที่มาของ file descriptor สามารถเป็นหนึ่งในหลายๆ การเรียกของระบบ (ทั้งหมดขึ้นอยู่กับว่า descriptor มีไว้เพื่ออะไร - คอนโซล, ซ็อกเก็ตเครือข่าย, ตัวไฟล์เอง หรืออย่างอื่น) แต่อย่างไรก็ตาม เรามองหา โทรกลับโดยส่งคืน 3 (เช่น .e. เรามองหา "= 3" ในผลลัพธ์การติดตาม) ผลลัพธ์นี้มี 2 รายการ: เปิด ที่ด้านบนสุดและ เบ้า ระหว่างกลาง. เปิด เปิดไฟล์แต่ ปิดการขาย(3) จะแสดงว่าปิดอีกครั้ง (เรค: ตัวอธิบายไฟล์สามารถนำมาใช้ซ้ำได้เมื่อเปิดและปิด) เรียก เบ้า() เหมาะเพราะเป็นอันสุดท้ายแล้ว อ่าน()และปรากฎว่า bcrontab ทำงานกับบางสิ่งผ่านซ็อกเก็ตได้ บรรทัดถัดไปแสดงว่าตัวอธิบายไฟล์มีความเกี่ยวข้อง ซ็อกเก็ตโดเมนยูนิกซ์ ระหว่างทาง /var/run/bcron-spool.

ดังนั้นเราจึงจำเป็นต้องค้นหากระบวนการที่เกี่ยวข้องด้วย ซ็อกเก็ตยูนิกซ์ อีกด้านหนึ่ง มีเคล็ดลับเล็กๆ น้อยๆ สองสามข้อสำหรับจุดประสงค์นี้ ซึ่งทั้งสองอย่างนี้มีประโยชน์สำหรับการดีบักการปรับใช้เซิร์ฟเวอร์ ประการแรกคือการใช้ netstat หรือใหม่กว่า ss (สถานะซ็อกเก็ต) คำสั่งทั้งสองแสดงการเชื่อมต่อเครือข่ายที่ใช้งานอยู่ของระบบและรับคำสั่ง -l เพื่ออธิบายซ็อกเก็ตการฟังตลอดจนตัวดำเนินการ -p เพื่อแสดงโปรแกรมที่เชื่อมต่อกับซ็อกเก็ตในฐานะไคลเอนต์ (มีตัวเลือกที่มีประโยชน์อีกมากมาย แต่ทั้งสองตัวเลือกนี้ก็เพียงพอแล้วสำหรับงานนี้)

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

นี่แสดงว่าผู้ฟังคือคำสั่ง inixserverทำงานด้วยรหัสกระบวนการ 20629 (และบังเอิญใช้ file descriptor 3 เป็นซ็อกเก็ต)

เครื่องมือที่มีประโยชน์จริงๆ ตัวที่สองในการค้นหาข้อมูลเดียวกันเรียกว่า ลซ. มันแสดงรายการไฟล์ที่เปิดอยู่ทั้งหมด (หรือตัวอธิบายไฟล์) บนระบบ หรือคุณสามารถรับข้อมูลเกี่ยวกับไฟล์ใดไฟล์หนึ่งโดยเฉพาะ:

# 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

กระบวนการ 20629 เป็นเซิร์ฟเวอร์ที่มีอายุการใช้งานยาวนาน ดังนั้นคุณจึงสามารถแนบไปกับเซิร์ฟเวอร์ได้ สเตรซ ใช้บางอย่างเช่น strace -o /tmp/trace -p 20629. หากคุณแก้ไขงาน cron ในเทอร์มินัลอื่น คุณจะได้รับเอาต์พุตการติดตามพร้อมข้อผิดพลาด และนี่คือผลลัพธ์:

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

(ล่าสุด ยอมรับ() จะไม่สมบูรณ์เมื่อติดตาม) น่าเสียดายที่ผลลัพธ์นี้ไม่มีข้อผิดพลาดที่เรากำลังมองหา เราไม่เห็นข้อความใด ๆ ที่ bcrontag ส่งหรือรับจากซ็อกเก็ต ให้ควบคุมกระบวนการให้เสร็จสมบูรณ์แทน (โคลน, รอ4, ซิกชดี ฯลฯ) กระบวนการนี้ทำให้เกิดกระบวนการย่อย ซึ่งอย่างที่คุณอาจเดาได้ว่าเป็นการทำงานจริง และถ้าคุณต้องการตามรอยเธอ เพิ่มในการโทร สเตรส -f. นี่คือสิ่งที่เราจะพบเมื่อเราค้นหาข้อความแสดงข้อผิดพลาดในผลลัพธ์ใหม่ด้วย strace -f -o /tmp/trace -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 +++

ตอนนี้มีอะไรบางอย่าง กระบวนการ 21470 ได้รับข้อผิดพลาด "การเข้าถึงถูกปฏิเสธ" เมื่อพยายามสร้างไฟล์ที่เส้นทาง ทีเอ็มพี/สปูล.21470.1573692319.854640 (เกี่ยวข้องกับไดเร็กทอรีการทำงานปัจจุบัน) หากเรารู้ไดเร็กทอรีการทำงานปัจจุบัน เราก็จะรู้พาธแบบเต็มและสามารถทราบได้ว่าเหตุใดกระบวนการจึงไม่สามารถสร้างไฟล์ชั่วคราวในไดเร็กทอรีนั้นได้ ขออภัย กระบวนการนี้ได้ยุติลงแล้ว ดังนั้นคุณจึงใช้งานไม่ได้ lsof -p 21470 เพื่อค้นหาไดเร็กทอรีปัจจุบัน แต่คุณสามารถทำงานได้ในทิศทางตรงกันข้าม - มองหาการเรียกระบบ PID 21470 ที่เปลี่ยนไดเร็กทอรี (หากไม่มีเลย PID 21470 จะต้องสืบทอดมาจากพาเรนต์และนี่ก็ผ่านไปแล้ว lsof -p ไม่พบ) การเรียกของระบบนี้คือ ชดีร์ (ซึ่งง่ายต่อการค้นหาด้วยความช่วยเหลือของเครื่องมือค้นหาออนไลน์สมัยใหม่) และนี่คือผลลัพธ์ของการค้นหาแบบย้อนกลับตามผลลัพธ์การติดตาม ไปจนถึงเซิร์ฟเวอร์ 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 +++

(ถ้าหลงทางไปอ่านกระทู้ที่แล้วได้นะครับ. เกี่ยวกับการจัดการกระบวนการ *nix และเชลล์.) ดังนั้นเซิร์ฟเวอร์ PID 20629 จึงไม่ได้รับอนุญาตให้สร้างไฟล์ที่เส้นทาง /var/สปูล/cron/tmp/สปูล.21470.1573692319.854640. สาเหตุส่วนใหญ่มาจากการตั้งค่าการอนุญาตระบบไฟล์แบบคลาสสิก มาตรวจสอบกัน:

# 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

นั่นคือที่ฝังสุนัขไว้! เซิร์ฟเวอร์ทำงานในฐานะผู้ใช้ cron แต่เฉพาะรูทเท่านั้นที่ได้รับอนุญาตให้เขียนลงในไดเร็กทอรี /var/spool/cron/tmp/. คำสั่งง่ายๆ chown cron /var/สปูล/cron/tmp/ จะบังคับ bron ทำงานได้อย่างถูกต้อง (หากนั่นไม่ใช่ปัญหา ผู้ต้องสงสัยรายถัดไปที่เป็นไปได้มากที่สุดคือโมดูลความปลอดภัยของเคอร์เนล เช่น SELinux หรือ AppArmor ดังนั้นฉันจะตรวจสอบบันทึกข้อความเคอร์เนลด้วย dmesg.)

เบ็ดเสร็จ

การติดตามการเรียกของระบบอาจล้นหลามสำหรับผู้เริ่มต้น แต่ฉันหวังว่าฉันได้แสดงให้เห็นว่าสิ่งเหล่านี้เป็นวิธีที่รวดเร็วในการแก้ปัญหาการปรับใช้ทั่วไปทั้งระดับ ลองนึกภาพการพยายามแก้ไขจุดบกพร่องแบบหลายกระบวนการ bronใช้ดีบักเกอร์ทีละขั้นตอน

การแยกวิเคราะห์ผลลัพธ์การติดตามแบบย้อนกลับตามสายการเรียกของระบบต้องใช้ทักษะ แต่อย่างที่บอก เกือบทุกครั้งคือการใช้ สเตรซฉันเพิ่งได้รับผลการติดตามและค้นหาข้อผิดพลาดตั้งแต่ต้นจนจบ ถึงอย่างไร, สเตรซ ช่วยให้ฉันประหยัดเวลาได้มากในการดีบัก ฉันหวังว่ามันจะเป็นประโยชน์กับคุณเช่นกัน

ที่มา: will.com

เพิ่มความคิดเห็น