Тестирование указателей на валидность (C / C ++) - PullRequest
78 голосов
/ 15 февраля 2009

Есть ли способ определить (программно, конечно), является ли данный указатель "действительным"? Проверить NULL легко, но как насчет таких вещей, как 0x00001234? При попытке разыменования этого типа указателя возникает исключение / сбой.

Кроссплатформенный метод предпочтительнее, но также подходит для конкретной платформы (для Windows и Linux).

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

Ответы [ 28 ]

70 голосов
/ 15 февраля 2009

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

Вы не можете сделать эту проверку. Просто нет способа проверить, является ли указатель «действительным». Вы должны верить, что когда люди используют функцию, которая берет указатель, эти люди знают, что они делают. Если они передают вам 0x4211 в качестве значения указателя, то вы должны верить, что он указывает на адрес 0x4211. И если они «случайно» ударили по объекту, то даже если бы вы использовали какую-нибудь пугающую функцию операционной системы (IsValidPtr или что-то еще), вы все равно заметили бы ошибку и быстро не потерпели неудачу.

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

30 голосов
/ 15 февраля 2009

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

Не лучше ли программисту, использующему ваш API, получить ясное сообщение о том, что его код является поддельным, если он его сбивает, а не скрывает?

27 голосов
/ 15 февраля 2009

На Win32 / 64 есть способ сделать это. Попытайтесь прочитать указатель и перехватить полученное исключение SEH, которое будет выдано при сбое. Если это не бросает, то это действительный указатель.

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

Короче, не делай этого;)

У Раймонда Чена есть запись в блоге на эту тему: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

25 голосов
/ 12 мая 2015

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

  1. После вызова getpagesize () и округления указателя на страницу границу, вы можете вызвать mincore (), чтобы узнать, является ли страница действительной и если это происходит, чтобы быть частью рабочего рабочего процесса. Обратите внимание, что это требует некоторые ресурсы ядра, так что вы должны сравнить его и определить, если вызов этой функции действительно уместен в вашем API. Если ваш API будет обрабатывать прерывания или читать с последовательных портов в память, уместно назвать это, чтобы избежать непредсказуемого поведения.
  2. После вызова stat (), чтобы определить, есть ли доступный каталог / proc / self, вы можете открыть и прочитать / proc / self / maps найти информацию о регионе, в котором находится указатель. Изучите справочную страницу для proc, псевдофайл информации о процессе система. Очевидно, это относительно дорого, но вы можете в состоянии уйти с кэшированием результата разбора в массив Вы можете эффективно искать с помощью бинарного поиска. Также рассмотрим / Proc / Я / smaps. Если ваш API для высокопроизводительных вычислений, то программа захочет узнать о / proc / self / numa, который задокументировано под справочной страницей для numa, неоднородной памяти архитектура.
  3. Вызов get_mempolicy (MPOL_F_ADDR) подходит для высокопроизводительных вычислений API, где есть несколько потоков выполнение, и вы управляете своей работой, чтобы иметь сходство с неоднородной памятью поскольку это относится к ядрам процессора и ресурсам сокетов. Такой API конечно, также скажет вам, если указатель действителен.

В Microsoft Windows есть функция QueryWorkingSetEx, которая документирована в API статуса процесса (также в NUMA API). Как следствие изощренного программирования NUMA API, эта функция также позволит вам выполнять простую работу «тестирование указателей на валидность (C / C ++)», поэтому вряд ли она устареет как минимум в течение 15 лет.

15 голосов
/ 15 февраля 2009

AFAIK нет пути. Вы должны попытаться избежать этой ситуации, всегда устанавливая указатели в NULL после освобождения памяти.

7 голосов
/ 15 февраля 2009

Относительно ответа немного в этой теме:

IsBadReadPtr (), IsBadWritePtr (), IsBadCodePtr (), IsBadStringPtr () для Windows.

Мой совет: держитесь подальше от них, кто-то уже опубликовал это: http://blogs.msdn.com/oldnewthing/archive/2007/06/25/3507294.aspx

Это еще один пост на ту же тему и того же автора (я думаю): http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx («IsBadXxxPtr действительно должен называться CrashProgramRandomly»).

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

7 голосов
/ 15 февраля 2009

Посмотрите на этот и этот вопрос. Также обратите внимание на умных указателей .

6 голосов
/ 15 февраля 2009

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

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

Мы используем несколько различных методов в зависимости от требований к производительности / пропускной способности и надежности. Из наиболее предпочтительных:

  • отдельные процессы с использованием сокетов (часто передача данных в виде текста).

  • отдельные процессы, использующие разделяемую память (если большие объемы данных для передачи).

  • один и тот же процесс разделяет потоки через очередь сообщений (при частых коротких сообщениях).

  • один и тот же процесс разделяет потоки всех передаваемых данных, выделенных из пула памяти.

  • тот же процесс через прямой вызов процедуры - все переданные данные выделяются из пула памяти.

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

Использование пула памяти довольно просто в большинстве случаев и не должно быть неэффективным. Если ВЫ размещаете данные в первую очередь, то тривиально проверять указатели на присвоенные вами значения. Вы также можете сохранить выделенную длину и добавить «волшебные» значения до и после данных, чтобы проверить допустимый тип данных и переполнения данных.

4 голосов
/ 12 апреля 2012

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

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

Вот как я нашел это (в Windows): http://www.cplusplus.com/reference/clibrary/csignal/signal/

Чтобы дать краткий обзор:

#include <signal.h>

using namespace std;

void terminate(int param)
/// Function executed if a segmentation fault is encountered during the cast to an instance.
{
  cerr << "\nThe function received a corrupted reference - please check the user-supplied  dll.\n";
  cerr << "Terminating program...\n";
  exit(1);
}

...
void MyFunction()
{
    void (*previous_sigsegv_function)(int);
    previous_sigsegv_function = signal(SIGSEGV, terminate);

    <-- insert risky stuff here -->

    signal(SIGSEGV, previous_sigsegv_function);
}

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

2 голосов
/ 15 февраля 2009

Установка указателя на NULL до и после использования является хорошей техникой. Это легко сделать в C ++, если вы управляете указателями внутри класса, например (строка):

class SomeClass
{
public:
    SomeClass();
    ~SomeClass();

    void SetText( const char *text);
    char *GetText() const { return MyText; }
    void Clear();

private:
    char * MyText;
};


SomeClass::SomeClass()
{
    MyText = NULL;
}


SomeClass::~SomeClass()
{
    Clear();
}

void SomeClass::Clear()
{
    if (MyText)
        free( MyText);

    MyText = NULL;
}



void SomeClass::Settext( const char *text)
{
    Clear();

    MyText = malloc( strlen(text));

    if (MyText)
        strcpy( MyText, text);
}
...