1 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • เมื่อคอมไพล์และรันโปรแกรม 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 && ./hello
    
    ls 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
    
    • เพราะมี ; true Bash จึงยังมีงานต้องทำหลัง ./hello จบ และไม่แทนที่ตัวเองด้วยโปรแกรมนั้น
    • สามารถตรวจสอบข้อความ segmentation fault ได้โดยไม่ต้องมีสคริปต์ wrapper แยกต่างหาก

1 ความคิดเห็น

 
GN⁺ 4 시간 전
ความคิดเห็นจาก Lobste.rs
  • ดิสทริบิวชัน Linux รุ่นใหม่ ๆ มัก ปิดใช้งาน core dump เป็นค่าเริ่มต้น ทำให้ลำบากอยู่บ่อยครั้ง
    สามารถเปิดใช้งานใหม่ได้ด้วย ulimit -c unlimited แต่ดูเหมือนไฟล์ core จะไม่ได้ถูกสร้างในไดเรกทอรีเดียวกับไฟล์ปฏิบัติการ และ systemd น่าจะย้ายมันไปไว้ที่ไหนสักแห่ง
    ถ้าเปิด core dump เป็นค่าเริ่มต้นและวางไว้ในไดเรกทอรีเดียวกับไฟล์ปฏิบัติการเหมือน OpenBSD ก็คงดี แต่ตอนนี้เมื่อเกิดการแครช ส่วนใหญ่ต้องรันไฟล์ปฏิบัติการใหม่ใน gdb

    • ใน systemd สามารถใช้ coredumpctl เพื่อแสดงรายการและดึง core dump ล่าสุดได้
    • การวาง core dump ไว้ในตำแหน่งเดียวกับไฟล์ปฏิบัติการดูเป็นทางเลือกที่ค่อนข้างแปลก
      นอกจากจะไม่คิดไปหา core dump ในที่อย่าง /usr/bin แล้ว ตำแหน่งแบบนั้นก็มักอยู่บนระบบไฟล์ที่มีพื้นที่น้อยหรือเป็นแบบอ่านอย่างเดียว
      ถ้าเป็นตำแหน่งเริ่มต้น ไดเรกทอรีทำงาน น่าจะดีกว่ามากไม่ใช่หรือ
    • เหตุผลที่ไฟล์ core ถูกปิดใช้งานเป็นค่าเริ่มต้นน่าจะเป็นเพราะผู้เชี่ยวชาญด้านความปลอดภัยมองว่าผู้ใช้ใช้งาน Linux อย่างปลอดภัยไม่เป็น และไฟล์ core ที่ถูกทิ้งไว้โดยไม่ดูแลอาจถูกนำไปใช้ในทางที่ผิดได้
      ถึงขั้นรู้สึกเหมือนว่า แม้ผู้ใช้จะเปิดใช้ไฟล์ 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 core

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

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