Получить StartAddress из win32-потока из другого процесса
История:
Я написал многопоточное приложение в Win32, которое я начинаю с кода С#, используя класс Process
из пространства имен System.Diagnostics
.
Теперь, в коде С#, я хочу получить имя/символ начального адреса каждого потока, созданного в приложении Win32, чтобы я мог записывать информацию, связанную с потоком, такую как использование ЦП, в базу данных. В основном, код С# запускает несколько экземпляров приложения Win32, контролирует их, при необходимости убивает, а затем регистрирует информацию/ошибку/исключения/причину/etc в базе данных.
С этой целью я обернул два API Win32: SymInitialize
и SymFromAddr
в программном 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
}
И затем вызовите эти API из кода С#, используя pinvoke. Но это не работает, и 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 позволяет получить адрес начальной функции, специфичный для вашего приложения.
Но он не говорит, как получить адрес функции startinbg, специфичный для приложения, используя ProcessThread.StartAddress.
Вопрос:
Моя проблема сводится к тому, чтобы получить начальный адрес потока win32 из другого приложения (написанного на С#), как только я его получу, я также получу имя, используя вышеупомянутые API. Итак, как получить начальный адрес?
Я проверил свой 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);
Ответы
Ответ 1
Ключом является вызов функции NtQueryInformationThread
. Это не полностью "официальная" функция (возможно, недокументированная в прошлом?), Но в документации нет альтернативы для получения начального адреса потока.
Я завернул его в .NET-дружественный вызов, который принимает идентификатор потока и возвращает начальный адрес как 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
"module not found" я просто хотел упомянуть, что нужно вызвать SymInitialize
с помощью fInvadeProcess = true
ИЛИ загрузить модуль вручную, как описано в MSDN.
Я знаю, что вы говорите, что это не так в вашей ситуации, но я оставлю это в интересах любого, кто находит этот вопрос через эти ключевые слова.
Ответ 2
Здесь мое понимание проблемы.
У вас есть приложение С#, APP1, которое создает кучу потоков.
Эти потоки, в свою очередь, создают процесс. Я предполагаю, что эти потоки остаются в живых и отвечают за мониторинг процесса, который он породил.
Итак, для каждого потока в APP1 вы хотите, чтобы он перечислял информацию о потоках, порожденных в дочернем процессе этого потока.
Они могли бы сделать это в добрые старые дни:
- Содержит весь мой мониторинг потоков Win32 для данного процесса Win32 в DLL
- Вставить эту DLL в процесс, который я хотел отслеживать
- Использовать именованный канал или другой механизм RPC для связи из инъецированного процесса Win32 на хост APP1
Итак, в вашем основном threadproc в С# вы должны создать и контролировать именованный канал для вашего процесса для связи после его ввода.
В С++ land псевдо-код должен был бы создать приостановленный процесс, выделить в нем некоторую память, вставить вашу 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, который будет подключаться к именованному каналу, который вы настроили, и инициализировать все свои вещи. Затем запустите функцию, чтобы начать мониторинг и отчетность по именованному каналу.
Ваш С# threadproc будет контролировать именованный канал для его процесса и сообщать об этом до APP1.
UPDATE:
Я пропустил тот факт, что вы управляете кодом для Win32-процесса. В этом случае я просто передал бы аргумент процессу, который бы контролировал механизм RPC по вашему выбору для связи (общая память, именованные каналы, служба очереди, буфер обмена (ha) и т.д.).
Таким образом, ваш С# threadproc настраивает канал связи RPC и мониторинг, а затем предоставляет информацию о "адресе" вашему процессу Win32, чтобы он мог "набрать вас".
Я оставлю другие вещи там, если это полезно для всех, кто хочет контролировать процесс Win32, где они не отвечают за код.
Ответ 3
Ну, это определенно не простой подход, но, возможно, это поможет вам как-то. Вы должны иметь возможность получить трассировку стека другого потока, используя этот проект (StackWalk64) и в конечном итоге увидеть имя желаемой функции. У этого есть свои проблемы, в особенности производительность этого подхода, вероятно, не будет слишком высокой, но, как я понял, это одноразовая работа в потоке. Вопрос в том, сможет ли он в общем случае правильно пройти стек ваших (возможно оптимизированных) приложений.
Ответ 4
Во-первых, вы не можете действительно сделать это надежно: если вам придётся получить доступ к Thread.StartAddress
, прежде чем поток выполнит указатель функции или после возвращения функции, вы не сможете узнать, что такое начальная функция.
Во-вторых, более вероятным ответом является то, что при запуске функции запуска потока нет прямого сопоставления с функцией запуска.