From: Alex Henrie alexhenrie24@gmail.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58837 --- dlls/kernel32/kernel32.spec | 2 +- dlls/kernel32/thread.c | 2 +- include/winbase.h | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec index 3bb49206c6b..7721f87e4ad 100644 --- a/dlls/kernel32/kernel32.spec +++ b/dlls/kernel32/kernel32.spec @@ -645,7 +645,7 @@ @ stdcall GetCurrentProcessorNumber() NTDLL.NtGetCurrentProcessorNumber @ stdcall GetCurrentProcessorNumberEx(ptr) NTDLL.RtlGetCurrentProcessorNumberEx @ stdcall -norelay GetCurrentThread() KERNEL32_GetCurrentThread -@ stdcall -norelay GetCurrentThreadId() KERNEL32_GetCurrentThreadId +@ stdcall -norelay GetCurrentThreadId() @ stdcall -import GetCurrentThreadStackLimits(ptr ptr) @ stdcall -arch=win64 GetCurrentUmsThread() @ stdcall -import GetDateFormatA(long long ptr str ptr long) diff --git a/dlls/kernel32/thread.c b/dlls/kernel32/thread.c index 2ecc3bb5ed8..f0b3932880b 100644 --- a/dlls/kernel32/thread.c +++ b/dlls/kernel32/thread.c @@ -154,7 +154,7 @@ DWORD WINAPI KERNEL32_GetCurrentProcessId(void) * RETURNS * current thread identifier */ -DWORD WINAPI KERNEL32_GetCurrentThreadId(void) +DWORD WINAPI GetCurrentThreadId(void) { return HandleToULong(NtCurrentTeb()->ClientId.UniqueThread); } diff --git a/include/winbase.h b/include/winbase.h index c96f58a61b3..e593e8e0c9b 100644 --- a/include/winbase.h +++ b/include/winbase.h @@ -2677,11 +2677,15 @@ static FORCEINLINE HANDLE WINAPI GetCurrentThread(void) return (HANDLE)~(ULONG_PTR)1; }
+#ifndef _KERNEL32_ + static FORCEINLINE DWORD WINAPI GetCurrentThreadId(void) { return HandleToULong( ((HANDLE *)NtCurrentTeb())[9] ); }
+#endif + static FORCEINLINE DWORD WINAPI GetLastError(void) { return *(DWORD *)((void **)NtCurrentTeb() + 13);
Wait, what? How can this possibly make a difference?
On Thu Oct 23 17:26:48 2025 +0000, Elizabeth Figura wrote:
Wait, what? How can this possibly make a difference?
The program calls SymEnumSymbols to get the list of functions in memory, then it compiles some C code, injects it, and links it to those functions. If it can't find the symbol it's looking for, the linker step fails. The function names returned from SymEnumSymbols have to match what they would be on real Windows.
On Thu Oct 23 17:26:48 2025 +0000, Alex Henrie wrote:
The program calls SymEnumSymbols to get the list of functions in memory, then it compiles some C code, injects it, and links it to those functions. If it can't find the symbol it's looking for, the linker step fails. The function names returned from SymEnumSymbols have to match what they would be on real Windows.
hmm kinda strange...
does SymEnumSymbols() on native return an actual name for the (internal) functions different from the exported one?
(it's more likely, that even when using the MS symbol store, the .pdb for the system modules don't contain all these details, and dbghelp ends up building the symbol's table for the module with the only info available == the PE export table)
or does the program filters on dbghelp for public symbol only ? (SYMOPT_PUBLIC_ONLY in SymSetOption()?) that's not currently supported in builtin's dbghelp and could explain some mixups
does SymEnumSymbols() on native return an actual name for the (internal) functions different from the exported one?
I don't know if it's possible for the internal and external names to be completely different on real Windows, but one small difference is that on 32-bit Windows, the function name reported from SymEnumSymbols is simply GetCurrentThreadId, whereas Wine reports GetCurrentThreadId@0. (This patch only fixes the 64-bit version of Cheat Engine; 32-bit Cheat Engine still can't find the function because Wine isn't stripping off the @0 as it should.)
does the program filters on dbghelp for public symbol only ? (SYMOPT_PUBLIC_ONLY in SymSetOption()?)
I don't see any occurrences of SYMOPT_PUBLIC_ONLY in https://github.com/cheat-engine/cheat-engine
does SymEnumSymbols() on native return an actual name for the (internal) functions different from the exported one?
I don't know if it's possible for the internal and external names to be completely different on real Windows,
yes you can, eg in the .def linker file, in EXPORTS section, use exported_name=internal_name syntax
but one small difference is that on 32-bit Windows, the function name reported from SymEnumSymbols is simply GetCurrentThreadId, whereas Wine reports GetCurrentThreadId@0.
I can't repro this
which wine version are you using and which debug format for kernelbase? AFAICT
* in dwarf, decorated names are stored in DW_attr_linkage_name, which Wine's dbghelp doesn't use * in pdb, only the public symbols are decorated, and public symbols are (no longer) loaded for pdb in Wine's dbghelp
(and there's native dbghelp.dll bundled with the cheat-engine, so which dbghelp is used ? wine's or the bundled)
but one small difference is that on 32-bit Windows, the function name reported from SymEnumSymbols is simply GetCurrentThreadId, whereas Wine reports GetCurrentThreadId@0.
I can't repro this
It looks like the @0 appears in 32-bit Wine because [TSymbolloaderthread.LoadDLLSymbols](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5...) passes SLMFLAG_NO_SYMBOLS to [SymLoadModuleEx](https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symlo...). Here's a minimal example which prints "GetCurrentThreadId" on Windows and "KERNEL32_GetCurrentThreadId@0" on current Wine:
```c // Compile with i686-w64-mingw32-cc and -ldbghelp
#include <windows.h> #include <dbghelp.h> #include <shlwapi.h> #include <stdio.h>
#ifndef SLMFLAG_NO_SYMBOLS #define SLMFLAG_NO_SYMBOLS 4 #endif
BOOL CALLBACK sym_callback(SYMBOL_INFO *info, ULONG size, void *context) { if (StrStrIA(info->Name, "GetCurrentThreadId")) printf("%s\n", info->Name); return TRUE; }
int main(void) { ULONG64 base; SymInitialize(GetCurrentProcess(), NULL, FALSE); base = SymLoadModuleEx(GetCurrentProcess(), NULL, "C:\windows\system32\kernel32.dll", NULL, 0, 0, NULL, SLMFLAG_NO_SYMBOLS); SymEnumSymbols(GetCurrentProcess(), base, NULL, sym_callback, NULL); return 0; } ```
(and there's native dbghelp.dll bundled with the cheat-engine, so which dbghelp is used ? wine's or the bundled)
Cheat Engine is definitely using Wine's when running on Wine.
On Fri Oct 24 23:20:48 2025 +0000, Alex Henrie wrote:
but one small difference is that on 32-bit Windows, the function
name reported from SymEnumSymbols is simply GetCurrentThreadId, whereas Wine reports GetCurrentThreadId@0.
I can't repro this
It looks like the @0 appears in 32-bit Wine because [TSymbolloaderthread.LoadDLLSymbols](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5...) passes SLMFLAG_NO_SYMBOLS to [SymLoadModuleEx](https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symlo...). Here's a minimal example which prints "GetCurrentThreadId" on Windows and "KERNEL32_GetCurrentThreadId@0" on current Wine:
// Compile with i686-w64-mingw32-cc and -ldbghelp #include <windows.h> #include <dbghelp.h> #include <shlwapi.h> #include <stdio.h> #ifndef SLMFLAG_NO_SYMBOLS #define SLMFLAG_NO_SYMBOLS 4 #endif BOOL CALLBACK sym_callback(SYMBOL_INFO *info, ULONG size, void *context) { if (StrStrIA(info->Name, "GetCurrentThreadId")) printf("%s\n", info->Name); return TRUE; } int main(void) { ULONG64 base; SymInitialize(GetCurrentProcess(), NULL, FALSE); base = SymLoadModuleEx(GetCurrentProcess(), NULL, "C:\\windows\\system32\\kernel32.dll", NULL, 0, 0, NULL, SLMFLAG_NO_SYMBOLS); SymEnumSymbols(GetCurrentProcess(), base, NULL, sym_callback, NULL); return 0; }(and there's native dbghelp.dll bundled with the cheat-engine, so
which dbghelp is used ? wine's or the bundled) Cheat Engine is definitely using Wine's when running on Wine.
The more important factor appears to be the presence of DWARF debug symbols in my kernel32.dll. If I strip those out, the above test program prints "GetCurrentThreadId" as expected whether compiled as 32-bit or 64-bit.
It seems reasonable to expect debug builds of Wine to work equally well as non-debug builds. Is there any way to fix this bug without making the function's original name be exactly what it should be?
On Fri Oct 24 23:20:48 2025 +0000, Alex Henrie wrote:
The more important factor appears to be the presence of DWARF debug symbols in my kernel32.dll. If I strip those out, the above test program prints "GetCurrentThreadId" as expected whether compiled as 32-bit or 64-bit. It seems reasonable to expect debug builds of Wine to work equally well as non-debug builds. Is there any way to fix this bug without making the function's original name be exactly what it should be?
What happens when you comment out the (only) call to pe_load_coff_symbol_table?
@alexhenrie thanks for the repro... I now see what happens.
It's caused by some erroneous detection of which debug format to use (not what's in the debug format itself).
a proper fix will require some rework in dbghelp; it'll fix both the decoration in 32bit and the internal vs exported name mixup
(so this MR shall be no longer needed)
I've raised MR!9284 for this.
This merge request was closed by Alex Henrie.