2

我的问题的起源实际上源于希望在 Windows 上提供支持用户提供堆栈的 pthread 实现。具体来说,pthread_attr_setstack应该做一些有意义的事情。我的实际要求比这更复杂,但这对于帖子的目的来说已经足够了。

在 Fiber 或 Thread API 中没有用于提供堆栈的公共 Win API。我四处寻找偷偷摸摸的后门、变通方法和黑客,没有任何进展。事实上,我查看了 winpthread 的灵感来源,并忽略了提供给pthread_attr_setstack.

相反,我尝试了以下“解决方案”,看看它是否可行。我使用和ConvertThreadToFiber的通常组合创建了一个 Fiber 。在我提供了一个最小的堆栈大小。然后在光纤的入口点为堆栈分配内存,适当更改 TIB 字段:“堆栈基础”和“堆栈限制”(参见此处:http ://en.wikipedia.org/wiki/Win32_Thread_Information_Block ),然后将 ESP 设置为我的堆栈的高地址。CreateFiberExSwitchToFiberCreateFiberEx

(在现实世界的情况下,我会比这更好地设置堆栈并更改 EIP,以便此步骤的行为更像 posix 函数swapcontext,但你明白了)。

如果我在这个不同的堆栈上进行任何操作系统调用,那我就完蛋了(printf例如死了)。不过这对我来说不是问题。我可以确保在我的自定义堆栈上时我永远不会确保调用(因此我说我的实际需求涉及更多)。除了...我需要例外才能工作。他们没有!具体来说,如果我尝试在修改后的堆栈上抛出并捕获异常,那么我会得到一个断言

0xXXXXXXXX 处未处理的异常 ....

所以我的(含糊的)问题是,有没有人知道异常和自定义堆栈如何不能很好地结合在一起?我很欣赏这是完全不受支持的,并且可以很高兴地除了零响应或“走开”。事实上,我几乎已经决定我需要一个不同的解决方案,尽管这涉及到妥协,但我很可能会使用一个。但是,好奇心使我变得更好,所以我想知道为什么这不起作用。

在相关的说明中,我想知道 Cygwin 如何为 ucontext 处理这个问题。这里的源码http://szupervigyor.ddsi.hu/source/in/openjdk-6-6b18-1.8.13/cacao-0.99.4/src/vm/jit/i386/cygwin/ucontext.c使用GetThreadContext/SetThreadContext来实现上下文。但是,从实验中我看到,当从新上下文中抛出异常时,这也会失败。事实上,该SetThreadContext调用甚至不会更新 TIB 块!

编辑(基于@avakar 的回答)

以下代码与您的代码非常相似,演示了同样的失败。不同之处在于我没有启动第二个挂起的线程,而是将其挂起然后尝试更改上下文。当 try-catch 块被命中时,此代码显示了我所描述的错误foo。也许这根本不合法。值得注意的是,在这种情况下ExceptionList,TIB 的成员在被调用时是一个有效的指针modifyThreadContext,而在您的示例中它是-1。手动编辑它没有帮助。

正如我对您的回答的评论中提到的那样。这不正是我所需要的。我想从我当前所在的线程切换上下文。但是,文档SetThreadContext警告不要在活动线程上调用它。所以我猜如果下面的代码不起作用,那么我就没有机会让它在单个线程上工作。

namespace
{
HANDLE ghSemaphore = 0;

void foo()
{
    try
    {
        throw 6;
    }
    catch(...){}

    ExitThread(0);
}

void modifyThreadContext(HANDLE thread)
{
    typedef NTSTATUS WINAPI NtQueryInformationThread_t(HANDLE ThreadHandle, DWORD ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength);

    HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
    auto NtQueryInformationThread = (NtQueryInformationThread_t *)GetProcAddress(hNtdll, "NtQueryInformationThread");

    DWORD stackSize = 1024 * 1024;
    void * mystack = VirtualAlloc(0, stackSize, MEM_COMMIT, PAGE_READWRITE);

    DWORD threadInfo[7];
    NtQueryInformationThread(thread, 0, threadInfo, sizeof threadInfo, 0);

    NT_TIB * tib = (NT_TIB *)threadInfo[1];

    CONTEXT ctx = {};
    ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(thread, &ctx);

    ctx.Esp = (DWORD)mystack + stackSize - ((DWORD)tib->StackBase - ctx.Esp);
    ctx.Eip = (DWORD)&foo;
    tib->StackBase = (PVOID)((DWORD)mystack + stackSize);
    tib->StackLimit = (PVOID)((DWORD)mystack);
    SetThreadContext(thread, &ctx);
}

DWORD CALLBACK threadMain(LPVOID)
{
    ReleaseSemaphore(ghSemaphore, 1, NULL);
    while (1)
        Sleep(10000);
    // Never gets here
    return 1;
}
} // namespace

int main()
{
    ghSemaphore = CreateSemaphore(NULL, 0, 1, NULL);
    HANDLE th = CreateThread(0, 0, threadMain, 0, 0, 0);

    while (WaitForSingleObject(ghSemaphore, INFINITE) != WAIT_OBJECT_0);

    SuspendThread(th);

    modifyThreadContext(th);
    ResumeThread(th);

    while (WaitForSingleObject(th, 10) != WAIT_OBJECT_0);

    return 0;
}
4

1 回答 1

1

两个例外都printf对我有用,我不明白他们为什么不应该这样做。如果您发布代码,我们可以尝试查明发生了什么。

#include <windows.h>
#include <stdio.h>

DWORD CALLBACK ThreadProc(LPVOID)
{
    try
    {
        throw 1;
    }
    catch (int i)
    {
        printf("%d\n", i);
    }
    return 0;
}

typedef NTSTATUS WINAPI NtQueryInformationThread_t(HANDLE ThreadHandle, DWORD ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength);

int main()
{
    HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
    auto NtQueryInformationThread = (NtQueryInformationThread_t *)GetProcAddress(hNtdll, "NtQueryInformationThread");

    DWORD stackSize = 1024 * 1024;
    void * mystack = VirtualAlloc(0, stackSize, MEM_COMMIT, PAGE_READWRITE);

    DWORD dwThreadId;
    HANDLE hThread = CreateThread(0, 0, &ThreadProc, 0, CREATE_SUSPENDED, &dwThreadId);

    DWORD threadInfo[7];
    NtQueryInformationThread(hThread, 0, threadInfo, sizeof threadInfo, 0);

    NT_TIB * tib = (NT_TIB *)threadInfo[1];

    CONTEXT ctx = {};
    ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(hThread, &ctx);

    ctx.Esp = (DWORD)mystack + stackSize - ((DWORD)tib->StackBase - ctx.Esp);
    tib->StackBase = (PVOID)((DWORD)mystack + stackSize);
    tib->StackLimit = (PVOID)((DWORD)mystack);
    SetThreadContext(hThread, &ctx);

    ResumeThread(hThread);
    WaitForSingleObject(hThread, INFINITE);
}
于 2015-06-09T08:00:23.480 回答