Наиболее эффективная замена IsBadReadPtr?

У меня есть код Visual C++, который получает указатель на буфер с данными, которые должны обрабатываться моим кодом и длиной этого буфера. Из-за ошибки вне моего контроля иногда этот указатель входит в мой код неинициализированным или иным образом непригодным для чтения (т. е. он вызывает сбой, когда я пытаюсь получить доступ к данным в буфере.)

Итак, мне нужно проверить этот указатель, прежде чем я его использую. Я не хочу использовать IsBadReadPtr или IsBadWritePtr, потому что все согласны с тем, что они глючат. (Google их примеры.) Они также не потокобезопасны - это, вероятно, не проблема в этом случае, хотя потокобезопасное решение было бы неплохо.

Я видел предложения в сети по выполнению этого с помощью VirtualQuery или просто делая memcpy внутри обработчика исключений. Однако код, где эта проверка должна быть сделана, чувствителен ко времени, поэтому мне нужна самая эффективная проверка, которая также эффективна на 100%. Любые идеи были бы оцененный.

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

обратите внимание также, что мне все равно, если данные, указанные на действительны. Мой код ищет определенные шаблоны данных и игнорирует данные, если это не так найти их. Я просто пытаюсь предотвратить сбой, который происходит при запуске memcpy на этих данных, и обработка исключения в точке попытки memcpy потребует изменения дюжины мест в моем коде (но если бы у меня было что-то вроде IsBadReadPtr для вызова, мне пришлось бы только изменить код в одном месте).

10 ответов


потокобезопасное решение было бы неплохо

Я предполагаю, что только IsBadWritePtr не является потокобезопасным.

просто делаем memcpy внутри обработчика исключений

это фактически то, что делает IsBadReadPtr ... и если вы сделали это в своем коде, то ваш код будет иметь ту же ошибку, что и реализация IsBadReadPtr: http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx

-- Edit:--

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

  • знайте, какие потоки работают в вашем процессе
  • знаю, где стопки нитей, и насколько они большие
  • пройдите вниз по каждой стопке, delberately касаясь каждой страницы стека по крайней мере один раз, прежде чем начать вызывать isBadReadPtr

кроме того, некоторые комментарии, связанные с URL-адресом выше, также предлагают использовать VirtualQuery.


bool IsBadReadPtr(void* p)
{
    MEMORY_BASIC_INFORMATION mbi = {0};
    if (::VirtualQuery(p, &mbi, sizeof(mbi)))
    {
        DWORD mask = (PAGE_READONLY|PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY);
        bool b = !(mbi.Protect & mask);
        // check the page is not a guard page
        if (mbi.Protect & (PAGE_GUARD|PAGE_NOACCESS)) b = true;

        return b;
    }
    return true;
}

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

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

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

какая функция это?


Почему вы не можете вызвать api

AfxIsValidAddress ((p), sizeof (type), FALSE));


любая реализация проверки обоснованности память подчиняется тем же constriants, которые делают IsBadReadPtr не получится. Можете ли вы опубликовать пример callstack, где вы хотите проверить правильность памяти указателя, переданного вам из Windows? Это может помочь другим людям (включая меня) диагностировать, почему вам нужно это сделать в первую очередь.


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

пример (без кэширования):

BOOL CanRead(LPVOID p)
{
  MEMORY_BASIC_INFORMATION mbi;
  mbi.Protect = 0;
  ::VirtualQuery(((LPCSTR)p) + len - 1, &mbi, sizeof(mbi));
  return ((mbi.Protect & 0xE6) != 0 && (mbi.Protect & PAGE_GUARD) == 0);
}

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

Если вы думаете, что вам это нужно, и (uintptr_t)var


Если вам нужно прибегнуть к проверке шаблонов в данных, вот несколько советов:

  • Если вы упомянули использование IsBadReadPtr, вы, вероятно, разрабатываете для Windows x86 или x64.

  • вы можете проверить диапазон указателя. Указатели на объекты будут выровнены по словам. В 32-разрядных окнах указатели пользовательского пространства находятся в диапазоне 0x00401000-0x7FFFFFFF или для приложений с большим адресом 0x00401000-0xBFFFFFFF. Верхние 2 ГБ/1 ГБ зарезервировано для указателей пространства ядра.

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

  • Если ваш объект имеет VTable, и вы не используете другие классы, сравните его указатель VTable с другим указателем VTable из известного товара объект.

  • диапазон проверьте переменные, чтобы увидеть, если они, возможно, действительны. Например, bools может быть только 1 или 0, поэтому, если вы видите один со значением 242, это явно неправильно. Указатели также могут быть проверены на диапазон и проверены на выравнивание.

  • Если есть объекты, содержащиеся внутри, проверьте их VTables и данные, а также.

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

Если у вас нет хорошего объекта с известным адресом VTable, вы можете использовать эти правила для проверки допустимости VTable:

  • в то время как объект живет в памяти чтения/записи, а указатель VTable является частью объекта, сама VTable будет жить в памяти, которая только для чтения и не исполняется, и будет выровнена по границе слова. Он также будет принадлежать в модуль.
  • записи VTable являются указателями на код, который будет только для чтения и исполняемым, а не для записи. Для адресов кода нет ограничений выравнивания. Код будет принадлежать модулю.

боюсь, вам не повезло - нет возможности надежно проверить валидность указателя. Какой код Microsoft дает вам плохие указатели?


Если вы используете VC++ , то я предлагаю использовать определенные ключевые слова microsoft _ _ try _ _ кроме to и catch HW исключения