Определение, является ли указатель действительным - PullRequest
12 голосов
/ 04 января 2011

По моим наблюдениям, если вызывается free( ptr ), где ptr не является действительным указателем на выделенную системой память, происходит нарушение доступа.Допустим, я звоню free следующим образом:

LPVOID ptr = (LPVOID)0x12345678;
free( ptr );

Это определенно приведет к нарушению доступа.Есть ли способ проверить, что область памяти, на которую указывает ptr, является действительной выделенной системой памятью?

Мне кажется, что часть управления памятью ядра ОС Windows должна знать, какая память былавыделяется и какая память остается для выделения.Иначе как он мог узнать, достаточно ли памяти для выполнения данного запроса? (риторический) При этом представляется разумным заключить, что должна существовать функция (или набор функций), которая позволила бы пользователю определить, является ли указатель действительной памятью, выделенной системой.Возможно, Microsoft не обнародовала эти функции.Если Microsoft не предоставила такой API, я могу только предположить, что это было сделано по преднамеренной и конкретной причине.Принесет ли такая зацепка в систему значительную угрозу безопасности системы?

Ситуационный отчет

Хотя знание того, является ли указатель памяти действительным, может быть полезным во многих сценарияхЭто моя конкретная ситуация:

Я пишу драйвер для нового оборудования, которое должно заменить существующее оборудование, которое подключается к ПК через USB.Мой мандат состоит в том, чтобы написать новый драйвер так, чтобы обращения к существующему API для текущего драйвера продолжали работать в приложениях для ПК, в которых он используется.Таким образом, единственными необходимыми изменениями в существующих приложениях является загрузка соответствующих DLL драйверов при запуске.Проблема в том, что существующий драйвер использует обратный вызов для отправки полученных последовательных сообщений в приложение;указатель на выделенную память, содержащую сообщение, передается от драйвера к приложению посредством обратного вызова.В этом случае приложение несет ответственность за вызов другого API драйвера для освобождения памяти путем передачи того же указателя из приложения в драйвер.В этом сценарии второй API не может определить, действительно ли приложение вернуло указатель на действительную память.

Ответы [ 13 ]

19 голосов
/ 04 января 2011

На самом деле есть некоторые функции, называемые IsBadReadPtr(), IsBadWritePtr(), IsBadStringPtr() и IsBadCodePtr(), которые может делать работу, но не использовать ее никогда .Я упоминаю об этом только для того, чтобы вы знали, что эти параметры не , которые следует использовать.

Вам гораздо лучше убедиться, что вы установили все свои указатели на NULL или 0 когда он указывает на ничто и проверяет это.

Например:

// Set ptr to zero right after deleting the pointee.
delete ptr; // It's okay to call delete on zero pointers, but it
            // certainly doesn't hurt to check.

Примечание: Это может быть проблема производительности на некоторых компиляторах (см. раздел «Размер кода» на этой странице ), поэтому, возможно, стоит сначала выполнить самопроверку с нулем.

ptr = 0;

// Set ptr to zero right after freeing the pointee.
if(ptr != 0)
{
    free(ptr); // According to Matteo Italia (see comments)
               // it's also okay to pass a zero pointer, but
               // again it doesn't hurt.
    ptr = 0;
}

// Initialize to zero right away if this won't take on a value for now.
void* ptr = 0;

Еще лучше использовать какой-либо вариант RAII и никогда не придется иметь дело с указателями напрямую:

class Resource
{
public:
    // You can also use a factory pattern and make this constructor
    // private.
    Resource() : ptr(0)
    {
        ptr = malloc(42); // Or new[] or AcquiteArray() or something
        // Fill ptr buffer with some valid values
    }

    // Allow users to work directly with the resource, if applicable
    void* GetPtr() const { return ptr; }

    ~Resource()
    {
        if(ptr != 0)
        {
            free(ptr); // Or delete[] or ReleaseArray() or something

            // Assignment not actually necessary in this case since
            // the destructor is always the last thing that is called
            // on an object before it dies.
            ptr = 0;            
        }
    }

private:
    void* ptr;
};

Или использовать стандартные контейнеры, если это применимо (что действительно является применением RAII):

std::vector<char> arrayOfChars;
16 голосов
/ 04 января 2011

Краткий ответ: Нет.

В Windows есть функция, которая предположительно сообщает вам, если указатель указывает на реальную память ( IsBadreadPtr () и это похоже) но это не работает , и вы никогда не должны его использовать!

Истинное решение вашей проблемы - всегда инициализировать указатели в NULL и сбрасывать их вNULL, как только вы delete d их.

РЕДАКТИРОВАТЬ на основе ваших правок:

Вы действительно намекаете на гораздо более широкий вопрос: как вы можете быть уверены, что ваш код продолжает функционироватьправильно перед лицом клиентского кода, который облажается?

Это действительно должен быть вопрос самостоятельно.Простых ответов нет.Но это зависит от того, что вы подразумеваете под «продолжать функционировать должным образом».

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

Другая теория - даже не пытаться продолжить, а просто потерпеть неудачу.,Ошибка может быть изящной, и должна включать в себя некоторые подробные записи, чтобы проблема могла быть идентифицирована и, возможно, исправлена ​​в лаборатории.Кик сообщения об ошибках.Расскажите пользователю несколько вещей, чтобы попробовать в следующий раз.Создавайте мини-дампы и отправляйте их автоматически обратно в магазин.Но затем закройте.

Я склонен согласиться со второй теорией.Когда клиентский код начинает отправлять дерьмо, стабильность системы часто оказывается под угрозой.Они могли повредить кучи.Необходимые ресурсы могут быть недоступны.Кто знает, в чем может быть проблема.Вы можете получить некоторые хорошие данные с плохими, но вы даже не знаете, действительно ли хорошие данные хороши.Поэтому закройте как можно быстрее, чтобы снизить риск.

9 голосов
/ 04 января 2011

Чтобы решить вашу конкретную проблему, я не думаю, что вам нужно беспокоиться о проверке указателя.Если приложение передает вашей DLL неправильный адрес, оно представляет проблему управления памятью в приложении .Независимо от того, как вы кодируете свой драйвер, вы не можете исправить реальную ошибку.

Чтобы помочь разработчикам приложений отладить их проблему, вы можете добавить магическое число к объекту, к которому вы возвращаетесьприложение.Когда ваша библиотека вызывается для освобождения объекта, проверьте номер, а если его нет, выведите предупреждение об отладке и не освобождайте его!Т.е.:

#define DATA_MAGIC 0x12345678
struct data {
    int foo;    /* The actual object data. */
    int magic;  /* Magic number for memory debugging. */
};

struct data *api_recv_data() {
    struct data *d = malloc(sizeof(*d));
    d->foo = whatever;
    d->magic = DATA_MAGIC;
    return d;
}

void api_free_data(struct data *d) {
    if (d->magic == DATA_MAGIC) {
        d->magic = 0;
        free(d);
    } else {
        fprintf(stderr, "api_free_data() asked to free invalid data %p\n", d);
    }
}

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

9 голосов
/ 04 января 2011

Нет, вы должны знать, указывают ли ваши указатели на правильно распределенную память.

6 голосов
/ 04 января 2011

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

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

Кроме того, free является функцией стандартной библиотеки C ++, унаследованной от C, а не функцией WinAPI.

5 голосов
/ 04 января 2011

Прежде всего, в стандарте нет ничего, что гарантировало бы такую ​​вещь (free указатель без malloc ed - неопределенное поведение).

Во всяком случае, проходя мимо free, это просто извилистый путь к простой попытке доступа к этой памяти; если вы хотите проверить, является ли память, указанная указателем, доступной для чтения / записи в Windows, вам действительно следует просто попытаться справиться с исключением SEH; это действительно то, что делают функции IsBadxxxPtr, переводя такое исключение в код возврата.

Однако, это подход, который скрывает тонкие ошибки, как объяснено в этом сообщении Раймонда Чена ; Короче говоря, нет никакого безопасного способа определить, указывает ли указатель на что-то допустимое, и я думаю, что если вам нужен где-то такой тест, в этом коде есть некоторый недостаток дизайна.

4 голосов
/ 04 января 2011

Я не собираюсь повторять то, что все уже сказали, просто чтобы добавить к этим ответам, вот почему существуют умные указатели - используйте их!

Каждый раз, когда вам приходится обходить сбои из-за ошибок памяти - сделайте шаг назад, сделайте большой вдох и исправьте основную проблему - пытаться обойти их опасно!

РЕДАКТИРОВАТЬ на основе вашего обновления:

Есть два разумных способа, которые я могу придумать, чтобы сделать это.

  1. Клиентское приложение предоставляет буфер, в который вы помещаете сообщение, что означает, что ваш API не должен беспокоиться об управлении этой памятью - это требует изменений вашего интерфейса и кода клиента.
  2. Выизменить семантику интерфейса и заставить клиентов интерфейса не беспокоиться об управлении памятью (т.е. вы вызываете с указателем на что-то, что является допустимым только в контексте обратного вызова - если клиент требует, они делают свою собственную копиюданные).Это не меняет ваш интерфейс - вы все равно можете сделать обратный вызов с указателем, однако ваши клиенты должны будут проверить, что они не используют буфер вне этого контекста - потенциально, если они это сделают, это, вероятно, не то, что вы хотите, и поэтому можетбудет хорошо, что они это исправят (?)

Лично я бы пошел на последнее, если вы можете быть уверены, что буфер не используется за пределами обратного вызова.Если это так, то вам придется использовать хакерские атаки (например, предложенные с магическим числом - хотя это не всегда гарантированно работает, например, допустим, что в предыдущем блоке была какая-то форма переполнения буфера, и выкак-то переписать магическое число с дерьмом - что там происходит?)

2 голосов
/ 04 января 2011

Управление памятью приложения зависит от разработчика приложения, а не от операционной системы (даже на управляемых языках операционная система не выполняет эту работу, а сборщик мусора).Если вы размещаете объект в куче, вы обязаны правильно его освободить.Если вы этого не сделаете, ваше приложение будет терять память.Операционная система (по крайней мере, в случае с Windows) знает, сколько памяти она имеет для вашего приложения, и будет восстанавливать ее, когда ваше приложение закрывается (или падает), но не существует документированного способа (который работает) для запросаадрес памяти, чтобы увидеть, является ли он выделенным блоком.

Лучшее предложение, которое я могу вам дать: научитесь правильно управлять своей памятью.

1 голос
/ 04 января 2011

Вы, очевидно, определили, что вы закончили с объектом, на который у вас есть указатель, и если этот объект был malloc ed, вы хотите free его.Это не звучит как необоснованная идея, но тот факт, что у вас есть указатель на объект, ничего не говорит вам о том, как был выделен этот объект (с malloc, с new, с new[], сстек, как разделяемая память, как отображенный в памяти файл, как пул памяти APR , использующий сборщик мусора Boehm-Demers-Weiser и т. д.), поэтому нет никакого способачтобы определить правильный способ освобождения объекта (или, если освобождение вообще необходимо; у вас может быть указатель на объект в стеке).Это ответ на ваш настоящий вопрос.

Но иногда лучше ответить на вопрос, который следовало бы задать.И этот вопрос звучит так: «Как я могу управлять памятью в C ++, если я не всегда могу сказать такие вещи, как« как был выделен этот объект и как он должен быть освобожден »?»Это сложный вопрос, и, хотя это нелегко, можно управлять памятью, если следовать нескольким политикам.Всякий раз, когда вы слышите, как люди жалуются на правильное соединение каждого malloc с free, каждого new с delete и каждого new[] с delete[] и т. Д., Вы знаете, что они делают свою жизнь труднее, чем это необходимо,не следуя дисциплинированному режиму управления памятью.

Я собираюсь предположить, что вы передаете указатели на функцию, и когда функция будет выполнена, вы хотите, чтобы она очищала указатели.Эта политика, как правило, невозможно получить право.Вместо этого я бы порекомендовал придерживаться политики, согласно которой (1) если функция получает указатель от кого-то другого, то ожидается, что «кто-то еще» очистит (в конце концов, «кто-то еще» знает, как была распределена память) и (2) если функция выделяет объект, то в документации этой функции будет указано, какой метод следует использовать для освобождения объекта.Во-вторых, я очень рекомендую умные указатели и похожие классы.

Совет Страуструпа :

Если я создаю 10 000 объектов и у меня есть указатели на них, мне нужноудалить эти 10 000 объектов, а не 9 999 и не 10 001.Я не знаю, как это сделать.Если мне придется обработать 10 000 объектов напрямую, я облажусь.... Итак, довольно давно я подумал: «Хорошо, но я могу правильно обрабатывать небольшое количество объектов».Если мне нужно иметь дело с сотней объектов, я могу быть уверен, что правильно обработал 100, а не 99. Если я могу получить число до 10 объектов, я начинаю чувствовать себя счастливым.Я знаю, как убедиться, что я правильно обработал 10, а не только 9. "

Например, вам нужен код, подобный следующему:

#include <cstdlib>
#include <iostream>
#include "boost/shared_ptr.hpp"

namespace {
    // as a side note, there is no reason for this sample function to take int*s
    // instead of ints; except that I need a simple function that uses pointers
    int foo(int* bar, int* baz)
    {
        // note that since bar and baz come from outside the function, somebody
        // else is responsible for cleaning them up
        return *bar + *baz;
    }
}

int main()
{
    boost::shared_ptr<int> quux(new int(2));

    // note, I would not recommend using malloc with shared_ptr in general
    // because the syntax sucks and you have to initialize things yourself
    boost::shared_ptr<int> quuz(reinterpret_cast<int*>(std::malloc(sizeof(int))), std::free);
    *quuz = 3;

    std::cout << foo(quux.get(), quuz.get()) << '\n';
}
1 голос
/ 04 января 2011

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

Тот факт, что конкретный адрес не вызывает free() нарушения прав доступа, не означает, что можно безопасно освободить эту память. Адрес может фактически быть адресом в куче, так что нарушение доступа не происходит, и его освобождение приведет к повреждению кучи. Или это может быть даже действительный адрес для освобождения, и в этом случае вы освободили какой-то блок памяти, который еще может использоваться!

Вы действительно не предложили объяснения того, почему такой плохой подход следует даже рассматривать.

...