On Wed Apr 2 01:09:11 2025 +0000, Yeshun Ye wrote:
> Subjectively, I do believe it is indeed an issue with FreeType, as its
> documentation does not explicitly specify how to handle the high 32
> bits. I submitted the patch to Wine simply because I am more familiar
> with Wine. Since you think this approach is inappropriate, I will try
> submitting the patch to FreeType instead. Should I close this merge
> request now?
Yes, let's close this MR. If the freetype folks push back, we can reopen it.
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/7633#note_99643
x86_64 Windows and macOS both use `%gs` to access thread-specific data (Windows TEB, macOS TSD). To date, Wine has worked around this conflict by filling the most important TEB fields (`0x30`/`Self`, `0x58`/`ThreadLocalStorage`) in the macOS TSD structure (Apple reserved the fields for our use). This was sufficient for most Windows apps.
CrossOver's Wine had an additional hack to handle `0x60`/`ProcessEnvironmentBlock`, and binary patches for certain CEF binaries which directly accessed `0x8`/`StackBase`. Additionally, Apple's libd3dshared could activate a special mode in Rosetta 2 where code executing in certain regions would use the Windows TEB when accessing `%gs`.
Now that the PE separation is complete, GSBASE can be swapped when entering/exiting PE code. This is done in the syscall dispatcher, unix-call dispatcher, and for user-mode callbacks. GSBASE also needs to be set to the macOS TSD when entering signal handlers (in `init_handler()`), and then restored to the Windows TEB when exiting (in `leave_handler()`).
Some changes to the syscall dispatcher were needed to ensure that the TEB is not accessed through `%gs` while on the kernel stack (since a SIGUSR1 while on the kernel stack will result in GSBASE being set to the TSD).
---
I've tested this successfully on macOS 15 (Apple Silicon and Intel) and macOS 10.13 with several apps and games, including the `cefclient.exe` CEF sample.
Encouragingly, in some simple tests I didn't see a noticeable performance regression from this MR.
There are drawbacks though:
- libraries which jump directly from PE code into Unix code (expecting that %gs is always pointing to the macOS TSD) will crash. Notable examples are D3DMetal and DXMT. These will need to be changed to use Unix calls.
- If Windows code uses the `syscall` instruction directly, the stack pointer likely needs to be valid (which is probably not true on Windows). This is due to the syscall dispatcher saving registers onto the user stack and having to call `_thread_set_tsd_base`. I can't say I've ever seen direct syscalls done with an invalid `%rsp`, but it seems like something anticheat code might do.
---
macOS does not have a public API for setting GSBASE, but the private `_thread_set_tsd_base()` works and was added in macOS 10.12.
`_thread_set_tsd_base()` is a small thunk that sets `%esi`, `%eax`, and does the `syscall`: https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57….
The syscall instruction itself clobbers `%rcx` and `%r11`.
I've tried to save as few registers as possible when calling `_thread_set_tsd_base()`, but there may be room for improvement there.
---
I also tested an alternate implementation strategy for this which took advantage of the expanded "full" thread state which is passed to signal handlers when a process has set a user LDT. The full thread state includes GSBASE, so GSBASE is set back to whatever is in the sigcontext on return (like every other field in the context). This would avoid needing to explicitly reset GSBASE in `leave_handler()`.
This strategy was simpler, but I'm not using it for 2 reasons:
- the "full" thread state is only available starting with macOS 10.15, and we still support 10.13.
- more crucially, Rosetta 2 doesn't seem to correctly implement the GS.base field of the full thread state. It's set to 0 on entry, and isn't read on exit.
--
v7: ntdll: Remove x86_64 Mac-specific TEB access workarounds that are no longer needed.
ntdll: On macOS x86_64, swap GSBASE between the TEB and macOS TSD when entering/leaving PE code.
ntdll: Set %rsp to be inside syscall_frame before accessing %gs in x86_64 syscall dispatcher.
ntdll: Don't access the TEB through %gs when using the kernel stack in x86_64 syscall dispatcher.
ntdll: Ensure init_handler runs in signal handlers before any compiler-generated memset calls.
ntdll: Remove ugly fallback method for getting a thread's GSBASE on macOS.
https://gitlab.winehq.org/wine/wine/-/merge_requests/6866
In `generate_font_link_info`, because the return value of
`GdipCreateFontFromDC` is not checked, section->font might be set to NULL.
`GdipMeasureString` calls `gdip_format_string`, which then calls
`generate_font_link_info` and `font_link_get_text_extent_point`. In
`font_link_get_text_extent_point`, the font from `gdip_font_link_section` is also
not checked for NULL, which may cause a crash.
Therefore, in `generate_font_link_info`, when `GdipCreateFontFromDC` fails,
store `(GpFont *)base_font` (as with `IMLangFontLink_MapFont` failure) to ensure
the font in `gdip_font_link_section` is valid.
---
This issue was reproduced while using the [QQ Music](https://y.qq.com/download/index.html).
Below is the original winedbg log (on Wine 10.4 + wine-staging + DXVK,
with some native libraries used. If needed and permitted by WineHQ, I will list them.)
(PS: This issue should also be reproduced using generic wine master without third parties)
```
Wine-dbg>frame 1
Wine-dbg>bt
Backtrace:
0 0x00000078ed4d58 get_font_hfont+0x30(graphics=<couldn't compute location>, font=<couldn't compute location>, format=<couldn't compute location>, hfont=<couldn't compute location>, lfw_return=<couldn't compute
location>, matrix=<couldn't compute location>) [/home/up/wine/build32/../wine/dlls/gdiplus/graphics.c:2476] in gdiplus (0x0000000073f700)
=>1 0x00000078ed21bd font_link_get_text_extent_point+0xb9(info=000000000073F774, index=0, length=0xb, max_ext=0x7fffff83, fit=000000000073F7CC, size=000000000073F7B0) [/home/up/wine/build32/../wine/dlls/gdiplus/g
raphics.c:5537] in gdiplus (0x0000000073f74c)
2 0x00000078ed340d gdip_format_string+0x2f9(graphics=0000000003665DD0, hdc=0000000034010075, string=L"Q音听我想听的歌???", length=0xb, font=0000000003672700, rect=000000000073F864, format=0000000009FEB508, ignore_empt
y_clip=0x1, callback=0000000078EDADC8, user_data=000000000073F878) [/home/up/wine/build32/../wine/dlls/gdiplus/graphics.c:5663] in gdiplus (0x0000000073f81c)
3 0x00000078ea8283 GdipMeasureString+0x3cb(graphics=<couldn't compute location>, string=<couldn't compute location>, length=<couldn't compute location>, font=<couldn't compute location>, rect=<couldn't compute
location>, format=<couldn't compute location>, bounds=<couldn't compute location>, codepointsfitted=<couldn't compute location>, linesfilled=<couldn't compute location>) [/home/up/wine/build32/../wine/dlls/gdiplu
s/graphics.c:6050] in gdiplus (0x0000000073f8c0)
4 0x00000077d41fd2 in qqmusic (+0x31fd2) (0x0000000073f908)
5 0x00000077d3e1c6 in qqmusic (+0x2e1c6) (0x0000000073f96c)
6 0x00000077d3dd11 in qqmusic (+0x2dd11) (0x0000000073f9f8)
7 0x00000077f29bd4 in qqmusic (+0x219bd4) (0x0000000073fa70)
8 0x00000077f29a63 in qqmusic (+0x219a63) (0x0000000073faa0)
9 0x00000077f28760 in qqmusic (+0x218760) (0x0000000073fb08)
10 0x00000077b059d7 in common (+0x1059d7) (0x0000000073fb94)
11 0x00000077b06c35 in common (+0x106c35) (0x0000000073fbc8)
12 0x0000007a038b48 in user32 (+0x8b48) (0x0000000073fbf8)
13 0x0000007a093e43 call_window_proc+0xa7(hwnd=0000000000060138, msg=0x113, wp=0x400, lp=0, result=000000000073FC68, arg=0000000077B06B90) [/home/up/wine/build32/../wine/dlls/user32/winproc.c:111] in user32 (0x
0000000073fc40)
14 0x0000007a09c066 dispatch_win_proc_params+0xce(params=000000000073FCA0) [/home/up/wine/build32/../wine/dlls/user32/winproc.c:710] in user32 (0x0000000073fc7c)
15 0x0000007a09be4a dispatch_message+0x9e(msg=000000000073FDCC, ansi=0) [/home/up/wine/build32/../wine/dlls/user32/message.c:804] in user32 (0x0000000073fcd8)
16 0x0000007a04f530 DispatchMessageW+0xff(msg=000000000073FDCC) [/home/up/wine/build32/../wine/dlls/user32/message.c:890] in user32 (0x0000000073fd60)
17 0x000000780c001d in qqmusic (+0x3b001d) (0x0000000073fe38)
18 0x000000780c0ce6 in qqmusic (+0x3b0ce6) (0x0000000073fea8)
19 0x00000000403af4 in qqmusic (+0x3af4) (0x0000000073fefc)
20 0x00000000403bd6 in qqmusic (+0x3bd6) (0x0000000073ff50)
21 0x0000007bcdeb10 in kernel32 (+0xeb10) (0x0000000073ff68)
22 0x0000007be0d083 in ntdll (+0xd083) (0x0000000073ff78)
23 0x0000007be435b2 call_thread_func+0x2e(entry=000000000040678E, arg=00000000003F1000) [/home/up/wine/build32/../wine/dlls/ntdll/signal_i386.c:526] in ntdll (0x0000000073ffec)
Wine-dbg>info locals
0x00000078ed21bd font_link_get_text_extent_point+0xb9: (0073f74c)
struct gdip_format_string_info* info=000000000073F774 (parameter [EBP+8])
INT index=0 (parameter [EBP+12])
int length=0xb (parameter [EBP+16])
int max_ext=0x7fffff83 (parameter [EBP+20])
LPINT fit=000000000073F7CC (parameter [EBP+24])
SIZE* size=000000000073F7B0 (parameter [EBP+28])
DWORD to_measure_length=0x3 (local [EBP-16])
HFONT hfont=000000000D0A00AE (local [EBP-24])
HFONT oldhfont=000000004C0A00B0 (local [EBP-20])
SIZE sizeaux={cx=0x7c, cy=0x13} (local [EBP-32])
int i=0x8 (local [EBP-8])
int fitaux=0x8 (local [EBP-36])
struct gdip_font_link_section* section=00000000037E31A8 (local [EBP-12])
Wine-dbg>p *section
Exception c0000005
Wine-dbg>x/10x 0x00000000037E31A8
0x000000037e31a8: 0073f78c 037e3188 00000008 0000000b
0x000000037e31b8: 00000000 00000000 00000004 8146001b
0x000000037e31c8: 78e0c030 78e0c030
```
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/7700
On Wed Apr 2 01:09:11 2025 +0000, Huw Davies wrote:
> Could you explain why we need this? Why does it matter how freetype is
> compiled; isn't that an ABI breakage on their side?
Subjectively, I do believe it is indeed an issue with FreeType, as its documentation does not explicitly specify how to handle the high 32 bits. I submitted the patch to Wine simply because I am more familiar with Wine. Since you think this approach is inappropriate, I will try submitting the patch to FreeType instead. Should I close this merge request now?
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/7633#note_99632