Assassin's Creed Origins clobbers rbx in its main module (second of three) TLS callbacks. That is apparent right in the beginning of the TLS callback disassembly when rbx is set to '4' unconditionally without any prior save. That leads to a fault in call_tls_callbacks() which is still in __TRY block and gets handled. However the third TLS callback is not executed and that leads to intermittent hangs later on.
It is rather involved to make a TLS callback test in Wine testsuite as there is no portable way to generate a custom TLS callback. I've made a test program (based on the example here: https://lallouslab.net/2017/05/30/using-cc-tls-callbacks-in-visual-studio-wi...) and compiled it with MSVC. The source code is here: https://gist.github.com/gofman/3287a953bcab3a5c888a8d494461cb8a. The program calls all the callbacks on Windows 10 here.
There is also a similar wrapper already there for i386 on another occasion.
-- v2: ntdll: Preserve rbx register when calling DLL entry point on x64.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/loader.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c index 20a96664825..ae107b3c55b 100644 --- a/dlls/ntdll/loader.c +++ b/dlls/ntdll/loader.c @@ -264,7 +264,7 @@ void WINAPI RtlGetUnloadEventTraceEx(ULONG **size, ULONG **count, void **trace) * that only modifying esi leads to a crash, so use this one to backup * ebp while running the dll entry proc. */ -#ifdef __i386__ +#if defined(__i386__) extern BOOL call_dll_entry_point( DLLENTRYPROC proc, void *module, UINT reason, void *reserved ); __ASM_GLOBAL_FUNC(call_dll_entry_point, "pushl %ebp\n\t" @@ -298,13 +298,36 @@ __ASM_GLOBAL_FUNC(call_dll_entry_point, __ASM_CFI(".cfi_def_cfa %esp,4\n\t") __ASM_CFI(".cfi_same_value %ebp\n\t") "ret" ) -#else /* __i386__ */ +#elif defined(__x86_64__) +extern BOOL WINAPI call_dll_entry_point( DLLENTRYPROC proc, void *module, UINT reason, void *reserved ); +/* Some apps modify rbx in TLS entry point. */ +__ASM_GLOBAL_FUNC(call_dll_entry_point, + "pushq %rbx\n\t" + __ASM_SEH(".seh_pushreg %rbx\n\t") + __ASM_CFI(".cfi_adjust_cfa_offset 8\n\t") + __ASM_CFI(".cfi_rel_offset %rbx,0\n\t") + "subq $32,%rsp\n\t" + __ASM_SEH(".seh_stackalloc 32\n\t") + __ASM_SEH(".seh_endprologue\n\t") + __ASM_CFI(".cfi_adjust_cfa_offset 32\n\t") + "mov %rcx,%r10\n\t" + "mov %rdx,%rcx\n\t" + "mov %r8d,%edx\n\t" + "mov %r9,%r8\n\t" + "call *%r10\n\t" + "addq $32,%rsp\n\t" + __ASM_CFI(".cfi_adjust_cfa_offset -32\n\t") + "popq %rbx\n\t" + __ASM_CFI(".cfi_adjust_cfa_offset -8\n\t") + __ASM_CFI(".cfi_same_value %rbx\n\t") + "ret" ) +#else static inline BOOL call_dll_entry_point( DLLENTRYPROC proc, void *module, UINT reason, void *reserved ) { return proc( module, reason, reserved ); } -#endif /* __i386__ */ +#endif
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
v2: - remove the unintentional change in generic call_dll_entry_point() ("WINAPI" qualifier), thanks Matteo for spotting it.