วิธีติดตามข้อผิดพลาด "ฟรีหรือเสียหายสองเท่า"


94

เมื่อฉันเรียกใช้โปรแกรม (C ++) ของฉันมันขัดข้องด้วยข้อผิดพลาดนี้

* ตรวจพบ glibc * ./load: ฟรีสองเท่าหรือเสียหาย (! prev): 0x0000000000c6ed50 ***

ฉันจะติดตามข้อผิดพลาดได้อย่างไร

ฉันลองใช้std::coutคำสั่งprint ( ) แต่ไม่ประสบความสำเร็จ สามารถgdbทำให้ง่ายขึ้น?


5
ฉันสงสัยว่าทำไมทุกคนถึงNULLแนะนำตัวชี้ (ซึ่งมาสก์ข้อผิดพลาดซึ่งถูกจับได้เนื่องจากคำถามนี้แสดงให้เห็นอย่างดี) แต่ไม่มีใครแนะนำว่าอย่าทำการจัดการหน่วยความจำด้วยตนเองเลยซึ่งเป็นไปได้ดีมากใน C ++ ฉันไม่ได้เขียนdeleteมาหลายปีแล้ว (และใช่รหัสของฉันมีความสำคัญต่อประสิทธิภาพมิฉะนั้นจะไม่ถูกเขียนด้วย C ++)
SBI

2
@sbi: การคอรัปชั่นมากมายและสิ่งที่คล้ายกันแทบจะไม่ถูกจับได้อย่างน้อยก็ไม่เกิดขึ้น NULLตัวชี้อาจทำให้โปรแกรมของคุณขัดข้องก่อนหน้านี้
Hasturkun

@Hasturkun: ฉันไม่เห็นด้วยอย่างยิ่ง แรงจูงใจที่สำคัญสำหรับNULLตัวชี้คือการป้องกันไม่ให้วินาทีdelete ptr;ระเบิดซึ่งเป็นการปิดบังข้อผิดพลาดเพราะวินาทีdeleteนั้นไม่ควรเกิดขึ้น (นอกจากนี้ยังใช้เพื่อตรวจสอบว่าตัวชี้ยังคงชี้ไปยังวัตถุที่ถูกต้องหรือไม่ แต่นั่นทำให้เกิดคำถามว่าทำไมคุณถึงมีตัวชี้ในขอบเขตที่ไม่มีวัตถุให้ชี้)
SBI

คำตอบ:


64

หากคุณใช้ glibc คุณสามารถตั้งค่าMALLOC_CHECK_ตัวแปรสภาพแวดล้อมเป็นได้2ซึ่งจะทำให้ glibc ใช้เวอร์ชันที่ยอมรับข้อผิดพลาดmallocซึ่งจะทำให้โปรแกรมของคุณยกเลิกเมื่อถึงจุดที่ทำ double free

คุณสามารถตั้งค่าจาก gdb โดยใช้set environment MALLOC_CHECK_ 2คำสั่งก่อนรันโปรแกรมของคุณ โปรแกรมควรยกเลิกโดยมีการfree()โทรปรากฏใน backtrace

ดูหน้าคนmalloc()สำหรับข้อมูลเพิ่มเติม


2
การตั้งค่าMALLOC_CHECK_2แก้ไขปัญหาฟรีสองเท่าของฉันได้จริง (แม้ว่าจะไม่ได้แก้ไขหากอยู่ในโหมดดีบักเท่านั้น)
puk

4
@puk ฉันมีปัญหาเดียวกันการตั้งค่า MALLOC_CHECK_ เป็น 2 จะหลีกเลี่ยงปัญหาที่ไม่เกิดขึ้นสองเท่าของฉัน มีตัวเลือกอะไรอีกบ้างที่จะแทรกโค้ดให้น้อยลงเพื่อสร้างปัญหาและให้ backtrace?
Wei Zhong

นอกจากนี้ยังมีที่การตั้งค่า MALLOC_CHECK_ เพื่อหลีกเลี่ยงปัญหา ผู้แสดงความคิดเห็น / ใครก็ตาม ... คุณคิดวิธีอื่นในการแสดงปัญหาหรือไม่?
pellucidcoder

"เมื่อ MALLOC_CHECK_ ถูกตั้งค่าเป็นค่าที่ไม่ใช่ศูนย์จะมีการใช้การใช้งานแบบพิเศษ (มีประสิทธิภาพน้อยกว่า) ซึ่งออกแบบมาเพื่อให้ทนต่อข้อผิดพลาดง่าย ๆ เช่นการโทรฟรีสองครั้งด้วยอาร์กิวเมนต์เดียวกันหรือการใช้งานเกินไบต์เดียว (ปิด - โดยหนึ่งจุดบกพร่อง) " gnu.org/software/libc/manual/html_node/…ดังนั้นดูเหมือนว่า MALLOC_CHECK_ จะใช้เพื่อหลีกเลี่ยงข้อผิดพลาดของหน่วยความจำธรรมดาเท่านั้นไม่สามารถตรวจจับได้
pellucidcoder

จริงๆแล้ว .... support.microfocus.com/kb/doc.php?id=3113982ดูเหมือนว่าการตั้งค่า MALLOC_CHECK_ เป็น 3 จะมีประโยชน์มากที่สุดและสามารถใช้ตรวจหาข้อผิดพลาดได้
pellucidcoder

32

มีอย่างน้อยสองสถานการณ์ที่เป็นไปได้:

  1. คุณกำลังลบเอนทิตีเดียวกันสองครั้ง
  2. คุณกำลังลบบางสิ่งที่ไม่ได้รับการจัดสรร

สำหรับข้อแรกฉันขอแนะนำอย่างยิ่งให้ NULL-ing ตัวชี้ที่ถูกลบทั้งหมด

คุณมีสามทางเลือก:

  1. ใหม่มากเกินไปและลบและติดตามการจัดสรร
  2. ใช่ใช้ gdb แล้วคุณจะได้รับ backtrace จากความผิดพลาดของคุณและนั่นอาจเป็นประโยชน์มาก
  3. ตามที่แนะนำ - ใช้ Valgrind - มันไม่ใช่เรื่องง่ายที่จะเข้ามา แต่จะช่วยให้คุณประหยัดเวลาได้เป็นพันเท่าในอนาคต ...

2. อาจทำให้เกิดความเสียหาย แต่ฉันไม่คิดว่าข้อความนี้จะปรากฏขึ้นโดยทั่วไปเนื่องจากการตรวจสอบความสมบูรณ์จะทำเฉพาะในฮีปเท่านั้น อย่างไรก็ตามฉันคิดว่า 3. heap buffer overflow เป็นไปได้
Matthew Flaschen

สิ่งที่ดี. จริงฉันพลาดที่จะทำให้ตัวชี้เป็นโมฆะและต้องเผชิญกับข้อผิดพลาดนี้ บทเรียน!
hrushi

26

คุณสามารถใช้ gdb แต่ครั้งแรกที่ผมจะพยายามValgrind ดูคู่มือเริ่มต้นอย่างรวดเร็ว

โดยสังเขป Valgrind จะใช้โปรแกรมของคุณเพื่อให้สามารถตรวจจับข้อผิดพลาดหลายประเภทในการใช้หน่วยความจำที่จัดสรรแบบไดนามิกเช่นการเพิ่มอิสระสองครั้งและเขียนเลยจุดสิ้นสุดของบล็อกหน่วยความจำที่จัดสรร (ซึ่งอาจทำให้ฮีปเสียหายได้) ตรวจจับและรายงานข้อผิดพลาด ทันทีที่เกิดขึ้นจึงชี้ให้คุณทราบถึงสาเหตุของปัญหาโดยตรง


1
@SMR ในกรณีนี้ส่วนสำคัญของคำตอบคือทั้งหน้าที่เชื่อมโยงกันใหญ่ ดังนั้นการรวมลิงก์ไว้ในคำตอบก็ทำได้ดี คำสองสามคำเกี่ยวกับสาเหตุที่ผู้เขียนชอบ Valgrind มากกว่า gdb และวิธีที่เขาจะจัดการกับปัญหาเฉพาะคือ IMHO สิ่งที่ขาดหายไปจากคำตอบจริงๆ
ndemou

20

กฎพื้นฐานสามประการ:

  1. ตั้งค่าตัวชี้เป็นNULLหลังว่าง
  2. ตรวจสอบNULLก่อนปล่อย
  3. เริ่มต้นตัวชี้NULLในการเริ่มต้น

การรวมกันของทั้งสามทำงานได้ดีทีเดียว


1
ฉันไม่ใช่ผู้เชี่ยวชาญด้าน C แต่โดยปกติฉันสามารถให้ศีรษะอยู่เหนือน้ำได้ ทำไมต้อง # 1? มันเป็นเพียงแค่การที่โปรแกรมของคุณหยุดชะงักเมื่อคุณลองและเข้าถึงตัวชี้ที่ไม่เสียค่าใช้จ่ายไม่ใช่แค่ข้อผิดพลาดแบบเงียบ?
Daniel Harms

1
@ ความแม่นยำ: ใช่นั่นคือประเด็น ถือเป็นแนวทางปฏิบัติที่ดี: การมีตัวชี้ไปยังหน่วยความจำที่ถูกลบถือเป็นความเสี่ยง
ereOn

10
พูดอย่างเคร่งครัดฉันคิดว่า # 2 ไม่จำเป็นเนื่องจากคอมไพเลอร์ส่วนใหญ่จะอนุญาตให้คุณพยายามลบตัวชี้ว่างโดยไม่ทำให้เกิดปัญหา ฉันแน่ใจว่าจะมีคนแก้ไขฉันถ้าฉันผิด :)
ส่วนประกอบ 10

11
@ Component10 ฉันคิดว่าการปลดปล่อย NULL เป็นสิ่งจำเป็นสำหรับมาตรฐาน C เพื่อไม่ทำอะไรเลย
Demi

2
@Demetri: ใช่คุณพูดถูก"ถ้าค่าของตัวถูกดำเนินการลบเป็นตัวชี้ค่าว่างการดำเนินการจะไม่มีผล" (ISO / IEC 14882: 2003 (E) 5.3.5.2)
ส่วนประกอบ 10

15

คุณสามารถใช้valgrindเพื่อแก้ไขข้อบกพร่องได้

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
*** glibc detected *** ./t1: double free or corruption (top): 0x00000000058f7010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3a3127245f]
/lib64/libc.so.6(cfree+0x4b)[0x3a312728bb]
./t1[0x400500]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3a3121d994]
./t1[0x400429]
======= Memory map: ========
00400000-00401000 r-xp 00000000 68:02 30246184                           /home/sand/testbox/t1
00600000-00601000 rw-p 00000000 68:02 30246184                           /home/sand/testbox/t1
058f7000-05918000 rw-p 058f7000 00:00 0                                  [heap]
3a30e00000-3a30e1c000 r-xp 00000000 68:03 5308733                        /lib64/ld-2.5.so
3a3101b000-3a3101c000 r--p 0001b000 68:03 5308733                        /lib64/ld-2.5.so
3a3101c000-3a3101d000 rw-p 0001c000 68:03 5308733                        /lib64/ld-2.5.so
3a31200000-3a3134e000 r-xp 00000000 68:03 5310248                        /lib64/libc-2.5.so
3a3134e000-3a3154e000 ---p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a3154e000-3a31552000 r--p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a31552000-3a31553000 rw-p 00152000 68:03 5310248                        /lib64/libc-2.5.so
3a31553000-3a31558000 rw-p 3a31553000 00:00 0
3a41c00000-3a41c0d000 r-xp 00000000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41c0d000-3a41e0d000 ---p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41e0d000-3a41e0e000 rw-p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
2b1912300000-2b1912302000 rw-p 2b1912300000 00:00 0
2b191231c000-2b191231d000 rw-p 2b191231c000 00:00 0
7ffffe214000-7ffffe229000 rw-p 7ffffffe9000 00:00 0                      [stack]
7ffffe2b0000-7ffffe2b4000 r-xp 7ffffe2b0000 00:00 0                      [vdso]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0                  [vsyscall]
Aborted
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck ./t1
==20859== Memcheck, a memory error detector
==20859== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20859== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20859== Command: ./t1
==20859==
==20859== Invalid free() / delete / delete[]
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004FF: main (t1.c:8)
==20859==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004F6: main (t1.c:7)
==20859==
==20859==
==20859== HEAP SUMMARY:
==20859==     in use at exit: 0 bytes in 0 blocks
==20859==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20859==
==20859== All heap blocks were freed -- no leaks are possible
==20859==
==20859== For counts of detected and suppressed errors, rerun with: -v
==20859== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20899== Memcheck, a memory error detector
==20899== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20899== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20899== Command: ./t1
==20899==
==20899== Invalid free() / delete / delete[]
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004FF: main (t1.c:8)
==20899==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004F6: main (t1.c:7)
==20899==
==20899==
==20899== HEAP SUMMARY:
==20899==     in use at exit: 0 bytes in 0 blocks
==20899==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20899==
==20899== All heap blocks were freed -- no leaks are possible
==20899==
==20899== For counts of detected and suppressed errors, rerun with: -v
==20899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

การแก้ไขที่เป็นไปได้อย่างหนึ่ง:

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 x=NULL;
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
[sand@PS-CNTOS-64-S11 testbox]$

[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20958== Memcheck, a memory error detector
==20958== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20958== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20958== Command: ./t1
==20958==
==20958==
==20958== HEAP SUMMARY:
==20958==     in use at exit: 0 bytes in 0 blocks
==20958==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==20958==
==20958== All heap blocks were freed -- no leaks are possible
==20958==
==20958== For counts of detected and suppressed errors, rerun with: -v
==20958== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

ดูบล็อกเกี่ยวกับการใช้ลิงก์ Valgrind


โปรแกรมของฉันใช้เวลาประมาณ 30 นาทีในการทำงานบน Valgrind อาจใช้เวลา 18 ถึง 20 ชั่วโมงจึงจะเสร็จสิ้น
Kemin Zhou

12

ด้วยคอมไพเลอร์ C ++ ที่ทันสมัยคุณสามารถใช้น้ำยาฆ่าเชื้อเพื่อติดตาม

ตัวอย่างตัวอย่าง:

โปรแกรมของฉัน:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

{
   int * i = new int();
   delete i;
   //i = NULL;
   delete i;
}

รวบรวมด้วยน้ำยาฆ่าเชื้อที่อยู่:

# g++-7.1 d_free.cxx -Wall -Werror -fsanitize=address -g

ดำเนินการ:

# ./a.out 
=================================================================
==4836==ERROR: AddressSanitizer: attempting double-free on 0x602000000010 in thread T0:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b2c in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:11
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)
    #3 0x400a08  (/media/sf_shared/jkr/cpp/d_free/a.out+0x400a08)

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b1b in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:9
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

previously allocated by thread T0 here:
    #0 0x7f35b2d7a040 in operator new(unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:80
    #1 0x400ac9 in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:8
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

SUMMARY: AddressSanitizer: double-free /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140 in operator delete(void*, unsigned long)
==4836==ABORTING

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับน้ำยาฆ่าเชื้อคุณสามารถตรวจสอบสิ่งนี้หรือสิ่งนี้หรือเอกสารคอมไพเลอร์ c ++ ที่ทันสมัย ​​(เช่น gcc เสียงดังเป็นต้น)


5

คุณใช้ตัวชี้อัจฉริยะเช่น Boost shared_ptrหรือไม่? get()ถ้าเป็นเช่นนั้นตรวจสอบว่าคุณได้โดยตรงโดยใช้ที่ใดก็ได้ชี้ดิบโดยการเรียก ฉันพบว่านี่เป็นปัญหาที่พบบ่อย

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

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

int main(int argc, char* argv[])
{
    char* ptr = new char[20];

    delete[] ptr;
    ptr = 0;  // Comment me out and watch me crash and burn.
    delete[] ptr;
}

แก้ไข: เปลี่ยนdeleteไปdelete[]เป็น PTR เป็นอาร์เรย์ของถ่าน


ฉันไม่ได้ใช้คำสั่งลบใด ๆ ในโปรแกรมของฉัน สิ่งนี้อาจเป็นปัญหาได้หรือไม่?
neuromancer

1
@Phenom: ทำไมคุณไม่ใช้การลบ? เป็นเพราะคุณใช้ตัวชี้อัจฉริยะหรือไม่? สันนิษฐานว่าคุณกำลังใช้รหัสใหม่ในการสร้างวัตถุบนฮีป? หากคำตอบของทั้งสองข้อนี้คือใช่คุณกำลังใช้ get / set บนตัวชี้อัจฉริยะเพื่อคัดลอกตัวชี้ดิบหรือไม่? ถ้าเป็นเช่นนั้นอย่า! คุณกำลังทำลายการนับการอ้างอิง หรือคุณอาจกำหนดตัวชี้จากรหัสไลบรารีที่คุณเรียกไปยังตัวชี้อัจฉริยะ หากคุณไม่ได้ 'เป็นเจ้าของ' หน่วยความจำที่ชี้ไปก็อย่าทำเช่นนั้นเนื่องจากทั้งไลบรารีและตัวชี้อัจฉริยะจะพยายามลบออก
ส่วนประกอบ 10

-2

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

ซึ่งกำลังปิดไฟล์ที่คุณปิดไปแล้ว

หากคุณไม่ใส่ใจและมีสองฟังก์ชันที่แตกต่างกันให้ปิดไฟล์เดียวกันฟังก์ชันที่สองจะสร้างข้อผิดพลาดนี้


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