94 lines
12 KiB
PowerShell
94 lines
12 KiB
PowerShell
param([int]$ProcId = 0, [int]$DurationSec = 30, [int]$IntervalMs = 20, [string]$Out = 'C:\Users\re\samp.txt', [string]$StackThreads = 'exe')
|
|
# 32-bit stack sampler for a WoW64 process. Run under C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
|
|
if ([IntPtr]::Size -ne 4) { throw 'run under 32-bit powershell' }
|
|
if ($ProcId -eq 0) { $ProcId = (Get-Process | Where-Object { $_.Name -like 'Sword*' } | Select-Object -First 1).Id }
|
|
$src = @'
|
|
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.IO; using System.Diagnostics;
|
|
public class Samp {
|
|
[DllImport("kernel32")] static extern IntPtr OpenProcess(uint a, bool b, int pid);
|
|
[DllImport("kernel32")] static extern IntPtr OpenThread(uint a, bool b, uint tid);
|
|
[DllImport("kernel32")] static extern uint SuspendThread(IntPtr h);
|
|
[DllImport("kernel32")] static extern uint ResumeThread(IntPtr h);
|
|
[DllImport("kernel32")] static extern bool GetThreadContext(IntPtr h, IntPtr ctx);
|
|
[DllImport("kernel32")] static extern bool ReadProcessMemory(IntPtr h, IntPtr addr, byte[] buf, int n, out int read);
|
|
[DllImport("kernel32")] static extern bool GetThreadTimes(IntPtr h, out long c, out long e, out long k, out long u);
|
|
[DllImport("kernel32")] static extern bool CloseHandle(IntPtr h);
|
|
[DllImport("kernel32")] static extern IntPtr CreateToolhelp32Snapshot(uint f, uint pid);
|
|
[DllImport("kernel32")] static extern bool Thread32First(IntPtr s, ref THREADENTRY32 e);
|
|
[DllImport("kernel32")] static extern bool Thread32Next(IntPtr s, ref THREADENTRY32 e);
|
|
[DllImport("kernel32")] static extern int GetThreadPriority(IntPtr h);
|
|
[DllImport("psapi")] static extern bool EnumProcessModulesEx(IntPtr h, IntPtr[] mods, int cb, out int needed, uint flags);
|
|
[DllImport("psapi")] static extern bool GetModuleInformation(IntPtr h, IntPtr m, out MODULEINFO mi, int cb);
|
|
[DllImport("psapi", CharSet=CharSet.Unicode)] static extern int GetModuleFileNameExW(IntPtr h, IntPtr m, StringBuilder s, int n);
|
|
[DllImport("ntdll")] static extern int NtQueryInformationThread(IntPtr h, int cls, out IntPtr info, int len, out int ret);
|
|
[DllImport("ntdll")] static extern int NtQuerySystemInformation(int cls, IntPtr buf, int len, out int ret);
|
|
[StructLayout(LayoutKind.Sequential)] public struct THREADENTRY32 { public uint dwSize, cntUsage, th32ThreadID, th32OwnerProcessID; public int tpBasePri, tpDeltaPri; public uint dwFlags; }
|
|
[StructLayout(LayoutKind.Sequential)] public struct MODULEINFO { public IntPtr lpBaseOfDll; public uint SizeOfImage; public IntPtr EntryPoint; }
|
|
public class Mod { public uint Base, Size; public string Name; public List<KeyValuePair<uint,string>> Exp = new List<KeyValuePair<uint,string>>(); public uint CodeStart, CodeEnd; }
|
|
public class Thr { public uint Tid; public IntPtr H; public uint Start; public long Create; public long LastCpu; public double TotCpu; public bool Stack; public int Pri; }
|
|
IntPtr hp; List<Mod> mods = new List<Mod>(); List<Thr> thrs = new List<Thr>(); StreamWriter w; int pid;
|
|
byte[] tmp = new byte[8192];
|
|
uint RD(uint a){ int r; if(!ReadProcessMemory(hp,(IntPtr)a,tmp,4,out r)||r!=4) return 0xFFFFFFFF; return BitConverter.ToUInt32(tmp,0);}
|
|
bool ReadBuf(uint a, byte[] b, int n){ int r; return ReadProcessMemory(hp,(IntPtr)a,b,n,out r) && r==n; }
|
|
Mod FindMod(uint a){ foreach(var m in mods) if(a>=m.Base && a<m.Base+m.Size) return m; return null; }
|
|
public string Sym(uint a){ var m=FindMod(a); if(m==null) return "0x"+a.ToString("x8"); string best=null; uint boff=0; int lo=0,hi=m.Exp.Count-1; uint rva=a-m.Base;
|
|
// binary search last export <= rva
|
|
int idx=-1; while(lo<=hi){int mid=(lo+hi)/2; if(m.Exp[mid].Key<=rva){idx=mid;lo=mid+1;} else hi=mid-1;}
|
|
if(idx>=0 && rva-m.Exp[idx].Key < 0x10000){ best=m.Exp[idx].Value; boff=rva-m.Exp[idx].Key; return m.Name+"!"+best+"+0x"+boff.ToString("x"); }
|
|
return m.Name+"+0x"+rva.ToString("x")+"(0x"+a.ToString("x8")+")"; }
|
|
void LoadMods(){ IntPtr[] arr=new IntPtr[1024]; int need; EnumProcessModulesEx(hp,arr,arr.Length*4,out need,0x01);
|
|
int n=need/4; for(int i=0;i<n;i++){ MODULEINFO mi; GetModuleInformation(hp,arr[i],out mi,12); var sb=new StringBuilder(512); GetModuleFileNameExW(hp,arr[i],sb,512);
|
|
var m=new Mod(); m.Base=(uint)mi.lpBaseOfDll; m.Size=mi.SizeOfImage; m.Name=Path.GetFileName(sb.ToString()); mods.Add(m);
|
|
try{ uint pe=m.Base+RD(m.Base+0x3C); uint erva=RD(pe+0x78); uint esz=RD(pe+0x7C); m.CodeStart=m.Base+RD(pe+0x2C); m.CodeEnd=m.CodeStart+RD(pe+0x1C);
|
|
if(erva!=0 && erva!=0xFFFFFFFF){ uint ed=m.Base+erva; uint nf=RD(ed+0x14), nn=RD(ed+0x18), af=RD(ed+0x1C), an=RD(ed+0x20), ao=RD(ed+0x24);
|
|
if(nn<20000){ byte[] names=new byte[nn*4]; ReadBuf(m.Base+an,names,(int)nn*4); byte[] ords=new byte[nn*2]; ReadBuf(m.Base+ao,ords,(int)nn*2); byte[] funcs=new byte[nf*4]; ReadBuf(m.Base+af,funcs,(int)nf*4);
|
|
byte[] nb=new byte[96]; for(int k=0;k<nn;k++){ uint nrva=BitConverter.ToUInt32(names,k*4); ushort o=BitConverter.ToUInt16(ords,k*2); if(o>=nf) continue; uint frva=BitConverter.ToUInt32(funcs,o*4);
|
|
if(frva>=erva && frva<erva+esz) continue; int r; ReadProcessMemory(hp,(IntPtr)(m.Base+nrva),nb,96,out r); int z=Array.IndexOf(nb,(byte)0); if(z<0) z=96; m.Exp.Add(new KeyValuePair<uint,string>(frva,Encoding.ASCII.GetString(nb,0,z))); }
|
|
m.Exp.Sort((x,y)=>x.Key.CompareTo(y.Key)); } } } catch {} } }
|
|
void LoadThreads(string which){ IntPtr s=CreateToolhelp32Snapshot(4,0); var e=new THREADENTRY32(); e.dwSize=28; if(Thread32First(s,ref e)){ do{ if(e.th32OwnerProcessID==(uint)pid){ var t=new Thr(); t.Tid=e.th32ThreadID; t.H=OpenThread(0x1FFFFF,false,t.Tid); IntPtr sa; int ret; NtQueryInformationThread(t.H,9,out sa,4,out ret); t.Start=(uint)sa; long c,x,k,u; GetThreadTimes(t.H,out c,out x,out k,out u); t.Create=c; t.LastCpu=k+u; t.Pri=GetThreadPriority(t.H); thrs.Add(t);} e.dwSize=28; } while(Thread32Next(s,ref e)); } CloseHandle(s);
|
|
thrs.Sort((a,b)=>a.Create.CompareTo(b.Create)); var exe=mods[0];
|
|
foreach(var t in thrs){ bool inExe = t.Start>=exe.Base && t.Start<exe.Base+exe.Size; t.Stack = which=="all" || (which=="exe" && (inExe || t==thrs[0])) ; } }
|
|
// returns state, waitreason for tid via NtQuerySystemInformation(5)
|
|
IntPtr sysbuf=IntPtr.Zero; int sysbufLen=0;
|
|
Dictionary<uint,int[]> QueryStates(){ var d=new Dictionary<uint,int[]>(); int ret; if(sysbuf==IntPtr.Zero){ sysbufLen=1<<20; sysbuf=Marshal.AllocHGlobal(sysbufLen);} int st=NtQuerySystemInformation(5,sysbuf,sysbufLen,out ret); if(st!=0){ if(ret>sysbufLen){ Marshal.FreeHGlobal(sysbuf); sysbufLen=ret+65536; sysbuf=Marshal.AllocHGlobal(sysbufLen); st=NtQuerySystemInformation(5,sysbuf,sysbufLen,out ret);} if(st!=0) return d; }
|
|
int off=0; while(true){ int next=Marshal.ReadInt32(sysbuf,off); int nth=Marshal.ReadInt32(sysbuf,off+4); int p=Marshal.ReadInt32(sysbuf,off+68); if(p==pid){ for(int i=0;i<nth;i++){ int to=off+184+i*64; uint tid=(uint)Marshal.ReadInt32(sysbuf,to+36); d[tid]=new int[]{Marshal.ReadInt32(sysbuf,to+52),Marshal.ReadInt32(sysbuf,to+56),Marshal.ReadInt32(sysbuf,to+48),Marshal.ReadInt32(sysbuf,to+40)}; } break; } if(next==0) break; off+=next; } return d; }
|
|
bool IsCode(uint a){ var m=FindMod(a); return m!=null && a>=m.CodeStart && a<m.CodeEnd; }
|
|
bool LooksLikeRet(uint a){ byte[] b=new byte[8]; if(!ReadBuf(a-8,b,8)) return false; // call rel32 (E8), call [mem] FF15, call reg FFd0-d7, call [reg+disp8] FF5x, call [reg+disp32] FF9x
|
|
if(b[3]==0xE8) return true; if(b[2]==0xFF && (b[3]&0x38)==0x10) return true; if(b[6]==0xFF && (b[7]&0x38)==0x10) return true; if(b[5]==0xFF && (b[6]&0x38)==0x10) return true; if(b[1]==0xFF && (b[2]&0x38)==0x10) return true; if(b[4]==0xFF && (b[5]&0x38)==0x10) return true; return false; }
|
|
public void Run(int pid_, int durSec, int intervalMs, string outPath, string which){ pid=pid_; hp=OpenProcess(0x1FFFFF,false,pid); if(hp==IntPtr.Zero) throw new Exception("OpenProcess failed"); LoadMods(); LoadThreads(which);
|
|
w=new StreamWriter(outPath,false); w.WriteLine("# pid "+pid+" start "+DateTime.Now.ToString("HH:mm:ss.fff")+" interval "+intervalMs+"ms dur "+durSec+"s");
|
|
foreach(var m in mods) w.WriteLine("M 0x"+m.Base.ToString("x8")+" 0x"+m.Size.ToString("x")+" "+m.Name+" exports="+m.Exp.Count+" code=0x"+m.CodeStart.ToString("x8")+"-0x"+m.CodeEnd.ToString("x8"));
|
|
foreach(var t in thrs) w.WriteLine("T "+t.Tid+" start="+Sym(t.Start)+" pri="+t.Pri+" stack="+t.Stack+" cpu0="+(t.LastCpu/10000.0).ToString("F0")+"ms");
|
|
IntPtr ctx=Marshal.AllocHGlobal(2048); byte[] stk=new byte[4096]; var sw=Stopwatch.StartNew(); long nextCpu=0; int ns=0; var hist=new Dictionary<string,Dictionary<string,int>>(); var stateHist=new Dictionary<string,Dictionary<string,int>>();
|
|
var frameHist=new Dictionary<string,Dictionary<string,int>>();
|
|
while(sw.ElapsedMilliseconds < durSec*1000L){ long now=sw.ElapsedMilliseconds; var states=QueryStates();
|
|
foreach(var t in thrs){ if(!t.Stack) continue; int[] stt; states.TryGetValue(t.Tid,out stt); string sst = stt==null?"?":(stt[0]+"/"+stt[1]);
|
|
// suspend + context
|
|
if(SuspendThread(t.H)==0xFFFFFFFF){ continue; }
|
|
Marshal.WriteInt32(ctx,0,0x10007); bool ok=GetThreadContext(t.H,ctx); uint eip=0,ebp=0,esp=0; if(ok){ ebp=(uint)Marshal.ReadInt32(ctx,180); eip=(uint)Marshal.ReadInt32(ctx,184); esp=(uint)Marshal.ReadInt32(ctx,196); }
|
|
var frames=new List<string>(); var seen=new HashSet<uint>();
|
|
if(ok){ frames.Add(Sym(eip)); uint bp=ebp; for(int i=0;i<40 && bp!=0 && bp!=0xFFFFFFFF;i++){ uint ret=RD(bp+4); uint nb=RD(bp); if(ret==0||ret==0xFFFFFFFF) break; if(!IsCode(ret)) break; frames.Add(Sym(ret)); seen.Add(ret); if(nb<=bp) break; bp=nb; }
|
|
// stack scan fallback for frames the ebp chain misses
|
|
int got; if(ReadProcessMemory(hp,(IntPtr)esp,stk,4096,out got)){ var scan=new List<string>(); for(int i=0;i+4<=got;i+=4){ uint v=BitConverter.ToUInt32(stk,i); if(IsCode(v) && !seen.Contains(v) && LooksLikeRet(v)){ scan.Add(Sym(v)); if(scan.Count>=24) break; } } if(scan.Count>0) frames.Add("|scan| "+string.Join(" < ",scan.ToArray())); } }
|
|
ResumeThread(t.H);
|
|
string key=t.Tid.ToString(); if(!hist.ContainsKey(key)){hist[key]=new Dictionary<string,int>(); stateHist[key]=new Dictionary<string,int>(); frameHist[key]=new Dictionary<string,int>();}
|
|
string top= frames.Count>0?frames[0]:"?"; string topFn = top.Contains("+0x")? top.Substring(0,top.LastIndexOf("+0x")):top; if(!hist[key].ContainsKey(topFn)) hist[key][topFn]=0; hist[key][topFn]++;
|
|
if(!stateHist[key].ContainsKey(sst)) stateHist[key][sst]=0; stateHist[key][sst]++;
|
|
var fseen=new HashSet<string>(); foreach(var f in frames){ if(f.StartsWith("|scan|")) continue; string fn=f.Contains("+0x")? f.Substring(0,f.LastIndexOf("+0x")):f; if(fseen.Add(fn)){ if(!frameHist[key].ContainsKey(fn)) frameHist[key][fn]=0; frameHist[key][fn]++; } }
|
|
w.WriteLine("S "+now+" "+t.Tid+" st="+sst+" "+string.Join(" < ",frames.ToArray()));
|
|
}
|
|
if(now>=nextCpu){ nextCpu=now+500; var sb=new StringBuilder(); sb.Append("C "+now); foreach(var t in thrs){ long c,x,k,u; if(!GetThreadTimes(t.H,out c,out x,out k,out u)) continue; long cpu=k+u; double d=(cpu-t.LastCpu)/10000.0; t.LastCpu=cpu; t.TotCpu+=d; if(d>0.5){ sb.Append(" "+t.Tid+":"+d.ToString("F0")); } } w.WriteLine(sb.ToString()); }
|
|
ns++; int sleep=(int)(intervalMs-(sw.ElapsedMilliseconds-now)); if(sleep>0) System.Threading.Thread.Sleep(sleep); }
|
|
w.WriteLine("# end "+DateTime.Now.ToString("HH:mm:ss.fff")+" samples="+ns);
|
|
w.WriteLine("## per-thread total CPU ms over run");
|
|
foreach(var t in thrs) if(t.TotCpu>1) w.WriteLine("CPU "+t.Tid+" "+t.TotCpu.ToString("F0")+" start="+Sym(t.Start)+" pri="+t.Pri);
|
|
foreach(var kv in hist){ w.WriteLine("## thread "+kv.Key+" state hist"); var sl=new List<KeyValuePair<string,int>>(stateHist[kv.Key]); sl.Sort((a,b)=>b.Value.CompareTo(a.Value)); foreach(var s in sl) w.WriteLine(" "+s.Value+" "+s.Key);
|
|
w.WriteLine("## thread "+kv.Key+" top-of-stack hist"); var l=new List<KeyValuePair<string,int>>(kv.Value); l.Sort((a,b)=>b.Value.CompareTo(a.Value)); int c2=0; foreach(var s in l){ w.WriteLine(" "+s.Value+" "+s.Key); if(++c2>=40) break; }
|
|
w.WriteLine("## thread "+kv.Key+" inclusive frame hist"); var l2=new List<KeyValuePair<string,int>>(frameHist[kv.Key]); l2.Sort((a,b)=>b.Value.CompareTo(a.Value)); c2=0; foreach(var s in l2){ w.WriteLine(" "+s.Value+" "+s.Key); if(++c2>=60) break; } }
|
|
w.Close(); }
|
|
}
|
|
'@
|
|
Add-Type -TypeDefinition $src
|
|
$s = New-Object Samp
|
|
$s.Run($ProcId, $DurationSec, $IntervalMs, $Out, $StackThreads)
|
|
"done -> $Out"
|