Необработанное исключение в отладчике Rad Studio

У меня есть большое приложение, которое недавно начало проявлять довольно странное поведение при работе в отладчике. Во-первых, основы:

OS: Windows 7 64-bit.
Application: Multithreaded VCL app with many dlls, bpls, and other components.
Compiler/IDE: Embarcadero RAD Studio 2010.

Наблюдаемый симптом таков: пока отладчик подключен к моему приложению, некоторые задачи приводят к сбою приложения. Подробности также озадачивают: мое приложение останавливается с сообщением Windows, говорящим: "YourApplication перестала работать". И он помогает предложить отправить мини-накопитель Microsoft.

Следует отметить: приложение не падает, когда отладчик не подключен. Кроме того, отладчик не указывает на какие-либо исключения или другие проблемы во время работы приложения.

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

Эти сбои также происходят на компьютерах моих коллег, с тем же поведением, которое я наблюдаю. Это приводит меня к тому, что я не подозреваю о неудачной установке чего-либо на моем компьютере. Мои коллеги, испытывающие эту проблему, также работают с 64-разрядной версией Windows 7. У меня нет коллег, которые не испытывают проблемы.

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

Exception Information

ThreadId:         0x000014C0
Code:             0x4000001F Unknown (4000001F)
Address:          0x773F2507
Flags:            0x00000000
NumberParameters: 0x00000001
    0x00000000

Google показывает, что код 0x4000001F - это STATUS_WX86_BREAKPOINT. Microsoft бесполезно описывает его как "Код состояния исключения, который используется подсистемой эмуляции Win32 x86".

Вот данные стека (которые, похоже, не меняются):

0x773F2507: ntdll.dll+0x000A2507: RtlQueryCriticalSectionOwner + 0x000000E8
0x773F3DAB: ntdll.dll+0x000A3DAB: RtlQueryProcessLockInformation + 0x0000020D
0x773D2ED9: ntdll.dll+0x00082ED9: RtlUlonglongByteSwap + 0x00005C69
0x773F3553: ntdll.dll+0x000A3553: RtlpQueryProcessDebugInformationRemote + 0x00000044
0x74F73677: kernel32.dll+0x00013677: BaseThreadInitThunk + 0x00000012
0x77389F02: ntdll.dll+0x00039F02: RtlInitializeExceptionChain + 0x00000063
0x77389ED5: ntdll.dll+0x00039ED5: RtlInitializeExceptionChain + 0x00000036

Стоит отметить, что существует функция epilog на 0x773F24ED, что скорее предполагает, что RtlQueryCriticalSectionOwner - это красная селедка. Аналогично, функция epilog ставит под сомнение RtlQueryProcessLockInformation. Смещение 0x5C69 ставит под сомнение RtlUlonglongByteSwap. Другие символы выглядят законными.

В частности, RtlpQueryProcessDebugInformationRemote выглядит законно. Некоторые люди в Интернете (http://www.cygwin.com/ml/cygwin-talk/2006-q2/msg00050.html), похоже, считают, что он создан отладчиком для сбора отладочной информации. Мне кажется, эта теория кажется мне интересной, так как она появляется только при прикреплении отладчика.

Как всегда, когда что-то ломается, что-то изменилось, что сломало его. В этом случае что-то динамически загружает новую dll. Я могу привести к тому, что авария перестанет происходить, не динамически загружая конкретную DLL. Я не уверен, что загрузка dll связана, но вот подробности, на всякий случай:

Источник dll - C. Здесь указаны параметры компиляции, которые не установлены по умолчанию:

Language Compliance: ANSI
Merge duplicate strings: True
Read-only strings: True
PCH usage: Do not use
Dynamic RTL: False

(Параметры проекта говорят, что для динамического RTL по умолчанию установлено значение False, хотя при создании проекта dll было установлено значение True.)

DLL загружается с LoadLibrary и освобождается FreeLibrary. Кажется, все хорошо с загрузкой и разгрузкой модуля. Однако вскоре после того, как библиотека выгружена (с FreeLibrary), вышеупомянутый поток вылетает из программы. Для отладки я удалил все фактические вызовы в библиотеку (в том числе, для большего тестирования, DllMain). Никакая комбинация вызовов или не звонков, DllMain или DllMain или что-либо еще, похоже, каким-либо образом изменяло поведение аварии. Просто загрузка и выгрузка dll вызывает крах позже.

Кроме того, изменение dll для использования Dynamic RTL также приводит к прекращению аварийного потока отладчика. Это нежелательно, потому что скомпилированная dll действительно должна использоваться без возможности использования CodeGear Runtime. Кроме того, размер dll важен. Код C, содержащийся в dll, не использует никаких библиотек. (Он не содержит заголовков, даже стандартных заголовков библиотек. Нет malloc/free, no printf, no nothin '. Он содержит только функции, которые зависят исключительно от их входов и не требуют динамического выделения.) Это также нежелательно, потому что "исправление" ошибка, изменяя материал, пока он не работает, не понимая, почему он работает, на самом деле никогда не является хорошим планом. (Это приводит к появлению ошибок и странным правилам кодирования. Но на самом деле, если я не могу найти что-либо еще, я могу признать поражение по этому счету.)

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

Любые идеи или предложения будут оценены.

Ответы

Ответ 1

Я решил вышеупомянутую проблему, используя модифицированную версию обходного решения PatchINT3, которое было опубликовано в 2007 году для BDS 2006:

procedure PatchINT3;
const
  INT3: Byte = $CC;
  NOP: Byte = $90;
var
  NTDLL: THandle;
  BytesWritten: DWORD;
  Address: PByte;
begin
  if Win32Platform <> VER_PLATFORM_WIN32_NT then
    Exit;
  NTDLL := GetModuleHandle('NTDLL.DLL');
  if NTDLL = 0 then
    Exit;
  Address := GetProcAddress(NTDLL, 'RtlQueryCriticalSectionOwner');
  if Address = nil then
    Exit;
  Inc(Address, $E8);
  try
    if Address^ <> INT3 then
      Exit;

    if WriteProcessMemory(GetCurrentProcess, Address, @NOP, 1, BytesWritten)
      and (BytesWritten = 1) then
      FlushInstructionCache(GetCurrentProcess, Address, 1);
  except
    //Do not panic if you see an EAccessViolation here, it is perfectly harmless!
    on EAccessViolation do
      ;
  else
    raise;
  end;
end;

Вызов этой процедуры один раз после загрузки DLL в поток. Патч исправляет точку останова пользователя в версии ntdll.dll 6.1.7601.17725 и изменяет ее на NOP.

Если на ожидаемом адресе нет точки прерывания пользователя (INT3 (= $CC)), процедура патча ничего не делает и выходит.

Надеюсь, что поможет,
Andreas

Сноска
Оригинальный источник PatchINT3 можно найти здесь:
http://coding.derkeiler.com/Archive/Delphi/borland.public.delphi.non-technical/2007-01/msg04431.html

Footnote2
Эта же функция в С++:

void PatchINT3()
{
   unsigned char INT3   = 0xCC;
   unsigned char NOP    = 0x90;

   if (Win32Platform != VER_PLATFORM_WIN32_NT)
   {
      return;
   }

   HMODULE ntdll = GetModuleHandle(L"NTDLL.DLL");
   if (ntdll == NULL)
   {
      return;
   }

   unsigned char *address = (unsigned char*)GetProcAddress(ntdll,
      "RtlQueryCriticalSectionOwner");
   if (address == NULL)
   {
      return;
   }

   address += 0xE8;

   try
   {
      if (*address != INT3)
      {
         return;
      }

      unsigned long bytes_written = 0;
      if (WriteProcessMemory(GetCurrentProcess(), address, &NOP, 1,
         &bytes_written) && (bytes_written == 1))
      {
         FlushInstructionCache(GetCurrentProcess, address, 1);
      }
   }
   catch (EAccessViolation &e)
   {
      //Do not panic if you see an EAccessViolation
      //here, it is perfectly harmless!
   }
   catch(...)
   {
      throw;
   }
}

Ответ 2

Просто идея...

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

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

И в отладчике VS есть возможность отключить исключения (Debug- > Exceptions...- > [Add]). Затем все потоки будут заморожены в момент возникновения исключения. Я не знаю о RAD, но трюк, чтобы сделать это программно, кажется WaitForDebugEvent().

Я мог ошибаться, но я думаю, что есть вероятность, что ошибка в отладчике не будет вашим кодом. В этом случае обходное решение IMHO полностью простительно. Удачи!

Ответ 3

Я не могу ответить на это, потому что я не вижу код...

Но...

1) В Borland С++, по крайней мере, с С++ из BDS, может быть доказуемая проблема с функцией realloc в многопоточной библиотеке. Является ли ваш код на С++ использованием realloc?

2) Стек, который вы показываете, более чем вероятно вызывается в результате того, что ваш код на самом деле нажимает "CALL BAD_ADRESS", и это может произойти в результате ошибки в вашем собственном коде. Другими словами, в загружаемой DLL, вероятно, есть функция, которая выполняет что-то, что перезаписывает исполняемый код в вашей программе с помощью нежелательной почты, а затем, когда запущена уже запущенная секция, она сбой.

Другой способ - если что-то в dll С++ модифицирует стек ниже, где он запускается, а затем ваш код нажимает это позже.

3) Проверьте настройки флагов CPU для вашей DLL. Библиотеки Borland иногда используют конфликтующие флаги CPU при входе, и вам может потребоваться сохранить и восстановить до вызова в DLL. Например, если вы вызываете плагин VST с С++ из Delphi и не устанавливаете флаги правильно, вы можете получить последующее деление на нулевые ошибки из плагина VST, который был скомпилирован с отключенным исключением.

Ответ 4

У нас была такая же проблема сегодня. В нашем случае авария происходит, если имеет точку останова после вызова TOpenDialog- > Execute() (который, как мне кажется, использует диалог из shell32.dll) (Windows 7 x64, С++ Builder XE2)

После удаления iCloud (v2.1.0.39) проблема была перезаписана.

К сожалению, мы все еще рассматриваем аналогичную проблему, с которой наши клиенты несколько раз сталкиваются с нашим продуктом выпуска под Windows Vista. После выбора файла с помощью TOpenDialog приложение сбой в gdiplus.dll с нарушением прав доступа, удаление iCloud также устраняет проблему.