首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否可以将线程执行转移到另一个线程?

是否可以将线程执行转移到另一个线程?
EN

Stack Overflow用户
提问于 2015-05-19 06:07:20
回答 2查看 404关注 0票数 1

目前,我正在尝试从当前线程将线程执行转移到另一个新创建的线程的可能性(我希望它是一个正确的单词);下面是示例:

  • Thread1运行
  • Thread1在代码中间停下来并创建Thread2
  • Thread2从Thread1停止的代码中间继续

编辑:更新了该示例。

代码语言:javascript
复制
#include "stdafx.h"
#include <memory>
#include <windows.h>
#include <cassert>

int _eax, _ebx, _ecx, _edx;
int _ebp, _esp, _esi, _edi;
int _eip;
int _flags;
int _jmp_addr;
bool thread_setup = false;
CONTEXT PrevThreadCtx;
HANDLE thread_handle;

int _newt_esp;
int _newt_ret;

DWORD WINAPI RunTheThread(LPVOID lpParam)
{
    // 1000 is more than enough, call to CreateThread() should already return by now.
    Sleep(1000);

    ResumeThread(thread_handle);
    return 0;
}

DWORD WINAPI DummyPrologueEpilogue(LPVOID lpParam)
{
    return 123;
}

__declspec(naked) void TransferThread(LPVOID lpParam)
{
    //longjmp(jmpbuf, 0);=
    __asm
    {
        call get_eip;
        cmp[_newt_esp], 0;
        mov[_newt_ret], eax;
        jz setup_new_thread;
        jmp DummyPrologueEpilogue;

get_eip:
        mov eax, [esp];
        ret;

setup_new_thread:
        pushad;
        mov[_newt_esp], esp;

        mov eax, [_flags];
        push eax;
        popfd;

        mov eax, [_eax];
        mov ebx, [_ebx];
        mov ecx, [_ecx];
        mov edx, [_edx];

        mov ebp, [_ebp];
        mov esp, [_esp];
        mov esi, [_esi];
        mov edi, [_edi];

        jmp [_eip];
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    int x = 100;
    char szTest[256];

    sprintf_s(szTest, "x = %d", x);

    //HideThread();

    //setjmp(jmpbuf);

    __asm
    {
        // Save all the register
        mov[_eax], eax;
        mov[_ebx], ebx;
        mov[_ecx], ecx;
        mov[_edx], edx;

        mov[_ebp], ebp;
        mov[_esp], esp;
        mov[_esi], esi;
        mov[_edi], edi;

        push eax;

        // Save the flags
        pushfd;
        pop eax;
        mov[_flags], eax;

        // If we on *new thread* jmp to end_asm, otherwise continue...
        call get_eip;
        mov[_eip], eax;
        mov al, byte ptr[thread_setup];
        test al, al;
        jnz end_asm;

        mov eax, [jmp_self];
        mov[_jmp_addr], eax;

        pop eax;

        mov[_newt_esp], 0;
        mov byte ptr[thread_setup], 1;
        push 0;
        push CREATE_SUSPENDED;
        push 0;
        push TransferThread;
        push 0;
        push 0;
        call CreateThread;
        mov [thread_handle], eax;

        // Create another thread just to resume 'TransferThread()'/*new thread* to give time to
        // __stdcall below to return properly, thus restoring the stack.
        // So the *new thread* does not accidentally pop the value from stacks or the __stdcall cleanup
        // code doesn't accidentally overwrites new pushed value from *new thread*.
        push 0;
        push 0;
        push 0;
        push RunTheThread;
        push 0;
        push 0;
        call CreateThread;

        // Jump to self, consumes CPU
jmp_self:
        jmp jmp_self;
        nop;
        nop;
        jmp end_asm;

get_eip:
        mov eax, [esp];
        ret;
end_asm:
    }

    // Test stack-based variable
    MessageBoxA(0, szTest, "Hello World!", MB_OK);
    assert(x = 100);

    x += GetCurrentThreadId();
    sprintf_s(szTest, "x = %d", x);

    HMODULE hMod = LoadLibrary(TEXT("comctl32"));
    FreeLibrary(hMod);

    try
    {
        std::unique_ptr<char[]> pTest(new char[256]);

        sprintf_s(pTest.get(), 256, "WinApi call test. Previous loadLibrary() call return %X", hMod);
        MessageBoxA(0, pTest.get(), "Hello World!", MB_OK);
    } catch (...) {}

    char *pszTest = (char*) malloc(256);
    if (pszTest)
    {
        float f = 1.0;
        f *= (float) GetCurrentThreadId();

        sprintf_s(pszTest, 256, "Current Thread ID = %X, Thread handle = %X, FP Test = %f", GetCurrentThreadId(), GetCurrentThread(), f);
        MessageBoxA(0, pszTest, "Hello World!", MB_OK);

        free( pszTest );
    }

    // printf() from *new thread* will fail on stkchk()
    //printf("Simple test\n");

    // Let's terminate this *new* thread and continue the old thread
    if (thread_setup)
    {
        DWORD OldProtect;
        thread_setup = false;

        VirtualProtect((PVOID)_jmp_addr, 2, PAGE_EXECUTE_READWRITE, &OldProtect);
        *(int*)(_jmp_addr) = 0x90909090; // Prev thread not suspended. Just hope this op is atomic.

        // Operation below will change the stack pointer
        //VirtualProtect((PVOID)_jmp_addr, 2, OldProtect, &OldProtect);
        //FlushInstructionCache(GetCurrentProcess(), (PVOID)_jmp_addr, 2);

        __asm {
            push eax;
            mov eax, jmp_self2;
            mov[_jmp_addr], eax;
            pop eax;
jmp_self2:
            jmp jmp_self2;
            nop;
            nop;
            mov esp, [_newt_esp];
            popad;
            jmp _newt_ret;
        }
    }
    else
    {
        DWORD OldProtect;
        VirtualProtect((PVOID)_jmp_addr, 2, PAGE_EXECUTE_READWRITE, &OldProtect);
        *(int*)(_jmp_addr) = 0x90909090; // Prev thread not suspended. Just hope this op is atomic.
    }

    // Show both thread can be exited cleanly... with some hacks.
    DWORD dwStatus;
    while (GetExitCodeThread(thread_handle, &dwStatus) && dwStatus == STILL_ACTIVE) Sleep(10);
    printf("*New Thread* exited with status %d (Expected 123), Error=%X\n", dwStatus, GetLastError());
    assert(dwStatus == 123);

    printf("Test printf from original thread!\n");
    printf("printf again!\n");
    printf("and again!\n");
    Sleep( 1000 );

    return 0;
}

代码可能读起来很痛苦,因为它主要由asm组成。因此,我添加了一点评论,以帮助。现在我已经测试了,这是很有可能的,但有一些问题。调用少数win api似乎不错,但是调用printf肯定会在stkchk()函数上崩溃(访问被拒绝)。如果有任何建议,我会尝试其他办法。

EN

回答 2

Stack Overflow用户

发布于 2015-05-19 08:23:32

这是不可能的(编辑:可能可以像JS1提到的那样使用OS (如GetThreadContext )成功切换,但其他限制仍然适用)

问题是,新线程需要运行前一个线程堆栈。您可以直接使用旧堆栈,也可以将旧堆栈复制到新堆栈。这两种方法都是不可能的:因为依赖堆栈的指针(例如,框架指针)无法复制堆栈,并且不能使用旧堆栈,因为操作系统将检测线程从堆栈中消失,并抛出堆栈溢出或下溢。

如果操作系统没有检测到堆栈的错位,这可能是可能的。如果是这样的话,那么您可以加载旧的ESP和EBP来使用旧堆栈(就像以前那样)。您的当前代码有一些问题(如果它可以工作的话),因为您在保存堆栈指针(ESP)之后推送了一些寄存器。当你重新装ESP的时候,就好像你从来没有推过任何东西。ESP指针确实是需要小心处理的特例。请注意,在这种情况下,您甚至不需要关心新堆栈,它将被忽略。这意味着你不需要任何特别的裸体声明。

另一个注意,如果您能够做到这一点,如果您不恢复以前的代码流,则两个线程都不能终止。旧线程在新线程运行时不应该使用堆栈,因此不能终止,而新线程不能在旧堆栈上终止。每个堆栈在底部(或顶部,用于自顶向下堆栈)包含依赖线程的清理代码。

票数 2
EN

Stack Overflow用户

发布于 2015-05-19 06:56:51

作为一个FYI,我没有尝试以下内容,但是您可能可以使用一个裸函数(仅限微软编译器)来实现这样的工作:https://msdn.microsoft.com/en-us/library/5ekezyy2.aspx

有很多限制:https://msdn.microsoft.com/en-us/library/4d12973a.aspx,但是使用裸函数启动线程并没有列出限制。一个裸函数将删除prolog/epilog,并允许您尝试并从前一个线程传输上下文。

您还可以通过解释器实现这一点:基本上保存程序的解释状态,然后在单独的线程上启动。

因为我想不出实际的用例,所以我不知道您为什么要这样做。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30317906

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档