c# - 从另一个进程获取win32线程的StartAddress

标签 c# c++ dll process pinvoke

背景:

我在 Win32 中编写了一个多线程应用程序,我使用 System.Diagnostics 命名空间中的 Process 类从 C# 代码开始。

现在,在 C# 代码中,我想获取在 Win32 应用程序中创建的每个线程的起始地址的名称/符号,以便我可以将线程相关信息(例如 CPU 使用率)记录到数据库中。基本上,C# 代码会启动 Win32 应用程序的多个实例,监控它们,在需要时终止,然后将 info/error/exceptions/reason/etc 记录到数据库中。

为此,我封装了两个 Win32 API 即。 SymInitializeSymFromAddr在自己编写的对程序员友好的API中,如下所示:

extern "C"
{
    //wraps SymInitialize
    DllExport bool initialize_handler(HANDLE hModue);

    //wraps SymFromAddr
    DllExport bool get_function_symbol(HANDLE hModule, //in
                                       void *address,  //in
                                       char *name);    //out
}

然后使用 pinvoke 从 C# 代码调用这些 API。但它不起作用,并且 GetLastError 给出 126 error code这意味着:

The specified module could not be found

我将 Process.Handle 作为 hModule 传递给这两个函数; initialize_handler 似乎有效,但 get_function_symbol 无效;它给出了上述错误。我不确定我是否传递了正确的句柄。我尝试传递以下句柄:

Process.MainWindowHandle
Process.MainModule.BaseAddress

两者都在第一步本身失败(即调用 initialize_handler 时)。我将 Process.Threads[i].StartAddress 作为第二个参数传递,这似乎是失败的原因,因为 ProcessThread.StartAddress 似乎是 RtlUserThreadStart 函数,不是应用程序特定的启动函数的地址。 MSDN says about it :

Every Windows thread actually begins execution in a system-supplied function, not the application-supplied function. The starting address for the primary thread is, therefore, the same (as it represents the address of the system-supplied function) for every Windows process in the system. However, the StartAddress property allows you to get the starting function address that is specific to your application.

但它没有说明如何使用 ProcessThread.StartAddress 获取特定于应用程序的 startinbg 函数地址。

问题:

我的问题归结为从另一个应用程序(用 C# 编写)获取 win32 线程的起始地址,一旦我得到它,我也会使用上述 API 获取名称。那么如何获取起始地址呢?


我从 C++ 代码测试了我的符号查找 API。如果给出正确的开头地址,则可以将地址解析为符号。

这是我的 p/invoke 声明:

[DllImport("UnmanagedSymbols.dll", SetLastError = true, CallingConvention= CallingConvention.Cdecl)]
static extern bool initialize_handler(IntPtr hModule);

[DllImport("UnmanagedSymbols.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
static extern bool get_function_symbol(IntPtr hModule, IntPtr address, StringBuilder name);

最佳答案

关键是调用NtQueryInformationThread功能。这不是一个完全“官方”的函数(过去可能没有记录?),但文档建议没有其他方法可以获取线程的起始地址。

我已经将它封装到一个对 .NET 友好的调用中,该调用接受一个线程 ID 并将起始地址作为 IntPtr 返回。此代码已在 x86 和 x64 模式下测试过,后者在 32 位和 64 位目标进程上都进行了测试。

我没有测试的一件事是以低权限运行它;我希望这段代码要求调用者拥有 SeDebugPrivilege

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        PrintProcessThreads(Process.GetCurrentProcess().Id);
        PrintProcessThreads(4156); // some other random process on my system
        Console.WriteLine("Press Enter to exit.");
        Console.ReadLine();
    }

    static void PrintProcessThreads(int processId)
    {
        Console.WriteLine(string.Format("Process Id: {0:X4}", processId));
        var threads = Process.GetProcessById(processId).Threads.OfType<ProcessThread>();
        foreach (var pt in threads)
            Console.WriteLine("  Thread Id: {0:X4}, Start Address: {1:X16}",
                              pt.Id, (ulong) GetThreadStartAddress(pt.Id));
    }

    static IntPtr GetThreadStartAddress(int threadId)
    {
        var hThread = OpenThread(ThreadAccess.QueryInformation, false, threadId);
        if (hThread == IntPtr.Zero)
            throw new Win32Exception();
        var buf = Marshal.AllocHGlobal(IntPtr.Size);
        try
        {
            var result = NtQueryInformationThread(hThread,
                             ThreadInfoClass.ThreadQuerySetWin32StartAddress,
                             buf, IntPtr.Size, IntPtr.Zero);
            if (result != 0)
                throw new Win32Exception(string.Format("NtQueryInformationThread failed; NTSTATUS = {0:X8}", result));
            return Marshal.ReadIntPtr(buf);
        }
        finally
        {
            CloseHandle(hThread);
            Marshal.FreeHGlobal(buf);
        }
    }

    [DllImport("ntdll.dll", SetLastError = true)]
    static extern int NtQueryInformationThread(
        IntPtr threadHandle,
        ThreadInfoClass threadInformationClass,
        IntPtr threadInformation,
        int threadInformationLength,
        IntPtr returnLengthPtr);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, int dwThreadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CloseHandle(IntPtr hObject);

    [Flags]
    public enum ThreadAccess : int
    {
        Terminate = 0x0001,
        SuspendResume = 0x0002,
        GetContext = 0x0008,
        SetContext = 0x0010,
        SetInformation = 0x0020,
        QueryInformation = 0x0040,
        SetThreadToken = 0x0080,
        Impersonate = 0x0100,
        DirectImpersonation = 0x0200
    }

    public enum ThreadInfoClass : int
    {
        ThreadQuerySetWin32StartAddress = 9
    }
}

我的系统上的输出:

Process Id: 2168    (this is a 64-bit process)
  Thread Id: 1C80, Start Address: 0000000001090000
  Thread Id: 210C, Start Address: 000007FEEE8806D4
  Thread Id: 24BC, Start Address: 000007FEEE80A74C
  Thread Id: 12F4, Start Address: 0000000076D2AEC0
Process Id: 103C    (this is a 32-bit process)
  Thread Id: 2510, Start Address: 0000000000FEA253
  Thread Id: 0A0C, Start Address: 0000000076F341F3
  Thread Id: 2438, Start Address: 0000000076F36679
  Thread Id: 2514, Start Address: 0000000000F96CFD
  Thread Id: 2694, Start Address: 00000000025CCCE6

除了括号中的内容,因为这需要额外的 P/Invoke。


关于 SymFromAddress “找不到模块”错误,我只想提一下,需要使用 fInvadeProcess = true 调用 SymInitialize 或加载手动模块,as documented on MSDN .

我知道您说您的情况并非如此,但为了方便通过那些关键字找到此问题的任何人,我将保留此内容。

关于c# - 从另一个进程获取win32线程的StartAddress,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8679406/

相关文章:

c# - 无法在 Azure 应用程序洞察中获取进程 CPU

c# - 如何从放置在表单上的面板中捕获值

c++ - 霍尔分区不起作用?

C++ 虚拟继承 : static_cast "this" to virtual parent in initializer list of derived

c# - 同一节点上的 Xpath 多个条件

c# - 如何检查一个对象是否*完全*是一个类,而不是派生类?

c++ - 取消引用 NULL 指针是否保证使 C/C++ 中的程序崩溃?

c++ - 如何在c中调用一个dll文件。我想向它传输一个xml文件

通过 Eclipse CDT 创建共享库

c# - 将抽象 C++ 结构返回到 C#