2

The origin of my question effectively stems from wanting to provide an implementation of pthreads on Windows which supports user provide stacks. Specifically, pthread_attr_setstack should do something meaningful. My actual requirements are a bit more involved than this but this is good enough for the purpose of the post.

There are no public Win APIs for providing a stack in either the Fiber or Thread APIs. I've searched around for sneaky backdoors, workarounds and hacks, there's nothing going. In fact, I looked that the winpthread source for inspiration and that ignores any stack provided to pthread_attr_setstack.

Instead I tried the following "solution" to see if it would work. I create a Fiber using the usual combination of ConvertThreadToFiber, CreateFiberEx and SwitchToFiber. In CreateFiberEx I provide a minimal stack size. In the entry point of the fibre I then allocate memory for a stack, change the TIB fields: "Stack Base" and "Stack Limit" appropriately (see here: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block) and then set ESP to the high address of my stack.

(In a real world case I would setup the stack better than this and change EIP as well so that this step behaves more like the posix funciton swapcontext, but you get the idea).

If I make any OS calls when on this different stack then I'm pretty much screwed (printf for example dies). However this isn't an issue for me. I can ensure that I never make sure calls when on my custom stack (hence why I said my actual requirements are a bit more involved). Except...I need exceptions to work. And they don't! Specifically, if I try to throw and catch an exception on my modified stack then I get an assert

Unhandled exception at 0xXXXXXXXX ....

So my (vague) question is, does anyone have any insight as to how exceptions and a custom stack might not be playing nicely together? I appreciate that this is totally unsupported and can happily except nil response or "go away". In fact, I've pretty much decided that I need a different solution and, despite this involving compromise, I'm likely to use one. However, curiosity gets the better of me so I'd like to know why this doesn't work.

On a related note, I wondered how Cygwin dealt with this for ucontext. The source here http://szupervigyor.ddsi.hu/source/in/openjdk-6-6b18-1.8.13/cacao-0.99.4/src/vm/jit/i386/cygwin/ucontext.c uses GetThreadContext/SetThreadContext to implement ucontext. However, from experimentation I see that this also fails when an exception is thrown from inside a new context. In fact the SetThreadContext call doesn't even update the TIB block!

EDIT (based on the answer from @avakar)

The following code, which is very similar to yours, demonstrates the same failure. The difference is that I don't start the second thread suspended but suspend it then try to change context. This code exhibits the error I was describing when the try-catch block is hit in foo. Perhaps this simply isn't legal. One notable thing is that in this situation the ExceptionList member of the TIB is a valid pointer when modifyThreadContext is called, whereas in your example it's -1. Manually editing this doesn't help.

As mentioned in my comment to your answer. This isn't precisely what I need. I would like to switch contexts from the thread I'm current on. However, the docs for SetThreadContext warn not to call this on an active thread. So I'm guessing that if the below code doesn't work then I have no chance of making it work on a single thread.

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;
}
Andrew Parker
  • 1,425
  • 2
  • 20
  • 28
  • Exception handling is indeed the problem, you did not setup the NT_TIB.ExceptionList. A bad problem as well is that you have no decent way to deal with or report stack overflow. You are just helping entirely too much, a fiber already has a perfectly usable stack. You asked for it in the CreateFiber() call, the 1st argument sets its size. – Hans Passant Jun 09 '15 at 10:53
  • Your comment implies that NT_TIB.ExceptionList can be set at all (at least to something meaningful), is that the case? I presume this gets set by the thread/fibre entry point, which is of course a closed box. Btw, I appreciate that the API provides a set way of doing things and, undoubtedly, a good one. Unfortunately I have requirements which mean that I need to do this. At least I do until this provably not achievable in any sane way. In which case it's time for a redesign! – Andrew Parker Jun 09 '15 at 12:07
  • Have you looked at http://www.boost.org/doc/libs/1_58_0/libs/context/doc/html/context/overview.html? – avakar Jun 09 '15 at 12:59
  • Anyway, it might be better to give us an actual motivation. Helping you solve tiny fragments of your problem will rarely help you reach an optimal solution. – avakar Jun 09 '15 at 15:51
  • I'll take a look at the boost context source. And fair point re the motiviation. I was hoping we wouldn't need full details but we're probably at the stage where I should expand so as to save wasted energy. I'll do some more investigation then add more details. – Andrew Parker Jun 10 '15 at 02:32

1 Answers1

2

Both exceptions and printf work for me, and I don't see why they shouldn't. If you post your code, we can try to pinpoint what's going on.

#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);
}
avakar
  • 32,009
  • 9
  • 68
  • 103
  • Thanks for the response. Very useful as this might be all I actually need to make things work (although I need to go away and experiment with a few things). Also, I didn't know there was a "supported" way to get the TIB. I was just querying the segment register fs in assembly to get the TIB and manually editing it! So another useful tip. – Andrew Parker Jun 09 '15 at 09:47
  • I've modified my code to be more like yours for discussion. I can confirm this works as is, but the problem for me is two fold. Firstly, in general I need the current thread to be able to change it's own context (like swapcontext would do). I tried this, even with mods similar to yours, and I get the same problem. I then tried with multithreaded code like yours and still get a problem *if* the thread already starts then suspends. I'll paste that code in my original qn and discuss there as I'm running out of chara.... – Andrew Parker Jun 09 '15 at 09:49