Как определить, включен ли возвращенный указатель в стек или кучу - программирование
Подтвердить что ты не робот

Как определить, включен ли возвращенный указатель в стек или кучу

У меня есть архитектура плагина, где я вызываю функции в динамической библиотеке, и они возвращают мне char*, который является ответом, он используется на более позднем этапе.

Это подпись функции плагина:

char* execute(ALLOCATION_BEHAVIOR* free_returned_value, unsigned int* length);

где ALLOCATION_BEHAVIOR должен быть: DO_NOT_FREE_ME, FREE_ME, DELETE_ME, где плагин (в библиотеке) сообщает мне, как плагин выделил строку, которую он только что вернул: DO_NOT_FREE_ME сообщает мне, что это это переменная, которую я не должен касаться (например, const static char*, которая никогда не изменяется) FREE_ME говорит мне, что я должен использовать free() для освобождения возвращаемого значения, а DELETE_ME говорит мне использовать delete[] для получения избавиться от утечек памяти.

Очевидно, что я не доверяю плагинам, поэтому я хотел бы иметь возможность проверить, что если он скажет мне free() переменную, действительно, это действительно может быть освобождено... Возможно ли это использование сегодняшняя технология C/С++ для Linux/Windows?

4b9b3361

Ответ 1

Отличия между malloc/free и new/delete обычно невозможны, по крайней мере, не надежным и/или переносным способом. Тем более, что new просто wrapp malloc в любом случае во многих реализациях.

Ни один из следующих альтернатив для различения кучи/стека не был протестирован, но все они должны работать.

Linux:

  • Решение, предложенное Luca Tettananti, проанализирует /proc/self/maps, чтобы получить диапазон адресов стека.
  • В первую очередь при запуске clone ваш процесс предполагает поставку стека. Поскольку вы его поставляете, вы автоматически знаете, где он находится.
  • Вызов функции GCC __builtin_frame_address с увеличением параметра уровня до тех пор, пока он не вернет 0. Затем вы узнаете глубину. Теперь вызовите __builtin_frame_address снова с максимальным уровнем и один раз с уровнем 0. Все, что живет в стеке, обязательно должно быть между этими двумя адресами.
  • sbrk(0) как первое при запуске и помните значение. Всякий раз, когда вы хотите узнать, находится ли что-то в куче, sbrk(0) снова - то, что в куче должно быть между двумя значениями. Обратите внимание, что это не будет надежно работать с распределителями, использующими сопоставление памяти для больших распределений.

Зная расположение и размер стека (альтернативы 1 и 2), тривиально узнать, находится ли адрес в этом диапазоне. Если это не так, это обязательно "куча" (если кто-то не пытается быть супер-умным и дает вам указатель на статический глобальный или указатель на функцию или такой...).

Окна:

  • Используйте CaptureStackBackTrace, все, что находится в стеке, должно находиться между возвращенным массивом указателей первым и последним.
  • Используйте GCC-MinGW (и __builtin_frame_address, который должен работать), как указано выше.
  • Используйте GetProcessHeaps и HeapWalk для проверки каждого выделенного блока для соответствия. Если ни один из кучек не соответствует ни одному из куч, он, следовательно, выделяется в стеке (... или отображение памяти, если кто-то пытается быть супер-умным с вами).
  • Используйте HeapReAlloc с HEAP_REALLOC_IN_PLACE_ONLY и с точно таким же размером. Если это не удается, блок памяти, начинающийся с заданного адреса, не выделяется в куче. Если он "преуспевает", он не работает.
  • Используйте GetCurrentThreadStackLimits (только для Windows 8/2012)
  • Вызвать NtCurrentTeb() (или прочитать fs:[18h]) и использовать поля StackBase и StackLimit возвращенного TEB.

Ответ 2

Я сделал тот же вопрос пару лет назад на comp.lang.c, мне понравился ответ Джеймса Куйпера:

Да. Следите за ним, когда вы его выделяете.

Способ сделать это - использовать концепцию владения памятью. В все время в течение жизни блока выделенной памяти, вы должен всегда иметь один и только один указатель, который "владеет" этим блоком. Другие указатели могут указывать на этот блок, но только владеющий указатель должен когда-либо быть передан в free().

Если возможно, указатель на владельца должен быть зарезервирован для цель владения указателями; он не должен использоваться для хранения указателей на памяти, которой он не владеет. Обычно я стараюсь, чтобы указатель инициализируется вызовом malloc(); если это не возможно, он должен быть установлен в NULL когда-то до первого использования. Я также постарайтесь убедиться, что срок действия указателя владельца заканчивается сразу после того, как я освобожу() память, которой он владеет. Однако, когда это невозможно, установите его в NULL сразу после освобождения() в этой памяти. Принимая эти меры предосторожности, вы не должны позволять non-null, владеющий указателем, не передавая его сначала (free).

Если у вас есть проблемы с отслеживанием того, какие указатели "владеют", указатели, добавьте комментарий об этом факте рядом с их выражением. Если у вас много проблем, используйте соглашение об именах, чтобы отслеживать эта функция.

Если по какой-либо причине невозможно зарезервировать указатель на владельца переменная исключительно для владения памятью, на которую она указывает, вы следует отложить отдельную переменную флагов, чтобы отслеживать, не тот указатель в настоящее время владеет памятью, на которую он указывает. Создание struct, которая содержит как указатель, так и флаг владения естественный способ справиться с этим - он гарантирует, что они не будут разделены.

Если у вас довольно сложная программа, может потребоваться передать владение памятью от одной переменной-указателя до другой. Если это так, убедитесь, что любая память, принадлежащая целевому указателю, free() d перед передачей, и если время жизни источника указатель заканчивается сразу же после передачи, установите указатель источника на НОЛЬ. Если вы используете флаги владения, reset их соответственно.

Ответ 3

Плагин/library/что-то не должно возвращать перечисление через переданный указатель ALLOCATION_BEHAVIOR *. Это бесполезно, в лучшем случае. Схема "deallocation" принадлежит данным и должна быть инкапсулирована им.

Я бы предпочел вернуть указатель объекта некоторого базового класса, который имеет виртуальный член функции release(), который основное приложение может вызывать всякий раз, когда он хочет/нуждается, и обрабатывает "dealloaction", как требуется для этого объекта. release() ничего не может сделать, перетащить объект в кеш, указанный в личном memebr объекта, только delete(), в зависимости от того, какое переопределение применяется подклассами плагина.

Если это невозможно, потому что плагин написан на другом языке или построен с другим компилятором, плагин может возвращать функцию, а также данные, чтобы основное приложение могло вызвать ее обратно с указателем данных как параметр для освобождения. Это, по крайней мере, позволяет помещать char * и функцию * в один и тот же объект/структуру на стороне С++, тем самым сохраняя хотя бы некоторое подобие инкапсуляции и позволяя плагину выбирать любую схему освобождения, которую он хочет.

Изменить. Подобная схема также будет работать безопасно, если плагин использовал другую кучу, чем основное приложение, возможно, это в DLL, которая имеет свой собственный распределитель.

Ответ 4

В Linux вы можете проанализировать /proc/self/maps, чтобы извлечь местоположение стека и кучи, а затем проверить, попадает ли указатель в один из диапазонов.

Это не скажет вам, должна ли память обрабатываться свободным или удаленным. Если вы контролируете архитектуру, вы можете позволить плагину освободить выделенную память, добавив соответствующую API (IOW, a plugin_free, которая симметрична вашей execute). Другой распространенный шаблон - отслеживать распределения в объекте контекста (созданного во время init), который передается плагину при каждом вызове и затем используется плагином при завершении работы, чтобы выполнить очистку.

Ответ 5

Как они выделяют что-то в стеке, которое вы можете затем освободить, так как они вернулись? Это просто ужасно умрет. Даже использование этого умрет ужасно.

Если вы хотите проверить, вернули ли вы указатель на статические данные, вы, вероятно, захотите получить верхнюю и нижнюю части кучи (которые, я уверен, доступны на linux, используя sbrk) и см. если возвращаемый указатель находится в этом диапазоне или нет.

Конечно, возможно, что даже допустимый указатель в этом диапазоне не должен быть освобожден, потому что они спрятали еще одну копию, которую они будут использовать позже. И если вы не доверяете им, вы не должны им доверять.

Ответ 6

Я использую следующий код для проверки студенческих заданий. Возвращение стека памяти является распространенной ошибкой, поэтому я хотел автоматически проверить это.

Использование sbrk

Этот метод должен работать на всех вариантах Unix и на всех архитектурах ЦП.

#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

bool points_to_heap(void* init_brk, void* pointer){
    void* cur_brk = sbrk(0);
    return ((init_brk <= pointer) && (pointer <= cur_brk));
}

int main(void){
    void* init_brk = sbrk(0);
    int* heapvar = malloc(10);
    int i = 0;
    int* stackvar = &i;
    assert(points_to_heap(init_brk, heapvar));
    assert(!points_to_heap(init_brk, stackvar));
    return 0;
}

Использование /proc/self/maps

Две проблемы с этим методом:

  • Этот код специфичен для Linux, работающего на 64-битном процессоре x86.
  • Этот метод, похоже, не работает в модульных тестах, написанных с использованием libcheck. Там все переменные стека также рассматриваются как переменные кучи.
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

void get_heap_bounds(uint64_t* heap_start, uint64_t* heap_end){
    FILE *stream;
    char *line = NULL;
    size_t len = 0;
    ssize_t nread;

    stream = fopen("/proc/self/maps", "r");

    while ((nread = getline(&line, &len, stream)) != -1) {
        if (strstr(line, "[heap]")){
            sscanf(line, "%" SCNx64 "-%" SCNx64 "", heap_start, heap_end);
            break;
        }
    }

    free(line);
    fclose(stream);
}

bool is_heap_var(void* pointer){
    uint64_t heap_start = 0;
    uint64_t heap_end = 0;
    get_heap_bounds(&heap_start, &heap_end);

    if (pointer >= (void*)heap_start && pointer <= (void*)heap_end){
        return true;
    }
    return false;
}

Обратная связь по этому коду приветствуется!

Ответ 7

Вам нужно использовать некоторые инструменты отладки, чтобы определить, находится ли указатель в стеке или в куче. В Windows загрузите Sysinternals Suite. Это предоставляет различные средства для отладки.