ฉันจะใช้ valgrind เพื่อค้นหาหน่วยความจำรั่วในโปรแกรมได้อย่างไร
โปรดใครสักคนช่วยฉันและอธิบายขั้นตอนในการดำเนินการตามขั้นตอนหรือไม่
ฉันใช้ Ubuntu 10.04 และฉันมีโปรแกรมa.c
โปรดช่วยฉันด้วย
ฉันจะใช้ valgrind เพื่อค้นหาหน่วยความจำรั่วในโปรแกรมได้อย่างไร
โปรดใครสักคนช่วยฉันและอธิบายขั้นตอนในการดำเนินการตามขั้นตอนหรือไม่
ฉันใช้ Ubuntu 10.04 และฉันมีโปรแกรมa.c
โปรดช่วยฉันด้วย
คำตอบ:
อย่าดูถูก 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)
IndexOutOfBoundsException
พิมพ์ปัญหาบางครั้งรอยรั่ว / ข้อผิดพลาดของคุณสามารถเชื่อมโยงซึ่งกันและกันเหมือน IDE ค้นพบว่าคุณยังไม่ได้พิมพ์วงเล็บปิด การแก้ไขปัญหาหนึ่งสามารถแก้ไขปัญหาอื่น ๆ ได้ดังนั้นให้มองหาประเด็นที่เป็นตัวการที่ดีและใช้แนวคิดเหล่านี้:
gdb
เป็นไปได้) และมองหาข้อผิดพลาดทางเงื่อนไข / postcondition แนวคิดคือการติดตามการทำงานของโปรแกรมของคุณในขณะที่มุ่งเน้นไปที่อายุการใช้งานของหน่วยความจำที่จัดสรรไว้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)
อยู่นอกขอบเขตอีกครั้งเหมือนกับการเขียนที่ไม่ถูกต้อง การอ่านที่ไม่ถูกต้องนั้นเป็นผลมาจากข้อผิดพลาดแบบหนึ่งต่อหนึ่ง ดูทางด้านขวาของการมอบหมายงานของคุณ
ฉันจะรู้ได้อย่างไรว่าการรั่วไหลเป็นของฉัน ฉันจะพบการรั่วไหลของฉันได้อย่างไรเมื่อฉันใช้รหัสของผู้อื่น? ฉันพบการรั่วไหลที่ไม่ใช่ของฉัน ฉันควรทำอะไร ทั้งหมดเป็นคำถามที่ถูกกฎหมาย ครั้งแรก 2 ตัวอย่างในโลกแห่งความจริงที่แสดงการเผชิญหน้าร่วมกัน 2 ชั้น
#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 แยกจากกัน บทเรียนที่นี่:
อ่านเอกสาร จริงๆ. บางครั้งก็ยากที่จะเข้าใจ แต่พวกเขากำลังบอกคุณว่าทำไมสิ่งเหล่านี้เกิดขึ้น แต่เรามี
คำถามที่มีอยู่เกี่ยวกับข้อผิดพลาดของหน่วยความจำนี้
#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 ทำงานอย่างไร หากคุณมีสิ่งเหล่านี้โปรดทำสิ่งที่อยากรู้:
memcheck
เครื่องมือที่ถูกเปิดใช้งานโดยค่าเริ่มต้น?
memcheck
เป็นเครื่องมือเริ่มต้น:--tool=<toolname> [default: memcheck]
ลองสิ่งนี้:
valgrind --leak-check=full -v ./your_program
ตราบใดที่มีการติดตั้ง valgrind โปรแกรมจะผ่านโปรแกรมของคุณและบอกคุณว่ามีอะไรผิดปกติ มันสามารถให้คำแนะนำและสถานที่โดยประมาณที่คุณอาจพบรอยรั่ว หากคุณกำลัง segfault'ing gdb
ลองใช้มันผ่าน
your_program
== ชื่อปฏิบัติการหรือคำสั่งใด ๆ ที่คุณใช้เพื่อเรียกใช้แอปพลิเคชันของคุณ
คุณสามารถเรียกใช้:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
คุณสามารถสร้างนามแฝงในไฟล์. 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 ในไดเรกทอรีปัจจุบัน