我在Win32中编写了一个多线程应用程序,我使用Process
命名空间中的System.Diagnostics
类从C#代码开始。
现在,在C#代码中,我想获取在Win32应用程序中创建的每个线程的起始地址的名称/符号,以便我可以将与线程相关的信息(例如CPU使用情况)记录到数据库中。基本上,C#代码启动Win32应用程序的多个实例,监视它们,在需要时终止,然后将info / error / exceptions / reason / etc记录到数据库。
为此,我已经包装了两个Win32 API viz。我自己编写的程序友好API中的SymInitialize
和SymFromAddr
,如下所示:
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);
答案 0 :(得分:15)
关键是调用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
“找不到模块”错误,我只想提一下,需要使用SymInitialize
调用fInvadeProcess = true
或手动加载模块as documented on MSDN。< / p>
我知道你说在你的情况下不是这种情况,但我会留下这个,以便通过那些关键字找到这个问题的任何人的利益。
答案 1 :(得分:2)
以下是我对问题的理解。
你有一个C#app,APP1可以创建一堆线程。
反过来,这些线程每个都会创建一个进程。我假设这些线程保持活跃并且负责监视它产生的进程。
因此,对于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,它将连接到你设置的命名管道,并初始化你所有的东西。然后触发一个函数来开始监视和报告命名管道。
您的C#threadproc将监视命名管道的进程,并将其报告到APP1。
<强>更新强>
我错过了你控制Win32进程代码的事实。在这种情况下,我只会将一个参数传递给proccess,它将控制您选择进行通信的RPC机制(共享内存,命名管道,队列服务,剪贴板(ha)等)。
这样,您的C#threadproc设置RPC通信通道和监控,然后向您的Win32进程提供“地址”信息,以便它可以“回拨”。
我会留下其他东西,以防其他人想要监控Win32进程,而不负责代码。
答案 2 :(得分:0)
嗯,这绝对不是直截了当的方法,但也许它会以某种方式帮助你。您应该能够以this项目(StackWalk64)使用的方式获取另一个线程的堆栈跟踪,并最终查看所需函数的名称。它有自己的问题,特别是这种方法的性能可能不会太高,但据我所知,这是每次线程操作的一次性。问题是,它通常能够正确地处理您的(可能是优化的)应用程序的堆栈。
答案 3 :(得分:0)
首先,您无法可靠地执行此操作:如果您在线程执行函数指针之前或函数返回之后碰巧访问Thread.StartAddress
,您将无法知道启动函数实际是什么
其次,更可能的答案是在管理线程启动功能时没有直接映射到启动函数。