GDB เสียหาย stack frame - จะแก้จุดบกพร่องได้อย่างไร?


113

ฉันมีการติดตามสแต็กต่อไปนี้ เป็นไปได้ไหมที่จะทำสิ่งที่เป็นประโยชน์จากสิ่งนี้สำหรับการดีบัก?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

จะเริ่มดูโค้ดได้ที่ไหนเมื่อเราได้รับ a Segmentation faultและ stack trace ไม่มีประโยชน์?

หมายเหตุ: หากฉันโพสต์รหัสผู้เชี่ยวชาญ SO จะให้คำตอบแก่ฉัน ฉันต้องการรับคำแนะนำจาก SO และค้นหาคำตอบด้วยตัวเองดังนั้นฉันจึงไม่โพสต์รหัสที่นี่ ขอโทษ.


โปรแกรมของคุณอาจกระโดดลงไปในวัชพืช - คุณสามารถกู้คืนอะไรจากตัวชี้สแต็กได้หรือไม่?
Carl Norum

1
สิ่งที่ควรพิจารณาอีกประการหนึ่งคือหากตั้งค่าตัวชี้เฟรมอย่างถูกต้อง คุณกำลังสร้างโดยไม่มีการปรับให้เหมาะสมหรือผ่านการตั้งค่าสถานะเช่น-fno-omit-frame-pointer? นอกจากนี้สำหรับความเสียหายของหน่วยความจำvalgrindอาจเป็นเครื่องมือที่เหมาะสมกว่าหากเป็นตัวเลือกสำหรับคุณ
FatalError

คำตอบ:


155

ที่อยู่ปลอมเหล่านั้น (0x00000002 และอื่น ๆ ) เป็นค่าพีซีไม่ใช่ค่า SP ตอนนี้เมื่อคุณได้รับ SEGV ประเภทนี้พร้อมที่อยู่พีซีปลอม (เล็กมาก) 99% ของเวลาที่เกิดจากการโทรผ่านตัวชี้ฟังก์ชันปลอม โปรดทราบว่าการโทรเสมือนใน C ++ ถูกนำไปใช้ผ่านตัวชี้ฟังก์ชันดังนั้นปัญหาใด ๆ กับการโทรเสมือนสามารถแสดงได้ในลักษณะเดียวกัน

คำสั่งการโทรทางอ้อมเพียงแค่กดพีซีหลังจากการโทรเข้าสู่สแต็กจากนั้นตั้งค่าพีซีเป็นค่าเป้าหมาย (ปลอมในกรณีนี้) ดังนั้นหากนี่คือสิ่งที่เกิดขึ้นคุณสามารถยกเลิกได้อย่างง่ายดายโดยการดึงพีซีออกจากสแต็กด้วยตนเอง . ในรหัส x86 32 บิตคุณเพียงแค่ทำ:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

ด้วยรหัส x86 64 บิตที่คุณต้องการ

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

จากนั้นคุณควรจะสามารถทำbtและหารหัสได้

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


5
@George: gdb executable corefileจะเปิด gdb พร้อมไฟล์ปฏิบัติการและไฟล์หลัก ณ จุดที่คุณสามารถทำได้bt(หรือคำสั่งด้านบนตามด้วยbt) ...
Chris Dodd

2
@mk .. ARM ไม่ได้ใช้สแต็กสำหรับที่อยู่ที่ส่งคืน แต่จะใช้การลงทะเบียนลิงก์แทน ดังนั้นโดยทั่วไปจะไม่มีปัญหานี้หรือถ้าเป็นเช่นนั้นก็มักเกิดจากความเสียหายของสแต็กอื่น ๆ
Chris Dodd

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

1
หมายถึงการหลอกลวง?
Danny Lo

5
ARM ไม่ใช่ x86 - ตัวชี้สแต็กถูกเรียกspไม่ใช่espหรือrspและคำสั่งการโทรจะเก็บที่อยู่ผู้ส่งคืนในlrรีจิสเตอร์ไม่ใช่บนสแต็ก ดังนั้นสำหรับ ARM set $pc = $lrทั้งหมดที่คุณต้องการจริงๆที่จะยกเลิกการโทร หาก$lrไม่ถูกต้องคุณมีปัญหาที่ยากกว่ามากในการผ่อนคลาย
Chris Dodd

44

หากสถานการณ์ค่อนข้างง่ายคำตอบของ Chris Dodd คือคำตอบที่ดีที่สุด ดูเหมือนว่ามันจะกระโดดผ่านตัวชี้ NULL

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

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

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

  • บางทีการตั้งค่าจุดเฝ้าดูการดีบักเกอร์หรือการแทรกการวินิจฉัยprintfในตัวแปรวิกฤตอาจนำไปสู่A ha ที่จำเป็น!
  • บางทีการเปลี่ยนเงื่อนไขการทดสอบด้วยอินพุตที่แตกต่างกันจะให้ข้อมูลเชิงลึกมากกว่าการดีบัก
  • บางทีดวงตาคู่ที่สองอาจบังคับให้คุณตรวจสอบสมมติฐานของคุณหรือรวบรวมหลักฐานที่มองข้ามไป
  • บางครั้งสิ่งที่ต้องทำก็คือไปทานอาหารเย็นและคิดถึงหลักฐานที่รวบรวมได้

โชคดี!


13
หากไม่มีดวงตาคู่ที่สองเป็ดยางจะได้รับการพิสูจน์แล้วว่าเป็นทางเลือกอื่น
Matt

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

อาจเป็นประโยชน์: GDB: Automatic 'Next'ing
user202729

28

สมมติว่าตัวชี้สแต็กถูกต้อง ...

อาจเป็นไปไม่ได้ที่จะทราบแน่ชัดว่า SEGV เกิดขึ้นจาก backtrace - ฉันคิดว่าสองสแต็กเฟรมแรกถูกเขียนทับทั้งหมด 0xbffff284 ดูเหมือนจะเป็นที่อยู่ที่ถูกต้อง แต่อีกสองรายการถัดไปไม่ใช่ หากต้องการดูสแต็กอย่างละเอียดยิ่งขึ้นคุณสามารถลองทำสิ่งต่อไปนี้:

gdb $ x / 32ga $ rsp

หรือตัวแปร (แทนที่ 32 ด้วยตัวเลขอื่น) ซึ่งจะพิมพ์คำจำนวนหนึ่ง (32) โดยเริ่มจากตัวชี้สแต็กของขนาดยักษ์ (g) จัดรูปแบบเป็นที่อยู่ (a) พิมพ์ 'help x' เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับรูปแบบ

การใช้รหัสของคุณเป็นเครื่องมือ 'printf' ของแมวมองอาจไม่ใช่ความคิดที่ไม่ดีในกรณีนี้


มีประโยชน์อย่างไม่น่าเชื่อขอบคุณ - ฉันมีสแต็กที่ย้อนกลับไปเพียงสามเฟรมแล้วกด "Backtrace หยุด: เฟรมก่อนหน้าเหมือนกับเฟรมนี้ (สแต็กเสียหาย?)"; ฉันเคยทำอะไรแบบนี้ในโค้ดในตัวจัดการข้อยกเว้นของ CPU มาก่อน แต่จำinfo symbolวิธีอื่นไม่ได้นอกจากวิธีทำใน gdb
เอนกาย

23
FWIW บนอุปกรณ์ ARM 32 บิต: x/256wa $sp =)
ยืมตัว

2
@leander บอกได้ไหมว่า X / 256wa คืออะไร? ฉันต้องการมันสำหรับ ARM 64 บิต โดยทั่วไปจะเป็นประโยชน์หากคุณสามารถอธิบายได้ว่ามันคืออะไร
..

5
ตามคำตอบ 'x' = ตรวจสอบตำแหน่งหน่วยความจำ; มันพิมพ์ 'w' = คำจำนวนหนึ่งออกมา (ในกรณีนี้คือ 256) และตีความเป็น 'a' = address มีข้อมูลเพิ่มเติมในคู่มือการ GDB ที่เป็นsourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory
เอนกาย

7

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


3

หากเป็นการเขียนทับสแต็กค่าอาจสอดคล้องกับสิ่งที่จำได้จากโปรแกรม

ตัวอย่างเช่นฉันเพิ่งพบว่าตัวเองกำลังมองไปที่กอง

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

และ0x342dคือ 13357 ซึ่งกลายเป็น node-id เมื่อฉัน grepped บันทึกแอปพลิเคชันสำหรับมัน สิ่งนี้ช่วย จำกัด ไซต์ผู้สมัครให้แคบลงในทันทีที่อาจเกิดการเขียนทับสแต็ก

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