From 70ee998c958d7ed855e271d5192962530a6baa7b Mon Sep 17 00:00:00 2001 From: Eusebiu Marcu Date: Sun, 3 Apr 2011 16:52:26 +0300 Subject: [PATCH] Add memory pad for debugged process memory --- data/resources/StringResources.resx | 9 + .../image/BitmapResources/BitmapResources.res | 2 + .../image/BitmapResources/PadIcons/memory.png | Bin 0 -> 3273 bytes .../Debugger.AddIn/Debugger.AddIn.addin | 18 ++ .../Debugger.AddIn/Debugger.AddIn.csproj | 2 + .../Pads/Commands/MemoryPadCommands.cs | 64 +++++++ .../Debugger/Debugger.AddIn/Pads/MemoryPad.cs | 169 ++++++++++++++++++ .../Debugger.Core/Interop/NativeMethods.cs | 94 +++++++++- src/AddIns/Debugger/Debugger.Core/Process.cs | 4 + .../Src/Gui/Pads/AbstractConsolePad.cs | 31 +++- .../Resources/BitmapResources.resources | Bin 670938 -> 674415 bytes 11 files changed, 390 insertions(+), 3 deletions(-) create mode 100644 data/resources/image/BitmapResources/PadIcons/memory.png create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Pads/Commands/MemoryPadCommands.cs create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Pads/MemoryPad.cs diff --git a/data/resources/StringResources.resx b/data/resources/StringResources.resx index 787a4f0294..f29cd9261e 100644 --- a/data/resources/StringResources.resx +++ b/data/resources/StringResources.resx @@ -5728,6 +5728,15 @@ Microsoft.Tools.WindowsInstallerXml.Extensions.NetFxCompiler, WixNetFxExtension< Object Graph + + Memory + + + Jump to address: + + + Refresh + Parallel Stacks diff --git a/data/resources/image/BitmapResources/BitmapResources.res b/data/resources/image/BitmapResources/BitmapResources.res index 1cb487ede6..3e8469ea86 100644 --- a/data/resources/image/BitmapResources/BitmapResources.res +++ b/data/resources/image/BitmapResources/BitmapResources.res @@ -46,6 +46,8 @@ ParallelStacks.ZoomControl = PadIcons\ZoomControl.png OutputPad.Toolbar.ClearOutputWindow = OutputPadIcons\ClearOutputWindow.png OutputPad.Toolbar.ToggleWordWrap = OutputPadIcons\ToggleWordWrap.png +#Memory pad +MemoryPad.Icon = PadIcons\memory.png Icons.16x16.OpenFolderBitmap = ProjectBrowserIcons\Folder.Open.png Icons.16x16.ClosedFolderBitmap = ProjectBrowserIcons\Folder.Closed.png diff --git a/data/resources/image/BitmapResources/PadIcons/memory.png b/data/resources/image/BitmapResources/PadIcons/memory.png new file mode 100644 index 0000000000000000000000000000000000000000..879019b26bf4fdf550279f3dee49277bdba3e291 GIT binary patch literal 3273 zcmV;)3^wzLP)004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ(iwV_E---f zE+8EQQ5a?h7|H;{3{7l^s6a#!5dlSzpnw6Rp-8NVVj(D~U=K(TP+~BOsHkK{)=GSN zdGF=r_s6~8+Gp=`_t|@&wJrc8PaiHX1(pIJnJ3@}dN|Wpg-6h_{Qw4dfB~ieFj?uT zzCrH6KqN0W7kawL3H*!R3;{^|zGdj?Pp5H0=h0sk8Wyh&7ga7GLtw0fuTQ>mB{3?=`JbBsZ3rr0E=h-EE#ca>7pWA znp#_08k!lIeo?6Zy7)IG?(HJI3i#YJh}QRq?XUb&>HuKOifXg#4_nNB06Mk;Ab0-{ zo8}<^Bt?B|zwyO+XySQ^7YI^qjEyrhGmW?$mXWxizw3WG{0)8aJtOgUzn6#Z%86wP zlLT~e-B>9}DMCIyJ(bDg&<+1Q#Q!+(uk%&0*raG}W_n!s* z`>t?__>spaFD&Aut10z!o?HH?RWufnX30 z)&drY2g!gBGC?lb3<^LI*ah~2N>BspK_h4ZCqM@{4K9Go;5xVo?tlki1dM~{UdPU)xj{ZqAQTQoLvauf5<ZgZNI6o6v>;tbFLDbRL8g&+C=7~%qN5B^ zwkS_j2#SSDLv276qbgBHQSGQ6)GgE~Y6kTQO-3uB4bV1dFZ3#O96A$SfG$Tjpxe-w z(09<|=rSYbRd;g|%>I!rO<0Hzgl9y5R$!^~o_Sb3}g)(-23Wnu-`0_=Y5 zG3+_)Aa)%47DvRX;>>XFxCk5%mxn9IHQ~!?W?(_!4|Qz6*Z? zKaQU#NE37jc7$L;0%0?ug3v;^M0iMeMI;i{iPppbBA2*{SV25ayh0o$z9Y$y^hqwH zNRp7WlXQf1o^+4&icBVJlO4$sWC3|6xsiO4{FwY!f+Arg;U&SA*eFpY(JnD4@j?SR-`K0DzX#{6;CMMSAv!Fl>(L4DIHeoQ<_y) zQT9+yRo<_BQF&U0rsAlQpi-uCR%J?+qH3?oRV`CJr}~U8OLw9t(JSaZ^cgiJHBU96 zTCG~Y+Pu1sdWd?SdaL>)4T1(kBUYnKqg!J}Q&rPfGgq@&^S%~di=h>-wNI;8Yff87 zJ4}0Dt zz%@8vFt8N8)OsmzY2DIcLz1DBVTNI|;iwVK$j2zpsKe-mv8Hi^@owW@<4-0QCP^ms zCJ#(yOjnrZnRc1}YNl_-GOIGXZB90KH{WR9Y5sDV!7|RWgUjw(P%L~cwpnyre6+N( zHrY-t*ICY4 zUcY?IPTh`aS8F$7Pq&Y@KV(1Rpyt4IsB?JYsNu+VY;c@#(sN31I_C7k*~FRe+~z#z zV&k&j<-9B6>fu`G+V3Xg7UEXv_SjwBJ8G6!a$8Ik+VFL5OaMFr+(FGBh%@F?24>HLNsjWR>x%^{cLj zD}-~yJ0q|Wp%D!cv#Z@!?_E6}X%SfvIkZM+P1c&LYZcZetvwSZ8O4k`8I6t(i*Abk z!1QC*F=u1EVya_iST3x6tmkY;b{Tt$W5+4wOvKv7mc~xT*~RUNn~HacFOQ$*x^OGG zFB3cyY7*uW{SuEPE+mB|wI<_|qmxhZWO#|Zo)ndotdxONgVci5ku;mMy=gOiZ+=5M zl)fgtQ$Q8{O!WzMgPUHd;& z##i2{a;|EvR;u1nJ$Hb8VDO;h!Im23nxdNbhq#CC)_T;o*J;<4AI2QcIQ+Cew7&Oi z#@CGv3JpaKACK^kj2sO-+S6#&*x01hRMHGL3!A5oMIO8Pjq5j^Eru<%t+dvnoA$o+&v?IGcZV;atwS+4HIAr!T}^80(JeesFQs#oIjrJ^h!wFI~Cpe)(drQ}4Me zc2`bcwYhrg8sl2Wb<6AReHMLfKUnZUby9Y>+)@{ z+t=@`yfZKqGIV!1a(Lt}`|jkuqXC)@%*Rcr{xo>6OEH*lc%TLr*1x5{cQYs>ht;Of}f>-u708W z;=5lQf9ac9H8cK_|8n8i;#cyoj=Wy>x_j1t_VJtKH}i9aZ{^<}eaCp$`#$Xb#C+xl z?1zevdLO$!d4GDiki4+)8~23s`{L#u!Te zSaefwW^{L9a%BKeVQFr3E>1;MAa*k@H7+-&gq_R)00GrWL_t(IPpy;7OB+!X#{G}l zja|9x%AKpa>8chAg+g&rT~tA+F4|2uE=&v2JdmUariPNqyOU%xnPxIkj1u!QnM_P3 z57H!rK&ziQ*D4ic7OfX97Y^Lt_nq(DJ27z|>%CLQs=bcoO%02mDtNtqikBY?c>W=e zr)ya}Svkhc+e1t*9N_W1f~i+B9=(w8@RsJx9&d)E9lx5`ec}S9ka=DCLE{D-*gl4meWHO1tV1Pn zq1Wpn7K<4JNQsiBR))hNR8?ixsTInjR;w8UEEWqIjRw5qX;6p+l*glu$KxP(l*gS@ z_&NBEPVX0-PA5vG5_~=%y4@}ki3DrK;c&13$Mp>Y*uU-Heh1%W6~P~6a6$n)-ZZw| z66|{-Y`bx6?FumOMDh7c1SSX1w$-iN2GFbemwg|9@&6c?8-{4)%#(h>00000NkvXX Hu0mjfhPEN| literal 0 HcmV?d00001 diff --git a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin index 210f63b594..28c0df125a 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin +++ b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin @@ -117,6 +117,13 @@ class = "ICSharpCode.SharpDevelop.Gui.Pads.ConsolePad" defaultPosition = "Bottom, Hidden" /> + + + + + + + + + diff --git a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj index 6de0fd70b0..ddced0ecf8 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj +++ b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj @@ -102,6 +102,7 @@ CallStackPad.xaml Code + @@ -120,6 +121,7 @@ WatchListAutoCompleteCell.xaml + DrawSurface.xaml Code diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/Commands/MemoryPadCommands.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/Commands/MemoryPadCommands.cs new file mode 100644 index 0000000000..8b37bbffb0 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/Commands/MemoryPadCommands.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) +using System; +using System.Windows.Controls; +using System.Windows.Input; + +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Gui.Pads +{ + public sealed class JumpToAddressCommand : AbstractComboBoxCommand + { + MemoryPad pad; + ComboBox comboBox; + + protected override void OnOwnerChanged(EventArgs e) + { + this.pad = this.Owner as MemoryPad; + if (this.pad == null) + return; + + comboBox = this.ComboBox as ComboBox; + + if (this.comboBox == null) + return; + + comboBox.KeyUp += (s, ea) => { if (ea.Key == Key.Enter) Run(); }; + comboBox.IsEditable = true; + comboBox.Width = 130; + + base.OnOwnerChanged(e); + } + + public override void Run() + { + if (this.pad != null && this.comboBox != null) { + pad.JumpToAddress(comboBox.Text); + } + base.Run(); + } + } + + public sealed class RefreshMemoryCommand : AbstractCommand + { + MemoryPad pad; + + protected override void OnOwnerChanged(EventArgs e) + { + this.pad = this.Owner as MemoryPad; + if (this.pad == null) + return; + + base.OnOwnerChanged(e); + } + + public override void Run() + { + if (this.pad == null) + return; + + this.pad.Refresh(true); + } + } +} diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/MemoryPad.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/MemoryPad.cs new file mode 100644 index 0000000000..1ba1f5735d --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/MemoryPad.cs @@ -0,0 +1,169 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; + +using Debugger; +using Debugger.Interop; +using ICSharpCode.Core; +using ICSharpCode.Core.Presentation; +using ICSharpCode.SharpDevelop.Debugging; + +namespace ICSharpCode.SharpDevelop.Gui.Pads +{ + public sealed class MemoryPad : DebuggerPad + { + Dictionary addressesMapping = new Dictionary(); + ConsoleControl console; + int addressStep = 16; + + Process debuggedProcess; + + public MemoryPad() + { + this.console = new ConsoleControl(); + this.panel.Children.Add(console); + this.console.Encoding = Encoding.Default; + RefreshPad(); + this.console.SetReadonly(); + } + + protected override ToolBar BuildToolBar() + { + return ToolBarService.CreateToolBar(panel, this, "/SharpDevelop/Pads/MemoryPad/ToolBar"); + } + + protected override void SelectProcess(Process process) + { + if (debuggedProcess != null) { + debuggedProcess.Paused -= OnProcessPaused; + } + debuggedProcess = process; + if (debuggedProcess != null) { + debuggedProcess.Paused += OnProcessPaused; + } + } + + public override void RefreshPad() + { + Refresh(); + base.RefreshPad(); + } + + public void JumpToAddress(string address) + { + try { + if (address.StartsWith("0x")) + address = address.Substring(2); + + long addr = Int64.Parse(address, NumberStyles.AllowHexSpecifier); + long mod = addr % addressStep; + + int line; + if (addressesMapping.ContainsKey(addr - mod)) + line = addressesMapping[addr - mod]; + else + line = 1; + + console.SelectText(line, 0, 8); + console.JumpToLine(line); + } catch (System.Exception ex) { + #if DEBUG + LoggingService.Error(ex.Message); + #endif + } + } + + public void Refresh(bool force = false) + { + if (debuggedProcess == null || debugger.IsProcessRunning) + return; + + if (!force && addressesMapping.Count > 0) + return; + + if (force) { + addressesMapping.Clear(); + console.Clear(); + } + + long address; + byte[] memory = debuggedProcess.ReadProcessMemory(out address); + + if (memory == null) + return; + + int index = 0; + int div = memory.Length / addressStep; + int mod = memory.Length % addressStep; + + while (index < div) { + StringBuilder sb = new StringBuilder(); + addressesMapping.Add(address, index + 1); + // write address + sb.Append(address.ToString("X8"));address += addressStep; + sb.Append(" "); + + // write bytes + for (int i = 0; i < addressStep; ++i) { + sb.Append(memory[index * addressStep + i].ToString("X2") + " "); + } + // write chars + StringBuilder sb1 = new StringBuilder(); + for (int i = 0; i < addressStep; ++i) { + sb1.Append(((char)memory[index * addressStep + i]).ToString()); + } + string s = sb1.ToString(); + s = Regex.Replace(s, @"\r\n", string.Empty); + s = Regex.Replace(s, @"\n", string.Empty); + s = Regex.Replace(s, @"\r", string.Empty); + sb.Append(s); + sb.Append(Environment.NewLine); + + // start writing in console + console.Append(sb.ToString()); + + index++; + } + + if (mod != 0) { + // write the rest of memory + StringBuilder sb = new StringBuilder(); + addressesMapping.Add(address, index + 1); + // write address + sb.Append(address.ToString("X8")); + sb.Append(" "); + + // write bytes + for (int i = 0; i < mod; ++i) { + sb.Append(memory[index * addressStep + i].ToString("X2") + " "); + } + // write chars + StringBuilder sb1 = new StringBuilder(); + for (int i = 0; i < mod; ++i) { + sb1.Append(((char)memory[index * addressStep + i]).ToString()); + } + string s = sb1.ToString(); + s = Regex.Replace(s, @"\r\n", string.Empty); + s = Regex.Replace(s, @"\n", string.Empty); + s = Regex.Replace(s, @"\r", string.Empty); + sb.Append(s); + + sb.Append(Environment.NewLine); + + // start writing in console + console.Append(sb.ToString()); + } + } + + private void OnProcessPaused(object sender, ProcessEventArgs e) + { + Refresh(); + } + } +} diff --git a/src/AddIns/Debugger/Debugger.Core/Interop/NativeMethods.cs b/src/AddIns/Debugger/Debugger.Core/Interop/NativeMethods.cs index 0a541f1e61..5c99cf079c 100644 --- a/src/AddIns/Debugger/Debugger.Core/Interop/NativeMethods.cs +++ b/src/AddIns/Debugger/Debugger.Core/Interop/NativeMethods.cs @@ -7,8 +7,37 @@ using System; using System.Runtime.InteropServices; using System.Text; +using Debugger.Interop.CorDebug; + namespace Debugger.Interop -{ +{ + [StructLayout(LayoutKind.Sequential)] + public struct MEMORY_BASIC_INFORMATION + { + public IntPtr BaseAddress; + public IntPtr AllocationBase; + public uint AllocationProtect; + public IntPtr RegionSize; + public uint State; + public uint Protect; + public uint Type; + } + + [Flags] + public enum ProcessAccessFlags : uint + { + All = 0x001F0FFF, + Terminate = 0x00000001, + CreateThread = 0x00000002, + VMOperation = 0x00000008, + VMRead = 0x00000010, + VMWrite = 0x00000020, + DupHandle = 0x00000040, + SetInformation = 0x00000200, + QueryInformation = 0x00000400, + Synchronize = 0x00100000 + } + public static class NativeMethods { [DllImport("kernel32.dll")] @@ -22,6 +51,69 @@ namespace Debugger.Interop [DllImport("mscoree.dll", CharSet=CharSet.Unicode)] public static extern int GetRequestedRuntimeVersion(string exeFilename, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pVersion, Int32 cchBuffer, out Int32 dwLength); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool VirtualQueryEx(IntPtr hProcess, + IntPtr lpAddress, + out MEMORY_BASIC_INFORMATION lpBuffer, + uint dwLength); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, + UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + [Out] byte[] lpBuffer, + int dwSize, + out int lpNumberOfBytesRead + ); + + public static byte[] ReadProcessMemory(this Process process, out long baseAddress) + { + uint handle = process.CorProcess.GetHandle(); + + var proc = System.Diagnostics.Process.GetProcessById((int)process.Id); + baseAddress = proc.MainModule.BaseAddress.ToInt64(); + long addr = baseAddress; + + byte[] memory = null; + + while (true) + { + byte[] temp = new byte[1024]; + int outSize; + bool success = ReadProcessMemory(new IntPtr(handle), new IntPtr(addr), temp, temp.Length, out outSize); + + addr += 1024; + + if (outSize == 0) + break; + + if (memory == null) { + memory = new byte[outSize]; + Array.Copy(temp, memory, outSize); + } else { + // expand memory + byte[] newTemp = new byte[memory.Length]; + Array.Copy(memory, newTemp, memory.Length); + + memory = new byte[memory.Length + outSize]; + Array.Copy(newTemp, memory, newTemp.Length); + Array.Copy(temp, 0, memory, newTemp.Length, outSize); + } + + if (!success) // break when we cannot read anymore + break; + } + + return memory; + } } } diff --git a/src/AddIns/Debugger/Debugger.Core/Process.cs b/src/AddIns/Debugger/Debugger.Core/Process.cs index eec5f36165..b788876e84 100644 --- a/src/AddIns/Debugger/Debugger.Core/Process.cs +++ b/src/AddIns/Debugger/Debugger.Core/Process.cs @@ -362,6 +362,10 @@ namespace Debugger get { return pauseSession == null; } } + public uint Id { + get { return corProcess.GetID(); } + } + public bool IsPaused { get { return !IsRunning; } } diff --git a/src/Main/Base/Project/Src/Gui/Pads/AbstractConsolePad.cs b/src/Main/Base/Project/Src/Gui/Pads/AbstractConsolePad.cs index 762743bf1a..a1e0e5208a 100755 --- a/src/Main/Base/Project/Src/Gui/Pads/AbstractConsolePad.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/AbstractConsolePad.cs @@ -1,19 +1,21 @@ // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) -using ICSharpCode.AvalonEdit; -using ICSharpCode.Core.Presentation; using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; + +using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Core; +using ICSharpCode.Core.Presentation; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Editor.AvalonEdit; @@ -303,6 +305,21 @@ namespace ICSharpCode.SharpDevelop.Gui } } + public Encoding Encoding { + get { + return this.editor.Encoding; + } + set { + this.editor.Encoding = value; + } + } + + public void SelectText(int line, int column, int length) + { + int offset = this.editor.Document.GetOffset(new TextLocation(line, column)); + this.editor.Select(offset, length); + } + public void SetHighlighting(string language) { editor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition(language); @@ -338,6 +355,11 @@ namespace ICSharpCode.SharpDevelop.Gui this.editor.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; } + public void JumpToLine(int line) + { + this.editor.ScrollToLine(line); + } + public int CommandOffset { get { return readOnlyRegion.EndOffset; } } @@ -369,6 +391,11 @@ namespace ICSharpCode.SharpDevelop.Gui if (handler != null) handler(this, e); } + + public void Clear() + { + editor.Clear(); + } } public class BeginReadOnlySectionProvider : IReadOnlySectionProvider diff --git a/src/Main/StartUp/Project/Resources/BitmapResources.resources b/src/Main/StartUp/Project/Resources/BitmapResources.resources index 31f13c839a84d77e83d51b94cd22212c891fc8cc..929a830270edf97a2d8cc19c06c985ff30aeb656 100644 GIT binary patch delta 2755 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 QH}{$c^wZ`6W6Q+<08S4&ga7~l