【问题标题】:Get StartAddress of win32 thread from another process从另一个进程获取win32线程的StartAddress
【发布时间】:2011-12-30 12:24:31
【问题描述】:

背景:

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

现在,在 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 这意味着:

找不到指定的模块

我将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

每个 Windows 线程实际上都是在系统提供的函数中开始执行的,而不是应用程序提供的函数。因此,对于系统中的每个 Windows 进程,主线程的起始地址都是相同的(因为它表示系统提供的函数的地址)。 不过,StartAddress 属性允许您获取特定于您的应用程序的起始函数地址。

但它没有说明如何使用 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);

【问题讨论】:

  • 您忘记发布 [DllImport] 声明,如果它在 C++ 中工作,肯定是问题的原因。
  • @HansPassant:我意识到了这一点,因此我立即发布了。
  • 返回类型需要[MarshalAs],C++ bool == byte。但这不太可能是原因。 ProcessThread.StartAddress 不确定,因为线程实际上是从 Windows 函数 (RtlUserThreadStart) 开始的。文档承诺会提供一些帮助,但我不知道这在流程外能有多好。显然,这不是您在 C++ 中测试过的。
  • 那么你想用“名称/起始地址的符号”来识别线程吗?如果是这样,难道您不能只跟踪您创建的线程或将线程名称设置为您想要的任何名称吗?
  • ①问题标题是“给定[...]起始地址,如何获取函数名”,但大部分问题都集中在获取起始地址上。 ② “我将 Process.Threads[i].StartAddress 作为第二个参数传递”这句话不清楚:作为什么函数的第二个参数? ③ 你将什么作为第三个参数传递给SymInitialize? — 请用您的问题解决这些问题,这样您将有更高的机会得到满意的答案。

标签: c# c++ dll process pinvoke


【解决方案1】:

关键是调用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

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

【讨论】:

  • 我试过这种方法,它奏效了;但是,此答案的当前形式未编译,并且缺少许多其他信息。所以我会在某个时候编辑这个答案,添加更多细节,包括代码,使其更加完美。然后我会接受这个作为答案(如果我没有得到更好的答案)。谢谢。 :-)
  • 哦,顺便说一句,+1。请删除另一个答案。这对我没有任何帮助。
  • 现在这看起来更好,并且是完整的答案。非常感谢。
【解决方案2】:

这是我对问题的理解。

您有一个 C# 应用程序 APP1,它创建了一堆线程。

这些线程依次创建一个进程。我假设这些线程保持活动状态并负责监控它产生的进程。

因此,对于 APP1 中的每个线程,您希望它枚举在该线程的子进程中产生的线程的信息。

在过去,我会这样做的方式是:

  • 将我对给定 Win32 进程的所有 Win32 线程监控编码到 DLL 中
  • 将该 DLL 注入我要监控的进程中
  • 使用命名管道或其他 RPC 机制从注入的 Win32 进程与主机 APP1 进行通信

因此,在 C# 的主线程过程中,您将创建并监视一个命名管道,以便您的进程在注入后进行通信。

在 C++ 领域,伪代码将创建一个挂起的进程,在该进程中分配一些内存,将您的 DLL 注入该进程,然后创建一个远程线程来执行您注入的 dll:

char * dllName = "your cool dll with thread monitoring stuff.dll"

// Create a suspended process
CreateProces("your Win32 process.exe", ...CREATE_SUSPENDED..., pi)

// Allocate memory in the process to hold your DLL name to load
lpAlloc = VirtualAlloc(ph.hProcess, ... MEM_COMMIT, PAGE_READWRITE)

// Write the name of your dll to load in the process memory
WriteProcessMemeory(pi.hProcess, lpAlloc, dllName, ...)

// Get the address of LoadLibrary
fnLoadLibrary = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA")

// Create a remote thread in the process, giving it the threadproc for LoadLibrary
// and the argument of your DLL name
hTrhead = CreateRemoteThread(pi.hProcess, ..., fnLoadLibrary, lpAlloc, ...)

// Wait for your dll to load
WaitForSingleObject(hThread)

// Go ahead and start the Win32 process
ResumeThread(ph.hThread)

在您的 DLL 中,您可以将代码放入 DLL_PROCESS_ATTACH 中,该代码将连接到您设置的命名管道,并初始化您的所有内容。然后触发一个函数以开始监视和报告命名管道。

您的 C# threadproc 将监视命名管道的进程,并将其报告给 APP1。

更新:

我错过了您控制 Win32 进程的代码这一事实。在这种情况下,我只需向进程传递一个参数,该参数将控制您选择的通信 RPC 机制(共享内存、命名管道、队列服务、剪贴板 (ha) 等)。

这样,您的 C# threadproc 设置 RPC 通信通道和监控,然后将“地址”信息提供给您的 Win32 进程,以便它可以“回拨”。

我会把其他的东西留在那里,以防其他人想要监控他们不负责代码的 Win32 进程。

【讨论】:

  • 忘了提我的主要观点(或者相信,如果我错了,有人纠正我):你不能直接在另一个进程中调用一个函数。
  • 这个想法听起来很有趣。我必须在空闲时间试试这个,然后我会回来找你的。 +1 用于阐述该方法。
【解决方案3】:

嗯,这绝对不是简单的方法,但也许它会以某种方式帮助你。您应该能够以this 项目(StackWalk64)使用的方式获取另一个线程的堆栈跟踪,并最终看到所需函数的名称。它有其自身的问题,尤其是这种方法的性能可能不会太高,但据我了解,这是每个线程操作一次。问题是,它通常是否能够正确遍历您的(可能已优化的)应用程序堆栈。

【讨论】:

    【解决方案4】:

    首先,你不能真正可靠地做到这一点:如果你碰巧在线程执行函数指针之前或函数返回之后访问Thread.StartAddress,你将无法知道启动函数实际上是什么。

    其次,更可能的答案是在管理线程启动函数时没有直接映射到启动函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-08
      • 1970-01-01
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多