В настоящее время я разрабатываю программу сканирования памяти для лабораторного задания, цель которой — прочитать и проанализировать карту памяти самого процесса, в частности, путем чтения из /proc/self/maps. Программа предназначена для анализа этого файла, определения читаемых областей памяти, а затем подсчета вхождений символа «А» в эти области. Эта функциональность реализована и работает корректно при обычном выполнении.
Однако когда я запускаю свою программу под Valgrind с параметрами --leak-check=full --track-origins=yes --error-exitcode=1 --show-leak-kinds=all --read-var-info=yes --malloc-fill=0xAA --free-fill=0xFF, она сообщает о множественных ошибках, связанных с неинициализированными значениями и недопустимыми чтениями памяти. В частности, Valgrind выводит предупреждения о том, что «Условный переход или перемещение зависит от неинициализированных значений» и «Недопустимое чтение размера 1», указывая на то, что моя программа может пытаться прочитать память за пределами выделенных областей или использовать неинициализированную память для принятия решений.
Интересно, что Valgrind также сообщает об отсутствии утечек памяти, что говорит о том, что проблема не в том, что память не освобождается, а в том, как осуществляется доступ к памяти и ее использование во время выполнения программы. Это несоответствие между нормальной работой программы без видимых проблем и сообщением Valgrind о серьезных ошибках вызывает недоумение.
Соответствующая часть вывода Valgrind включает предупреждения о необработанных кодах DW_OP_, которые могут указывать на более глубокую проблему, связанную с отладочной информацией или способом интерпретации адресов памяти. Кроме того, сообщение об ошибке «Адрес 0x4037000 составляет 0 байт после ограничения сегмента данных brk 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
@UniversE, мне неясно, что именно вы подразумеваете под «выделенным», но отображение (виртуальной) памяти в адресное пространство процесса означает, что система обещает, что процесс может получить доступ к этой памяти. Система может лгать (например, Linux известен чрезмерным использованием памяти), но если система не может выполнить свои обещания на практике, то можно ожидать, что попытки доступа к этой памяти на самом деле потерпят неудачу, а не просто вызовут предупреждение от Valgrind.
@JohnBollinger, возможно, моя терминология не на 100% точна. Но под «выделенным» я подразумевал, что память принадлежит живому объекту, «выделенному» вызовом malloc. Valgrind, скорее всего, отслеживает эти выделения и сообщает о «недопустимом» чтении, если вы пытаетесь прочитать из памяти, которая не принадлежит такому объекту. Если я не ошибаюсь, это также - каким-то образом - похоже на то, куда движется ваш ответ.
Вы правы, @UniversE, то, что вы описываете, соответствует моему ответу. Ваш первоначальный комментарий показался мне запутанным, потому что он очень предварительный. Большая часть памяти, отображенной в адресное пространство данной программы, определенно не соответствует объектам, выделенным через распределитель библиотеки C.





Учитывая эти подробности, я ищу рекомендации о том, как диагностировать и устранять ошибки, о которых сообщает Valgrind, в моей программе сканирования памяти.
Легко: не делай этого.
Мне особенно интересно понять, почему возникают эти неинициализированные значения и недопустимые чтения, и как изменить мою программу, чтобы устранить эти проблемы, сохранив при этом ее функциональность.
Вы, вероятно, не можете.
Интересно, что Valgrind также сообщает об отсутствии утечек памяти, что говорит о том, что проблема не в том, что память не освобождается, а в том, как осуществляется доступ к памяти и ее использование во время выполнения программы. Это несоответствие между нормальной работой программы без видимых проблем и сообщением Valgrind о серьезных ошибках вызывает недоумение.
Не совсем. Необходимо понимать, что диагностику Valgrind следует интерпретировать с точки зрения семантики исходного языка(ов) анализируемой программы. Особенно, если исходным языком является C или C++, к которым Valgrind особенно настроен. Если Valgrind отлично справляется со своей задачей при анализе программы на языке C, то
каждая попытка разыменовать указатель, который определенно не указывает на живой объект, согласно спецификации языка C, должна генерировать диагностику. Ваша диагностика «неверное чтение» находится в этой категории.
каждая попытка прочитать объект, которому не было присвоено положительное значение, согласно спецификации языка C, должна генерировать диагностику. Ваш «Условный прыжок или ход зависит от неинициализированных значений» находится в этой категории.
Ваша программа действительно содержит серьезные ошибки, если судить исключительно по спецификации языка. Он свирепствует с поведением, которое не определено языком C. И это неудивительно, потому что ISO C просто не определяет способ делать то, что вы хотите. Это не означает, что вы не можете выполнить эту работу с помощью программы на языке C. Ваша конкретная реализация C вполне может поддерживать это. Многие так и делают. Это означает, что некоторые вещи, которые должна делать такая программа, относятся к числу тех, которые Valgrind призван обнаруживать и сообщать о них.
Конечно, у Valgrind есть параметры, влияющие на то, о каких проблемах он будет сообщать, но мне трудно представить, что вы ожидаете от Valgrind, чтобы обнаружить, если вы отключите всю диагностику, связанную с преднамеренным поведением вашей программы. Valgrind просто не подходит для анализа программы, которая намеренно делает то же, что и ваша. Возможно, то, что вы надеялись сказать вам Валгриндом, можно определить просто по тому, доставлен ли в программу SIGBUS или SIGSEGV.
Если говорить от имени главного участника Valgrind за последние несколько лет, то это совершенно неверно. Я не понимаю, как вы можете отредактировать это, чтобы сделать его правильным или полезным для ОП.
По совпадению, я сейчас работаю над кодом в Valgrind, который выполняет синтаксический анализ mmap. Это используется для двух целей: получения сопоставления памяти самого инструмента Valgrind (Valgrind не может перехватить mmap для собственного сопоставления, поскольку оно не запущено), а также для проверки mmap, если вы запускаете с --sanity-level=3 или выше.
предупреждения о необработанных кодах DW_OP_
Вероятно, старая версия Валгринда. Попробуйте взять что-нибудь более свежее.
--leak-check=full --track-origins=yes --error-exitcode=1 --show-leak-kinds=all --read-var-info=yes
Вам не нужны ни один из этих вариантов для того, что вы делаете. Они просто сделают ваш бег медленнее. Я рекомендую вам не использовать их или, по крайней мере, использовать их только после того, как увидите ошибку.
Вы действительно ищете «A» (0x41) или 0xAA? Я не уверен, что вы используете --malloc-fill=0xAA.
Итак, почему вы видите проблемы? Memcheck знает обо всей выделенной памяти (как в вашем коде, так и в коде запуска перед main()). Он также знает о стеке. malloc() не просто выделяет один бит памяти каждый раз, когда вы его вызываете. Скорее, он использует пулы различных размеров степени двойки и распределяет ресурсы из этих пулов. При больших выделениях памяти для получения памяти будет использоваться анонимный mmap — см. справочную страницу malloc. Это означает две вещи.
Conditional jump or move depends on uninitialised value(s) из «if (*ptr == 'A')»Invalid read of size 1.Попробуйте что-нибудь вроде этого. Это скорее грубая сила.
#include <memcheck.h>
int count_a_in_region(unsigned long start, unsigned long end) {
int count = 0;
VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(start, end - start);
// 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++;
}
}
VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(start, end - start);
return count;
}
Если вышеперечисленное не работает или вы хотите иметь больше контроля над происходящим, попробуйте использовать VALGRIND_CHECK_MEM_IS_ADDRESSABLE. Это вернет 0, если диапазон адресный. Если он не полностью адресуемый, он вернет первый неадресуемый адрес. Вероятно, вам придется использовать его несколько раз, чтобы найти все адресные диапазоны в регионе mmap.
Если вам нужно отключить ошибки «Условный переход», попробуйте VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE или VALGRIND_CHECK_MEM_IS_DEFINED.
Спасибо! избавился от всех ошибок, которые у меня были!
Я мог бы добавить VALGRIND_CHECK_MEM_IS_UNADDRESSABLE, который может быть полезен для поиска неадресуемых диапазонов.
Просто дикая догадка, потому что я точно не знаю, как работает valgrind. Но вы читаете память на основе адресов отображений виртуального адресного пространства. Это не обязательно означает, что за этими адресами действительно выделена память. Я предполагаю, что valgrind жалуется на чтение памяти с адресов, которые никогда не были выделены.