2011-06-20 23 views
14

Mam dużą aplikację, która ostatnio zaczęła wykazywać dziwne zachowanie podczas działania w debugerze. Po pierwsze, Podstawy:Nieobsługiwany wyjątek w programie Rad Studio Debugger Wątek

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

Obserwowany objawem jest to, że kiedy debugger jest dołączony do wniosku, że niektóre zadania spowodować awarię aplikacji. Szczegóły są ponadto kłopotliwe: Moja aplikacja zatrzymuje się z komunikatem systemu Windows, mówiąc: "Twoja aplikacja przestała działać." Pomocne jest również wysyłanie minizrzutu do firmy Microsoft.

Należy zauważyć: aplikacja nie zawiesza się, gdy debugger nie jest podłączony. Ponadto debugger nie wskazuje żadnych wyjątków ani innych problemów podczas działania aplikacji.

Ustawienie i przechodzenie przez punkty przerwania wydaje się mieć wpływ na moment, w którym aplikacja ulega awarii, ale podejrzewam, że jest to objaw debugowania wątku innego niż problematyczny.

Te awarie występują również na komputerach moich współpracowników, z takim samym zachowaniem, jakie obserwuję. Prowadzi mnie to do nie podejrzenia, że ​​nieudana instalacja czegoś na moim komputerze w szczególności. Moi koledzy, u których występuje ten problem, również mają 64-bitowy system Windows 7. Nie mam kolegów, którzy nie doświadczają tego problemu.

Zebrałem przeanalizowaną liczbę pełnych zrzutów z awarii. Odkryłem, że niepowodzenie faktycznie za każdym razem miało miejsce w tym samym miejscu. Oto dane z wyjątkiem składowisk (jest zawsze taka sama, z wyjątkiem oczywiście ThreadID):

Exception Information 

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

Google ujawnia, że ​​kod 0x4000001F jest rzeczywiście STATUS_WX86_BREAKPOINT. Microsoft niepoprawnie opisuje go jako "kod statusu wyjątku, który jest używany przez podsystem emulacji Win32 x86."

Oto szczegóły stosu (co nie wydaje się zmieniać):

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 

Warto zauważyć, że nie wydaje się być funkcja w 0x773F24ED epilog, co raczej sugeruje, że RtlQueryCriticalSectionOwner jest czerwony śledź. Podobnie funkcja epilog rzuca wątpliwość na RtlQueryProcessLockInformation. Przesunięcie 0x5C69 rzuca cień na RtlUlonglongByteSwap. Pozostałe symbole wyglądają jednak legalnie.

W szczególności RtlpQueryProcessDebugInformationRemote wygląda legalnie. Niektórzy ludzie w Internecie (http://www.cygwin.com/ml/cygwin-talk/2006-q2/msg00050.html) wydają się myśleć, że są one tworzone przez debugger w celu zbierania informacji o debugowaniu. Ta teoria wydaje mi się rozsądna, ponieważ wydaje się, że pojawia się tylko wtedy, gdy dołączony jest debugger.

Jak zawsze, gdy coś się załamuje, coś się zmieniło, co je zepsuło. W tym przypadku to coś dynamicznie ładuje nową bibliotekę dll. Mogę spowodować awarię, aby przestać się dzieje, nie ładując dynamicznie konkretnej biblioteki DLL. Nie jestem przekonany, że ładowanie DLL jest związane, ale tutaj są szczegóły, na wszelki wypadek:

źródło

DLL C. Oto opcje kompilacji, które nie są ustawione na domyślne:

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

(Opcje projektu mówią False jest domyślny dla Dynamic RTL, chociaż został ustawiony na True, gdy stworzyłem projekt dll.)

Dll jest ładowany z LoadLibrary i zwolniony z FreeLibrary. Wszystko wydaje się być w porządku przy ładowaniu i rozładowywaniu modułu.Jednak wkrótce po wyładowaniu biblioteki (za pomocą FreeLibrary) wspomniany wątek powoduje awarię programu. W przypadku debugowania usunąłem wszystkie faktyczne wywołania do biblioteki (w tym, aby przeprowadzić więcej testów, DllMain). Żadna kombinacja wywołań, połączeń, DllMain lub DllMain, ani nic innego nie zmieniło w żaden sposób zachowania awarii. Wystarczy załadować i wyładować dll wywoła awarię później.

Co więcej, zmiana biblioteki DLL w celu użycia Dynamicznego RTL powoduje również przerwanie wątku debuggera. Jest to niepożądane, ponieważ skompilowana biblioteka dll powinna być użyteczna bez dostępnego oprogramowania CodeGear Runtime. Również rozmiar dll jest ważny. Kod C zawarty w bibliotece dll nie korzysta z żadnych bibliotek. (Nie zawiera nagłówków, nawet standardowych nagłówków bibliotek, nie zawiera malloc/free, printf, no nicin.) Zawiera tylko funkcje, które zależą wyłącznie od ich danych wejściowych i nie wymagają dynamicznego przydzielania.) Jest to również niepożądane, ponieważ "naprawia" błąd poprzez zmianę rzeczy, dopóki nie działa bez zrozumienia, dlaczego działa, to naprawdę nigdy nie jest dobry plan. (Zwykle prowadzi to do nawrotów błędów i dziwnych praktyk kodowania, ale tak naprawdę, jeśli nie mogę znaleźć niczego innego, mogę przyznać się do porażki z tego powodu.)

I w końcu mój problem może być związany do jednego z tych zagadnień: byłoby mile widziane

Wszelkie pomysły i sugestie.

+0

Czy wątek kończy się po wyładowaniu biblioteki DLL? Jeśli tak, powinieneś wywoływać FreeLibraryAndExitThread zamiast wykonywać dwa różne połączenia. Czy używasz debugowania wielowątkowego (/ MTd) dla biblioteki środowiska wykonawczego zarówno dla biblioteki DLL, jak i aplikacji? – CariElf

+0

Do pierwszego nie. Biblioteka DLL jest ładowana i rozładowywana w głównym wątku. Do drugiego, tak mi się wydaje. – alficles

+0

To jest trudny problem. Wygląda na to, że problem dotyczy kodu uruchamiania biblioteki uruchomieniowej C. Upewnij się, że program C++ Builder nie jest statycznie połączony z pewną biblioteką/pakietem. Jakie są argumenty wiersza poleceń do linkera? Musiałem rozwiązać problem z błędem kodu startowego TChart (przed wywołaniem głównej funkcji). Co za ból ... – Mike

Odpowiedz

0

Po prostu pomysł ...

Być może trzeba będzie zamknąć wątek. Stan, który obserwujesz, wydaje się być trochę po rzeczywistym błędzie.

Po pierwsze, twoje ślady stosu wydają mi się niepełne. Co to jest podstawowy katalog główny stosu tego wątku? Jakie było pochodzenie tego wątku?

I, w debugerze VS istnieje możliwość włamania do wyjątków, (Debugowanie-> Wyjątki ...-> [Dodaj]). Wtedy wszystkie wątki zostaną zamrożone w momencie wystąpienia wyjątku. Nie wiem o RAD, ale sztuczka, aby zrobić to programowo wydaje się być WaitForDebugEvent().

Mogę się mylić, ale myślę, że istnieje spora szansa, że ​​błąd jest w debugerze, a nie w twoim kodzie. W takim przypadku bolesne obejście jest w pełni wybaczalne IMHO. Powodzenia!

0

nie mogę odpowiedzieć na to pytanie, ponieważ nie widzę kodu ...

Ale ...

1) w Borland C++ przynajmniej w C++ z BDS nie może być udowodnione problem z funkcją realloc w bibliotece wielowątkowej. Czy twój kod C++ używa realloc?

2) Prezentowany stos jest prawdopodobnie wywoływany w wyniku tego, że twój kod rzeczywiście trafia w "CALL BAD_ADRESS" i może się zdarzyć w wyniku błędu we własnym kodzie. Innymi słowy, w ładowanej bibliotece DLL istnieje prawdopodobnie funkcja, która robi coś, co nadpisuje kod wykonywalny w twoim programie śmieciami, a kiedy uruchomi się sekcja "śmieci", ulega awarii.

Innym sposobem jest, jeśli coś w bibliotece dll C++ modyfikuje stos poniżej, w którym działa, a następnie twój kod trafia to później.

3) Sprawdź ustawienia flag procesora dla biblioteki DLL.Biblioteki Borland czasami używają flag kolidujących z CPU po wejściu i może być konieczne zapisanie i przywrócenie przed wywołaniem w bibliotece DLL. Na przykład, jeśli wywołasz wtyczkę VST wykonaną w C++ z Delphi i nie ustawisz poprawnie flag, możesz uzyskać kolejne dzielenie przez zero błędów z wtyczki VST, która została skompilowana z wyłączonym wyjątkiem.

9

I rozwiązać Powyższy problem za pomocą zmodyfikowanej wersji obejście PatchINT3, który został opublikowany w 2007 roku dla 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; 

połączenia to rutynowe raz po załadowaniu biblioteki DLL w swoim wątku. Poprawka naprawia punkt przerwania użytkownika w wersji ntdll.dll 6.1.7601.17725 i zmienia ją na NOP.

Jeśli nie ma punktu przerwania użytkownika (kod operacji INT3 (= $ CC)) pod spodziewanym adresem, procedura łatania nic nie robi i kończy działanie.

nadzieję, że pomoże,
Andreas

Przypis
Oryginalny źródłem PatchINT3 można znaleźć tutaj:
http://coding.derkeiler.com/Archive/Delphi/borland.public.delphi.non-technical/2007-01/msg04431.html

Footnote2
tej samej funkcji w 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

Masz link do źródła tego? – xan

+0

Miałem podobny problem z Delphi2010 i modułem współdzielonym Apache. Ta łata działa dobrze! – mrabat

0

Dziś mieliśmy ten sam problem. W naszym przypadku zdarza się katastrofa, jeśli mamy punkt przerwania po wywołaniu TOpenDialog-> Execute() (który jest przy użyciu okna z powłoki shell32.dll) (Windows 7 x64, C++ Builder XE2)

Po odinstalowaniu iCloud (v2. 1.0.39), kwestia została ponownie rozpatrzona.

Niestety wciąż szukamy podobnego problemu, który nasi klienci mają czasami z naszym produktem w wersji Windows Vista. Po wybraniu pliku przy użyciu TOpenDialog aplikacja ulega awarii w gdiplus.dll z naruszeniem dostępu, usunięcie iCloud wydaje się również rozwiązać problem.

Powiązane problemy