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> Exp = new List>(); 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 mods = new List(); List thrs = new List(); 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=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=nf) continue; uint frva=BitConverter.ToUInt32(funcs,o*4); if(frva>=erva && frva(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 QueryStates(){ var d=new Dictionary(); 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=m.CodeStart && a>(); var stateHist=new Dictionary>(); var frameHist=new Dictionary>(); 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(); var seen=new HashSet(); 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(); 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(); stateHist[key]=new Dictionary(); frameHist[key]=new Dictionary();} 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(); 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>(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>(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>(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"