Skip to content

Support the PE TLS callback in Windows ARM64X images - #421

Merged
Lastique merged 1 commit into
boostorg:developfrom
Keisawaakira:fix/arm64x_tls_bug
Nov 11, 2026
Merged

Lastique merged 1 commit into
boostorg:developfrom
Keisawaakira:fix/arm64x_tls_bug

Conversation

@Keisawaakira

Copy link
Copy Markdown

Support the PE TLS callback in Windows ARM64X images

Problem

An [ARM64X](https://learn.microsoft.com/windows/arm/arm64x-pe) image (linked with /MACHINE:ARM64X) contains
native ARM64 code and ARM64EC code and can be loaded by both ARM64 and x64 processes. It has a single
TLS directory; the linker builds its callback array from the native .CRT$XL* contributions.

src/win32/tss_pe.cpp registers on_tls_callback (internal linkage) in .CRT$XLC. In an ARM64X image
that links the static Boost.Thread library (ARM64 + ARM64EC variants), the callback array therefore
points at the native ARM64 on_tls_callback in both views, i.e. x64 processes that load the image
call native ARM64 code from the loader's TLS callback dispatch (DLL_THREAD_ATTACH/DETACH, …).
We hit this with a driver DLL: attaching WinDbg to x64 processes that had loaded it failed.

The MSVC CRT has the same situation for its own TLS callback and solves it in tlsdyn.obj: the ARM64EC
object carries /ARM64XSAMEADDRESS:__dyn_tls_init, which makes the linker emit a thunk that dispatches
to the ARM64 function in the native view and to the ARM64EC function in the x64 view.

Change

When BOOST_THREAD_ARM64X is defined (ARM64 / ARM64EC targets only), register an extern "C"
forwarder boost_thread_arm64x_on_tls_callback and emit /arm64xsameaddress for it from the ARM64EC
object, exactly like the CRT.

Opt-in because the directive makes the ARM64EC object reference the native symbol: the ARM64EC library
must then be linked together with the ARM64 one (for example both merged into one ARM64X library with
lib /machine:arm64x, as the CRT libraries are), also for ARM64EC-only images. Linking the ARM64EC
library alone fails with LNK2001: unresolved external symbol boost_thread_arm64x_on_tls_callback (Arm64 Native Symbol). Enabling it by default would therefore break existing ARM64EC-only builds that
use the plain b2/CMake output; making it the default would need the build to produce ARM64X archives
for ARM64EC, which could be a follow-up.

Without the macro the compiled code is unchanged on every target (object files for x86, x64, ARM64
and ARM64EC built from the old and new tss_pe.cpp disassemble identically, modulo the
anonymous-namespace hash that derives from the file path).

The .CRT$XIU/XCU/XTU initializers are not affected: the CRT runs the native and the ARM64EC
initializer tables in their respective views.

Validation

builds the four Win32 sources of Boost. Thread as ARM64 and ARM64EC, merges them into an ARM64X
library, links a small ARM64X DLL that uses boost::thread, and prints the TLS callbacks of both views
(applying the ARM64X dynamic relocations of the image), resolving thunk targets via the PDB.
MSVC 14.50 (VS 2026), WDK 10.0.28000:

===== boostorg/thread develop (unpatched)
0x180015320: same code in both views: `anonymous namespace'::on_tls_callback  (native ARM64 code, also called in x64 processes)
===== patched, without BOOST_THREAD_ARM64X
0x180015320: same code in both views: `anonymous namespace'::on_tls_callback  (native ARM64 code, also called in x64 processes)
===== patched, /DBOOST_THREAD_ARM64X
0x180130930: native -> boost_thread_arm64x_on_tls_callback | x64/EC -> boost_thread_arm64x_on_tls_callback (ARM64EC)

Also checked with MSVC 14.44 (VS 2022 17.14): ARM64X, ARM64EC-only (with the ARM64X library) and ARM64
images link; ARM64EC-only with only the ARM64EC library fails with the documented LNK2001.
In a large production DLL the patched library has been in use for ARM64, ARM64EC, ARM64X and the
ARM64X "pure forwarder" layout.

@Keisawaakira

Copy link
Copy Markdown
Author

@Lastique

Hi Lastique,

Could you take a look at this PR, or request for another reviewer? I don't seem to have permission to request reviewers myself.

This is a blocker for our team right now. We're stuck on an older Boost version with a custom patch, which is keeping us from upgrading our toolchain. As a result, we're missing out on new amazing features and critical bug fixes that other teams already get to use. It’s also creating a headache for managing our dev environments, since we have to maintain our patched Boost side-by-side with the up-to-date version everyone else is on.

If the code quality isn't up to your standards for merging, feel free to edit it. All we care about is getting the TLS callback bug resolved, not how it's implemented.

BR
Keisawa

Comment thread doc/configuration.qbk Resolved
An ARM64X image contains native ARM64 and ARM64EC code and can be loaded by
both ARM64 and x64 processes. It has a single TLS directory whose callback
array the linker builds from the native .CRT$XL* entries, so the callback
registered by tss_pe.cpp (a function with internal linkage) is the native
ARM64 one in both views: x64 processes loading an ARM64X image linked with
the static library call native ARM64 code from the loader's TLS callback
dispatch. In practice this breaks e.g. attaching a debugger to such
x64 processes.

When BOOST_THREAD_ARM64X is defined, register an extern "C" forwarder
instead and mark it /ARM64XSAMEADDRESS from the ARM64EC object file, as the
MSVC CRT does for __dyn_tls_init. The linker then emits a thunk that
dispatches to the ARM64 forwarder in the native view and to the ARM64EC
forwarder in the x64 view.

The directive makes the ARM64EC object reference the ARM64 symbol, so the
ARM64EC library has to be linked together with the ARM64 one (e.g. as an
ARM64X library containing both, like the MSVC CRT libraries), also for
ARM64EC-only images. This is why the behaviour is opt-in; without the macro
the generated code is unchanged on all targets.
@Lastique
Lastique merged commit bf6f5a7 into boostorg:develop Nov 11, 2026
1 check was pending
@Lastique

Copy link
Copy Markdown
Member

Thanks.

Lastique added a commit that referenced this pull request Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants