Нарушение доступа между строками

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

procedure TSummaryThread.Execute;
var
  When: String;
begin
  try
    FCarcassListCS := TCriticalSection.Create;
    FCarcassLists := TObjectList.Create;
    while (not Terminated) do
      begin
        try
          When := 'GetMessage';
          if (GetMessage(Msg, 0, 0, 0))
            then begin
                  if (Msg.message = WM_SUMMARISE)
                  then begin
                        When := 'WM_SUMMARISE';
                        Update(True);
                       end
                else if (Msg.message = WM_UPDATE)
                  then begin
                        When := 'WM_UPDATE';
                        Update(False);
                       end
                else if (Msg.message = WM_RELOAD_SETUP)
                  then begin
                        When := 'WM_RELOAD_SETUP';
                        ReloadSetup(Pointer(Msg.wParam));
                       end
                else if (Msg.message = WM_SEND_TO)
                  then begin
                        When := 'WM_SEND_TO';
                        SendToThread(Pointer(Msg.wParam));
                       end
                else if (Msg.message = WM_UPDATE_ORDER_SUM)
                  then begin
                        When := 'WM_UPDATE_ORDER_SUM';
                        DoOrderSummary;
                       end
                else if (Msg.message = WM_SUMMARISE_LOAD)
                  then begin
                        When := 'WM_SUMMARISE_LOAD';
                        LoadAndSummarise;
                       end
                  else begin
                        When := 'DispatchMessage';
                        DispatchMessage(Msg);
                       end;
                 end;
        except
          on E: Exception do
            Log('Exception (' + E.ClassName + ') in summary  thread at ' + When + ': ' + E.Message);
        end;
      end;
  except
    on E: Exception do
      Log('Exception (' + E.ClassName + ') in summary  thread (2) at ' + When + ': ' + E.Message);
end;

procedure TSummaryThread.Update(Full: Boolean);
var
  CarcassList: TObjectList;
  Where: String;
begin
  try
    Where := 'GetList';
    FCarcassListCS.Enter;
    try
      if (FCarcassLists.Count = 0)
        then raise Exception.Create('No carcass list found');
      CarcassList := FCarcassLists[0] as TObjectList;
      FCarcassLists.Extract(CarcassList);
    finally
      FCarcassListCS.Leave;
    end;
    FSummaryListCS.Enter;
    try
      Where := 'LoadFiles';
      SetFiles;
      try
        Where := 'AddCcs';
        CarcassList := GetShiftCarcassList(CarcassList);
        AddCarcassesToSummaries(CarcassList, Full);
      finally
        CloseFiles;
      end;
      Where := 'Send';
      SendSummaries;
      Where := 'Done';
    finally
      FreeAndNil(CarcassList);
      FSummaryListCS.Leave;
    end;
  except
    on E: Exception do
    begin
      E.Message := 'Update(' + Where + '): ' + E.Message;
      raise;
    end;
  end;
end;

Из журнала я могу сказать, что When := 'WM_UPDATE'; попал, а Where := 'GetList'; нет. Это наводит меня на мысль, что нарушение прав доступа происходит, когда локальная переменная Where в процедуре Update() добавляется в стек.

Какие дополнительные средства диагностики можно добавить, чтобы выяснить причину проблемы? Можно ли как-нибудь проверить, не поврежден ли стек и/или куда в стек добавляется переменная Where?

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

Точная ошибка:

Исключение (EAccessViolation) в сводной ветке на WM_UPDATE: нарушение прав доступа по адресу 004B6759 в модуле «Application.exe». Чтение адреса 00000008


person energ1ser    schedule 23.06.2014    source источник
comment
(1) точное сообщение об ошибке? (2) вы уверены, что объект потока действителен и еще не ушел?   -  person JensG    schedule 24.06.2014
comment
Как узнать, что происходит нарушение прав доступа? Каков фактический симптом/журнал? Неспособность поместить переменную в стек не генерирует AV, и присвоение значения (особенно литерала) String в любом случае не помещает эти данные в стек. В этом коде нет ничего, что могло бы вызвать AV, если вы утверждаете, что AV исходит от самого диспетчера памяти RTL, и в этом случае ваше приложение облажалось, и вам нужно перезапустить его.   -  person Remy Lebeau    schedule 24.06.2014
comment
И кстати, когда дело доходит до отслеживания проблем с памятью, постарайтесь уменьшить выделение памяти, не используйте выделения для отслеживания проблемы. Используйте OutputDebugString() для ведения журнала и используйте такой инструмент, как DebugView, чтобы увидеть эти сообщения, когда вы запускаете приложение за пределами отладчик IDE.   -  person Remy Lebeau    schedule 24.06.2014
comment
@JensG Я добавил точное сообщение об ошибке. Верхняя часть кода принадлежит методу TSummaryThread.Execute, поэтому я думаю, что объект потока все еще действителен.   -  person energ1ser    schedule 24.06.2014
comment
Сообщение об ошибке, которое вы показали, означает, что доступ к члену класса осуществляется через указатель nil. Единственное место, где показанный вами код обращается к любому такому указателю, - это вызов Update() через указатель Self, который сам по себе не вызовет AV до тех пор, пока Update() не попытается получить доступ к члену класса, или если сам Update() является виртуальным и, следовательно, опирается на vtable вызываемого объекта.   -  person Remy Lebeau    schedule 24.06.2014
comment
@RemyLebeau Update() не является виртуальным. Объявлено так процедура Update(Full: Boolean);   -  person energ1ser    schedule 24.06.2014
comment
@ Energ1ser: вызов невиртуальной процедуры — это просто простой переход, поэтому виртуальная таблица не задействована. Код, который вы показали, не может вызвать AV, поэтому он должен быть в коде, который вы не показали.   -  person Remy Lebeau    schedule 24.06.2014
comment
Ваш код не достигает строки, которая добавляет 'Update('+Where+')' к E.Message, поэтому AV не может происходить внутри блока try, который вы показали для Update(), он должен происходить где-то еще. Если не произойдет сбой самого prepend, что восходит к возможности повреждения диспетчера памяти RTL. Если бы это происходило, вы могли бы повредить случайную память в любое время из любого места кода, и Update() был бы просто ничего не подозревающей жертвой, если бы ему довелось получить доступ к поврежденной памяти. Это гораздо более редкая возможность, и ее гораздо сложнее диагностировать и исправить, если она случайная.   -  person Remy Lebeau    schedule 24.06.2014
comment
В каком контексте выполняется попытка... кроме блока? Похоже, что это глобальное пространство имен, но у вас есть объект «Msg», на который оно ссылается. Это может быть источником исключения. Я полагаю, что он должен быть расположен внутри члена класса TSummaryThread, который имеет тег «сообщение» в конце. Пожалуйста, покажите объявление метода, которое содержит его в заголовке класса, а также то, как он выглядит вокруг блока try...except, и где объявлен экземпляр или ссылка объекта 'Msg'.   -  person David Schwartz    schedule 24.06.2014
comment
Я никогда не думал о сбое prepend. Я все еще думаю, что это должно быть в предоставленном коде, поскольку между строкой When := 'WM_UPDATE'; и строкой Update(False)' ничего нет. После него тоже ничего нет, кроме блока try.   -  person energ1ser    schedule 24.06.2014
comment
Что такое Msg?   -  person davea    schedule 24.06.2014
comment
@davea Msg — это запись Windows.TMsg   -  person energ1ser    schedule 24.06.2014
comment
@ Energ1ser: На самом деле есть код, выполняемый до try внутри Update() - прологи настраивают фрейм стека и фрейм обработчика исключений. Ни чего не может скинуть AV. И вы не ответили на вопрос Дэвида о том, откуда на самом деле берется TMsg. Это WindowProc? Обработчик событий OnMessage? Обработчик message? Вам нужно предоставить больше контекстной информации, если вы ожидаете, что люди будут помогать в дальнейшем. В нынешнем виде код, который вы показали, НЕ диагностируется как есть.   -  person Remy Lebeau    schedule 24.06.2014
comment
@RemyLebeau Я добавил больше кода, но больше не могу добавлять, не добавляя сотни строк.   -  person energ1ser    schedule 24.06.2014
comment
Включите madexcept в процесс и получите реальную информацию. Выглядит как нулевой объект. Возможно, «я» равно нулю.   -  person David Heffernan    schedule 24.06.2014
comment
Мне кажется, что self - ЕДИНСТВЕННЫЙ кандидат.   -  person Hugh Jones    schedule 24.06.2014
comment
от +1 до Self. Вот почему я задал вопрос (2) выше.   -  person JensG    schedule 24.06.2014
comment
@Energ1ser сказал: но не может больше добавлять, не добавляя сотни строк. Полное определение класса может оказаться полезным, и его не должно быть слишком много.   -  person Disillusioned    schedule 24.06.2014
comment
@Energ1ser сказал: Верхний фрагмент кода взят из метода TSummaryThread.Execute, поэтому я думаю, что объект потока все еще действителен. Ваша логика ошибочна. Верхняя часть кода находится за пределами цикла while. Таким образом, экземпляр может изначально быть действительным. Затем другой поток уничтожает его, и следующая итерация терпит неудачу. Я предлагаю вам добавить ведение журнала отладки, чтобы знать, когда ваш итоговый поток будет уничтожен.   -  person Disillusioned    schedule 24.06.2014
comment
Наконец, я немного обеспокоен тем, как вы добавили код к своему вопросу «по частям». Вы уверены, что они на 100 % идентичны? Например. Если отсутствует другой код, возможно, When просто содержит значение из предыдущей итерации и неточно отражает когда возникает ошибка.   -  person Disillusioned    schedule 24.06.2014


Ответы (1)


В параметрах проекта вашего исполняемого файла найдите настройки компоновщика. Убедитесь, что параметр Map file настроен на Detailed. Пока вы находитесь на странице настроек компоновщика, проверьте адрес Image base. Скорее всего, это $00400000, если не написать, то запомните, на что оно установлено.

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

Теперь возьмите адрес, указанный в сообщении об исключении (в данном случае $004B6759). Вычтите базовый адрес исполняемого файла ($00400000) из адреса исключения. Это дает вам $6759. Вычтите 1000 долларов (я почти уверен, что это вычитание, если кто-то не укажет, что это следует добавить в комментариях). Остается $5759. Это смещение в исполняемом файле, где произошло нарушение прав доступа.

Используя файл карты, который вы загрузили ранее, загляните в раздел Detailed map of segments. Найдите первую запись, которая меньше или равна $B5759, за которой сразу следует сегмент, превышающий $B5759. Имя блока сканирования, связанного с сегментом (тот, который помечен "M=").

Найдите название объекта в файле карты, вы найдете раздел «Номера строк для сегмента XXX(..) .text», где XXX — это название объекта, который вы ищете. В этом разделе будет список номеров строк и смещение до первого байта кода для этого номера строки. Найдите номер первой строки (на основе адреса), который меньше или равен $B5759, за которым сразу следует смещение, превышающее $B5759.

Это сообщит вам номер строки (и исходного файла), содержащей скомпилированный код, который фактически вызывает нарушение прав доступа.

И, как заявляли другие, учитывая, что исключением является чтение 8 долларов, вы, вероятно, пытаетесь прочитать нулевой указатель или объект... возможно, второе поле записи или объекта (при условии, что 32-битный код).

Удачи.

person William    schedule 24.06.2014