ฉันจะใช้ valgrind เพื่อค้นหาหน่วยความจำรั่วได้อย่างไร


183

ฉันจะใช้ valgrind เพื่อค้นหาหน่วยความจำรั่วในโปรแกรมได้อย่างไร

โปรดใครสักคนช่วยฉันและอธิบายขั้นตอนในการดำเนินการตามขั้นตอนหรือไม่

ฉันใช้ Ubuntu 10.04 และฉันมีโปรแกรมa.cโปรดช่วยฉันด้วย


16
คุณใช้ valgrind เพื่อทดสอบโปรแกรมที่คอมไพล์ของคุณไม่ใช่ซอร์สโค้ด
Tony

6
คำตอบที่ได้รับจาก @RageD ด้านล่างถูกต้องทำไมคุณไม่ยอมรับมัน
Pratik Singhal

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

1
ที่เกี่ยวข้อง: ด้วยเครื่องมือใด ๆ : stackoverflow.com/questions/6261201/ …
Ciro Santilli 郝海东冠状病病六四事件法轮功

คำตอบ:


297

วิธีการเรียกใช้ Valgrind

อย่าดูถูก OP แต่สำหรับผู้ที่มาที่คำถามนี้และยังใหม่กับ Linux คุณอาจต้องติดตั้ง Valgrindบนระบบของคุณ

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind ใช้งานได้ง่ายสำหรับรหัส C / C ++ แต่สามารถใช้ได้กับภาษาอื่น ๆ เมื่อกำหนดค่าอย่างถูกต้อง (ดูที่นี่สำหรับ Python)

ในการเรียกใช้ Valgrindให้ส่งไฟล์ปฏิบัติการเป็นอาร์กิวเมนต์ (พร้อมกับพารามิเตอร์ใด ๆ ไปยังโปรแกรม)

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

ธงสั้น ๆ :

  • --leak-check=full: "การรั่วไหลของแต่ละบุคคลจะแสดงในรายละเอียด"
  • --show-leak-kinds=all: แสดงชนิดของการรั่วไหล "แน่นอนทางอ้อมเป็นไปได้และเข้าถึงได้" ในรายงาน "เต็ม"
  • --track-origins=yes: ชอบผลลัพธ์ที่มีประโยชน์มากกว่าความเร็ว วิธีนี้จะติดตามต้นกำเนิดของค่าที่ไม่ได้กำหนดค่าเริ่มต้นซึ่งอาจมีประโยชน์มากสำหรับข้อผิดพลาดของหน่วยความจำ พิจารณาปิดถ้า Valgrind ช้าอย่างไม่อาจยอมรับได้
  • --verbose: สามารถบอกคุณเกี่ยวกับพฤติกรรมที่ผิดปกติของโปรแกรมของคุณ ทำซ้ำเพื่อให้คำฟุ่มเฟื่อยมากขึ้น
  • --log-file: เขียนลงไฟล์ มีประโยชน์เมื่อเอาต์พุตเกินพื้นที่เทอร์มินัล

สุดท้ายคุณต้องการดูรายงาน Valgrind ที่มีลักษณะดังนี้:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated

All heap blocks were freed -- no leaks are possible

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

ฉันมีการรั่วไหล แต่ที่ไหน ?

ดังนั้นคุณมีหน่วยความจำรั่วและ Valgrind ไม่ได้พูดอะไรที่มีความหมาย บางทีสิ่งนี้:

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (in /home/Peri461/Documents/executable)

ลองดูที่รหัส C ที่ฉันเขียนด้วย:

#include <stdlib.h>

int main() {
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;
}

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

gcc -o executable -std=c11 -Wall main.c         # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c  # add -ggdb3 to it

ตอนนี้ด้วยบิลด์ debug นี้Valgrind จะชี้ไปที่บรรทัดของรหัสที่ จัดสรรหน่วยความจำที่รั่วไหล! (ข้อความเป็นสิ่งสำคัญ: อาจไม่ตรงกับที่คุณรั่ว แต่สิ่งที่รั่วออกไปร่องรอยช่วยให้คุณค้นหาว่า อยู่ที่ไหน )

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (main.c:4)

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

  • ใช้ประโยชน์จากwww.cplusplus.com ! มันมีเอกสารที่ยอดเยี่ยมเกี่ยวกับฟังก์ชั่น C / C ++
  • คำแนะนำทั่วไปสำหรับหน่วยความจำรั่ว:
    • ตรวจสอบให้แน่ใจว่าหน่วยความจำที่จัดสรรแบบไดนามิกของคุณได้รับการปลดปล่อย
    • อย่าจัดสรรหน่วยความจำและลืมกำหนดตัวชี้
    • อย่าเขียนทับตัวชี้ด้วยตัวชี้ใหม่ยกเว้นว่ามีหน่วยความจำเก่าเป็นอิสระ
  • คำแนะนำทั่วไปสำหรับข้อผิดพลาดของหน่วยความจำ:
    • เข้าถึงและเขียนไปยังที่อยู่และดัชนีที่คุณมั่นใจว่าเป็นของคุณ ข้อผิดพลาดของหน่วยความจำแตกต่างจากการรั่วไหล พวกเขามักจะIndexOutOfBoundsException พิมพ์ปัญหา
    • อย่าเข้าถึงหรือเขียนไปยังหน่วยความจำหลังจากพ้น
  • บางครั้งรอยรั่ว / ข้อผิดพลาดของคุณสามารถเชื่อมโยงซึ่งกันและกันเหมือน IDE ค้นพบว่าคุณยังไม่ได้พิมพ์วงเล็บปิด การแก้ไขปัญหาหนึ่งสามารถแก้ไขปัญหาอื่น ๆ ได้ดังนั้นให้มองหาประเด็นที่เป็นตัวการที่ดีและใช้แนวคิดเหล่านี้:

    • รายการฟังก์ชั่นในรหัสของคุณที่ขึ้นอยู่กับ / ขึ้นอยู่กับรหัส "offending" ที่มีข้อผิดพลาดของหน่วยความจำ ติดตามการทำงานของโปรแกรม (อาจgdbเป็นไปได้) และมองหาข้อผิดพลาดทางเงื่อนไข / postcondition แนวคิดคือการติดตามการทำงานของโปรแกรมของคุณในขณะที่มุ่งเน้นไปที่อายุการใช้งานของหน่วยความจำที่จัดสรรไว้
    • ลองคอมเม้นท์บล็อก "offending" ของโค้ด (ด้วยเหตุผลดังนั้นโค้ดของคุณยังคงคอมไพล์) หากข้อผิดพลาด Valgrind หายไปคุณจะพบว่าอยู่ที่ไหน
  • หากสิ่งอื่นล้มเหลวให้ลองค้นหาดู Valgrind มีเอกสารด้วย!

ดูการรั่วไหลและข้อผิดพลาดทั่วไป

ดูพอยน์เตอร์ของคุณ

60 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
   by 0x4005E4: resizeArray (main.c:12)
   by 0x40062E: main (main.c:19)

และรหัส:

#include <stdlib.h>
#include <stdint.h>

struct _List {
    int32_t* data;
    int32_t length;
};
typedef struct _List List;

List* resizeArray(List* array) {
    int32_t* dPtr = array->data;
    dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
    return array;
}

int main() {
    List* array = calloc(1, sizeof(List));
    array->data = calloc(10, sizeof(int32_t));
    array = resizeArray(array);

    free(array->data);
    free(array);
    return 0;
}

ในฐานะผู้ช่วยสอนฉันเห็นข้อผิดพลาดนี้บ่อยครั้ง นักเรียนใช้ประโยชน์จากตัวแปรในตัวและลืมปรับปรุงตัวชี้ดั้งเดิม ข้อผิดพลาดที่นี่คือการสังเกตเห็นว่าreallocสามารถย้ายหน่วยความจำที่จัดสรรไว้ที่อื่นและเปลี่ยนตำแหน่งของตัวชี้ จากนั้นเราก็ไปresizeArrayโดยไม่บอก array->dataว่าอาเรย์ไหนถูกย้ายไป

การเขียนไม่ถูกต้อง

1 errors in context 1 of 1:
Invalid write of size 1
   at 0x4005CA: main (main.c:10)
 Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
   at 0x4C2B975: calloc (vg_replace_malloc.c:711)
   by 0x400593: main (main.c:5)

และรหัส:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* alphabet = calloc(26, sizeof(char));

    for(uint8_t i = 0; i < 26; i++) {
        *(alphabet + i) = 'A' + i;
    }
    *(alphabet + 26) = '\0'; //null-terminate the string?

    free(alphabet);
    return 0;
}

โปรดสังเกตว่า Valgrind ชี้ให้เราเห็นบรรทัดของโค้ดด้านบน อาร์เรย์ขนาด 26 มีการจัดทำดัชนี [0,25] ซึ่งเป็นสาเหตุที่*(alphabet + 26)การเขียนที่ไม่ถูกต้อง - อยู่นอกขอบเขต การเขียนที่ไม่ถูกต้องเป็นผลลัพธ์ทั่วไปของข้อผิดพลาดแบบออฟไลน์ ดูที่ด้านซ้ายของการมอบหมายงานของคุณ

อ่านไม่ถูกต้อง

1 errors in context 1 of 1:
Invalid read of size 1
   at 0x400602: main (main.c:9)
 Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x4005E1: main (main.c:6)

และรหัส:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* destination = calloc(27, sizeof(char));
    char* source = malloc(26 * sizeof(char));

    for(uint8_t i = 0; i < 27; i++) {
        *(destination + i) = *(source + i); //Look at the last iteration.
    }

    free(destination);
    free(source);
    return 0;
}

Valgrind ชี้เราไปยังบรรทัดที่ถูกคอมเม้นต์ด้านบน
*(destination + 26) = *(source + 26);ดูซ้ำสุดท้ายที่นี่ซึ่งเป็น อย่างไรก็ตาม*(source + 26)อยู่นอกขอบเขตอีกครั้งเหมือนกับการเขียนที่ไม่ถูกต้อง การอ่านที่ไม่ถูกต้องนั้นเป็นผลมาจากข้อผิดพลาดแบบหนึ่งต่อหนึ่ง ดูทางด้านขวาของการมอบหมายงานของคุณ


topia โอเพนซอร์ส (U / Dys)

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

Jansson : ไลบรารี JSON

#include <jansson.h>
#include <stdio.h>

int main() {
    char* string = "{ \"key\": \"value\" }";

    json_error_t error;
    json_t* root = json_loads(string, 0, &error); //obtaining a pointer
    json_t* value = json_object_get(root, "key"); //obtaining a pointer
    printf("\"%s\" is the value field.\n", json_string_value(value)); //use value

    json_decref(value); //Do I free this pointer?
    json_decref(root);  //What about this one? Does the order matter?
    return 0;
}

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

ทำไม? ตัวแปรvalueนี้ถือเป็น "ข้อมูลอ้างอิงที่ยืม" ใน Jansson API Jansson ติดตามความทรงจำของคุณและคุณต้องdecref โครงสร้าง JSON แยกจากกัน บทเรียนที่นี่: อ่านเอกสาร จริงๆ. บางครั้งก็ยากที่จะเข้าใจ แต่พวกเขากำลังบอกคุณว่าทำไมสิ่งเหล่านี้เกิดขึ้น แต่เรามี คำถามที่มีอยู่เกี่ยวกับข้อผิดพลาดของหน่วยความจำนี้

SDL : ไลบรารีกราฟิกและเกม

#include "SDL2/SDL.h"

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    SDL_Quit();
    return 0;
}

มีอะไรผิดปกติกับการเป็นรหัสนี้ ? มันรั่วอย่างต่อเนื่อง ~ 212 KiB สำหรับฉัน ใช้เวลาสักครู่เพื่อคิดเกี่ยวกับมัน เราเปิด SDL แล้วปิด ตอบ? ไม่มีอะไรผิด.

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

คำตอบแก่ความว่างเปล่า

ฉันจะรู้ได้อย่างไรว่าการรั่วไหลเป็นของฉัน
มันคือ. (แน่นอนว่า 99% ต่อไป)

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

ฉันพบการรั่วไหลที่ไม่ใช่ของฉัน ฉันควรทำอะไร
ใช่ APIs ส่วนใหญ่มีวิธีการรายงานข้อบกพร่องและปัญหา ใช้มัน! ช่วยตอบแทนเครื่องมือที่คุณใช้ในโครงการของคุณ!


อ่านเพิ่มเติม

ขอบคุณที่อยู่กับฉันมานาน ฉันหวังว่าคุณจะได้เรียนรู้บางสิ่งบางอย่างขณะที่ฉันพยายามโน้มน้าวผู้คนจำนวนมากที่มาถึงคำตอบนี้ บางสิ่งที่ฉันหวังว่าคุณจะถามไปตลอดทาง: ตัวจัดสรรหน่วยความจำของ C ทำงานอย่างไร สิ่งที่จริงแล้วหน่วยความจำรั่วและข้อผิดพลาดของหน่วยความจำคืออะไร? แตกต่างจาก segfaults อย่างไร Valgrind ทำงานอย่างไร หากคุณมีสิ่งเหล่านี้โปรดทำสิ่งที่อยากรู้:


4
คำตอบที่ดีกว่านี้น่าเสียดายที่นี่ไม่ใช่คำตอบที่ยอมรับได้
A. Smoliak

ฉันเชื่อว่าเป็นวิธีปฏิบัติที่ดีในการทำสิ่งนี้ฉันทำเอง
A. Smoliak

1
ฉันสามารถติดดาวคำตอบนี้และใช้เป็นข้อมูลอ้างอิงในอนาคตสำหรับตัวเองได้หรือไม่? การทำงานที่ดี!
Zap

ไม่memcheckเครื่องมือที่ถูกเปิดใช้งานโดยค่าเริ่มต้น?
abhiarora

@abhiarora ใช่ หน้าคนบอกเราว่าmemcheckเป็นเครื่องมือเริ่มต้น:--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

ลองสิ่งนี้:

valgrind --leak-check=full -v ./your_program

ตราบใดที่มีการติดตั้ง valgrind โปรแกรมจะผ่านโปรแกรมของคุณและบอกคุณว่ามีอะไรผิดปกติ มันสามารถให้คำแนะนำและสถานที่โดยประมาณที่คุณอาจพบรอยรั่ว หากคุณกำลัง segfault'ing gdbลองใช้มันผ่าน


"your_program" หมายถึงอะไร นี่คือตำแหน่งโค้ดต้นฉบับหรือชื่อแอปพลิเคชันเช่นไฟล์ apk หรือไม่
HoangVu

7
your_program== ชื่อปฏิบัติการหรือคำสั่งใด ๆ ที่คุณใช้เพื่อเรียกใช้แอปพลิเคชันของคุณ
RageD


1

คุณสามารถสร้างนามแฝงในไฟล์. bashrc ดังนี้

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

ดังนั้นเมื่อใดก็ตามที่คุณต้องการตรวจสอบรอยรั่วของหน่วยความจำ

vg ./<name of your executable> <command line parameters to your executable>

สิ่งนี้จะสร้างไฟล์บันทึก Valgrind ในไดเรกทอรีปัจจุบัน

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