修改Windows、TIB和异常上的堆栈

Modifying the stack on Windows, TIB and exceptions

本文关键字:堆栈 异常 Windows TIB 修改      更新时间:2023-10-16

我的问题的根源实际上是想在Windows上提供一个支持用户提供堆栈的pthreads实现。具体来说,pthread_attr_setstack应该做一些有意义的事情。我的实际需求比这更复杂一些,但这对于这篇文章的目的来说已经足够了。

没有公共的Win api可以在光纤或线程api中提供堆栈。我找了很多后门,变通办法和黑客手段,都没找到。事实上,我看了winpthread源代码的灵感,它忽略了任何堆栈提供给pthread_attr_setstack

相反,我尝试了下面的"解决方案",看看它是否有效。我使用ConvertThreadToFiber, CreateFiberExSwitchToFiber的通常组合创建一个光纤。在CreateFiberEx中,我提供了最小的堆栈大小。然后,在光纤的入口点,我为堆栈分配内存,适当地更改TIB字段:"堆栈基础"answers"堆栈限制"(见这里:http://en.wikipedia.org/wiki/Win32_Thread_Information_Block),然后将ESP设置为堆栈的高地址。

(在现实世界的情况下,我将设置堆栈比这更好,并改变EIP,使这一步更像posix函数swapcontext,但你明白了)。

如果我在这个不同的堆栈上进行任何操作系统调用,那么我就完蛋了(例如printf就死了)。然而,这对我来说不是问题。我可以确保在我的自定义堆栈上我永远不会确保调用(因此我说我的实际需求有点复杂)。除了……我需要例外来工作。他们没有!具体来说,如果我试图在修改后的堆栈上抛出并捕获异常,那么我将得到一个assert

0xXXXXXXXX ....未处理异常

所以我(模糊)的问题是,有没有人有任何见解异常和自定义堆栈可能不会很好地发挥在一起?我明白这是完全不支持的,可以高兴地除了零回应或"走开"。事实上,我已经决定我需要一个不同的解决方案,尽管这涉及到妥协,我可能会使用一个。然而,好奇心控制了我,所以我想知道为什么这不起作用。

在一个相关的说明,我想知道Cygwin如何处理这个无上下文。这里的源代码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来实现ucontext。然而,从实验中我发现,当从新上下文内部抛出异常时,这种方法也会失败。事实上,SetThreadContext调用甚至不更新TIB块!

编辑(基于@avakar的答案)

下面的代码(与您的代码非常相似)演示了相同的失败。不同的是,我不启动第二个线程暂停,但暂停它,然后尝试改变上下文。这段代码显示了我在foo中遇到try-catch块时所描述的错误。也许这就是不合法的。值得注意的是,在这种情况下,当调用modifyThreadContext时,TIB的ExceptionList成员是一个有效的指针,而在您的示例中它是-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;
}

例外和printf都为我工作,我不明白为什么他们不应该。如果你把你的代码贴出来,我们可以试着查明发生了什么。

#include <windows.h>
#include <stdio.h>
DWORD CALLBACK ThreadProc(LPVOID)
{
    try
    {
        throw 1;
    }
    catch (int i)
    {
        printf("%dn", 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);
}