http://bugs.winehq.org/show_bug.cgi?id=21562
Summary: Multiple init/deinit of msvcrt (std fd's) causes pluto app installer to hang Product: Wine Version: 1.1.37 Platform: x86 URL: http://pluto.brain-killer.org/download/pluto_FE_w32_en _0036.exe OS/Version: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: msvcrt AssignedTo: wine-bugs@winehq.org ReportedBy: focht@gmx.net
Hello,
an interesting one...
WINEDEBUG=+tid,+seh,+loaddll,+msvcrt,+server,+relay wine ./pluto_FE_w32_en_0036.exe >>log.txt 2>&1
The app installer creates an additional worker (unpack) thread from its main thread:
--- snip --- 0009:Call KERNEL32.CreateThread(00000000,00000000,0042b674,00000000,00000000,00436718) ret=0042c3e8 0009: *fd* 6 <- 22 0009: new_thread( access=001f03ff, attributes=00000000, suspend=1, request_fd=6 ) 0009: new_thread() = 0 { tid=001b, handle=0004 } ... 001b:Starting thread proc 0x42b674 (arg=(nil)) ... 0009:Ret KERNEL32.CreateThread() retval=00000004 ret=0042c3e8 --- snip ---
The app installer main thread contains a loop to check if the (unpack) worker thread (handle = 0x4) is still alive. While doing so, it pumps messages and delays the next check, illustrated by following code snippet:
--- snip --- 0009:Ret KERNEL32.Sleep() retval=00000000 ret=004167e4 0009:Call KERNEL32.GetExitCodeThread(00000004,0032fd9c) ret=0042c40f 0009: get_thread_info( handle=0004, tid_in=0000 ) 0009: get_thread_info() = 0 { pid=0008, tid=001b, teb=7ffd4000, affinity=ffffffffffffffff, creation_time=1caa28d3911cf76 (-0.0285120), exit_time=0, exit_code=259, priority=0, last=0 } 0009:Ret KERNEL32.GetExitCodeThread() retval=00000001 ret=0042c40f 0009:Call user32.PeekMessageA(0032fd0c,00000000,00000000,00000000,00000001) ret=004167d9 ... 0009:Ret user32.PeekMessageA() retval=00000000 ret=004167d9 0009:Call KERNEL32.Sleep(00000001) ret=004167e4 --- snip ---
Unfortunately something goes wrong and the app installer refuses to terminate - because the worker checking loop suddenly became a forever loop:
--- snip --- ... 0009:Call KERNEL32.Sleep(00000001) ret=004167e4 0009:Ret KERNEL32.Sleep() retval=00000000 ret=004167e4 0009:Call KERNEL32.GetExitCodeThread(00000004,0032fd9c) ret=0042c40f 0009: get_thread_info( handle=0004, tid_in=0000 ) 0009: get_thread_info() = INVALID_HANDLE { pid=0000, tid=0000, teb=00000000, affinity=00000000, creation_time=0, exit_time=0, exit_code=0, priority=0, last=0 } 0009:Ret KERNEL32.GetExitCodeThread() retval=00000000 ret=0042c40f 0009:Call user32.PeekMessageA(0032fd0c,00000000,00000000,00000000,00000001) ret=004167d9 0009: get_message( flags=00000001, get_win=00000000, get_first=00000000, get_last=ffffffff, hw_id=00000000, wake_mask=00000000, changed_mask=00000000 ) 0009: get_message() = PENDING { win=00000000, msg=00000000, wparam=00000000, lparam=00000000, type=0, time=00000000, active_hooks=80000000, total=0, data={} } ... 0009:Ret user32.PeekMessageA() retval=00000000 ret=004167d9 --- snip ---
The installer code doesn't evaluate the return code of GetExitCodeThread() but instead accesses the exit code variable directly, comparing it to 0x103 -> STILL_ACTIVE. Because the thread once lived in this state, this variable is never touched by Wine in case the thread handle becomes invalid. GetExitCodeThread() fails but the variable still contains the last known "good" value -> STILL_ACTIVE (the app doesn't reinit it).
The app still thinks the worker is alive and goes into forever loop.
Now the culprit: the thread handle, delivered to caller upon returning from CreateThread() is _never_ closed by app explicitly -> CloseHandle() so GetExitCodeThread() should be successful in any case.
We have to go back in time to unearth the problem ... lets look what happens to this thread handle (0x4).
--- snip --- ... 001b:Call KERNEL32.FreeLibrary(10000000) ret=004242bb 001b:Call PE DLL (proc=0x10001148,module=0x10000000 L"unpack.dll",reason=PROCESS_DETACH,res=(nil)) 001b:Ret PE DLL (proc=0x10001148,module=0x10000000 L"unpack.dll",reason=PROCESS_DETACH,res=(nil)) retval=1 001b:Call PE DLL (proc=0x5de0cc9c,module=0x5de00000 L"crtdll.dll",reason=PROCESS_DETACH,res=(nil)) 001b:Ret PE DLL (proc=0x5de0cc9c,module=0x5de00000 L"crtdll.dll",reason=PROCESS_DETACH,res=(nil)) retval=1 001b:Call PE DLL (proc=0x5c7cd8b4,module=0x5c790000 L"msvcrt.dll",reason=PROCESS_DETACH,res=(nil)) ... 001b:trace:msvcrt:DllMain (0x5c790000, DLL_PROCESS_DETACH, (nil)) pid(8), tid(1b), tls(4) 001b:trace:msvcrt:msvcrt_free_mt_locks : uninitializing all mtlocks ... 001b:trace:msvcrt:MSVCRT__close :fd (3) handle (0xfffffff6) 001b:Call KERNEL32.CloseHandle(fffffff6) ret=5c7ad9fc 001b: close_handle( handle=0004 ) 001b: close_handle() = 0 001b:Ret KERNEL32.CloseHandle() retval=00000001 ret=5c7ad9fc 001b:trace:msvcrt:msvcrt_free_fd :fd (3) freed 001b:trace:msvcrt:MSVCRT__close :ok ... 001b:trace:msvcrt:MSVCRT__close :fd (4) handle (0xfffffff5) 001b:Call KERNEL32.CloseHandle(fffffff5) ret=5c7ad9fc 001b: close_handle( handle=0008 ) 001b: close_handle() = 0 001b:Ret KERNEL32.CloseHandle() retval=00000001 ret=5c7ad9fc 001b:trace:msvcrt:msvcrt_free_fd :fd (4) freed 001b:trace:msvcrt:MSVCRT__close :ok ... 001b:trace:msvcrt:MSVCRT__close :fd (5) handle (0xfffffff4) 001b:Call KERNEL32.CloseHandle(fffffff4) ret=5c7ad9fc 001b: close_handle( handle=000c ) 001b: close_handle() = 0 001b:Ret KERNEL32.CloseHandle() retval=00000001 ret=5c7ad9fc 001b:trace:msvcrt:msvcrt_free_fd :fd (5) freed 001b:trace:msvcrt:MSVCRT__close :ok 001b:trace:msvcrt:MSVCRT__fcloseall :closed (3) handles ... 001b:trace:msvcrt:MSVCRT__close :fd (0) handle (0x8) 001b:Call KERNEL32.CloseHandle(00000008) ret=5c7ad9fc 001b: close_handle( handle=0008 ) 001b: close_handle() = INVALID_HANDLE 001b:Ret KERNEL32.CloseHandle() retval=00000000 ret=5c7ad9fc ... 001b:warn:msvcrt:MSVCRT__close :failed-last error (6) ... 001b:trace:msvcrt:MSVCRT__close :ok ... 001b:trace:msvcrt:MSVCRT__close :fd (1) handle (0xc) 001b:Call KERNEL32.CloseHandle(0000000c) ret=5c7ad9fc 001b: close_handle( handle=000c ) 001b: close_handle() = INVALID_HANDLE 001b:Ret KERNEL32.CloseHandle() retval=00000000 ret=5c7ad9fc ... 001b:warn:msvcrt:MSVCRT__close :failed-last error (6) ... 001b:trace:msvcrt:MSVCRT__close :ok ... 001b:trace:msvcrt:MSVCRT__close :fd (2) handle (0x50) 001b:Call KERNEL32.CloseHandle(00000050) ret=5c7ad9fc 001b: close_handle( handle=0050 ) 001b: close_handle() = 0 001b:Ret KERNEL32.CloseHandle() retval=00000001 ret=5c7ad9fc 001b:trace:msvcrt:msvcrt_free_fd :fd (2) freed 001b:Call KERNEL32.GetStdHandle(fffffff4) ret=5c7ac729 001b:Ret KERNEL32.GetStdHandle() retval=0000000c ret=5c7ac729 001b:trace:msvcrt:MSVCRT__close :ok ... 001b:trace:msvcrt:msvcrt_free_console :Closing console handles --- snip ---
*eek* ... the thread handle gets closed as part of msvcrt cleanup!
PROCESS_DETACH -> msvcrt_free_io() -> MSVCRT__fcloseall()
This will close fd(3), fd(4), fd(5) -> stdin,stdout,stderr
Corresponding msvcrt init:
--- snip --- ... 001b:Call PE DLL (proc=0x5c7cd8b4,module=0x5c790000 L"msvcrt.dll",reason=PROCESS_ATTACH,res=(nil)) ... 001b:trace:msvcrt:DllMain (0x5c790000, DLL_PROCESS_ATTACH, (nil)) pid(8), tid(1b), tls(0) ... 001b:Call KERNEL32.GetStartupInfoA(0096e4b8) ret=5c7acdd6 001b:Ret KERNEL32.GetStartupInfoA() retval=00000011 ret=5c7acdd6 001b:Call KERNEL32.GetStdHandle(fffffff6) ret=5c7acf77 001b:Ret KERNEL32.GetStdHandle() retval=00000004 ret=5c7acf77 001b:Call KERNEL32.DuplicateHandle(ffffffff,00000004,ffffffff,5c7db2e0,00000000,00000001,00000002) ret=5c7acfc0 001b: dup_handle( src_process=ffffffff, src_handle=0004, dst_process=ffffffff, access=00000000, attributes=00000002, options=00000002 ) 001b: dup_handle() = 0 { handle=0008, self=1, closed=0 } 001b:Ret KERNEL32.DuplicateHandle() retval=00000001 ret=5c7acfc0 001b:Call KERNEL32.GetStdHandle(fffffff5) ret=5c7acff6 001b:Ret KERNEL32.GetStdHandle() retval=00000008 ret=5c7acff6 001b:Call KERNEL32.DuplicateHandle(ffffffff,00000008,ffffffff,5c7db304,00000000,00000001,00000002) ret=5c7ad03f 001b: dup_handle( src_process=ffffffff, src_handle=0008, dst_process=ffffffff, access=00000000, attributes=00000002, options=00000002 ) 001b: dup_handle() = 0 { handle=000c, self=1, closed=0 } 001b:Ret KERNEL32.DuplicateHandle() retval=00000001 ret=5c7ad03f 001b:Call KERNEL32.GetStdHandle(fffffff4) ret=5c7ad075 001b:Ret KERNEL32.GetStdHandle() retval=0000000c ret=5c7ad075 001b:Call KERNEL32.DuplicateHandle(ffffffff,0000000c,ffffffff,5c7db328,00000000,00000001,00000002) ret=5c7ad0be 001b: dup_handle( src_process=ffffffff, src_handle=000c, dst_process=ffffffff, access=00000000, attributes=00000002, options=00000002 ) 001b: dup_handle() = 0 { handle=0050, self=1, closed=0 } 001b:Ret KERNEL32.DuplicateHandle() retval=00000001 ret=5c7ad0be 001b:trace:msvcrt:msvcrt_init_io :handles (0x8)(0xc)(0x50) --- snip ---
The culprit: msvcrt is actually loaded and unloaded _two_ times!
Upon the first unload sequence all std/fd handles are teared down (including handle 0x4) - this makes the handle values available in server again.
When the worker thread is created it gets the unused handle 0x4 assigned from wineserver. This is the handle which is then polled by installers GetExitCodeThread() loop.
When msvcrt is loaded a second time, GetStdHandle( STD_XXX) will still deliver its original value from PEB (0x4, 0x8, 0xC). Upon second msvcrt unload, fd(3), fd(4), fd(5) are closed again but this time they close something completely different (indication are also fd close errors following), leaving the app installer in forever loop.
Regards