我目前正在为一项实验室作业开发一个内存扫描程序,其目标是读取和分析进程本身的内存映射,特别是从
/proc/self/maps
读取。该程序旨在解析该文件,识别可读内存区域,然后计算这些区域内字符“A”的出现次数。此功能已实现,并且在正常执行期间似乎可以正常工作。
但是,当我使用选项
--leak-check=full --track-origins=yes --error-exitcode=1 --show-leak-kinds=all --read-var-info=yes --malloc-fill=0xAA --free-fill=0xFF
在 Valgrind 下运行程序时,它会报告与未初始化值和无效内存读取相关的多个错误。具体来说,Valgrind 输出有关“条件跳转或移动取决于未初始化值”和“大小 1 的无效读取”的警告,表明我的程序可能尝试读取超出分配区域的内存或使用未初始化的内存进行决策。
有趣的是,Valgrind 还报告说没有内存泄漏,这表明问题不在于内存未被释放,而在于程序执行期间如何访问和使用内存。正常运行的程序没有明显问题,而 Valgrind 报告严重错误,这种差异令人费解。
Valgrind 输出的相关部分包括有关未处理的 DW_OP_ 代码的警告,这可能暗示与调试信息或内存地址解释方式相关的更深层次问题。此外,错误消息“Address 0x4037000 is 0 bytes after the brk dataegment limit 0x4037000”表明尝试在允许的边界处读取内存,这与无效读取错误一致。
鉴于这些详细信息,我正在寻求有关如何诊断和解决内存扫描程序中 Valgrind 报告的错误的指导。我特别有兴趣了解为什么会发生这些未初始化的值和无效读取,以及如何修改我的程序以消除这些问题,同时保持其功能。
这是我的代码
/// @file main.c
#include "parser.h"
#include "scanner.h"
#include <stdio.h>
#include <stdlib.h>
int main() {
MemoryRegion *regions = NULL;
int count = 0;
if (parse_maps(®ions, &count) != 0) {
fprintf(stderr, "Error parsing /proc/self/maps\n");
return 1;
}
scan_memory(regions, count);
free(regions); // Clean up
return 0;
}
/// @file parser.c
#include "parser.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int parse_maps(MemoryRegion **regions, int *count) {
assert(regions != NULL);
FILE *file = fopen("/proc/self/maps", "r");
if (!file) {
perror("Failed to open /proc/self/maps");
return -1;
}
char line[496]; // buffer for reading lines
*count = 0;
int capacity = 12; // initial capacity
*regions = calloc(capacity, sizeof(MemoryRegion));
if (!*regions) {
perror("Failed to allocate memory for regions");
fclose(file);
return -1;
}
// loop through each line in the file
while (fgets(line, sizeof(line), file)) {
MemoryRegion region; // temporary region to store the current line
char *ptr = line;
char *endptr;
region.start_addr = strtoul(ptr, &endptr, 16); // parse the start address
if (ptr == endptr) {
continue;
}
ptr = endptr + 1; // skip the '-'
region.end_addr = strtoul(ptr, &endptr, 16); // parse the end address
if (ptr == endptr) {
continue;
}
ptr = endptr + 1; // skip the space
if (sscanf(ptr, "%4s", region.permissions) != 1) {
continue;
}
if (strstr(line, "[vvar]") || region.permissions[0] != 'r') {
continue;
}
// check if needed to reallocate memory
if (*count == capacity) {
capacity *= 2;
*regions = realloc(*regions, capacity * sizeof(MemoryRegion));
if (!*regions) {
perror("Failed to reallocate memory for regions");
fclose(file);
return -1;
}
}
(*regions)[*count] = region; // store the region
(*count)++;
}
fclose(file);
return 0;
}
/// @file parser.h
#ifndef MEMSCAN_PARSER_H
#define MEMSCAN_PARSER_H
#include <stdio.h>
/// @brief MemoryRegion - struct to store memory region information
typedef struct {
unsigned long start_addr;
unsigned long end_addr;
char permissions[5];
} MemoryRegion;
/// @brief parse_maps - parses /proc/self/maps and stores the memory regions
/// @param regions - pointer to an array of MemoryRegion structs
/// @param count - pointer to an integer to store the number of memory regions
/// @return - 0 on success, -1 on failure
int parse_maps(MemoryRegion **regions, int *count);
/// @file scanner.c
#include "scanner.h"
#include "parser.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int count_a_in_region(unsigned long start, unsigned long end) {
int count = 0;
// Ensure we do not attempt to read the end address
for (char *ptr = (char *)(uintptr_t)start;
ptr < (char *)(uintptr_t)end - 1; ptr++) {
if (*ptr == 'A') {
count++;
}
}
return count;
}
void scan_memory(MemoryRegion *regions, int count) {
assert(regions != NULL);
for (int i = 0; i < count; i++) {
MemoryRegion region = regions[i];
assert(region.end_addr > region.start_addr); // ensure end is greater than start
size_t size = region.end_addr - region.start_addr;
int countA = 0;
if (region.permissions[0] == 'r') {
countA = count_a_in_region(region.start_addr, region.end_addr);
if (countA == -1) {
fprintf(stderr, "Error counting 'A' in region %d\n", i);
continue;
}
}
printf("%d: 0x%lx - 0x%lx %s Number of bytes read [%zu] Number of 'A' is [%d]\n", i, region.start_addr, region.end_addr, region.permissions, size, countA);
}
}
/// @file scanner.h
#ifndef SCANNER_H
#define SCANNER_H
#include "parser.h"
/// @brief count_a_in_region - counts the number of 'A' characters in the specified memory region
/// @param start - the start address of the memory region
/// @param end - the end address of the memory region
/// @return - the number of 'A' characters in the region, or -1 on error
int count_a_in_region(unsigned long start, unsigned long end);
/// @brief scan_memory - scans the memory regions for the specified pattern
/// @param regions - pointer to an array of MemoryRegion structs
/// @param count - the number of memory regions
void scan_memory(MemoryRegion *regions, int count);
#endif //SCANNER_H
我在计数函数中尝试过-1,当我摆脱对字母的计数时,没有 valgrind 错误
鉴于这些详细信息,我正在寻求有关如何诊断和解决内存扫描程序中 Valgrind 报告的这些错误的指导。
简单:不要这样做。
我特别想了解为什么会发生这些未初始化的值和无效读取,以及如何修改我的程序以消除这些问题,同时保持其功能。
你可能做不到。
有趣的是,Valgrind 还报告说没有内存泄漏,这表明问题不在于内存未被释放,而在于程序执行期间如何访问和使用内存。正常运行的程序没有明显问题,而 Valgrind 报告严重错误,这种差异令人费解。
其实不然。您需要了解 Valgrind 诊断应根据分析程序源语言的语义进行解释。尤其是当源语言是 C 或 C++ 时,Valgrind 特别适合。如果 Valgrind 在分析 C 程序时完美地完成了它的工作,那么
每次尝试取消引用未明确指向活动对象的指针(根据 C 语言规范判断)都应该生成诊断信息。您的“无效读取”诊断属于此类别
根据 C 语言规范判断,每次尝试读取未为其明确赋值的对象时,都应生成诊断信息。您的“条件跳转或移动取决于未初始化的值”属于此类别。
仅根据语言规范来判断,您的程序确实存在严重错误。它充斥着 C 语言未定义的行为。这并不奇怪,因为 ISO C 根本没有定义一种方法来完成您想做的事情。现在,这并不意味着您不能使用 C 程序来完成这项工作。您的特定 C 实现可能会很好地支持它。很多人都这样做。它确实意味着此类程序必须执行的某些操作正是 Valgrind 旨在检测和报告的操作之一。
当然,Valgrind 确实有一些选项会影响它将报告的问题类型,但我很难想象如果您禁用了与程序的有意行为相关的所有诊断,您期望 Valgrind 会检测到什么。 Valgrind 根本不适合分析故意执行与您的程序相同的操作的程序。也许您希望 Valgrind 告诉您的内容可以简单地通过是否将
SIGBUS
或 SIGSEGV
传递给程序来确定。