เหตุใดฉันจึงได้รับความล้มเหลวในการยืนยัน C malloc


86

ฉันกำลังใช้การหารและพิชิตอัลกอริทึมพหุนามดังนั้นฉันจึงสามารถเปรียบเทียบกับการใช้งาน OpenCL ได้ แต่ฉันไม่สามารถmallocทำงานได้ เมื่อฉันเรียกใช้โปรแกรมมันจะจัดสรรสิ่งต่างๆมากมายตรวจสอบบางสิ่งจากนั้นส่งsize/2ไปยังอัลกอริทึม จากนั้นเมื่อฉันตีเส้นmallocอีกครั้งมันจะพ่นสิ่งนี้ออกมา:

malloc.c:3096: sYSMALLOc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 * (sizeof(size_t))) - 1)) & ~((2 * (sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long)old_end & pagemask) == 0)' failed.
Aborted

บรรทัดในคำถามคือ:

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

มีความคิดอะไรเกิดขึ้นบ้าง? ฉันกำลังพยายามหาวิธีรวบรวม GCC ที่ใหม่กว่าในกรณีที่เป็นข้อผิดพลาดของคอมไพเลอร์ แต่ฉันสงสัยจริงๆ


ฉันสงสัยว่าปัญหาเป็นเส้นก่อนหน้านั้นจริงๆ อาจจะฟรีสองเท่า?
Mitch Wheat

บรรทัดที่ 3 ในโปรแกรม: int * mult (ขนาด int, int * a, int * b) {int * out, i, j, * tmp1, * tmp2, * tmp3, * tmpa1, * tmpa2, * tmpb1, * tmpb2 , d, * res1, * res2; fprintf (stdout, "size:% d \ n", ขนาด); ออก = (int *) malloc (sizeof (int) * ขนาด * 2);
คริส

คำตอบ:


100

99.9% เป็นไปได้ว่าคุณมีหน่วยความจำที่เสียหาย (บัฟเฟอร์มากเกินไปหรือน้อยเกินไปเขียนไปยังตัวชี้หลังจากที่ได้รับอิสระเรียกว่าฟรีสองครั้งบนตัวชี้เดียวกันเป็นต้น)

เรียกใช้รหัสของคุณภายใต้Valgrindเพื่อดูว่าโปรแกรมของคุณทำอะไรไม่ถูกต้อง


1
แก้ไขแล้ว. Valgrind ช่วยได้แน่นอน ฉันถอดรหัส matlab เก่าของฉันผิดและมี for loop ที่วนซ้ำบน j จากนั้นข้างในก็ทำ j ++ ซึ่งส่วนใหญ่เขียนทับอาร์เรย์ที่เขียนอยู่และทำให้ malloc ล้มเหลว ขอบคุณสำหรับความช่วยเหลือ!
คริส

Valgrind เป็นเพียงเครื่องมือที่ฉันต้องการเพื่อหาว่าเกิดอะไรขึ้นเมื่อฉันได้รับข้อผิดพลาดนี้ ขอบคุณที่กล่าวถึง
alexwells

78

เพื่อให้คุณเข้าใจมากขึ้นว่าทำไมสิ่งนี้จึงเกิดขึ้นฉันต้องการขยายคำตอบของ @ r-samuel-klatchko สักหน่อย

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

การสร้างสิ่งนี้ (และทำให้สิ่งต่างๆง่ายขึ้นอย่างมาก) เมื่อคุณเรียกmallocใช้จำเป็นต้องมีตัวชี้ไปยังส่วนถัดไปของหน่วยความจำที่พร้อมใช้งาน วิธีง่ายๆอย่างหนึ่งในการทำเช่นนี้คือการดูบิตหน่วยความจำก่อนหน้านี้ที่ให้ไปและย้ายsizeไบต์ลงไป (หรือขึ้น) ในหน่วยความจำ กับการดำเนินการนี้คุณจะจบลงด้วยความจำของคุณกำลังมองหาบางสิ่งบางอย่างเช่นนี้หลังจากการจัดสรรp1, p2และp3:

แล้วอะไรคือสาเหตุของข้อผิดพลาดของคุณ?

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

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

ตามที่ระบุไว้ก่อนหน้านี้เป็นเพียงการทำให้เข้าใจผิดขั้นต้น แต่ก็เพียงพอที่จะอธิบายประเด็นนี้ได้ การนำ glibc ไปใช้งานmallocมีมากกว่า 5k บรรทัดและมีการวิจัยจำนวนมากเกี่ยวกับวิธีการสร้างกลไกการจัดสรรหน่วยความจำแบบไดนามิกที่ดีดังนั้นการครอบคลุมทั้งหมดด้วยคำตอบ SO จึงเป็นไปไม่ได้ หวังว่าสิ่งนี้จะทำให้คุณได้เห็นถึงสิ่งที่ทำให้เกิดปัญหาจริงๆ!


17

ทางเลือกอื่นในการใช้ Valgrind:

ฉันมีความสุขมากเพราะฉันเพิ่งช่วยเพื่อนแก้จุดบกพร่องของโปรแกรม โปรแกรมของเขามีปัญหานี้แน่นอน ( malloc()ทำให้ถูกยกเลิก) โดยมีข้อความแสดงข้อผิดพลาดเดียวกันจาก GDB

ฉันรวบรวมโปรแกรมของเขาโดยใช้Address Sanitizerกับ

แล้วก็วิ่งgdb new. เมื่อโปรแกรมถูกยุติโดยSIGABRTสาเหตุในภายหลังmalloc()ข้อมูลที่เป็นประโยชน์จำนวนมากจะถูกพิมพ์ออกมา:

มาดูผลลัพธ์โดยเฉพาะการติดตามสแต็ก:

new.c:59ส่วนแรกกล่าวว่ามีการดำเนินการเขียนที่ไม่ถูกต้องที่ บรรทัดนั้นอ่าน

new.c:55ส่วนที่สองกล่าวว่าหน่วยความจำที่เขียนไม่ดีที่เกิดขึ้นในที่ถูกสร้างขึ้น บรรทัดนั้นอ่าน

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

สรุป: ลองใช้-fsanitize=addressGCC หรือ Clang จะมีประโยชน์มากเมื่อแก้ไขปัญหาหน่วยความจำ


1
คุณเพิ่งช่วยชีวิตฉัน
Nate Symer

2

คุณอาจเอาชนะเกิน mem ที่จัดสรรไว้ที่ไหนสักแห่ง จากนั้น sw ที่ซ่อนอยู่จะไม่รับมันจนกว่าคุณจะโทรหา malloc

อาจมีค่ายามที่ถูกจับโดย malloc

แก้ไข ... เพิ่มสิ่งนี้สำหรับความช่วยเหลือในการตรวจสอบขอบเขต

http://www.lrde.epita.fr/~akim/ccmp/doc/bounds-checking.html


2

ฉันได้รับข้อความต่อไปนี้คล้ายกับข้อความของคุณ:

    โปรแกรม: malloc.c: 2372: sysmalloc: Assertion `(old_top == (((mbinptr) (((ถ่าน *) & ((av) -> ถังขยะ [((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((ไม่ได้ลงนามแบบยาว) (old_size)> = (ยาวไม่ได้ลงนาม) (((__ builtin_offsetof (โครงสร้าง malloc_chunk, fd_nextsize)) + ((2 * (sizeof (size_t))) - 1)) & ~ ((2 * (sizeof (size_t))) - 1))) && ((old_top) -> ขนาด & 0x1) && ((ไม่ได้ลงชื่อแบบยาว) old_end & pagemask) == 0) 'ล้มเหลว

เกิดข้อผิดพลาดในการเรียกวิธีการบางอย่างก่อนหน้านี้เมื่อใช้ malloc เขียนทับเครื่องหมายคูณ '*' ด้วย '+' อย่างผิดพลาดเมื่ออัปเดตแฟกเตอร์หลัง sizeof () - ตัวดำเนินการในการเพิ่มฟิลด์ให้กับอาร์เรย์ถ่านที่ไม่ได้ลงชื่อ

นี่คือรหัสที่รับผิดชอบต่อข้อผิดพลาดในกรณีของฉัน:

    UCHAR * b = (UCHAR *) malloc (ขนาดของ (UCHAR) +5);
    b [INTBITS] = (การคำนวณบางอย่าง);
    b [BUFSPC] = (การคำนวณบางอย่าง);
    b [BUFOVR] = (การคำนวณบางอย่าง);
    b [BUFMEM] = (การคำนวณบางอย่าง);
    b [MATCHBITS] = (การคำนวณบางอย่าง);

ในวิธีอื่นในภายหลังฉันใช้ malloc อีกครั้งและทำให้เกิดข้อความแสดงข้อผิดพลาดที่แสดงด้านบน การโทรนั้น (ง่ายพอ):

    UCHAR * b = (UCHAR *) malloc (ขนาด (UCHAR) * 50);

คิดว่าใช้เครื่องหมาย '+' ในการโทรครั้งที่ 1 ซึ่งนำไปสู่การคำนวณผิดพลาดร่วมกับการเริ่มต้นอาร์เรย์ทันทีหลังจากนั้น (การเขียนทับหน่วยความจำที่ไม่ได้จัดสรรให้กับอาร์เรย์) ทำให้เกิดความสับสนในแผนที่หน่วยความจำของ malloc ดังนั้นการโทรครั้งที่ 2 ผิดพลาด


0

เราได้รับข้อผิดพลาดนี้เนื่องจากเราลืมคูณด้วย sizeof (int) หมายเหตุอาร์กิวเมนต์ malloc (.. ) คือจำนวนไบต์ไม่ใช่จำนวนคำของเครื่องหรืออะไรก็ตาม


0

ฉันมีปัญหาเดียวกันฉันใช้ malloc ทับ n ซ้ำอีกครั้งในการวนซ้ำเพื่อเพิ่มข้อมูลสตริง char * ใหม่ ฉันประสบปัญหาเดียวกัน แต่หลังจากปล่อยvoid free()ปัญหาหน่วยความจำที่จัดสรรถูกจัดเรียง


-2

ฉันกำลังโอนแอปพลิเคชันหนึ่งจาก Visual C ไปยัง gcc ผ่าน Linux และฉันก็มีปัญหาเดียวกันกับ

malloc.c: 3096: sYSMALLOc: การยืนยันโดยใช้ gcc บน UBUNTU 11

ฉันย้ายรหัสเดียวกันไปยังการแจกจ่าย Suse (บนคอมพิวเตอร์เครื่องอื่น) และไม่มีปัญหาใด ๆ

ฉันสงสัยว่าปัญหาไม่ได้อยู่ในโปรแกรมของเรา แต่อยู่ใน libc ของตัวเอง

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