กำหนดบรรทัดของรหัสที่ทำให้เกิดความผิดพลาดในการแบ่งเซ็กเมนต์


151

หนึ่งจะกำหนดว่าข้อผิดพลาดอยู่ในรหัสที่ทำให้เกิดความผิดพลาดแบ่งส่วนได้อย่างไร

คอมไพเลอร์ของฉันสามารถgccแสดงตำแหน่งของความผิดพลาดในโปรแกรมได้หรือไม่?


5
ไม่มี gcc / gdb ไม่สามารถ คุณสามารถหาข้อมูลที่ segfault เกิดขึ้น แต่ข้อผิดพลาดที่เกิดขึ้นจริงอาจจะเป็นที่สถานที่ที่แตกต่างกันโดยสิ้นเชิง

คำตอบ:


218

GCC ไม่สามารถทำเช่นนั้นได้ แต่ GDB (ตัวดีบัก ) สามารถทำได้ รวบรวมโปรแกรมของคุณโดยใช้-gสวิตช์เช่นนี้

gcc program.c -g

จากนั้นใช้ gdb:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

นี่คือบทแนะนำที่ดีเพื่อให้คุณเริ่มต้นกับ GDB

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


28
โปรดสังเกตว่าโดยทั่วไปแล้ว segfault ที่เกิดขึ้นมักจะเป็นเพียงร่องรอยว่า "ความผิดพลาดที่ทำให้เกิด" มันอยู่ในรหัส เบาะแสสำคัญ แต่ไม่จำเป็นว่าปัญหาจะอยู่ที่ใด
mpez0

9
คุณยังสามารถใช้ (bt เต็ม) เพื่อรับรายละเอียดเพิ่มเติมได้
ant2009

1
ฉันพบว่ามีประโยชน์: gnu.org/software/gcc/bugs/segfault.html
Loves Probability

2
ใช้เป็นชวเลขเป็นbt backtrace
rustyx

43

นอกจากนี้คุณยังสามารถvalgrindลองใช้ถ้าคุณติดตั้งvalgrindและเรียกใช้

valgrind --leak-check=full <program>

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


2
+1, Valgrind เร็วกว่ามาก / ง่ายต่อการใช้เพื่อตรวจสอบข้อผิดพลาดของหน่วยความจำ สำหรับบิลด์ที่ไม่ได้รับการออพติไมซ์ด้วยสัญลักษณ์การดีบั๊กมันจะบอกคุณว่ามี segfault เกิดขึ้นที่ไหน
Tim Post

1
น่าเศร้า segfault ของฉันหายไปเมื่อรวบรวมกับ -g -O0 และรวมกับ valgrind
JohnMudd

2
--leak-check=fullจะไม่ช่วยในการดีบัก segfaults มันมีประโยชน์สำหรับการดีบักการรั่วไหลของหน่วยความจำเท่านั้น
ks1322

@JohnMudd ฉันมี segfault ปรากฏขึ้นเพียงประมาณ 1% ของไฟล์อินพุตที่ทดสอบถ้าคุณทำซ้ำอินพุตที่ล้มเหลวมันจะไม่ล้มเหลว ปัญหาของฉันเกิดจากการมัลติเธรด จนถึงตอนนี้ฉันยังไม่พบบรรทัดของโค้ดที่ทำให้เกิดปัญหานี้ ฉันกำลังลองใช้ซ้ำเพื่อปกปิดปัญหานี้ในตอนนี้ หากใช้ตัวเลือก -g ความผิดพลาดจะหายไป!
Kemin Zhou

18

คุณสามารถใช้ core dump จากนั้นตรวจสอบด้วย gdb ในการรับข้อมูลที่เป็นประโยชน์คุณต้องรวบรวมด้วยการ-gตั้งค่าสถานะ

เมื่อใดก็ตามที่คุณได้รับข้อความ:

 Segmentation fault (core dumped)

ไฟล์แกนถูกเขียนลงในไดเรกทอรีปัจจุบันของคุณ และคุณสามารถตรวจสอบได้ด้วยคำสั่ง

 gdb your_program core_file

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

ตรวจสอบให้แน่ใจว่าระบบของคุณไม่ได้ตั้งค่าขนาดไฟล์ดัมพ์หลักเป็นศูนย์ คุณสามารถตั้งค่าให้ไม่ จำกัด ด้วย:

ulimit -c unlimited

ระวังหน่อยนะ! การทิ้งแกนกลางนั้นอาจกลายเป็นเรื่องใหญ่


ฉันเปลี่ยนเป็น arch-linux เมื่อเร็ว ๆ นี้ ไดเรกทอรีปัจจุบันของฉันไม่มีไฟล์ core dump ฉันจะสร้างมันได้อย่างไร
Abhinav

คุณไม่ได้สร้างมันขึ้นมา Linux ทำ การทิ้งขยะหลักจะถูกจัดเก็บในสถานที่ที่แตกต่างกันใน Linos - Google สำหรับ Arch Linux อ่านwiki.archlinux.org/index.php/Core_dump
Mawg พูดว่าคืนสถานะโมนิก้า

7

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

คอมไพเลอร์สมัยใหม่มาพร้อมกับการ-fsanitize=addressตั้งค่าสถานะที่สะดวกเพิ่มเวลาในการคอมไพล์และโอเวอร์เฮดเวลาทำงาน

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

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

เอาต์พุตมีความซับซ้อนมากกว่า gdb เล็กน้อยที่จะส่งออก แต่มีอัพไซด์:

  • ไม่จำเป็นต้องทำให้เกิดปัญหาอีกครั้งเพื่อรับการติดตามสแต็ก เพียงเปิดใช้งานการตั้งค่าสถานะในระหว่างการพัฒนาก็เพียงพอแล้ว

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


¹นั่นคือเสียงดังกราว 3.1 ขึ้นไปและGCC 4.8+


สิ่งนี้มีประโยชน์มากที่สุดสำหรับฉัน ฉันมีข้อบกพร่องเล็กน้อยที่เกิดขึ้นแบบสุ่มโดยมีความถี่ประมาณ 1% ฉันประมวลผลไฟล์อินพุตจำนวนมากด้วย (16 ขั้นตอนหลักแต่ละไฟล์ทำโดยไบนารี C หรือ C ++ ที่แตกต่างกัน) ขั้นตอนต่อมาจะทริกเกอร์การแบ่งกลุ่มข้อผิดพลาดแบบสุ่มเท่านั้นเนื่องจากการทำเธรดหลาย เป็นการยากที่จะดีบัก ตัวเลือกนี้ก่อให้เกิดการส่งออกข้อมูลการแก้ปัญหาอย่างน้อยก็ให้ฉันเป็นจุดเริ่มต้นสำหรับการตรวจสอบรหัสเพื่อค้นหาตำแหน่งของข้อผิดพลาด
Kemin Zhou

2

คำตอบของลูคัสเกี่ยวกับการทิ้งขยะหลักเป็นสิ่งที่ดี ใน. cshrc ของฉันฉันมี:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

เพื่อแสดง backtrace โดยการป้อน 'core' และประทับวันที่เพื่อให้แน่ใจว่าฉันกำลังดูไฟล์ที่ถูกต้อง :(

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


4
และอย่าลืมulimit -c unlimitedเปิดใช้งานการถ่ายโอนข้อมูลหลักตั้งแต่แรก
James Morris

@James: ถูกต้อง ลูคัสพูดถึงเรื่องนี้แล้ว และสำหรับพวกเราที่ยังคงติดอยู่ใน csh ให้ใช้ 'จำกัด ' และฉันไม่เคยอ่าน CYGWIN stackdumps (แต่ฉันไม่ได้ลองเป็นเวลา 2 หรือ 3 ปี)
โจเซฟ Quinsey

2

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

หากสิ่งอื่นล้มเหลวคุณสามารถคอมไพล์โปรแกรมของคุณใหม่ด้วยคำสั่ง debug-print ชั่วคราว (เช่นfprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);) โรยตลอดสิ่งที่คุณเชื่อว่าเป็นส่วนที่เกี่ยวข้องของรหัสของคุณ จากนั้นเรียกใช้โปรแกรมและสังเกตสิ่งที่พิมพ์ครั้งสุดท้ายก่อนที่จะเกิดข้อผิดพลาดเกิดขึ้น - คุณรู้ว่าโปรแกรมของคุณได้มาถึงจุดนั้นดังนั้นความผิดพลาดจะต้องเกิดขึ้นหลังจากจุดนั้น เพิ่มหรือลบ debug-prints, คอมไพล์ใหม่และรันการทดสอบอีกครั้งจนกว่าคุณจะทำให้มันแคบลงเป็นรหัสบรรทัดเดียว ณ จุดนี้คุณสามารถแก้ไขข้อบกพร่องและลบชั่วคราว debug-prints ทั้งหมดได้

มันค่อนข้างน่าเบื่อ แต่ก็มีข้อได้เปรียบในการทำงานทุกที่ - เพียงครั้งเดียวก็อาจไม่ได้หากคุณไม่สามารถเข้าถึง stdout หรือ stderr ด้วยเหตุผลบางอย่างหรือหากข้อผิดพลาดที่คุณพยายามแก้ไขนั้นเป็นเผ่าพันธุ์ -condition ซึ่งพฤติกรรมเปลี่ยนแปลงเมื่อเวลาของโปรแกรมเปลี่ยนแปลง (เนื่องจาก debug-prints จะทำให้โปรแกรมช้าลงและเปลี่ยนเวลาของมัน)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.