Jak ubić wiszący wątek w natywnym DLL

0

Problem:
Mam aplikację konsolową, która korzysta z zewnętrznej biblioteki, która korzysta z natywnej dllki. Gdy aplikacja się kończy mam wszystkie komunikaty ze swojego kodu, ale samo okno się nie zamyka (podejrzewam, że jako usługa też będzie wisieć). Zrzut pamięci ujawnił, że wszystkie wątki zostały zakończone oprócz jednego, którego lokalizacja wskazuje właśnie na ten natywny dll (okno Threads w Visual Studio). Najlepiej byłoby sprawić, żeby wątek sam się zakończył, ale obawiam się, że nie jestem władny aby to uczynić.

Aplikacja oparta jest na AutoFac. Lifetime scope tworzy dla mnie inna biblioteka i ona nim zarządza.

Próbowałem:

  • Dobrać się do tego wątku w Dispose jednej z klas, ale jedyne co udało mi się otrzymać, to wątki systemowe, z którymi nic nie da się zrobić.
  • Uruchamiać Environment.Exit() w Dispose()
  • Uruchamiać Taska który po Task.Delay().Wait() robi CurrentProcess.Kill()
  • Uruchamiać CurrentProcess.Kill() po Thread.Sleep()

W obu powyższych przypadkach kod się nie uruchamia - podejrzewam, że aplikacja ubija/anuluje uruchomienie wątku, który ma czekać.

Znane mi rozwiązania:

  • Process.CurrentProcess.Kill() w Dispose jednego z obiektów - działa, ale jest bardzo słabe - nie ma gwarancji w jakiej kolejności będą zwalniane obiekty
  • zewnętrzny exec odpalany na Dispose jednego z obiektów (dedykowanego myślę), który po odpowiednim okresie czasu ubije mój proces. To jest moje ostateczne rozwiązanie, które wdrożę, gdy wszystko inne zawiedzie

Czy ktoś z Pań i Panów programistów ma lepszy pomysł jak rozprawić się z tym wątkiem? Będę wdzięczny za wszelkie sugestie.

0

Zawiesić (SuspendThread), pobrać CONTEXT (GetThreadContect), zaalokować pamięć na kod zamykający wątek (VirtualAlloc + ExitThread), ustawić CONTEXT.Eip na zaalokowany kod, odpalić kod wątku (ResumeThread), czekać na zakończenie (WaitForSingleObject) :), to tak kreatywnie :)

0

Bardzo kreatywnie. Ale który wątek zawiesić/przestawić kontekst?

0
Bartosz Wójcik napisał(a):

Zawiesić (SuspendThread), pobrać CONTEXT (GetThreadContect), zaalokować pamięć na kod zamykający wątek (VirtualAlloc + ExitThread), ustawić CONTEXT.Eip na zaalokowany kod, odpalić kod wątku (ResumeThread), czekać na zakończenie (WaitForSingleObject)

@Bartosz Wójcik A to wszystko po to, żeby ubić wątek, który skuteczniej zabijesz przez TerminateThread. Który antywirus pozwoli na wstrzykiwanie kodu do obcego procesu? Skąd weźmiesz kod do wstrzyknięcia i skąd będziesz wiedział, co w tym kodzie pozwoli zakończyć wątek bez błędów?

@Sarrus - czy w Twoim procesie jest więcej wątków niż główny i tej dll? Może też warto przeanalizować na czym wisi ten wątek, może to jakiś obiekt IPC, który będziesz mógł "ręcznie" zamknąć.

0
ŁF napisał(a):

@Sarrus - czy w Twoim procesie jest więcej wątków niż główny i tej dll?

Tak jest ich więcej.

ŁF napisał(a):

Może też warto przeanalizować na czym wisi ten wątek, może to jakiś obiekt IPC, który będziesz mógł "ręcznie" zamknąć.

Jedynie wiem, że w tej dllce. Jak mogę to przeanalizować?

0
Sarrus napisał(a):

Bardzo kreatywnie. Ale który wątek zawiesić/przestawić kontekst?

Bardzo prosto, enumerując procesy https://msdn.microsoft.com/en-us/library/windows/desktop/ms686852(v=vs.85).aspx

ŁF napisał(a):
Bartosz Wójcik napisał(a):

Zawiesić (SuspendThread), pobrać CONTEXT (GetThreadContect), zaalokować pamięć na kod zamykający wątek (VirtualAlloc + ExitThread), ustawić CONTEXT.Eip na zaalokowany kod, odpalić kod wątku (ResumeThread), czekać na zakończenie (WaitForSingleObject)

@Bartosz Wójcik A to wszystko po to, żeby ubić wątek, który skuteczniej zabijesz przez TerminateThread. Który antywirus pozwoli na wstrzykiwanie kodu do obcego procesu? Skąd weźmiesz kod do wstrzyknięcia i skąd będziesz wiedział, co w tym kodzie pozwoli zakończyć wątek bez błędów?

A skąd wiesz, że TerminateThread zadziała? Poza tym ja dałem rozwiązanie kreatywne, a nie najprostsze. Co do wstrzykiwania kodu do WŁASNEGO procesu nie widzę problemu. Nie wiem skąd wziąłeś fakt, że to obcy proces. Skąd weźmiesz kod? Jako programista wygenerujesz go sobie, np. biblioteką AsmJit(). Nie chodzi o zakończenie wątku bez błędów, ale jego ubicie, jeśli chodzi o antywirusy to też nie wiem skąd to wziąłeś (możesz wymienić które to zablokują, żebyśmy mieli jaśniejszy obraz sytuacji?), czytałeś w ogóle pytanie?

1
Sarrus napisał(a):
ŁF napisał(a):

@Sarrus - czy w Twoim procesie jest więcej wątków niż główny i tej dll?

Tak jest ich więcej.

ŁF napisał(a):

Może też warto przeanalizować na czym wisi ten wątek, może to jakiś obiekt IPC, który będziesz mógł "ręcznie" zamknąć.

Jedynie wiem, że w tej dllce. Jak mogę to przeanalizować?

Reverse engineering, wrzuć sobie do IDA i szybko zlokalizujesz co tworzy wątki po funkcji CreateThread() jak dobrze pójdzie to znajdziesz też wskaźnik gdzie zapisuje uchwyt i pobierając adres jakiejś dowolnej funkcji, będziesz mógł wywołać TerminateThread() używając relatywnego położenia uchwytu wątku względem dowolnej eksportowanej funkcji z załadowanej biblioteki.

1
Bartosz Wójcik napisał(a):

Reverse engineering, wrzuć sobie do IDA i szybko zlokalizujesz co tworzy wątki po funkcji CreateThread() jak dobrze pójdzie to znajdziesz też wskaźnik gdzie zapisuje uchwyt i pobierając adres jakiejś dowolnej funkcji, będziesz mógł wywołać TerminateThread() używając relatywnego położenia uchwytu wątku względem dowolnej eksportowanej funkcji z załadowanej biblioteki.

Nie doczytałem, że to ten sam proces, reszta się zgadza.
Statyczna analiza dlaczego wątek się wiesza - powodzenia. Od kilku wersji Windows istnieją narzędzia do analizowania Wait Chain, najprostsze z nich znajdziesz w Tak Managerze (Shift+Ctrl+Esc, zakładka Details, PPM na procesie -> Analyze Wait Chain). W process explorerze od Sysinternals AFAIR jest ciut mądrzejsze narzędzie, pozwoli wylistować wątki i ich stany, chyba jest też lista obiektów IPC. Może pomoże https://msdn.microsoft.com/en-us/library/windows/hardware/ff540592(v=vs.85).aspx, zerknij też na odpowiedź z WinDbg w http://superuser.com/questions/10086/hung-process-in-windows-is-there-any-way-to-see-why.

0

Mam stack wątku z Process Explorera:

0 ntoskrnl.exe!KeSynchronizeExecution+0x3de6
1 ntoskrnl.exe!KeWaitForMutexObject+0xc7a
2 ntoskrnl.exe!KeWaitForMutexObject+0x709
3 ntoskrnl.exe!KeWaitForMutexObject+0x375
4 ntoskrnl.exe!ExWaitForRundownProtectionRelease+0x12f8
5 ntoskrnl.exe!KeWaitForMutexObject+0x263a
6 ntoskrnl.exe!KeWaitForMutexObject+0xe8e
7 ntoskrnl.exe!KeWaitForMutexObject+0x709
8 ntoskrnl.exe!KeDelayExecutionThread+0x28c
9 ntoskrnl.exe!NtWaitForSingleObject+0x27c
10 ntoskrnl.exe!setjmpex+0x3983
11 wow64cpu.dll!TurboDispatchJumpAddressEnd+0x540
12 wow64cpu.dll!TurboDispatchJumpAddressEnd+0x421
13 wow64.dll!Wow64LdrpInitialize+0x212
14 wow64.dll!Wow64LdrpInitialize+0x120
15 ntdll.dll!EtwEventProviderEnabled+0x1821
16 ntdll.dll!memset+0x15d6d
17 ntdll.dll!LdrInitializeThunk+0xe

Zainstalowałem WDK i podziałam z WinDbg

EDIT:
Gdy próbuję attachować się WinDbg - proces się kończy :)

EDIT:
Chyba już nie będę drążyć tematu. Ubiję proces i tyle.
Wrzucam stacktrace wątku z dumpa już raczej jako ciekawostkę:

Not Flagged > 2524 0 Main Thread Main Thread SmartWebDLL.dll!0040bfc8 Normal
ntdll.dll!_NtDelayExecution@8()
SmartWebDLL.dll!0040bfc8()
[Frames below may be incorrect and/or missing, no symbols loaded for SmartWebDLL.dll]
SmartWebDLL.dll!0040bf61()
SmartWebDLL.dll!004cb1cf()
SmartWebDLL.dll!004cb090()
SmartWebDLL.dll!004015d7()
ntdll.dll!_LdrxCallInitRoutine@16()
ntdll.dll!LdrpCallInitRoutine()
ntdll.dll!LdrShutdownProcess()
ntdll.dll!RtlExitUserProcess()
mscoreei.dll!ComUtil::QIHelper<struct mpl::type_list<struct ICLRRuntimeHostInternal,class mpl::null_type> >::QI<class ComUtil::IUnknownCommon<struct ICLRRuntimeHostInternal,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type> >(struct _GUID const &,void * *,class ComUtil::IUnknownCommon<struct ICLRRuntimeHostInternal,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type> *)
mscoreei.dll!ComUtil::IUnknownCommon<struct ICLRRuntimeHostInternal,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type,class mpl::null_type>::QueryInterface(struct _GUID const &,void * *)
mscoreei.dll!CLRRuntimeInfoImpl::GetInterfaceInternal(struct _GUID const &,struct _GUID const &,void * *)
mscoreei.dll!CLRRuntimeInfoImpl::GetInterface(struct _GUID const &,struct _GUID const &,void * *)

1 użytkowników online, w tym zalogowanych: 0, gości: 1