หนึ่งจะกำหนดว่าข้อผิดพลาดอยู่ในรหัสที่ทำให้เกิดความผิดพลาดแบ่งส่วนได้อย่างไร
คอมไพเลอร์ของฉันสามารถgcc
แสดงตำแหน่งของความผิดพลาดในโปรแกรมได้หรือไม่?
หนึ่งจะกำหนดว่าข้อผิดพลาดอยู่ในรหัสที่ทำให้เกิดความผิดพลาดแบ่งส่วนได้อย่างไร
คอมไพเลอร์ของฉันสามารถgcc
แสดงตำแหน่งของความผิดพลาดในโปรแกรมได้หรือไม่?
คำตอบ:
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 เกิดขึ้นโดยทั่วไปเป็นเพียงร่องรอยว่า "ความผิดพลาดซึ่งทำให้" มันอยู่ในรหัส ตำแหน่งที่ระบุไม่จำเป็นว่าปัญหาจะอยู่ที่ใด
bt
backtrace
นอกจากนี้คุณยังสามารถvalgrind
ลองใช้ถ้าคุณติดตั้งvalgrind
และเรียกใช้
valgrind --leak-check=full <program>
จากนั้นโปรแกรมจะเรียกใช้โปรแกรมของคุณและแสดงร่องรอยสแต็กสำหรับ segfaults ใด ๆ รวมถึงหน่วยความจำที่ไม่ถูกต้องอ่านหรือเขียนและหน่วยความจำรั่ว มันมีประโยชน์มากจริงๆ
--leak-check=full
จะไม่ช่วยในการดีบัก segfaults มันมีประโยชน์สำหรับการดีบักการรั่วไหลของหน่วยความจำเท่านั้น
คุณสามารถใช้ core dump จากนั้นตรวจสอบด้วย gdb ในการรับข้อมูลที่เป็นประโยชน์คุณต้องรวบรวมด้วยการ-g
ตั้งค่าสถานะ
เมื่อใดก็ตามที่คุณได้รับข้อความ:
Segmentation fault (core dumped)
ไฟล์แกนถูกเขียนลงในไดเรกทอรีปัจจุบันของคุณ และคุณสามารถตรวจสอบได้ด้วยคำสั่ง
gdb your_program core_file
ไฟล์มีสถานะของหน่วยความจำเมื่อโปรแกรมขัดข้อง ดัมพ์หลักอาจมีประโยชน์ระหว่างการปรับใช้ซอฟต์แวร์ของคุณ
ตรวจสอบให้แน่ใจว่าระบบของคุณไม่ได้ตั้งค่าขนาดไฟล์ดัมพ์หลักเป็นศูนย์ คุณสามารถตั้งค่าให้ไม่ จำกัด ด้วย:
ulimit -c unlimited
ระวังหน่อยนะ! การทิ้งแกนกลางนั้นอาจกลายเป็นเรื่องใหญ่
: มีจำนวนของเครื่องมือที่มีอยู่ซึ่งช่วยให้การแก้จุดบกพร่องผิดพลาดของการแบ่งส่วนและฉันต้องการที่จะเพิ่มเครื่องมือที่ชื่นชอบของฉันไปยังรายการที่มีอยู่ 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+
คำตอบของลูคัสเกี่ยวกับการทิ้งขยะหลักเป็นสิ่งที่ดี ใน. cshrc ของฉันฉันมี:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
เพื่อแสดง backtrace โดยการป้อน 'core' และประทับวันที่เพื่อให้แน่ใจว่าฉันกำลังดูไฟล์ที่ถูกต้อง :(
เพิ่ม : หากมีข้อผิดพลาดความเสียหายสแต็กแล้ว backtrace นำไปใช้กับการถ่ายโอนข้อมูลหลักมักจะขยะ ในกรณีนี้การรันโปรแกรมภายในgdbสามารถให้ผลลัพธ์ที่ดีกว่าตามคำตอบที่ยอมรับได้ (สมมติว่าความผิดนั้นทำซ้ำได้ง่าย) และระวังกระบวนการหลาย ๆ อย่างที่ทิ้งคอร์ไปด้วย ระบบปฏิบัติการบางระบบเพิ่ม PID ให้กับชื่อของไฟล์คอร์
ulimit -c unlimited
เปิดใช้งานการถ่ายโอนข้อมูลหลักตั้งแต่แรก
คำตอบทั้งหมดข้างต้นถูกต้องและแนะนำ; คำตอบนี้มีจุดประสงค์เพื่อเป็นทางเลือกสุดท้ายหากไม่สามารถใช้วิธีการใด ๆ ข้างต้นได้
หากสิ่งอื่นล้มเหลวคุณสามารถคอมไพล์โปรแกรมของคุณใหม่ด้วยคำสั่ง debug-print ชั่วคราว (เช่นfprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
) โรยตลอดสิ่งที่คุณเชื่อว่าเป็นส่วนที่เกี่ยวข้องของรหัสของคุณ จากนั้นเรียกใช้โปรแกรมและสังเกตสิ่งที่พิมพ์ครั้งสุดท้ายก่อนที่จะเกิดข้อผิดพลาดเกิดขึ้น - คุณรู้ว่าโปรแกรมของคุณได้มาถึงจุดนั้นดังนั้นความผิดพลาดจะต้องเกิดขึ้นหลังจากจุดนั้น เพิ่มหรือลบ debug-prints, คอมไพล์ใหม่และรันการทดสอบอีกครั้งจนกว่าคุณจะทำให้มันแคบลงเป็นรหัสบรรทัดเดียว ณ จุดนี้คุณสามารถแก้ไขข้อบกพร่องและลบชั่วคราว debug-prints ทั้งหมดได้
มันค่อนข้างน่าเบื่อ แต่ก็มีข้อได้เปรียบในการทำงานทุกที่ - เพียงครั้งเดียวก็อาจไม่ได้หากคุณไม่สามารถเข้าถึง stdout หรือ stderr ด้วยเหตุผลบางอย่างหรือหากข้อผิดพลาดที่คุณพยายามแก้ไขนั้นเป็นเผ่าพันธุ์ -condition ซึ่งพฤติกรรมเปลี่ยนแปลงเมื่อเวลาของโปรแกรมเปลี่ยนแปลง (เนื่องจาก debug-prints จะทำให้โปรแกรมช้าลงและเปลี่ยนเวลาของมัน)