Segmentation fault ของฉันหายไปไหน?
(rmpr.xyz)- เมื่อคอมไพล์และรันโปรแกรม C ซ้ำ ๆ ด้วย
entrโปรแกรมhelloจบการทำงานผิดปกติ แต่หลังเอาต์พุตการคอมไพล์ ไม่มีข้อความSegmentation faultปรากฏขึ้น - ข้อความนี้ไม่ได้ถูกพิมพ์โดยโปรแกรมที่แครช แต่ถูกพิมพ์โดย เชลล์แม่ ที่เก็บกวาดโปรเซสลูกและตรวจพบว่าจบด้วย
SIGSEGV - แม้จะระบุ
bash -cชัดเจน หากคำสั่งที่เหลืออยู่มีเพียงคำสั่งเดียวในทางปฏิบัติ Bash ก็อาจไม่สร้างโปรเซสใหม่ แต่ แทนที่ตัวเองด้วยexecveทำให้เชลล์แม่ที่จะพิมพ์ข้อความหายไป - ในสคริปต์แยก Bash ที่เริ่มจาก shebang จะรัน
./helloเป็นโปรเซสลูกและรอ จึงตรวจพบSIGSEGVแล้วพิมพ์Segmentation fault (core dumped) - หากรัน
./helloใน subshell(./hello)หรือเพิ่ม; trueต่อท้ายเพื่อให้มีงานถัดไปเหลืออยู่ จะป้องกันการ optimize ด้วยexecและเห็นข้อความผิดพลาดได้
เส้นทางการรันที่ทำให้ข้อความหายไป
-
ใช้คำสั่ง
entrต่อไปนี้เพื่อคอมไพล์และรันซ้ำทุกครั้งที่hello.cเปลี่ยนls hello.c | entr -s "gcc -o hello hello.c && ./hello" -
helloจบการทำงานด้วย segmentation fault แต่ ไม่เหลือเอาต์พุตใด ๆ ไม่ว่าจะเป็นข้อความSegmentation faultหรือสถานะจบการทำงานที่ไม่ใช่ 0 ที่มองเห็นได้ -
แม้ครอบคำสั่งด้วย
bash -cผลลัพธ์ก็เหมือนเดิมls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello'" -
ในทางกลับกัน หากย้ายคำสั่งเดียวกันไปไว้ใน
run.shแล้วรัน ก็จะแสดงข้อผิดพลาด#!/bin/bash gcc -o hello hello.c && ./hellols hello.c | entr -s ./run.sh ./run.sh: line 2: 104465 Segmentation fault (core dumped) ./hello -
ตัวที่พิมพ์
Segmentation fault- โปรแกรมที่แครชได้จบไปแล้ว และ เชลล์แม่เป็นผู้เก็บกวาดโปรเซสลูก ตรวจพบว่าลูกตายด้วย
SIGSEGVแล้วจึงพิมพ์ข้อความ - ดังนั้น หากไม่มีเชลล์แม่เหลืออยู่เพื่อพิมพ์ข้อความ ก็จะไม่ปรากฏอะไรเลย
- ใน
bash -c "some_command"หากสิ่งที่ต้องรันมีเพียงคำสั่งนั้นในทางปฏิบัติ Bash อาจไม่ fork โปรเซสใหม่ แต่ แทนที่ตัวเองด้วยexecve - การ optimize นี้โดยทั่วไปจะไม่แสดงออกมาให้เห็นในการรันส่วนใหญ่
- ในรูปแบบสคริปต์
entrจะรัน./run.shเป็นโปรเซสลูก และ shebang จะเริ่ม Bash ตัวใหม่ - Bash ตัวใหม่จะรัน
gccแล้ว fork./helloและรอ ดังนั้นจึงตรวจพบการจบด้วยSIGSEGVของโปรเซสลูกและทิ้งข้อความข้อผิดพลาดไว้ได้
- โปรแกรมที่แครชได้จบไปแล้ว และ เชลล์แม่เป็นผู้เก็บกวาดโปรเซสลูก ตรวจพบว่าลูกตายด้วย
สองวิธีในการคงเชลล์แม่ไว้
- หากรันคำสั่งที่แครชใน subshell ข้อความจะกลับมาปรากฏอีกครั้ง
ls hello.c | entr -s "gcc -o hello hello.c && (./hello)" hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )- วงเล็บบังคับให้
./helloถูกรันใน subshell ที่ถูก fork ออกมา - Bash ยังคงบทบาทเป็นโปรเซสแม่ จึงเก็บกวาดการจบด้วย
SIGSEGVของ subshell และพิมพ์ข้อความได้
- วงเล็บบังคับให้
- หากทิ้งงานที่จะรันต่อไว้หลังคำสั่งที่แครช ก็สามารถป้องกัน การ optimize ด้วย
execได้ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello; true'" bash: line 1: 109516 Segmentation fault (core dumped) ./hello- เพราะมี
; trueBash จึงยังมีงานต้องทำหลัง./helloจบ และไม่แทนที่ตัวเองด้วยโปรแกรมนั้น - สามารถตรวจสอบข้อความ segmentation fault ได้โดยไม่ต้องมีสคริปต์ wrapper แยกต่างหาก
- เพราะมี
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
ดิสทริบิวชัน Linux รุ่นใหม่ ๆ มัก ปิดใช้งาน core dump เป็นค่าเริ่มต้น ทำให้ลำบากอยู่บ่อยครั้ง
สามารถเปิดใช้งานใหม่ได้ด้วย
ulimit -c unlimitedแต่ดูเหมือนไฟล์ core จะไม่ได้ถูกสร้างในไดเรกทอรีเดียวกับไฟล์ปฏิบัติการ และ systemd น่าจะย้ายมันไปไว้ที่ไหนสักแห่งถ้าเปิด core dump เป็นค่าเริ่มต้นและวางไว้ในไดเรกทอรีเดียวกับไฟล์ปฏิบัติการเหมือน OpenBSD ก็คงดี แต่ตอนนี้เมื่อเกิดการแครช ส่วนใหญ่ต้องรันไฟล์ปฏิบัติการใหม่ใน gdb
coredumpctlเพื่อแสดงรายการและดึง core dump ล่าสุดได้นอกจากจะไม่คิดไปหา core dump ในที่อย่าง
/usr/binแล้ว ตำแหน่งแบบนั้นก็มักอยู่บนระบบไฟล์ที่มีพื้นที่น้อยหรือเป็นแบบอ่านอย่างเดียวถ้าเป็นตำแหน่งเริ่มต้น ไดเรกทอรีทำงาน น่าจะดีกว่ามากไม่ใช่หรือ
ถึงขั้นรู้สึกเหมือนว่า แม้ผู้ใช้จะเปิดใช้ไฟล์ core แล้ว ก็ยังถูกถือว่าไม่รู้ว่ากำลังทำอะไรอยู่ จึงถูกซ่อนไว้ใน ที่ที่แม้แต่ root ก็ยังหาเจอยาก
หากเขียนโปรแกรมด้วย C หรือ D เป็นต้น แล้วอยากรู้สาเหตุการสิ้นสุดโปรแกรม ก็ตรวจสอบ ค่าที่คืนจาก system call
waitได้ และจะรู้ได้ว่าโปรแกรมสิ้นสุดเพราะสัญญาณหรือไม่หากระบบปฏิบัติการส่ง core dump ไปยังที่อื่น ก็มีข้อดีคือสามารถดีบักได้ทันทีเมื่อต้องการ หรือส่งรายงานข้อผิดพลาดไปยังดิสทริบิวชันได้
เมื่อเคอร์เนล Linux สร้าง core dump จะอ่านไฟล์พิเศษชื่อ
/proc/sys/kernel/core_patternเพื่อกำหนดตำแหน่งจัดเก็บแทนที่จะเขียนเป็นไฟล์ ก็สามารถส่งผ่าน pipe ไปยังโปรแกรมได้ด้วย จึงทำสิ่งอย่างการแสดงกล่องโต้ตอบ GUI, เรียกดีบักเกอร์ทันที, หรือส่งอีเมลรายงานไปยังนักพัฒนาได้
systemd ตั้งค่า
core_patternของเคอร์เนลให้เป็นค่าที่ตนเองจัดการ แต่แม้ไม่ได้ใช้ systemd ก็ยังใช้ความสามารถเดียวกันของเคอร์เนลได้มีเรื่องเล่าว่าในช่วงพัฒนา Windows XP การส่ง core dump ไปยัง Microsoft ช่วยแก้บั๊กหลายตัวที่อาจไม่เคยรู้ว่ามีอยู่ด้วยซ้ำ และเมื่อติดตั้งเครื่องมือพัฒนาบน Windows ดีบักเกอร์แบบ on-demand ก็มีประโยชน์มากทีเดียวเวลาพัฒนาโปรแกรมของตัวเอง
ความสามารถนี้ถูกนำเข้ามาใน Linux เมื่อปี 2003 และดูรายละเอียดได้ที่
man 5 coreCore dump ยอดเยี่ยมสำหรับการดีบัก แต่ถ้าวางไว้ข้างไฟล์ที่ดูไม่เกี่ยวข้องกัน ก็ทำให้ผู้ใช้ทั่วไปสับสน และอาจแย่มากเมื่อใช้เป็นระบบรายงานเพื่อวินิจฉัยปัญหา
ครั้งแรกที่เห็นว่ารายงานแครชของ Windows ที่ส่งไปมีข้อมูลมากแค่ไหนก็ถึงกับตกใจ
หากตั้งสมมติฐานว่ามีระบบรายงานแครชที่คุ้มครองข้อมูลส่วนบุคคล ก็ควรเรียนรู้วิธีอนุมานสาเหตุจากเพียง ข้อมูลสแต็กและรีจิสเตอร์ เพื่อให้วินิจฉัยแครชที่ส่งมาได้
ไม่เคยรู้เลยว่า Bash บางครั้ง
execคำสั่งในสคริปต์โดยตรง และเป็นความสามารถที่น่าทึ่งมากเนื่องจากตัวโปรเซส Bash เองก็ไม่ได้มี overhead มากนัก และในจุดที่จำเป็นก็มักใช้
execเป็นนิสัยอยู่แล้ว จึงรู้สึกเหมือนเป็นฟีเจอร์ที่ออกแบบมาไม่ค่อยดีนักอย่างไรก็ดี เมื่อรวมจำนวนการรันทั้งหมดแล้ว ก็คิดว่าอาจสร้างความแตกต่างได้มากกว่าที่คาดไว้