Получить StartAddress потока win32 из другого процесса - PullRequest
23 голосов
/ 30 декабря 2011

Справочная информация:

Я написал многопоточное приложение в Win32, которое я запускаю из кода C #, используя Process класс из System.Diagnostics пространства имен.

Теперь вКод C #, я хочу получить имя / символ начального адреса каждого потока, созданного в приложении Win32, чтобы я мог записывать информацию, связанную с потоками, такую ​​как использование процессора, в базу данных.По сути, код C # запускает несколько экземпляров приложения Win32, отслеживает их, убивает при необходимости, а затем записывает информацию / ошибки / исключения / причины / и т. Д. В базу данных.

Для этой цели я установил два Win32API, а именно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 из кода C #,используя pinvoke.Но это не работает, и GetLastError выдает 126 код ошибки , что означает:

Указанный модуль не найден

IПередаю 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 из другого приложения (написанного на C #), как только я его получу,получить имя также, используя вышеупомянутые API.Итак, как получить начальный адрес?


Я проверил мой API поиска символов из кода C ++.Он прекрасно работает для преобразования адреса в символ, если ему дан правильный адрес для начала.

Вот мои объявления 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);

Ответы [ 4 ]

17 голосов
/ 05 января 2012

Клавиша должна вызывать функцию 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's.


Относительно SymFromAddress ошибки "модуль не найден", я просто хотел упомянуть, что нужно вызвать SymInitialize с fInvadeProcess = true ИЛИ загрузить модуль вручную, , как описано в MSDN .

Я знаю, что вы говорите, что это не так в вашей ситуации, но я оставлю это в интересах любого, кто найдет этот вопрос через эти ключевые слова.

2 голосов
/ 05 января 2012

Вот мое понимание проблемы.

У вас есть приложение C #, APP1, которое создает несколько потоков.

Эти потоки, в свою очередь, создают каждый процесс.Я предполагаю, что эти потоки остаются в живых и отвечают за мониторинг процесса, который он породил.

Итак, для каждого потока в APP1 вы хотите, чтобы он перечислял информацию о потоках, порожденных в дочернем процессе этого потока.

То, как я бы сделал это в старые добрые времена, было бы:

  • Кодировать весь мой мониторинг потока Win32 данного процесса Win32 в DLL
  • Внедрение этой DLL в процесс, который я хотел отслеживать
  • Использование именованного канала или другого механизма RPC для связи между внедренным процессом Win32 и хостом APP1

Итак, в вашем основномThreadproc в 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 для связи (общая память, именованные каналы, служба очереди, буфер обмена (ха) и т. Д.).

Таким образомваш C # threadproc устанавливает канал связи RPC и мониторинг, а затем предоставляет информацию об «адресе» вашему процессу Win32, чтобы он мог «перезвонить вам».

Я оставлю другие вещи там, вна случай, если кому-то еще захочется отслеживать процесс Win32, если он не отвечает за код.

0 голосов
/ 05 января 2012

Во-первых, вы действительно не можете сделать это надежно: если вам случится получить доступ к Thread.StartAddress до того, как поток выполнит указатель функции или после того, как функция вернется, у вас не будет возможности узнать, что на самом деле является стартовой функцией.

Во-вторых, более вероятный ответ заключается в том, что прямого управления функцией запуска при управлении функцией запуска потока нет.

0 голосов
/ 05 января 2012

Ну, это определенно не простой подход, но, возможно, он вам как-то поможет. Вы должны иметь возможность получить трассировку стека другого потока способом, используемым этим проектом (StackWalk64), и в конечном итоге увидеть имя нужной функции. У него есть свои проблемы, особенно производительность этого подхода, вероятно, не будет слишком высокой, но, как я понял, это операция «один выстрел на поток». Вопрос в том, сможет ли он вообще правильно обходить стек ваших (возможно, оптимизированных) приложений.

...