【问题标题】:Windows Kernel Driver: How to determine if thread terminated?Windows 内核驱动程序:如何确定线程是否终止?
【发布时间】:2018-08-03 20:44:28
【问题描述】:

我有一个用于某些操作的线程,它需要处于活动状态,直到标志另有说明。

我使用PsCreateSystemThread 创建线程,然后使用ObReferenceObjectByHandle 获取ETHREAD 对象引用,以在使用KeWaitForSingleObject 卸载驱动程序之前等待线程终止。

创建线程并检索对它的引用的函数:

ntStatus = PsCreateSystemThread(
    &hThread,
    (ACCESS_MASK)0, NULL,
    (HANDLE)0, NULL,
    ThreadRoutine,
    (PVOID)pThreadData
);
if (!NT_SUCCESS(ntStatus))
{
    return ntStatus;
}

ntStatus = ObReferenceObjectByHandle(
    hThread,
    THREAD_ALL_ACCESS,
    NULL,
    KernelMode,
    (PVOID*)&ptThreadObject,
    NULL
);
if (!NT_SUCCESS(ntStatus))
{
    bStopThread = TRUE;
    ptThreadObject = NULL;
    return ntStatus;
}

线程例程:

LARGE_INTEGER liSleepTime;
while (FALSE == bThread)
{
    liSleepTime.QuadPart = 1000 * RELATIVE_MILLISECOND;
    KeDelayExecutionThread(KernelMode, FALSE, (&liSleepTime));

    ExAcquireFastMutex(&fmMutex);
    //DO SOMTHING
    ExReleaseFastMutex(&fmMutex);
}

PsTerminateSystemThread(STATUS_SUCCESS);

卸载驱动函数:

if (NULL != ptThreadObject)
{
    bStopThread = TRUE;

    KeWaitForSingleObject(
        (PVOID)ptThreadObject,
        Executive,
        KernelMode,
        FALSE,
        (&liTimeOut));
    ObDereferenceObject((PVOID)ptThreadObject);
    ptThreadObject= NULL;
}

我需要这个线程一直运行。

有没有办法检查线程是否提前终止?(如果它是由PsTerminateSystemThread 完成的,我可以在调用 PsTerminateSystemThread 终止线程之前添加一个“布尔值”并设置它) .

还有一个问题:

我在例程开始时终止了线程,并在调用 ObReferenceObjectByHandle 之前等待了 20 秒,但它没有失败。

ntStatus = PsCreateSystemThread(
    &hThread,
    (ACCESS_MASK)0, NULL,
    (HANDLE)0, NULL,
    ThreadRoutine,
    (PVOID)pThreadData
);
if (!NT_SUCCESS(ntStatus))
{
    return ntStatus;
}
// The ThreadRoutine calling PsTerminateSystemThread first and terminate.

liSleepTime.QuadPart = 20000 * RELATIVE_MILLISECOND;
KeDelayExecutionThread(KernelMode, FALSE, (&liSleepTime));

ntStatus = ObReferenceObjectByHandle(
    hThread,
    THREAD_ALL_ACCESS,
    NULL,
    KernelMode,
    (PVOID*)&ptThreadObject,
    NULL
);
if (!NT_SUCCESS(ntStatus))
{
    bStopThread = TRUE;
    ptThreadObject = NULL;
    return ntStatus;
}

为什么 ObReferenceObjectByHandle 成功而不失败? - 线程早就没了。

谢谢。

【问题讨论】:

    标签: c windows multithreading driver windows-kernel


    【解决方案1】:

    为什么ObReferenceObjectByHandle 成功而不失败? -线程 早已不复存在。

    但为什么它必须失败? ObReferenceObjectByHandle 简单地 return 返回指向对象主体的相应指针。 ETHREAD 在你的情况下。对象的状态 - 在这里没有任何作用。终止线程或不是绝对无关的。直到您拥有指向线程主体结构的句柄或引用指针 (ETHREAD) - 对象将不会被释放。因此,如果 hThread 是有效句柄 - ObReferenceObjectByHandle 必须成功。

    如何判断线程是否终止?

    非常简单 - 只需等待通过 KeWaitForSingleObject 说,您已经完成了。因为线程对象本身就是一种调度器对象,当线程终止时,它设置为信号状态并KeWaitForSingleObject返回。

    if (ptThreadObject)
    {
        bStopThread = TRUE;
    
        KeWaitForSingleObject(
            ptThreadObject,
            Executive,
            KernelMode,
            FALSE,
            0);
        ObDereferenceObject(ptThreadObject);
    }
    

    注意 - 您必须将 Timeout 设置为 0 以无限期地等待,直到线程终止(调度程序对象设置为信号状态)。你也不需要将ptThreadObject 转换为PVOID - 它已经是指针。 (PVOID)ptThreadObject 不是错误,而是多余和不必要的代码。

    有没有办法检查线程是否提前终止

    操作系统,我不明白你在过早下的意思。检查线程是否终止,我们可以通过等待它。但过早地只能在您的代码上下文中有意义。假设您可以通过 PsTerminateSystemThread 设置不同的线程退出状态,而不是(在线程终止后)通过 PsGetThreadExitStatus 获得此存在状态。如果线程仍在运行PsGetThreadExitStatus,则返回STATUS_PENDING。这个例程也可以部分用于检查线程状态——如果它返回任何不同于STATUS_PENDING的状态——线程被终止。但如果它返回STATUS_PENDING - 不清楚 - 或者线程仍在运行,或者线程通过PsTerminateSystemThread(STATUS_PENDING) 存在。当然使用STATUS_PENDING,因为存在状态是个坏主意,绝对不能使用。在这种情况下,您也可以使用PsGetThreadExitStatus 确定线程状态(运行/终止),但此例程不会等待。但是当线程终止时,您的驱动程序逻辑需要 wait,并且只有在此之后我们才能卸载驱动程序。所以只有KeWaitForSingleObject(或其他等待功能)是正确的解决方案。如果线程可以以不同的方式存在 - 在调用PsTerminateSystemThread 中使用不同的退出状态并在线程终止后通过PsGetThreadExitStatus 将其取回(所以在KeWaitForSingleObject 之后)

    但是对PsTerminateSystemThread 的调用是可选的——你可以简单地从ThreadRoutine 返回——在这种情况下,系统自己调用PsTerminateSystemThread(STATUS_SUCCESS);——所以在你的代码中调用PsTerminateSystemThread(STATUS_SUCCESS); 也是多余和不必要的代码。您需要调用 PsTerminateSystemThread 以防您希望返回状态不同于 STATUS_SUCCESScheck 在线程终止后返回状态。请注意,Windows 本身不会解释和使用线程退出状态。它只是将其存储在ETHREAD 对象中。如果您不查询并使用此状态 - 它的退出状态无关紧要。

    还有liSleepTime.QuadPart = 1000 * RELATIVE_MILLISECOND; 行,您可以从循环中退出 - 如果您使用 constant 超时,请在循环之前设置。

    如果在 asm 代码中使用特殊的线程入口点,我们根本不能等待驱动程序卸载中的线程终止。显然,在线程运行之前,不得卸载驱动程序。为此存在 2 个解决方案 - 一个是在驱动程序卸载例程中等待所有驱动程序线程终止。但存在另一个 - 在我们创建线程时添加对驱动程序对象的引用,并在线程退出时取消引用驱动程序对象。

    所以定义全局变量:

    PDRIVER_OBJECT gDriverObject;
    

    DriverEntry初始化它:gDriverObject = DriverObject;

    并以下一个方式启动线程:

    ObfReferenceObject(gDriverObject);
    
    NTSTATUS status = PsCreateSystemThread(
        &hThread,
        0, NULL,
        0, NULL,
        ThreadRoutine,
        pThreadData
        );
    if (!NT_SUCCESS(status))
    {
        ObfDereferenceObject(gDriverObject);
    }
    

    在线程退出时需要调用ObfDereferenceObject(gDriverObject);。但是在ObfDereferenceObject(gDriverObject); 之后,我们已经无法返回驱动程序代码——它已经可以被卸载了。所以这个调用不能从 c/c++ 代码中完成。在用户模式下存在 FreeLibraryAndExitThread,但在内核模式下没有这个 api 的模拟。唯一的解决方案 - 在 asm 代码中实现线程入口点 - 这个入口调用 c/c++ 线程例程,最后 jmp(但不是 call)到 @ 987654364@。

    将你的 c/c++ 过程定义为

    void NTAPI _ThreadRoutine(PVOID pv)
    {
    // not call PsTerminateSystemThread here !!
    }
    

    x64c 的代码 (ml64 /c /Cp $(InputFileName) -> $(InputName).obj)

    extern _ThreadRoutine : PROC
    extern gDriverObject : QWORD
    extern __imp_ObfDereferenceObject : QWORD
    
    _TEXT segment 'CODE'
    
    ThreadRoutine proc
        sub rsp,28h
        call _ThreadRoutine
        add rsp,28h
        mov rcx,gDriverObject
        jmp __imp_ObfDereferenceObject
    ThreadRoutine endp
    
    _TEXT ENDS
    
    end
    

    x86c 的代码 (ml /c /Cp $(InputFileName) -> $(InputName).obj)

    .686
    
    extern _gDriverObject:DWORD
    extern __imp_@ObfDereferenceObject@4:DWORD
    extern __ThreadRoutine : PROC
    
    _TEXT SEGMENT
    
    _ThreadRoutine proc
            mov eax,[esp]
            xchg eax,[esp+4]
            mov [esp],eax
            call __ThreadRoutine
            mov ecx,_gDriverObject
            jmp __imp_@ObfDereferenceObject@4
    _ThreadRoutine endp
    
    _TEXT ENDS
    END
    

    有了这个,您可以不等待退出工作线程 - 只需向他发出信号终止 (bStopThread = TRUE;) 并从驱动程序卸载返回。

    【讨论】:

    • @OrielCochavi - 句柄,指向线程的指针独立于正在运行的线程或终止。即使在线程终止后,它的对象 (ETHREAD) 也不会被销毁,直到存在指向它的指针或句柄。并且您可以在线程终止后查询线程的某些属性(退出代码)
    • 对不起,我写了长评论,误按了ENTER。上一条评论:“为什么 ObReferenceObjectByHandle 成功而不失败?-线程早就消失了。 - 糟糕,我没有考虑线程的打开句柄。”
    • 如何确定线程是否终止? - 我不能使用 KeWaitForSingleObject,因为我希望驱动程序继续执行其余的逻辑。但是我仍然想检查线程是否正在运行(不是使用keepalive检查或其他技术之类的技术-而是通过句柄或引用中的一些系统检查或属性,也许是使用句柄的一些查询?)。 有没有办法检查线程是否提前终止? - 我的意思是有人杀死了线程,所以它没有到达我可以通知自己的代码部分(使用布尔值或其他东西) 表示线程已死。
    • Windows 不允许从另一个线程终止内核模式线程。线程必须只终止自己。所以只有在外部终止你的线程 - 将内核模式 apc 插入它并从这里调用PsTerminateSystemThread。但在这种情况下可能会终止,只是无限等待调用..
    • 关于等待线程从驱动程序卸载中终止 - 你必须这样做,或者使用 jmp __imp_@ObfDereferenceObject@4 的技术 - 在这种情况下不需要等待
    猜你喜欢
    • 2019-02-10
    • 2012-02-03
    • 2019-07-14
    • 1970-01-01
    • 2010-09-22
    • 2013-09-13
    • 1970-01-01
    • 1970-01-01
    • 2017-03-02
    相关资源
    最近更新 更多