ho pensato che vorrei inviare una soluzione per questo per le persone in futuro.
Un modo è possibile gestire questo se non si vuole tuffarsi nel codice C++ non salvato e riscrivere in C# è di usare semplicemente questo programma su github:
https://github.com/makemek/cheatengine-threadstack-finder
Il download link diretto è qui:
https://github.com/makemek/cheatengine-threadstack-finder/files/685703/threadstack.zip
È possibile passare questo eseguibile un ID di processo e di analizzare l'indirizzo filo che vi serve.
Fondamentalmente, ciò che ho fatto è che il mio processo esegue l'exe, reindirizza l'output e lo analizza.
Quindi il processo si chiude e facciamo ciò di cui abbiamo bisogno: mi sento come se stessi barando, ma funziona.
L'uscita per threadstack.exe
appare tipicamente come questo:
PID 6540 (0x198c)
Grabbing handle
Success
PID: 6540 Thread ID: 0x1990
PID: 6540 Thread ID: 0x1b1c
PID: 6540 Thread ID: 0x1bbc
TID: 0x1990 = THREADSTACK 0 BASE ADDRESS: 0xbcff8c
TID: 0x1b1c = THREADSTACK 1 BASE ADDRESS: 0x4d8ff8c
TID: 0x1bbc = THREADSTACK 2 BASE ADDRESS: 0x518ff8c
Ecco il codice che ho in ultima analisi, usato per ottenere l'indirizzo che ho bisogno:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
////////////////////////////////////////////////////////////////////
// These are used to find the StardewValley.Farmer structure //
//////////////////////////////////////////////////////////////////
private IntPtr Thread0Address;
private IntPtr FarmerStartAddress;
private static int[] FARMER_OFFSETS = { 0x4, 0x478, 0x218, 0x24C };
private static int FARMER_FIRST = 0x264;
//////////////////////////////////////////////////////////////////
private async void hookAll()
{
SVProcess = Process.GetProcessesByName("Stardew Valley")[0];
SVHandle = OpenProcess(ProcessAccessFlags.All, true, SVProcess.Id);
SVBaseAddress = SVProcess.MainModule.BaseAddress;
Thread0Address = (IntPtr) await getThread0Address();
getFarmerStartAddress();
}
private Task<int> getThread0Address()
{
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "threadstack.exe",
Arguments = SVProcess.Id + "",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
if (line.Contains("THREADSTACK 0 BASE ADDRESS: "))
{
line = line.Substring(line.LastIndexOf(":") + 2);
return Task.FromResult(int.Parse(line.Substring(2), System.Globalization.NumberStyles.HexNumber));
}
}
return Task.FromResult(0);
}
private void getFarmerStartAddress()
{
IntPtr curAdd = (IntPtr) ReadInt32(Thread0Address - FARMER_FIRST);
foreach (int x in FARMER_OFFSETS)
curAdd = (IntPtr) ReadInt32(curAdd + x);
FarmerStartAddress = (IntPtr) curAdd;
}
private int ReadInt32(IntPtr addr)
{
byte[] results = new byte[4];
int read = 0;
ReadProcessMemory(SVHandle, addr, results, results.Length, out read);
return BitConverter.ToInt32(results, 0);
}

Se Sono interessato ad aggiornare il codice C++, credo che la parte rilevante sia qui.
E 'in realtà non sembra troppo complicato - Penso che tu stai solo afferrando l'indirizzo di base del kernal32.dll
e alla ricerca di tale indirizzo nella pila filo controllando per vedere se è >=
all'indirizzo di base o per <=
il base address + size
durante la lettura ogni 4 byte - avrei dovuto giocare con esso però.
DWORD GetThreadStartAddress(HANDLE processHandle, HANDLE hThread) {
/* rewritten from https://github.com/cheat-engine/cheat-engine/blob/master/Cheat%20Engine/CEFuncProc.pas#L3080 */
DWORD used = 0, ret = 0;
DWORD stacktop = 0, result = 0;
MODULEINFO mi;
GetModuleInformation(processHandle, GetModuleHandle("kernel32.dll"), &mi, sizeof(mi));
stacktop = (DWORD)GetThreadStackTopAddress_x86(processHandle, hThread);
/* The stub below has the same result as calling GetThreadStackTopAddress_x86()
change line 54 in ntinfo.cpp to return tbi.TebBaseAddress
Then use this stub
*/
//LPCVOID tebBaseAddress = GetThreadStackTopAddress_x86(processHandle, hThread);
//if (tebBaseAddress)
// ReadProcessMemory(processHandle, (LPCVOID)((DWORD)tebBaseAddress + 4), &stacktop, 4, NULL);
CloseHandle(hThread);
if (stacktop) {
//find the stack entry pointing to the function that calls "ExitXXXXXThread"
//Fun thing to note: It's the first entry that points to a address in kernel32
DWORD* buf32 = new DWORD[4096];
if (ReadProcessMemory(processHandle, (LPCVOID)(stacktop - 4096), buf32, 4096, NULL)) {
for (int i = 4096/4 - 1; i >= 0; --i) {
if (buf32[i] >= (DWORD)mi.lpBaseOfDll && buf32[i] <= (DWORD)mi.lpBaseOfDll + mi.SizeOfImage) {
result = stacktop - 4096 + i * 4;
break;
}
}
}
delete buf32;
}
return result;
}
È possibile ottenere gli indirizzi di base filo in C# in questo modo:
https://stackoverflow.com/a/8737521/1274820
La chiave è quello di chiamare la funzione NtQueryInformationThread
. Questa non è una funzione completamente "ufficiale" (probabilmente non documentata in passato?), Ma la documentazione non suggerisce alternative per ottenere l'indirizzo iniziale di una discussione.
L'ho incluso in una chiamata .NET che accetta un ID thread e restituisce l'indirizzo iniziale come IntPtr
. Questo codice è stato testato in modalità x86 e x64 e nel secondo è stato testato su un processo di destinazione sia a 32 bit che a 64 bit.
Una cosa che non ho testato era eseguire questo con bassi privilegi; Mi aspetto che questo codice richieda al chiamante di avere il 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
}
}
uscita sul mio sistema:
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
a parte la roba tra parentesi dal che richiede P extra/Invoke di.
Per quanto riguarda SymFromAddress
errore "il modulo non trovato", volevo solo dire che uno ha bisogno di chiamare SymInitialize
con fInvadeProcess = true
OPPURE caricare il modulo manualmente, as documented on MSDN.
Ho iniziato una taglia su questa domanda mentre sto cercando di capire la stessa cosa. Vale a dire, come ottenere l'indirizzo di 'THREADSTACK0' in C#. Ho trovato alcune informazioni su come è stato creato in cheatengine, e posso leggere l'indirizzo in cheatengine, ma nessuno dei threadstartaddresses sto tirando in C# corrisponde a ciò che CE restituisce – user1274820
Vedi qui: http://forum.cheatengine.org/ viewtopic.php? p = 5487976 – user1274820