Obtenir L'adresse de démarrage de win32 thread à partir d'un autre processus

Contexte:

j'ai écrit une application multi-threadée dans Win32, que je commence à partir du code C en utilisant Process classe System.Diagnostics espace de noms.

maintenant, dans le code C#, je veux obtenir le nom/symbole de l'adresse de début de chaque thread créé dans L'application Win32 pour que je puisse enregistrer les informations relatives au thread, comme L'utilisation CPU, dans la base de données. Fondamentalement, le code C # démarre plusieurs instances de L'Application Win32, les surveille, tue si nécessaire, et puis enregistre l'information / l'erreur/les exceptions/la raison / etc. dans la base de données.

pour ce faire, j'ai enveloppé deux API Win32 viz. SymInitialize et SymFromAddr dans programmeurs API écrit par moi-même, comme indiqué ci-dessous:

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
}

et ensuite appeler ces API à partir du code C#, en utilisant pinvoke. Mais il ne fonctionne pas et GetLastError donne 126code d'erreur ce qui signifie:

le module spécifié ne peut pas être

je suis de passage Process.HandlehModule pour les deux fonctions; initialize_handler semble fonctionner, mais get_function_symbol n'est pas; il donne l'erreur ci-dessus. Je ne suis pas sûr de passer la bonne poignée. J'ai essayé de passer les poignées suivantes:

Process.MainWindowHandle
Process.MainModule.BaseAddress

les deux échouent à la première étape elle-même (I. e en appelant initialize_handler). Je suis de passage Process.Threads[i].StartAddress comme deuxième argument, et cela semble être la cause de l'échec comme ProcessThread.StartAddress semble être l'adresse de RtlUserThreadStart fonction, l'adresse de la fonction de démarrage spécifique à l'application. MSDN dit à ce sujet:

chaque thread Windows commence en fait l'exécution dans une fonction fournie par le système, pas la fonction fournie par l'application. L'adresse de départ du thread primaire est donc la même (puisqu'elle représente l'adresse de la fonction fournie par le système) pour chaque processus Windows dans le système. cependant, la propriété StartAddress permet vous devez obtenir l'adresse de départ de la fonction qui est spécifique à votre application.

mais il ne dit pas comment obtenir l'adresse de la fonction startinbg spécifique à l'application, en utilisant ProcessThread.StartAddress.

Question:

mon problème consiste à obtenir l'adresse de départ du thread win32 à partir d'une autre application (écrite en C#), car une fois que je l'aurai, j'obtiendrai le nom aussi, en utilisant les API mentionnées ci-dessus. Alors, comment obtenir le départ l'adresse?


j'ai testé mon API de recherche de symboles à partir du code C++. Il fonctionne très bien pour résoudre l'adresse d'un symbole, si l'adresse correcte pour commencer.

Voici mes déclarations 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);
21
demandé sur Roman Starkov 2011-12-30 16:24:31

4 réponses

La clé est d'appeler le NtQueryInformationThread fonction. Ce n'est pas une fonction entièrement "officielle" (peut-être non documentée dans le passé?), mais la documentation ne suggère aucune alternative pour obtenir l'adresse de départ d'un thread.

Je l'ai enveloppé dans un appel. net-friendly qui prend un ID de thread et renvoie l'adresse de départ comme IntPtr. Ce code a été testé en mode x86 et x64, et dans ce dernier il a été testé à la fois sur une cible 32 bits et sur une cible 64 bits. processus.

une chose que je n'ai pas testée, c'était de lancer ceci avec de faibles privilèges; Je m'attendrais à ce que ce code exige que l'appelant ait le 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
    }
}

Sortie sur mon système:

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

mis à part les choses entre parenthèses puisque cela nécessite des P/Invoke'S.


Concernant SymFromAddress erreur "module not found", je voulais juste mentionner qu'il faut appeler SymInitializefInvadeProcess = true OU charger le module manuellement, tel que documenté sur MSDN.

je sais que tu dis que ce n'est pas le cas dans votre situation, mais je laisse cela pour le bénéfice de toute personne qui trouve cette question à l'aide de ceux mots-clés.

15
répondu Roman Starkov 2012-01-05 18:35:07

voici ce que je comprends du problème.

Vous avez un c # app, APP1 qui crée un tas de threads.

ces fils, à leur tour, créent chacun un processus. Je suppose que ces fils restent vivants et sont en charge de surveiller le processus qu'il a engendré.

donc pour chaque thread dans APP1, vous voulez qu'il énumère des informations sur les threads générés dans le processus enfant de ce thread.

de cette façon, je l'aurais fait dans le bon-vieux-temps serait:

  • Code tous mes Win32 thread de surveillance d'un processus Win32 dans une DLL
  • injecter cette DLL dans le processus que je voulais surveiller
  • utiliser un tube nommé ou un autre mécanisme RPC pour communiquer du processus Win32 injecté à L'hôte APP1

ainsi, dans votre threadproc principal en C#, vous créez et surveillez un tuyau nommé pour votre processus de communiquer une fois qu'il a été injecté.

dans C++ land, le pseudo code serait alors de créer un processus suspendu, allouer un peu de mémoire dans ce processus, injecter votre DLL dans le processus, puis créer un fil distant qui exécuterait votre dll injecté:

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)

dans votre DLL, vous pourriez mettre du code dans DLL_PROCESS_ATTACH qui se connecterait à la pipe nommée que vous avez configurée, et initialiser toutes vos affaires. Ensuite, activez une fonction pour commencer à surveiller et à faire rapport sur le tuyau nommé.

votre c # threadproc surveillerait le tuyau nommé pour son processus, et le signaler sur JUSQU'à APP1.

mise à jour:

j'ai manqué le fait que vous contrôliez le code pour le Proccess Win32. Dans ce cas, je passerais simplement un argument au proccess qui contrôlerait le mécanisme RPC de votre choix pour la communication (mémoire partagée, pipes nommées, service de file d'attente, presse-papiers (ha), etc).

de cette façon, votre c # threadproc met en place le canal de communication RPC et la surveillance, et puis fournit l '"adresse" de votre processus Win32 afin qu'il puisse "vous rappeler".

je vais laisser les autres choses là-haut au cas où il est utile à quiconque veut surveiller un processus Win32 où ils ne sont pas en charge du code.

2
répondu GalacticJello 2012-01-05 17:56:05

Eh bien, ce n'est certainement pas l'approche simple, mais peut-être qu'elle vous aidera d'une façon ou d'une autre. Vous devriez être capable d'obtenir la trace de la pile d'un autre thread d'une manière utilisée par projet (StackWalk64) et éventuellement voir le nom de la fonction désirée. Il a ses propres problèmes, en particulier la performance de cette approche ne sera probablement pas trop élevé, mais comme je l'ai compris ce est un coup par opération de filetage. La question Est, sera-t-il généralement en mesure de marcher correctement la pile de vos applications (probablement optimisées).

0
répondu konrad.kruczynski 2012-01-05 00:23:57

tout d'abord, vous ne pouvez pas vraiment faire cela de manière fiable: si vous arrivez à accéder Thread.StartAddress avant que le thread n'exécute le pointeur de fonction ou après le retour de la fonction, vous n'aurez aucun moyen de savoir ce qu'est réellement la fonction de départ.

Deuxièmement, la réponse la plus probable est qu'il n'y a pas de correspondance directe avec la fonction de démarrage lorsque la fonction de démarrage du thread est gérée.

0
répondu MSN 2012-01-05 19:42:42