Wow64SuspendThread fails with ERROR_INVALID_PARAMETER (87) when called from an x64 process on Windows 11 ARM64 (BUG)

Swiftsoft DEV 0 Reputation points
2026-10-24T16:10:54.5066667+00:00

On Windows 11 ARM64 (build 26300.9711), Wow64SuspendThread called from an x64 (emulated) app on a thread of an x86 app returns -1 with GetLastError() = 87, and the thread is not suspended. The same code built for ARM64 works. Wow64GetThreadContext requires Wow64SuspendThread first, so an x64 debugger on ARM64 cannot read a correct x86 context.

target.exe is any x86 program spinning in pause; jmp $-2 in main.

#include <windows.h>

#include <stdio.h>

int main() {

  STARTUPINFOA startup = {sizeof(startup)};

  PROCESS_INFORMATION target = {};

  char command[] = "target.exe";  // x86: pause; jmp $-2 in main

  CreateProcessA(nullptr, command, nullptr, nullptr, FALSE, 0, nullptr,

                 nullptr, &startup, &target);

  Sleep(1000);

  DWORD result = Wow64SuspendThread(target.hThread);

  printf("Wow64SuspendThread: %ld, GetLastError: %lu\n",

         static_cast<LONG>(result), GetLastError());

  TerminateProcess(target.hProcess, 0);

  return 0;

}

// target.cpp, build for x86: cl /O2 target.cpp
int main() {
  __asm {
  spin:
    pause
    jmp spin
  }
}


built for x64: Wow64SuspendThread: -1, GetLastError: 87

built for ARM64: Wow64SuspendThread: 0, GetLastError: 0

Likely cause: in an x64 process, RtlWow64SuspendThread resolves to its ARM64EC version. That version starts a helper thread in the x86 process at ntdll+0x243C70, which reads a callback pointer from ntdll+0x3B1748. That pointer is null in WOW64 processes, so the call returns 0xC000000D. The native version uses ntdll+0xFA2E0 and ntdll+0x3AE1B8, where the pointer is set, and works.

Is this a known issue, and is there a supported way to suspend a WOW64 thread from an x64 process on ARM64?

Windows development
Windows development

A set of tools and technologies that enable developers to build, optimize, and deploy applications and services within the Windows ecosystem.

0 comments No comments

1 answer

Sort by: Most helpful
  1. AI answer

    2026-10-24T16:12:13.5866667+00:00

    The supported practical workaround is to run the debugger as x86 or move the suspend/context operation into an ARM64-native component; the documented API contract provides no separate x64-emulated-on-ARM64 suspension API.

    This appears to violate the documented contract

    Your target is an x86 process running under WOW64 on ARM64. Microsoft documents that:

    • WOW64 supports 32-bit x86 applications on ARM64 Windows.^1^
    • Wow64SuspendThread suspends a WOW64 thread, is intended for 64-bit applications, and requires a thread handle with THREAD_SUSPEND_RESUME access.^2^
    • Wow64GetThreadContext requires the WOW64 thread to be suspended with Wow64SuspendThread; otherwise, a valid context cannot be obtained.^3^

    Your CreateProcess thread handle has the required access, the target is genuinely x86/WOW64, and the same operation succeeds from ARM64. Therefore, ERROR_INVALID_PARAMETER from the emulated x64 build is not explained by a documented caller, target, or access restriction.

    No public issue or Microsoft document in the available material confirms the particular RtlWow64SuspendThread/ARM64EC callback failure or lists build 26300.9711 as affected. The internal offsets and callback behavior you identified are undocumented implementation details and should not be used as a production workaround.

    Supported implementation options

    1. Build the debugger as x86

    For x86 user-mode applications on ARM64, Microsoft recommends using the x86 version of the debugger. An x86 caller can use ordinary SuspendThread and GetThreadContext; the documentation states that calls to the WOW64 variants from a 32-bit application have the same result as those ordinary APIs.^4^

    This is the cleanest choice if the debugger only handles x86 targets.

    2. Build the debugger or an out-of-process helper as ARM64

    If one debugger must handle several architectures, keep the main x64 application if necessary but delegate the x86 thread suspend/context operation to an ARM64-native helper. Your test already establishes that this path works on the affected build.

    The helper should:

    1. Receive the target PID/TID over IPC.
    2. Open the thread with at least THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT.
    3. Call Wow64SuspendThread.
    4. Initialize WOW64_CONTEXT.ContextFlags.
    5. Call Wow64GetThreadContext.
    6. Call ResumeThread.
    7. Return the captured context to the main process.

    For example:

    HANDLE thread = OpenThread(
        THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT,
        FALSE, threadId);
    
    DWORD previous = Wow64SuspendThread(thread);
    if (previous == (DWORD)-1)
        return GetLastError();
    
    WOW64_CONTEXT context{};
    context.ContextFlags = WOW64_CONTEXT_ALL;
    
    BOOL captured = Wow64GetThreadContext(thread, &context);
    DWORD error = captured ? ERROR_SUCCESS : GetLastError();
    
    ResumeThread(thread);
    CloseHandle(thread);
    return error;
    

    Suspending arbitrary threads can deadlock a process if they own synchronization objects, so this mechanism should remain debugger-only.^2^

    Do not silently replace it with SuspendThread in the x64 build

    SuspendThread does suspend a specified thread and requires the same suspend/resume access.^5^ However, Microsoft explicitly directs 64-bit callers to Wow64SuspendThread for WOW64 threads, and Wow64GetThreadContext specifically says to suspend with that function. Consequently, this sequence is not a documented equivalent:

    SuspendThread(thread);
    Wow64GetThreadContext(thread, &context);
    

    It may be useful as a diagnostic experiment, but it should not be treated as a supported workaround for obtaining a reliable x86 context.

    Report the Windows defect

    Submit a Feedback Hub recording and, for a debugger product, open a Microsoft support case. Include:

    • Windows edition and full build number.
    • Native machine type and caller/target machine types from IsWow64Process2.
    • The minimal x64 and ARM64 binaries.
    • Return values from Wow64SuspendThread and GetLastError.
    • Confirmation that the identical target succeeds from ARM64.
    • A trace or dump captured during the failed call.
    • Your observed ARM64EC/native divergence, clearly identified as analysis of undocumented internals.

    Microsoft’s support guidance asks for the application/component topology, relevant logs, and exact reproduction steps.^6^


    References

    1. Running 32-bit Applications
    2. Wow64SuspendThread - Win32 apps | Microsoft Learn
    3. Wow64GetThreadContext function (wow64apiset.h)
    4. Debugging on Arm64
    5. SuspendThread function (processthreadsapi.h) - Win32 apps
    6. Contacting Support
    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.