Как отслеживать нарушение доступа "по адресу 00000000"
Я знаю, как создать файл .map для отслеживания ошибок нарушения прав доступа, когда сообщение об ошибке содержит фактический адрес.
Но что, если в сообщении об ошибке говорится
Access violation at address 00000000. Read of address 00000000.
Где я начинаю искать причину этой проблемы...?
Ответы
Ответ 1
Нарушение доступа в любом месте рядом с адресом "00000000" указывает на доступ нулевого указателя. Вы используете что-то прежде, чем оно когда-либо было создано, скорее всего, или после того, как оно было FreeAndNil() 'd.
Много раз это вызвано доступом к компоненту в неправильном месте во время создания формы или путем использования вашей основной формы и доступа к чему-то в еще не созданном датамодуле.
MadExcept позволяет легко отслеживать эти вещи и бесплатно для некоммерческого использования. (На самом деле, лицензия на коммерческое использование довольно недорогая, а также стоит денег.)
Ответ 2
Принятый ответ не рассказывает всю историю.
Да, всякий раз, когда вы видите нули, используется указатель NULL
. Это потому, что NULL
по определению равен нулю. Поэтому вызов 0 NULL
может не так много говорить.
Интересно, что - сообщение о том, что NULL
упоминается дважды. Фактически, сообщение, которое вы сообщаете, выглядит немного похоже на сообщения, в которых операционные системы Windows-бренда показывают пользователя.
В сообщении говорится, что адрес NULL
попытался прочитать NULL
. Так что это значит? В частности, как сам адрес читается?
Мы обычно думаем о инструкциях по чтению и записи адреса из памяти по определенным адресам. Знание этого позволяет нам анализировать сообщение об ошибке. Сообщение пытается сформулировать, что инструкция по адресу NULL
пыталась прочитать NULL
.
Конечно, нет инструкции по адресу NULL
, поэтому мы думаем о NULL
как о специальном в нашем коде. Но каждая инструкция может рассматриваться как начинающаяся с попытки прочитать себя. Если регистр ЦП EIP
находится по адресу NULL
, тогда ЦП попытается прочитать код операции для инструкции с адреса 0x00000000 (NULL
). Эта попытка чтения NULL
завершится с ошибкой и сгенерирует полученное сообщение.
В отладчике обратите внимание, что EIP
равно 0x00000000 при получении этого сообщения. Это подтверждает описание, которое я вам дал.
Затем возникает вопрос: "Почему моя программа пытается выполнить адрес NULL
". Существует три возможности, которые spring:
- Вы пытаетесь выполнить вызов функции с помощью указателя функции, который вы объявили, присвоенного
NULL
, никогда не инициализировали в противном случае и не выполняете разыменование.
- Аналогично, вы можете вызвать "абстрактный" С++-метод, который имеет запись
NULL
в объекте vtable. Они создаются в вашем коде с синтаксисом virtual function_name()=0
.
- В вашем коде буфер буфера был переполнен при написании нулей. Нули были записаны за конец буфера стека по сохраненному адресу возврата. Когда функция позже выполняет свою инструкцию
ret
, из перезаписанного места памяти загружается значение 0x00000000 (NULL
). Этот тип ошибки, переполнение стека, является eponym нашего форума.
Поскольку вы упоминаете, что вы вызываете стороннюю библиотеку, я укажу, что это может быть ситуация в библиотеке, которая ожидает, что вы укажете указатель функции не NULL
в качестве ввода некоторого API. Они иногда называются функциями "обратного вызова".
Вам придется использовать отладчик, чтобы сузить причину вашей проблемы дальше, но указанные выше возможности помогут вам решить загадку.
Ответ 3
Вы начинаете смотреть рядом с тем кодом, который, как вы знаете, побежали, и вы перестаете смотреть, когда достигнете кода, который, как вы знаете, не запускался.
То, что вы ищете, вероятно, в каком-то месте, где ваша программа вызывает функцию через указатель функции, но этот указатель имеет значение null.
Возможно также, что у вас есть повреждение стека. Возможно, вы перезаписали адрес возврата функции с нулем, и исключение происходит в конце функции. Проверьте наличие возможных переполнений буфера, и если вы вызываете какие-либо функции DLL, убедитесь, что вы использовали правильное соглашение о вызове и количество параметров.
Это не обычный случай использования нулевого указателя, как непризнанная ссылка на объект или PChar. В этих случаях вы получите ненулевое значение "по адресу x". Поскольку инструкция возникла при нулевом адресе, вы знаете, что указатель инструкции CPU не указывал на какую-либо действительную инструкцию. Поэтому отладчик не может показать вам, какая строка кода вызвала проблему - нет строки кода. Вам нужно найти его, найдя код, который ведет к тому месту, где процессор перешел на неверный адрес.
Стол вызовов может оставаться неповрежденным, что должно по крайней мере приблизиться к вашей цели. Если у вас есть повреждение стека, вы, возможно, не сможете доверять стеку вызовов.
Ответ 4
Если вы получаете "Нарушение доступа по адресу 00000000.", вы вызываете указатель функции, который не был назначен - возможно, обработчик события или функция обратного вызова.
например
type
TTest = class(TForm);
protected
procedure DoCustomEvent;
public
property OnCustomEvent : TNotifyEvent read FOnCustomEvent write FOnCustomEvent;
end;
procedure TTest.DoCustomEvent;
begin
FOnCustomEvent(Self);
end;
Вместо
procedure TTest.DoCustomEvent;
begin
if Assigned(FOnCustomEvent) then // need to check event handler is assigned!
FOnCustomEvent(Self);
end;
Если ошибка присутствует в стороннем компоненте и вы можете отследить код нарушения, используйте пустой обработчик событий для предотвращения AV.
Ответ 5
Когда я наткнулся на эту проблему, я обычно начинаю смотреть на места, где я FreeAndNil() или просто xxx: = NIL; переменные и код после этого.
Когда ничего другого не помогло, я добавил функцию Log() для вывода сообщений из разных подозрительных мест во время выполнения, а затем позже просмотрел этот журнал, чтобы отслеживать, где в коде появляется нарушение доступа.
Есть много более элегантных решений, доступных для отслеживания этих нарушений, но если у вас их нет, старомодный метод проб и ошибок работает нормально.
Ответ 6
Вероятно, потому, что вы прямо или косвенно через вызов библиотеки обращаетесь к указателю NULL. В этом конкретном случае похоже, что вы перешли на NULL-адрес, который является b бит волосатым.
По моему опыту, самый простой способ отслеживать это - запустить его с помощью отладчика и выгрузить трассировку стека.
В качестве альтернативы вы можете сделать это "вручную" и добавить много записей в журнал, пока вы не сможете точно определить, какая функция (и, возможно, LOC) произошла.
Посмотрите Stack Tracer, что может помочь вам улучшить отладку.
Ответ 7
Используйте MadExcept. Или JclDebug.
Ответ 8
Я буду второй madExcept и аналогичные инструменты, такие как Eurekalog, но я думаю, что вы также можете пойти с FastMM. При включенном режиме debugmode он должен дать вам некоторые подсказки о том, что не так.
В любом случае, несмотря на то, что Delphi использует FastMM по умолчанию, стоит получить полный FastMM для дополнительного контроля за протоколированием.
Ответ 9
Вот реальное быстрое временное исправление, по крайней мере, до перезагрузки, но оно избавится от постоянного доступа. Я установил программу, которая работает нормально, но по какой-то причине есть точка, которая не была правильно установлена в правильном файле. Поэтому, когда он не может получить доступ к файлу, он открывает доступ, но вместо него он пытается запустить его, поэтому даже поиск местоположения, чтобы остановить его навсегда, будет продолжать появляться все больше и больше каждый 3 секунды. Чтобы остановить это, по крайней мере, временно, сделайте следующее...
- Ctl + Alt + Del
- Откройте диспетчер задач
- Запишите имя программы, запрашивающей доступ (вы можете увидеть ее на вкладке своего приложения)
- Перейдите на вкладку "Процессы".
- Прокрутите страницу до тех пор, пока не найдете, что процесс соответствует имени программы и щелкните по ней.
- Нажмите "Завершить процесс"
Это предотвратит постоянное появление окна, по крайней мере, до перезагрузки. Я знаю, что это не решает проблему, но, как и все, существует процесс устранения, и этот шаг здесь, по крайней мере, сделает его немного менее раздражающим.