Windows 内核驱动程序:如何确定线程是否终止?

Ori*_*avi 5 c windows multithreading driver windows-kernel

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

我使用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;
}
Run Code Online (Sandbox Code Playgroud)

线程例程:

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

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

PsTerminateSystemThread(STATUS_SUCCESS);
Run Code Online (Sandbox Code Playgroud)

卸载驱动函数:

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

    KeWaitForSingleObject(
        (PVOID)ptThreadObject,
        Executive,
        KernelMode,
        FALSE,
        (&liTimeOut));
    ObDereferenceObject((PVOID)ptThreadObject);
    ptThreadObject= NULL;
}
Run Code Online (Sandbox Code Playgroud)

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

有没有办法检查线程是否提前终止?(如果它是由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;
}
Run Code Online (Sandbox Code Playgroud)

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

谢谢。

RbM*_*bMm 2

为什么会ObReferenceObjectByHandle成功,不会失败?- 线索早已消失。

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

如何判断线程是否终止?

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

if (ptThreadObject)
{
    bStopThread = TRUE;

    KeWaitForSingleObject(
        ptThreadObject,
        Executive,
        KernelMode,
        FALSE,
        0);
    ObDereferenceObject(ptThreadObject);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

然而,调用PsTerminateSystemThread是可选的 - 您可以简单地返回ThreadRoutine- 在这种情况下系统您自己调用PsTerminateSystemThread(STATUS_SUCCESS);- 因此在您的代码中调用PsTerminateSystemThread(STATUS_SUCCESS);也是多余和不必要的代码。PsTerminateSystemThread仅当您希望返回状态不同于STATUS_SUCCESS线程终止后检查返回状态时才需要调用。请注意,Windows 本身不解释和使用线程退出状态。它只是将其存储在ETHREAD对象中。如果您不查询和使用此状态 - 它的退出状态是什么并不重要。

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

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

所以定义全局变量:

PDRIVER_OBJECT gDriverObject;
Run Code Online (Sandbox Code Playgroud)

DriverEntry初始化它:gDriverObject = DriverObject;

并以下面的方式启动线程:

ObfReferenceObject(gDriverObject);

NTSTATUS status = PsCreateSystemThread(
    &hThread,
    0, NULL,
    0, NULL,
    ThreadRoutine,
    pThreadData
    );
if (!NT_SUCCESS(status))
{
    ObfDereferenceObject(gDriverObject);
}
Run Code Online (Sandbox Code Playgroud)

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

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

void NTAPI _ThreadRoutine(PVOID pv)
{
// not call PsTerminateSystemThread here !!
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这样你就不能等待工作线程退出 - 只需向他发出终止(bStopThread = TRUE;)信号并从驱动程序卸载中返回。