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

背景:

我在Win32中编写了一个multithreading应用程序,我使用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 错误代码 ,这意味着:

指定的模块无法找到

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

 Process.MainWindowHandle Process.MainModule.BaseAddress 

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

每个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); 

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

我把它包装成一个.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(); 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或手动加载模块, 如MSDN上所述 。

我知道你说在你的情况下情况并非如此,但我会留下这个,以便通过这些关键字找到这个问题的任何人都能获益。

这就是我对这个问题的理解。

你有一个C#app,APP1创建了一堆线程。

反过来,这些线程每个都创建一个进程。 我假设这些线程保持活跃并负责监视它产生的进程。

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

他们这样做的方式我会在过去的好日子里做到这一点:

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

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

在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中,该DLL_PROCESS_ATTACH将连接到您设置的命名管道,并初始化您的所有内容。 然后触发一个函数以开始监视和报告命名管道。

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

更新:

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

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

我会留下其他的东西,以防其他人想要监视他们不负责代码的Win32进程。

嗯,这绝对不是直截了当的方法,但也许它会以某种方式帮助你。 您应该能够以该项目使用的方式(StackWalk64)获取另一个线程的堆栈跟踪,并最终看到所需函数的名称。 它有自己的问题,特别是这种方法的性能可能不会太高,但据我所知,这是每次线程操作的一次性。 问题是,它通常能够正确地处理您的(可能是优化的)应用程序的堆栈。

首先,您无法可靠地执行此操作:如果您在线程执行函数指针之前或在函数返回之后碰巧访问Thread.StartAddress ,您将无法知道启动函数实际上是什么。

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