Необработанное исключение в Rad Studio. - PullRequest
14 голосов
/ 20 июня 2011

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

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

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

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

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

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

Я собрал проанализированный ряд полных дампов сбоев.Я обнаружил, что сбой на самом деле происходил в том же месте каждый раз.Вот данные об исключениях из дампов (они всегда одинаковы, за исключением, конечно, 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

Стоит отметить, что в 0x773F24ED, по-видимому, имеется функциональный эпилог, что скорее говорит о том, что RtlQueryCriticalSectionOwner является красной сельдью.Аналогично, эпилог функции ставит под сомнение 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

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

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

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

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

Буду признателен за любые идеи или предложения.

Ответы [ 4 ]

9 голосов
/ 17 сентября 2012

Я решил вышеупомянутую проблему, используя модифицированную версию обходного пути 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)) по ожидаемому адресу, процедура исправленияничего не делает и выходит.

Надеюсь, что поможет,
Андреас

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

Footnote2
Та же функция в C ++:

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;
   }
}
0 голосов
/ 25 января 2013

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

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

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

0 голосов
/ 12 сентября 2012

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

Но ...

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

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

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

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

0 голосов
/ 04 августа 2011

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

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

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

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

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

...