├── .gitignore ├── CruncherSharp.Designer.cs ├── CruncherSharp.cs ├── CruncherSharp.csproj ├── CruncherSharp.csproj.user ├── CruncherSharp.resx ├── CruncherSharp.sln ├── CruncherSharpForm.Designer.cs ├── CruncherSharpForm.cs ├── CruncherSharpForm.resx ├── License.txt ├── Program.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── README.md ├── Screenshot.png ├── SelectNamespace.Designer.cs ├── SelectNamespace.cs ├── SelectNamespaceForm.Designer.cs ├── SelectNamespaceForm.cs ├── SelectNamespaceForm.resx ├── SymbolAnalyzer.cs ├── SymbolFunctionInfo.cs ├── SymbolInfo.cs ├── SymbolMemberInfo.cs ├── app.config └── crunchersharp.ico /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | obj/* 3 | .vs/* 4 | *.suo 5 | -------------------------------------------------------------------------------- /CruncherSharp.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace CruncherSharp 2 | { 3 | partial class CruncherSharp 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); 33 | this.mainMenu = new System.Windows.Forms.MenuStrip(); 34 | this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 35 | this.loadPDBToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 36 | this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 37 | this.openPdbDialog = new System.Windows.Forms.OpenFileDialog(); 38 | this.bindingSourceSymbols = new System.Windows.Forms.BindingSource(this.components); 39 | this.splitContainer2 = new System.Windows.Forms.SplitContainer(); 40 | this.textBoxCache = new System.Windows.Forms.MaskedTextBox(); 41 | this.label2 = new System.Windows.Forms.Label(); 42 | this.label1 = new System.Windows.Forms.Label(); 43 | this.textBoxFilter = new System.Windows.Forms.TextBox(); 44 | this.splitContainer1 = new System.Windows.Forms.SplitContainer(); 45 | this.dataGridSymbols = new System.Windows.Forms.DataGridView(); 46 | this.dataGridViewSymbolInfo = new System.Windows.Forms.DataGridView(); 47 | this.colField = new System.Windows.Forms.DataGridViewTextBoxColumn(); 48 | this.colFieldOffset = new System.Windows.Forms.DataGridViewTextBoxColumn(); 49 | this.colFieldSize = new System.Windows.Forms.DataGridViewTextBoxColumn(); 50 | this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); 51 | this.copyTypeLayoutToClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 52 | this.setPrefetchStartOffsetToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 53 | this.filterFeedbackLabel = new System.Windows.Forms.Label(); 54 | this.mainMenu.SuspendLayout(); 55 | ((System.ComponentModel.ISupportInitialize)(this.bindingSourceSymbols)).BeginInit(); 56 | this.splitContainer2.Panel1.SuspendLayout(); 57 | this.splitContainer2.Panel2.SuspendLayout(); 58 | this.splitContainer2.SuspendLayout(); 59 | this.splitContainer1.Panel1.SuspendLayout(); 60 | this.splitContainer1.Panel2.SuspendLayout(); 61 | this.splitContainer1.SuspendLayout(); 62 | ((System.ComponentModel.ISupportInitialize)(this.dataGridSymbols)).BeginInit(); 63 | ((System.ComponentModel.ISupportInitialize)(this.dataGridViewSymbolInfo)).BeginInit(); 64 | this.contextMenuStrip1.SuspendLayout(); 65 | this.SuspendLayout(); 66 | // 67 | // mainMenu 68 | // 69 | this.mainMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 70 | this.fileToolStripMenuItem}); 71 | this.mainMenu.Location = new System.Drawing.Point(0, 0); 72 | this.mainMenu.Name = "mainMenu"; 73 | this.mainMenu.Size = new System.Drawing.Size(1055, 24); 74 | this.mainMenu.TabIndex = 0; 75 | this.mainMenu.Text = "menuStrip1"; 76 | // 77 | // fileToolStripMenuItem 78 | // 79 | this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 80 | this.loadPDBToolStripMenuItem, 81 | this.exitToolStripMenuItem}); 82 | this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 83 | this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); 84 | this.fileToolStripMenuItem.Text = "File"; 85 | // 86 | // loadPDBToolStripMenuItem 87 | // 88 | this.loadPDBToolStripMenuItem.Name = "loadPDBToolStripMenuItem"; 89 | this.loadPDBToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); 90 | this.loadPDBToolStripMenuItem.Size = new System.Drawing.Size(177, 22); 91 | this.loadPDBToolStripMenuItem.Text = "Load PDB..."; 92 | this.loadPDBToolStripMenuItem.Click += new System.EventHandler(this.loadPDBToolStripMenuItem_Click); 93 | // 94 | // exitToolStripMenuItem 95 | // 96 | this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; 97 | this.exitToolStripMenuItem.Size = new System.Drawing.Size(177, 22); 98 | this.exitToolStripMenuItem.Text = "Exit"; 99 | this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); 100 | // 101 | // openPdbDialog 102 | // 103 | this.openPdbDialog.Filter = "Symbol files|*.pdb|All files|*.*"; 104 | // 105 | // splitContainer2 106 | // 107 | this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; 108 | this.splitContainer2.IsSplitterFixed = true; 109 | this.splitContainer2.Location = new System.Drawing.Point(0, 24); 110 | this.splitContainer2.Name = "splitContainer2"; 111 | this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal; 112 | // 113 | // splitContainer2.Panel1 114 | // 115 | this.splitContainer2.Panel1.Controls.Add(this.filterFeedbackLabel); 116 | this.splitContainer2.Panel1.Controls.Add(this.textBoxCache); 117 | this.splitContainer2.Panel1.Controls.Add(this.label2); 118 | this.splitContainer2.Panel1.Controls.Add(this.label1); 119 | this.splitContainer2.Panel1.Controls.Add(this.textBoxFilter); 120 | // 121 | // splitContainer2.Panel2 122 | // 123 | this.splitContainer2.Panel2.Controls.Add(this.splitContainer1); 124 | this.splitContainer2.Size = new System.Drawing.Size(1055, 340); 125 | this.splitContainer2.SplitterDistance = 25; 126 | this.splitContainer2.TabIndex = 1; 127 | // 128 | // textBoxCache 129 | // 130 | this.textBoxCache.Location = new System.Drawing.Point(363, 2); 131 | this.textBoxCache.Mask = "0000"; 132 | this.textBoxCache.Name = "textBoxCache"; 133 | this.textBoxCache.Size = new System.Drawing.Size(34, 20); 134 | this.textBoxCache.TabIndex = 4; 135 | this.textBoxCache.Text = "64"; 136 | this.textBoxCache.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.textBoxCache_KeyPress); 137 | this.textBoxCache.Leave += new System.EventHandler(this.textBoxCache_Leave); 138 | // 139 | // label2 140 | // 141 | this.label2.AutoSize = true; 142 | this.label2.Location = new System.Drawing.Point(300, 5); 143 | this.label2.Name = "label2"; 144 | this.label2.Size = new System.Drawing.Size(57, 13); 145 | this.label2.TabIndex = 3; 146 | this.label2.Text = "Cache line"; 147 | // 148 | // label1 149 | // 150 | this.label1.AutoSize = true; 151 | this.label1.Location = new System.Drawing.Point(12, 5); 152 | this.label1.Name = "label1"; 153 | this.label1.Size = new System.Drawing.Size(29, 13); 154 | this.label1.TabIndex = 1; 155 | this.label1.Text = "Filter"; 156 | // 157 | // textBoxFilter 158 | // 159 | this.textBoxFilter.Location = new System.Drawing.Point(53, 2); 160 | this.textBoxFilter.Name = "textBoxFilter"; 161 | this.textBoxFilter.Size = new System.Drawing.Size(179, 20); 162 | this.textBoxFilter.TabIndex = 0; 163 | this.textBoxFilter.TextChanged += new System.EventHandler(this.textBoxFilter_TextChanged); 164 | // 165 | // splitContainer1 166 | // 167 | this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 168 | this.splitContainer1.Location = new System.Drawing.Point(0, 0); 169 | this.splitContainer1.Name = "splitContainer1"; 170 | // 171 | // splitContainer1.Panel1 172 | // 173 | this.splitContainer1.Panel1.Controls.Add(this.dataGridSymbols); 174 | // 175 | // splitContainer1.Panel2 176 | // 177 | this.splitContainer1.Panel2.Controls.Add(this.dataGridViewSymbolInfo); 178 | this.splitContainer1.Size = new System.Drawing.Size(1055, 311); 179 | this.splitContainer1.SplitterDistance = 615; 180 | this.splitContainer1.TabIndex = 2; 181 | // 182 | // dataGridSymbols 183 | // 184 | this.dataGridSymbols.AllowUserToAddRows = false; 185 | this.dataGridSymbols.AllowUserToDeleteRows = false; 186 | this.dataGridSymbols.AllowUserToResizeRows = false; 187 | dataGridViewCellStyle1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); 188 | this.dataGridSymbols.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; 189 | this.dataGridSymbols.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 190 | this.dataGridSymbols.Dock = System.Windows.Forms.DockStyle.Fill; 191 | this.dataGridSymbols.Location = new System.Drawing.Point(0, 0); 192 | this.dataGridSymbols.MultiSelect = false; 193 | this.dataGridSymbols.Name = "dataGridSymbols"; 194 | this.dataGridSymbols.ReadOnly = true; 195 | this.dataGridSymbols.RowHeadersVisible = false; 196 | this.dataGridSymbols.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 197 | this.dataGridSymbols.Size = new System.Drawing.Size(615, 311); 198 | this.dataGridSymbols.TabIndex = 2; 199 | this.dataGridSymbols.SelectionChanged += new System.EventHandler(this.dataGridSymbols_SelectionChanged); 200 | this.dataGridSymbols.SortCompare += new System.Windows.Forms.DataGridViewSortCompareEventHandler(this.dataGridSymbols_SortCompare); 201 | // 202 | // dataGridViewSymbolInfo 203 | // 204 | this.dataGridViewSymbolInfo.AllowUserToAddRows = false; 205 | this.dataGridViewSymbolInfo.AllowUserToDeleteRows = false; 206 | this.dataGridViewSymbolInfo.AllowUserToResizeRows = false; 207 | this.dataGridViewSymbolInfo.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 208 | this.dataGridViewSymbolInfo.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 209 | this.colField, 210 | this.colFieldOffset, 211 | this.colFieldSize}); 212 | this.dataGridViewSymbolInfo.ContextMenuStrip = this.contextMenuStrip1; 213 | this.dataGridViewSymbolInfo.Dock = System.Windows.Forms.DockStyle.Fill; 214 | this.dataGridViewSymbolInfo.Location = new System.Drawing.Point(0, 0); 215 | this.dataGridViewSymbolInfo.MultiSelect = false; 216 | this.dataGridViewSymbolInfo.Name = "dataGridViewSymbolInfo"; 217 | this.dataGridViewSymbolInfo.ReadOnly = true; 218 | this.dataGridViewSymbolInfo.RowHeadersVisible = false; 219 | this.dataGridViewSymbolInfo.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 220 | this.dataGridViewSymbolInfo.Size = new System.Drawing.Size(436, 311); 221 | this.dataGridViewSymbolInfo.TabIndex = 0; 222 | this.dataGridViewSymbolInfo.CellMouseDoubleClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.dataGridViewSymbolInfo_CellMouseDoubleClick); 223 | this.dataGridViewSymbolInfo.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(this.dataGridViewSymbolInfo_CellPainting); 224 | this.dataGridViewSymbolInfo.SortCompare += new System.Windows.Forms.DataGridViewSortCompareEventHandler(this.dataGridSymbols_SortCompare); 225 | // 226 | // colField 227 | // 228 | this.colField.HeaderText = "Field"; 229 | this.colField.Name = "colField"; 230 | this.colField.ReadOnly = true; 231 | this.colField.Width = 210; 232 | // 233 | // colFieldOffset 234 | // 235 | this.colFieldOffset.HeaderText = "Offset"; 236 | this.colFieldOffset.Name = "colFieldOffset"; 237 | this.colFieldOffset.ReadOnly = true; 238 | // 239 | // colFieldSize 240 | // 241 | this.colFieldSize.HeaderText = "Size"; 242 | this.colFieldSize.Name = "colFieldSize"; 243 | this.colFieldSize.ReadOnly = true; 244 | // 245 | // contextMenuStrip1 246 | // 247 | this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 248 | this.copyTypeLayoutToClipboardToolStripMenuItem, 249 | this.setPrefetchStartOffsetToolStripMenuItem}); 250 | this.contextMenuStrip1.Name = "contextMenuStrip1"; 251 | this.contextMenuStrip1.Size = new System.Drawing.Size(243, 48); 252 | // 253 | // copyTypeLayoutToClipboardToolStripMenuItem 254 | // 255 | this.copyTypeLayoutToClipboardToolStripMenuItem.Name = "copyTypeLayoutToClipboardToolStripMenuItem"; 256 | this.copyTypeLayoutToClipboardToolStripMenuItem.Size = new System.Drawing.Size(242, 22); 257 | this.copyTypeLayoutToClipboardToolStripMenuItem.Text = "Copy Type Layout To Clipboard"; 258 | this.copyTypeLayoutToClipboardToolStripMenuItem.Click += new System.EventHandler(this.copyTypeLayoutToClipboardToolStripMenuItem_Click); 259 | // 260 | // setPrefetchStartOffsetToolStripMenuItem 261 | // 262 | this.setPrefetchStartOffsetToolStripMenuItem.Name = "setPrefetchStartOffsetToolStripMenuItem"; 263 | this.setPrefetchStartOffsetToolStripMenuItem.Size = new System.Drawing.Size(242, 22); 264 | this.setPrefetchStartOffsetToolStripMenuItem.Text = "Set Prefetch Start Offset"; 265 | this.setPrefetchStartOffsetToolStripMenuItem.Click += new System.EventHandler(this.setPrefetchStartOffsetToolStripMenuItem_Click); 266 | // 267 | // filterFeedbackLabel 268 | // 269 | this.filterFeedbackLabel.Location = new System.Drawing.Point(239, 5); 270 | this.filterFeedbackLabel.Name = "filterFeedbackLabel"; 271 | this.filterFeedbackLabel.Size = new System.Drawing.Size(120, 13); 272 | this.filterFeedbackLabel.TabIndex = 5; 273 | // 274 | // CruncherSharp 275 | // 276 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 277 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 278 | this.ClientSize = new System.Drawing.Size(1055, 364); 279 | this.Controls.Add(this.splitContainer2); 280 | this.Controls.Add(this.mainMenu); 281 | this.MainMenuStrip = this.mainMenu; 282 | this.Name = "CruncherSharp"; 283 | this.Text = "Cruncher #"; 284 | this.mainMenu.ResumeLayout(false); 285 | this.mainMenu.PerformLayout(); 286 | ((System.ComponentModel.ISupportInitialize)(this.bindingSourceSymbols)).EndInit(); 287 | this.splitContainer2.Panel1.ResumeLayout(false); 288 | this.splitContainer2.Panel1.PerformLayout(); 289 | this.splitContainer2.Panel2.ResumeLayout(false); 290 | this.splitContainer2.ResumeLayout(false); 291 | this.splitContainer1.Panel1.ResumeLayout(false); 292 | this.splitContainer1.Panel2.ResumeLayout(false); 293 | this.splitContainer1.ResumeLayout(false); 294 | ((System.ComponentModel.ISupportInitialize)(this.dataGridSymbols)).EndInit(); 295 | ((System.ComponentModel.ISupportInitialize)(this.dataGridViewSymbolInfo)).EndInit(); 296 | this.contextMenuStrip1.ResumeLayout(false); 297 | this.ResumeLayout(false); 298 | this.PerformLayout(); 299 | 300 | } 301 | 302 | #endregion 303 | 304 | private System.Windows.Forms.MenuStrip mainMenu; 305 | private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; 306 | private System.Windows.Forms.ToolStripMenuItem loadPDBToolStripMenuItem; 307 | private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; 308 | private System.Windows.Forms.OpenFileDialog openPdbDialog; 309 | private System.Windows.Forms.BindingSource bindingSourceSymbols; 310 | private System.Windows.Forms.SplitContainer splitContainer2; 311 | private System.Windows.Forms.SplitContainer splitContainer1; 312 | private System.Windows.Forms.DataGridView dataGridSymbols; 313 | private System.Windows.Forms.DataGridView dataGridViewSymbolInfo; 314 | private System.Windows.Forms.DataGridViewTextBoxColumn colField; 315 | private System.Windows.Forms.DataGridViewTextBoxColumn colFieldOffset; 316 | private System.Windows.Forms.DataGridViewTextBoxColumn colFieldSize; 317 | private System.Windows.Forms.Label label1; 318 | private System.Windows.Forms.TextBox textBoxFilter; 319 | private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; 320 | private System.Windows.Forms.ToolStripMenuItem copyTypeLayoutToClipboardToolStripMenuItem; 321 | private System.Windows.Forms.Label label2; 322 | private System.Windows.Forms.MaskedTextBox textBoxCache; 323 | private System.Windows.Forms.ToolStripMenuItem setPrefetchStartOffsetToolStripMenuItem; 324 | private System.Windows.Forms.Label filterFeedbackLabel; 325 | } 326 | } 327 | 328 | -------------------------------------------------------------------------------- /CruncherSharp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | using Dia2Lib; 9 | 10 | namespace CruncherSharp 11 | { 12 | public partial class CruncherSharp : Form 13 | { 14 | public CruncherSharp() 15 | { 16 | InitializeComponent(); 17 | m_table = CreateDataTable(); 18 | bindingSourceSymbols.DataSource = m_table; 19 | dataGridSymbols.DataSource = bindingSourceSymbols; 20 | 21 | dataGridSymbols.Columns[0].Width = 271; 22 | } 23 | 24 | private void exitToolStripMenuItem_Click(object sender, EventArgs e) 25 | { 26 | this.Close(); 27 | } 28 | 29 | private void loadPDBToolStripMenuItem_Click(object sender, EventArgs e) 30 | { 31 | if (openPdbDialog.ShowDialog() == DialogResult.OK) 32 | { 33 | m_symbols.Clear(); 34 | 35 | m_source = new DiaSourceClass(); 36 | try 37 | { 38 | Cursor.Current = Cursors.WaitCursor; 39 | m_source.loadDataFromPdb(openPdbDialog.FileName); 40 | m_source.openSession(out m_session); 41 | Cursor.Current = Cursors.Default; 42 | } 43 | catch (System.Runtime.InteropServices.COMException exc) 44 | { 45 | MessageBox.Show(this, exc.ToString()); 46 | return; 47 | } 48 | 49 | Cursor.Current = Cursors.WaitCursor; 50 | IDiaEnumSymbols allSymbols; 51 | m_session.findChildren(m_session.globalScope, SymTagEnum.SymTagUDT, null, 0, out allSymbols); 52 | 53 | // Temporarily clear the filter so, if current filter is invalid, we don't generate a ton of exceptions while populating the table 54 | var preExistingFilter = textBoxFilter.Text; 55 | textBoxFilter.Text = ""; 56 | 57 | { 58 | PopulateDataTable(m_table, allSymbols); 59 | Cursor.Current = Cursors.Default; 60 | 61 | // Sort by name by default (ascending) 62 | dataGridSymbols.Sort(dataGridSymbols.Columns[0], ListSortDirection.Ascending); 63 | bindingSourceSymbols.Filter = null;// "Symbol LIKE '*rde*'"; 64 | } 65 | 66 | // Restore the filter now that the table is populated 67 | textBoxFilter.Text = preExistingFilter; 68 | 69 | ShowSelectedSymbolInfo(); 70 | } 71 | } 72 | 73 | DataTable CreateDataTable() 74 | { 75 | DataTable table = new DataTable("Symbols"); 76 | 77 | DataColumn column = new DataColumn(); 78 | column.ColumnName = "Symbol"; 79 | column.ReadOnly = true; 80 | table.Columns.Add(column); 81 | column = new DataColumn(); 82 | column.ColumnName = "Size"; 83 | column.ReadOnly = true; 84 | column.DataType = System.Type.GetType("System.Int32"); 85 | table.Columns.Add(column); 86 | column = new DataColumn(); 87 | column.ColumnName = "Padding"; 88 | column.ReadOnly = true; 89 | column.DataType = System.Type.GetType("System.Int32"); 90 | table.Columns.Add(column); 91 | column = new DataColumn(); 92 | column.ColumnName = "Padding/Size"; 93 | column.ReadOnly = true; 94 | column.DataType = System.Type.GetType("System.Double"); 95 | table.Columns.Add(column); 96 | 97 | return table; 98 | } 99 | 100 | void PopulateDataTable(DataTable table, IDiaEnumSymbols symbols) 101 | { 102 | ulong cacheLineSize = GetCacheLineSize(); 103 | table.Rows.Clear(); 104 | 105 | table.BeginLoadData(); 106 | foreach (IDiaSymbol sym in symbols) 107 | { 108 | if (sym.length > 0 && !HasSymbol(sym.name)) 109 | { 110 | SymbolInfo info = new SymbolInfo(); 111 | info.Set(sym.name, "", sym.length, 0); 112 | info.ProcessChildren(sym); 113 | 114 | long totalPadding = info.CalcTotalPadding(); 115 | 116 | DataRow row = table.NewRow(); 117 | string symbolName = sym.name; 118 | row["Symbol"] = symbolName; 119 | row["Size"] = info.m_size; 120 | row["Padding"] = totalPadding; 121 | row["Padding/Size"] = (double)totalPadding / info.m_size; 122 | table.Rows.Add(row); 123 | 124 | m_symbols.Add(info.m_name, info); 125 | } 126 | } 127 | table.EndLoadData(); 128 | 129 | } 130 | bool HasSymbol(string name) 131 | { 132 | return m_symbols.ContainsKey(name); 133 | } 134 | ulong GetCacheLineSize() 135 | { 136 | return Convert.ToUInt32(textBoxCache.Text); 137 | } 138 | 139 | class SymbolInfo 140 | { 141 | public void ProcessChildren(IDiaSymbol symbol) 142 | { 143 | IDiaEnumSymbols children; 144 | symbol.findChildren(SymTagEnum.SymTagNull, null, 0, out children); 145 | foreach (IDiaSymbol child in children) 146 | { 147 | SymbolInfo childInfo; 148 | if (ProcessChild(child, out childInfo)) 149 | AddChild(childInfo); 150 | } 151 | // Sort children by offset, recalc padding. 152 | // Sorting is not needed normally (for data fields), but sometimes base class order is wrong. 153 | if (HasChildren()) 154 | { 155 | m_children.Sort(CompareOffsets); 156 | for (int i = 0; i < m_children.Count; ++i) 157 | { 158 | SymbolInfo child = m_children[i]; 159 | child.m_padding = CalcPadding(child.m_offset, i); 160 | } 161 | m_padding = CalcPadding((long)m_size, m_children.Count); 162 | } 163 | } 164 | bool ProcessChild(IDiaSymbol symbol, out SymbolInfo info) 165 | { 166 | info = new SymbolInfo(); 167 | if (symbol.isStatic != 0 || (symbol.symTag != (uint)SymTagEnum.SymTagData && symbol.symTag != (uint)SymTagEnum.SymTagBaseClass)) 168 | { 169 | return false; 170 | } 171 | // LocIsThisRel || LocIsNull || LocIsBitField 172 | if (symbol.locationType != 4 && symbol.locationType != 0 && symbol.locationType != 6) 173 | return false; 174 | 175 | ulong len = symbol.length; 176 | IDiaSymbol typeSymbol = symbol.type; 177 | if (typeSymbol != null) 178 | len = typeSymbol.length; 179 | 180 | string symbolName = symbol.name; 181 | if (symbol.symTag == (uint)SymTagEnum.SymTagBaseClass) 182 | symbolName = "Base: " + symbolName; 183 | 184 | info.Set(symbolName, (typeSymbol != null ? typeSymbol.name : ""), len, symbol.offset); 185 | 186 | return true; 187 | } 188 | long CalcPadding(long offset, int index) 189 | { 190 | long padding = 0; 191 | if (HasChildren() && index > 0) 192 | { 193 | SymbolInfo lastInfo = m_children[index - 1]; 194 | padding = offset - (lastInfo.m_offset + (long)lastInfo.m_size); 195 | } 196 | return padding > 0 ? padding : 0; 197 | } 198 | 199 | public void Set(string name, string typeName, ulong size, long offset) 200 | { 201 | m_name = name; 202 | m_typeName = typeName; 203 | m_size = size; 204 | m_offset = offset; 205 | } 206 | public bool HasChildren() { return m_children != null; } 207 | public long CalcTotalPadding() 208 | { 209 | long totalPadding = m_padding; 210 | if (HasChildren()) 211 | { 212 | foreach (SymbolInfo info in m_children) 213 | totalPadding += info.m_padding; 214 | } 215 | return totalPadding; 216 | } 217 | void AddChild(SymbolInfo child) 218 | { 219 | if (m_children == null) 220 | m_children = new List(); 221 | m_children.Add(child); 222 | } 223 | public bool IsBase() 224 | { 225 | return m_name.IndexOf("Base: ") == 0; 226 | } 227 | 228 | private static int CompareOffsets(SymbolInfo x, SymbolInfo y) 229 | { 230 | // Base classes have to go first. 231 | if (x.IsBase() && !y.IsBase()) 232 | return -1; 233 | if (!x.IsBase() && y.IsBase()) 234 | return 1; 235 | 236 | if (x.m_offset == y.m_offset) 237 | { 238 | return (x.m_size == y.m_size ? 0 : (x.m_size < y.m_size) ? -1 : 1); 239 | } 240 | else return (x.m_offset < y.m_offset) ? -1 : 1; 241 | } 242 | 243 | public string m_name; 244 | public string m_typeName; 245 | public ulong m_size; 246 | public long m_offset; 247 | public long m_padding; 248 | public List m_children; 249 | }; 250 | 251 | SymbolInfo FindSelectedSymbolInfo() 252 | { 253 | if (dataGridSymbols.SelectedRows.Count == 0) 254 | return null; 255 | 256 | DataGridViewRow selectedRow = dataGridSymbols.SelectedRows[0]; 257 | string symbolName = selectedRow.Cells[0].Value.ToString(); 258 | 259 | SymbolInfo info = FindSymbolInfo(symbolName); 260 | return info; 261 | } 262 | SymbolInfo FindSymbolInfo(string name) 263 | { 264 | SymbolInfo info; 265 | m_symbols.TryGetValue(name, out info); 266 | return info; 267 | } 268 | 269 | IDiaDataSource m_source; 270 | IDiaSession m_session; 271 | Dictionary m_symbols = new Dictionary(); 272 | DataTable m_table = null; 273 | long m_prefetchStartOffset = 0; 274 | 275 | private void dataGridSymbols_SelectionChanged(object sender, EventArgs e) 276 | { 277 | m_prefetchStartOffset = 0; 278 | ShowSelectedSymbolInfo(); 279 | } 280 | 281 | void ShowSelectedSymbolInfo() 282 | { 283 | dataGridViewSymbolInfo.Rows.Clear(); 284 | SymbolInfo info = FindSelectedSymbolInfo(); 285 | if (info != null) 286 | ShowSymbolInfo(info); 287 | } 288 | 289 | void ShowSymbolInfo(SymbolInfo info) 290 | { 291 | dataGridViewSymbolInfo.Rows.Clear(); 292 | if (!info.HasChildren()) 293 | return; 294 | 295 | long cacheLineSize = (long)GetCacheLineSize(); 296 | long prevCacheBoundaryOffset = m_prefetchStartOffset; 297 | 298 | if (prevCacheBoundaryOffset > (long)info.m_size) 299 | prevCacheBoundaryOffset = (long)info.m_size; 300 | 301 | long numCacheLines = 0; 302 | foreach (SymbolInfo child in info.m_children) 303 | { 304 | if (child.m_padding > 0) 305 | { 306 | long paddingOffset = child.m_offset - child.m_padding; 307 | string[] paddingRow = { "Padding", paddingOffset.ToString(), child.m_padding.ToString() }; 308 | dataGridViewSymbolInfo.Rows.Add(paddingRow); 309 | } 310 | 311 | // long childEndOffset = child.m_offset + (long)child.m_size; 312 | while (child.m_offset - prevCacheBoundaryOffset >= cacheLineSize) 313 | { 314 | numCacheLines = numCacheLines + 1; 315 | long cacheLineOffset = numCacheLines * cacheLineSize + m_prefetchStartOffset; 316 | string[] boundaryRow = { "Cacheline boundary", cacheLineOffset.ToString(), "" }; 317 | dataGridViewSymbolInfo.Rows.Add(boundaryRow); 318 | prevCacheBoundaryOffset = cacheLineOffset; 319 | } 320 | 321 | string[] row = { child.m_name, child.m_offset.ToString(), child.m_size.ToString() }; 322 | dataGridViewSymbolInfo.Rows.Add(row); 323 | dataGridViewSymbolInfo.Rows[dataGridViewSymbolInfo.Rows.Count - 1].Tag = child; 324 | if (child.m_typeName != null && child.m_typeName.Length != 0) 325 | { 326 | dataGridViewSymbolInfo.Rows[dataGridViewSymbolInfo.Rows.Count - 1].Cells[0].ToolTipText = child.m_typeName; 327 | } 328 | } 329 | // Final structure padding. 330 | if (info.m_padding > 0) 331 | { 332 | long paddingOffset = (long)info.m_size - info.m_padding; 333 | string[] paddingRow = { "Padding", paddingOffset.ToString(), info.m_padding.ToString() }; 334 | dataGridViewSymbolInfo.Rows.Add(paddingRow); 335 | } 336 | } 337 | 338 | private void dataGridSymbols_SortCompare(object sender, DataGridViewSortCompareEventArgs e) 339 | { 340 | if (e.Column.Index > 0) 341 | { 342 | e.Handled = true; 343 | int val1; 344 | int val2; 345 | Int32.TryParse(e.CellValue1.ToString(), out val1); 346 | Int32.TryParse(e.CellValue2.ToString(), out val2); 347 | e.SortResult = val1 - val2; 348 | } 349 | } 350 | 351 | private void dataGridViewSymbolInfo_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) 352 | { 353 | foreach (DataGridViewRow row in dataGridViewSymbolInfo.Rows) 354 | { 355 | DataGridViewCell cell = row.Cells[0]; 356 | if (cell.Value.ToString() == "Padding") 357 | { 358 | cell.Style.BackColor = Color.LightPink; 359 | row.Cells[1].Style.BackColor = Color.LightPink; 360 | row.Cells[2].Style.BackColor = Color.LightPink; 361 | } 362 | else if (cell.Value.ToString().IndexOf("Base: ") == 0) 363 | { 364 | cell.Style.BackColor = Color.LightGreen; 365 | row.Cells[1].Style.BackColor = Color.LightGreen; 366 | row.Cells[2].Style.BackColor = Color.LightGreen; 367 | } 368 | else if (cell.Value.ToString() == "Cacheline boundary") 369 | { 370 | cell.Style.BackColor = Color.LightGray; 371 | row.Cells[1].Style.BackColor = Color.LightGray; 372 | row.Cells[2].Style.BackColor = Color.LightGray; 373 | } 374 | } 375 | } 376 | 377 | private void textBoxFilter_TextChanged(object sender, EventArgs e) 378 | { 379 | try 380 | { 381 | if (textBoxFilter.Text.Length == 0) 382 | bindingSourceSymbols.Filter = null; 383 | else 384 | bindingSourceSymbols.Filter = "Symbol LIKE '" + textBoxFilter.Text + "'"; 385 | 386 | textBoxFilter.BackColor = Color.Empty; 387 | textBoxFilter.ForeColor = Color.Empty; 388 | filterFeedbackLabel.Text = ""; 389 | } 390 | catch (System.Data.EvaluateException) 391 | { 392 | textBoxFilter.BackColor = Color.Red; 393 | textBoxFilter.ForeColor = Color.White; 394 | filterFeedbackLabel.Text = "Invalid filter"; 395 | } 396 | } 397 | 398 | void dumpSymbolInfo(System.IO.TextWriter tw, SymbolInfo info) 399 | { 400 | tw.WriteLine("Symbol: " + info.m_name); 401 | tw.WriteLine("Size: " + info.m_size.ToString()); 402 | tw.WriteLine("Total padding: " + info.CalcTotalPadding().ToString()); 403 | tw.WriteLine("Members"); 404 | tw.WriteLine("-------"); 405 | 406 | foreach (SymbolInfo child in info.m_children) 407 | { 408 | if (child.m_padding > 0) 409 | { 410 | long paddingOffset = child.m_offset - child.m_padding; 411 | tw.WriteLine(String.Format("{0,-40} {1,5} {2,5}", "****Padding", paddingOffset, child.m_padding)); 412 | } 413 | 414 | tw.WriteLine(String.Format("{0,-40} {1,5} {2,5}", child.m_name, child.m_offset, child.m_size)); 415 | } 416 | // Final structure padding. 417 | if (info.m_padding > 0) 418 | { 419 | long paddingOffset = (long)info.m_size - info.m_padding; 420 | tw.WriteLine(String.Format("{0,-40} {1,5} {2,5}", "****Padding", paddingOffset, info.m_padding)); 421 | } 422 | } 423 | 424 | private void copyTypeLayoutToClipboardToolStripMenuItem_Click(object sender, EventArgs e) 425 | { 426 | SymbolInfo info = FindSelectedSymbolInfo(); 427 | if (info != null) 428 | { 429 | System.IO.StringWriter tw = new System.IO.StringWriter(); 430 | dumpSymbolInfo(tw, info); 431 | Clipboard.SetText(tw.ToString()); 432 | } 433 | } 434 | 435 | private void textBoxCache_KeyPress(object sender, KeyPressEventArgs e) 436 | { 437 | if (e.KeyChar == (char)Keys.Enter || e.KeyChar == (char)Keys.Escape) 438 | ShowSelectedSymbolInfo(); 439 | base.OnKeyPress(e); 440 | } 441 | 442 | private void textBoxCache_Leave(object sender, EventArgs e) 443 | { 444 | ShowSelectedSymbolInfo(); 445 | } 446 | 447 | private void setPrefetchStartOffsetToolStripMenuItem_Click(object sender, EventArgs e) 448 | { 449 | if (dataGridViewSymbolInfo.SelectedRows.Count != 0) 450 | { 451 | DataGridViewRow selectedRow = dataGridViewSymbolInfo.SelectedRows[0]; 452 | long symbolOffset = Convert.ToUInt32(selectedRow.Cells[1].Value); 453 | m_prefetchStartOffset = symbolOffset; 454 | ShowSelectedSymbolInfo(); 455 | } 456 | } 457 | 458 | private void dataGridViewSymbolInfo_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) 459 | { 460 | if (dataGridViewSymbolInfo.SelectedRows.Count != 0) 461 | { 462 | DataGridViewRow selectedRow = dataGridViewSymbolInfo.SelectedRows[0]; 463 | SymbolInfo info = selectedRow.Tag as SymbolInfo; 464 | if (info != null) 465 | { 466 | SelectSymbol(info.m_typeName); 467 | } 468 | } 469 | } 470 | 471 | void SelectSymbol(string name) 472 | { 473 | foreach (DataGridViewRow dr in dataGridSymbols.Rows) 474 | { 475 | DataGridViewCell dc = dr.Cells[0]; // name 476 | if (dc.Value.ToString() == name) 477 | { 478 | dataGridSymbols.CurrentCell = dc; 479 | break; 480 | } 481 | } 482 | } 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /CruncherSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {2F38B30C-7210-4699-8D54-7D9506AB6345} 9 | WinExe 10 | Properties 11 | CruncherSharp 12 | CruncherSharp 13 | v4.7.2 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | false 22 | publish\ 23 | true 24 | Disk 25 | false 26 | Foreground 27 | 7 28 | Days 29 | false 30 | false 31 | true 32 | 0 33 | 1.0.0.%2a 34 | false 35 | true 36 | 37 | 38 | true 39 | full 40 | false 41 | bin\Debug\ 42 | DEBUG;TRACE 43 | prompt 44 | 4 45 | false 46 | true 47 | 48 | 49 | pdbonly 50 | true 51 | bin\Release\ 52 | TRACE 53 | prompt 54 | 4 55 | false 56 | false 57 | 58 | 59 | crunchersharp.ico 60 | 61 | 62 | CruncherSharp.Program 63 | 64 | 65 | 66 | 67 | 3.5 68 | 69 | 70 | 3.5 71 | 72 | 73 | 3.5 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Form 84 | 85 | 86 | CruncherSharpForm.cs 87 | 88 | 89 | 90 | 91 | Form 92 | 93 | 94 | SelectNamespaceForm.cs 95 | 96 | 97 | 98 | 99 | 100 | 101 | CruncherSharpForm.cs 102 | Designer 103 | 104 | 105 | ResXFileCodeGenerator 106 | Resources.Designer.cs 107 | Designer 108 | 109 | 110 | True 111 | Resources.resx 112 | True 113 | 114 | 115 | SelectNamespaceForm.cs 116 | 117 | 118 | 119 | SettingsSingleFileGenerator 120 | Settings.Designer.cs 121 | 122 | 123 | True 124 | Settings.settings 125 | True 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | {106173A0-0173-4E5C-84E7-E915422BE997} 134 | 2 135 | 0 136 | 0 137 | tlbimp 138 | False 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | False 147 | Microsoft .NET Framework 4.6.2 %28x86 and x64%29 148 | true 149 | 150 | 151 | False 152 | .NET Framework 3.5 SP1 153 | false 154 | 155 | 156 | 157 | 158 | IF DEFINED GEARSTUDIO_CI regsvr32 /s \\ubisoft.org\projects\TechGroup\Public\Prism\GearStudio\GitLabBuild\extern\msdia\msdia140.dll 159 | 160 | 167 | -------------------------------------------------------------------------------- /CruncherSharp.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | publish\ 5 | 6 | 7 | 8 | 9 | 10 | en-US 11 | false 12 | 13 | -------------------------------------------------------------------------------- /CruncherSharp.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 121, 17 125 | 126 | 127 | 250, 17 128 | 129 | 130 | True 131 | 132 | 133 | True 134 | 135 | 136 | True 137 | 138 | 139 | 417, 17 140 | 141 | -------------------------------------------------------------------------------- /CruncherSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2002 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CruncherSharp", "CruncherSharp.csproj", "{2F38B30C-7210-4699-8D54-7D9506AB6345}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {2F38B30C-7210-4699-8D54-7D9506AB6345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2F38B30C-7210-4699-8D54-7D9506AB6345}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2F38B30C-7210-4699-8D54-7D9506AB6345}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2F38B30C-7210-4699-8D54-7D9506AB6345}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /CruncherSharpForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Windows.Forms; 9 | using static System.Int32; 10 | 11 | namespace CruncherSharp 12 | { 13 | public partial class CruncherSharpForm : Form 14 | { 15 | public enum SearchType 16 | { 17 | None, 18 | UnusedVTables, 19 | MSVCExtraPadding, 20 | MSVCEmptyBaseClass, 21 | UnusedInterfaces, 22 | UnusedVirtual, 23 | MaskingFunction, 24 | RemovedInline, 25 | } 26 | 27 | private readonly List _FunctionsToIgnore; 28 | private readonly Stack _NavigationStack; 29 | private readonly SymbolAnalyzer _SymbolAnalyzer; 30 | private readonly DataTable _Table; 31 | public bool _CloseRequested; 32 | private string _FileName; 33 | public bool _HasInstancesCount; 34 | public bool _HasSecondPDB; 35 | private ulong _PrefetchStartOffset; 36 | private SearchType _SearchCategory = SearchType.None; 37 | 38 | private SymbolInfo _SelectedSymbol; 39 | 40 | public CruncherSharpForm(SymbolAnalyzer symbolAnalyzer) 41 | { 42 | InitializeComponent(); 43 | 44 | _SymbolAnalyzer = symbolAnalyzer; 45 | _Table = CreateDataTable(); 46 | _Table.CaseSensitive = checkBoxMatchCase.Checked; 47 | _NavigationStack = new Stack(); 48 | _FunctionsToIgnore = new List(); 49 | _SelectedSymbol = null; 50 | _PrefetchStartOffset = 0; 51 | 52 | bindingSourceSymbols.DataSource = _Table; 53 | dataGridSymbols.DataSource = bindingSourceSymbols; 54 | 55 | dataGridSymbols.Columns[0].Width = 271; 56 | 57 | WindowState = FormWindowState.Maximized; 58 | } 59 | 60 | public SearchType SearchCategory 61 | { 62 | get => _SearchCategory; 63 | private set 64 | { 65 | _SearchCategory = value; 66 | UpdateCheckedOption(); 67 | } 68 | } 69 | 70 | private void Reset() 71 | { 72 | checkedListBoxNamespaces.Items.Clear(); 73 | _Table.Clear(); 74 | dataGridViewSymbolInfo.Rows.Clear(); 75 | dataGridViewFunctionsInfo.Rows.Clear(); 76 | _SelectedSymbol = null; 77 | labelCurrentSymbol.Text = ""; 78 | _SymbolAnalyzer.Reset(); 79 | } 80 | 81 | private void exitToolStripMenuItem_Click(object sender, EventArgs e) 82 | { 83 | Close(); 84 | } 85 | 86 | private void loadPDBToolStripMenuItem_Click(object sender, EventArgs e) 87 | { 88 | if (backgroundWorker.IsBusy) 89 | return; 90 | 91 | if (openPdbDialog.ShowDialog() != DialogResult.OK) 92 | return; 93 | 94 | _FileName = openPdbDialog.FileName; 95 | Text = "Cruncher# - " + _FileName; 96 | Reset(); 97 | btnLoad.Enabled = true; 98 | btnReset.Enabled = true; 99 | textBoxFilter.Focus(); 100 | } 101 | 102 | public void LoadPdb(string fileName, bool secondPDB) 103 | { 104 | _SelectedSymbol = null; 105 | toolStripProgressBar.Value = 0; 106 | toolStripStatusLabel.Text = "Loading PDB..."; 107 | if (!secondPDB) 108 | { 109 | Text = "Cruncher# - " + fileName; 110 | if (textBoxFilter.Text.Length > 0) 111 | Text += " Filter: " + textBoxFilter.Text; 112 | } 113 | 114 | var task = new LoadPDBTask 115 | { 116 | FileName = fileName, 117 | SecondPDB = secondPDB, 118 | Filter = textBoxFilter.Text, 119 | MatchCase = checkBoxMatchCase.Checked, 120 | WholeExpression = checkBoxMatchWholeExpression.Checked, 121 | UseRegularExpression = checkBoxRegularExpressions.Checked 122 | }; 123 | backgroundWorker.RunWorkerAsync(task); 124 | } 125 | 126 | private static DataTable CreateDataTable() 127 | { 128 | var table = new DataTable("Symbols"); 129 | table.Columns.Add(new DataColumn {ColumnName = "Symbol", ReadOnly = true}); 130 | table.Columns.Add(new DataColumn 131 | { 132 | ColumnName = "Size", 133 | ReadOnly = true, 134 | DataType = Type.GetType("System.UInt32") 135 | }); 136 | table.Columns.Add(new DataColumn 137 | { 138 | ColumnName = "Padding", 139 | ReadOnly = true, 140 | DataType = Type.GetType("System.UInt32") 141 | }); 142 | table.Columns.Add(new DataColumn 143 | { 144 | ColumnName = "Padding zones", 145 | ReadOnly = true, 146 | DataType = Type.GetType("System.UInt32") 147 | }); 148 | table.Columns.Add(new DataColumn 149 | { 150 | ColumnName = "Total padding", 151 | ReadOnly = true, 152 | DataType = Type.GetType("System.UInt32") 153 | }); 154 | table.Columns.Add(new DataColumn 155 | { 156 | ColumnName = "Padding %", 157 | ReadOnly = true, 158 | DataType = Type.GetType("System.UInt32") 159 | }); 160 | 161 | return table; 162 | } 163 | 164 | private void AddInstancesCount() 165 | { 166 | if (_HasInstancesCount) 167 | return; 168 | _HasInstancesCount = true; 169 | if (_HasSecondPDB) 170 | _Table.Columns.Add(new DataColumn 171 | { 172 | ColumnName = "Total delta", 173 | ReadOnly = true, 174 | DataType = Type.GetType("System.Int32") 175 | }); 176 | _Table.Columns.Add(new DataColumn 177 | { 178 | ColumnName = "Instances", 179 | ReadOnly = true, 180 | DataType = Type.GetType("System.UInt64") 181 | }); 182 | _Table.Columns.Add(new DataColumn 183 | { 184 | ColumnName = "Total count", 185 | ReadOnly = true, 186 | DataType = Type.GetType("System.UInt64") 187 | }); 188 | _Table.Columns.Add(new DataColumn 189 | { 190 | ColumnName = "Total size", 191 | ReadOnly = true, 192 | DataType = Type.GetType("System.UInt64") 193 | }); 194 | _Table.Columns.Add(new DataColumn 195 | { 196 | ColumnName = "Total waste", 197 | ReadOnly = true, 198 | DataType = Type.GetType("System.UInt64") 199 | }); 200 | } 201 | 202 | private void AddSecondPDB() 203 | { 204 | if (_HasSecondPDB) 205 | return; 206 | _HasSecondPDB = true; 207 | _Table.Columns.Add(new DataColumn 208 | { 209 | ColumnName = "New size", 210 | ReadOnly = true, 211 | DataType = Type.GetType("System.UInt32") 212 | }); 213 | _Table.Columns.Add(new DataColumn 214 | { 215 | ColumnName = "Delta", 216 | ReadOnly = true, 217 | DataType = Type.GetType("System.Int32") 218 | }); 219 | if (_HasInstancesCount) 220 | _Table.Columns.Add(new DataColumn 221 | { 222 | ColumnName = "Total delta", 223 | ReadOnly = true, 224 | DataType = Type.GetType("System.Int32") 225 | }); 226 | } 227 | 228 | private void AddSymbolToTable(SymbolInfo symbolInfo) 229 | { 230 | var row = _Table.NewRow(); 231 | 232 | row["Symbol"] = symbolInfo.Name; 233 | row["Size"] = symbolInfo.Size; 234 | row["Padding"] = symbolInfo.Padding; 235 | row["Padding zones"] = symbolInfo.PaddingZonesCount; 236 | row["Total padding"] = symbolInfo.TotalPadding.Value; 237 | row["Padding %"] = (uint) ((double) symbolInfo.TotalPadding.Value / symbolInfo.Size * 100); 238 | if (_HasInstancesCount) 239 | { 240 | row["Instances"] = symbolInfo.NumInstances; 241 | row["Total count"] = symbolInfo.TotalCount; 242 | row["Total size"] = symbolInfo.TotalCount * symbolInfo.Size; 243 | row["Total waste"] = symbolInfo.TotalCount * symbolInfo.Padding; 244 | } 245 | 246 | if (_HasSecondPDB) 247 | { 248 | row["New size"] = symbolInfo.NewSize; 249 | row["Delta"] = (long) symbolInfo.NewSize - (long) symbolInfo.Size; 250 | if (_HasInstancesCount && symbolInfo.NumInstances > 0) 251 | row["Total delta"] = ((long) symbolInfo.NewSize - (long) symbolInfo.Size) * 252 | (long) symbolInfo.NumInstances; 253 | } 254 | 255 | _Table.Rows.Add(row); 256 | } 257 | 258 | 259 | private ulong GetCacheLineSize() 260 | { 261 | return Convert.ToUInt32(textBoxCache.Text); 262 | } 263 | 264 | private SymbolInfo FindSelectedSymbolInfo() 265 | { 266 | if (dataGridSymbols.SelectedRows.Count == 0) return null; 267 | 268 | var selectedRow = dataGridSymbols.SelectedRows[0]; 269 | var symbolName = selectedRow.Cells[0].Value.ToString(); 270 | return _SymbolAnalyzer.FindSymbolInfo(symbolName); 271 | } 272 | 273 | private string GetFilterString() 274 | { 275 | var filters = new List(); 276 | if (textBoxFilter.Text.Length > 0) 277 | if (checkBoxMatchWholeExpression.Checked) 278 | filters.Add($"Symbol = '{textBoxFilter.Text.Replace("'", string.Empty)}'"); 279 | else if (checkBoxRegularExpressions.Checked) 280 | filters.Add($"Symbol LIKE '{textBoxFilter.Text.Replace("'", string.Empty)}'"); 281 | else 282 | filters.Add($"Symbol LIKE '*{textBoxFilter.Text.Replace("'", string.Empty)}*'"); 283 | if (!chkShowTemplates.Checked) 284 | filters.Add("Symbol NOT LIKE '*<*'"); 285 | if (checkedListBoxNamespaces.CheckedItems.Count > 0) 286 | { 287 | var namespaceFilters = checkedListBoxNamespaces.CheckedItems.Cast() 288 | .Select(item => $"Symbol LIKE '*{item.Replace("'", string.Empty)}::*'"); 289 | filters.Add($"({string.Join(" OR ", namespaceFilters)})"); 290 | } 291 | 292 | return string.Join(" AND ", filters); 293 | } 294 | 295 | private void dataGridSymbols_SelectionChanged(object sender, EventArgs e) 296 | { 297 | _PrefetchStartOffset = 0; 298 | if (dataGridSymbols.SelectedRows.Count > 1) 299 | ShowSelectionSummary(); 300 | else 301 | ShowSelectedSymbolInfo(); 302 | } 303 | 304 | private void dataGridSymbols_ParentSelected(object sender, EventArgs e) 305 | { 306 | dataGridSymbols.ClearSelection(); 307 | if (TrySelectSymbol(sender.ToString()) == false) 308 | { 309 | var symbolInfo = _SymbolAnalyzer.FindSymbolInfo(sender.ToString()); 310 | if (symbolInfo != null) 311 | ShowSymbolInfo(symbolInfo, true); 312 | } 313 | } 314 | 315 | private void dataGridSymbols_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) 316 | { 317 | _PrefetchStartOffset = 0; 318 | ShowSelectedSymbolInfo(); 319 | } 320 | 321 | private void ShowSelectionSummary() 322 | { 323 | dataGridViewSymbolInfo.Rows.Clear(); 324 | 325 | ulong totalMemory = 0; 326 | ulong totalWaste = 0; 327 | long totalDiff = 0; 328 | foreach (DataGridViewRow selectedRow in dataGridSymbols.SelectedRows) 329 | { 330 | var symbolName = selectedRow.Cells[0].Value.ToString(); 331 | var symbolInfo = _SymbolAnalyzer.FindSymbolInfo(symbolName); 332 | 333 | if (_HasInstancesCount) 334 | { 335 | totalMemory += symbolInfo.NumInstances * symbolInfo.Size; 336 | totalWaste += symbolInfo.NumInstances * symbolInfo.TotalPadding.Value; 337 | if (_HasSecondPDB) 338 | totalDiff += ((long) symbolInfo.NewSize - (long) symbolInfo.Size) * 339 | (long) symbolInfo.NumInstances; 340 | } 341 | else 342 | { 343 | totalMemory += symbolInfo.Size; 344 | totalWaste += symbolInfo.TotalPadding.Value; 345 | } 346 | } 347 | 348 | labelCurrentSymbol.Text = "Total memory: " + totalMemory + " total waste: " + totalWaste; 349 | if (_HasInstancesCount && _HasSecondPDB) 350 | labelCurrentSymbol.Text += " total diff: " + totalDiff; 351 | } 352 | 353 | private void ShowSelectedSymbolInfo() 354 | { 355 | dataGridViewSymbolInfo.Rows.Clear(); 356 | var info = FindSelectedSymbolInfo(); 357 | if (info != null) ShowSymbolInfo(info, false); 358 | } 359 | 360 | private bool TrySelectSymbol(string name) 361 | { 362 | foreach (DataGridViewRow dr in dataGridSymbols.Rows) 363 | { 364 | var dc = dr.Cells[0]; // name 365 | if (dc.Value.ToString() != name) 366 | continue; 367 | dataGridSymbols.CurrentCell = dc; 368 | return true; 369 | } 370 | 371 | return false; 372 | } 373 | 374 | private void ShowSymbolInfo(SymbolInfo info, bool addToStack) 375 | { 376 | labelCurrentSymbol.Text = info.Name; 377 | 378 | if (_SelectedSymbol != null && addToStack) 379 | _NavigationStack.Push(_SelectedSymbol); 380 | else if (!addToStack) _NavigationStack.Clear(); 381 | _SelectedSymbol = info; 382 | 383 | UpdateSymbolGrid(); 384 | } 385 | 386 | private void UpdateSymbolGrid() 387 | { 388 | dataGridViewSymbolInfo.Rows.Clear(); 389 | dataGridViewFunctionsInfo.Rows.Clear(); 390 | 391 | var prevCacheBoundaryOffset = _PrefetchStartOffset; 392 | 393 | if (prevCacheBoundaryOffset > _SelectedSymbol.Size) prevCacheBoundaryOffset = _SelectedSymbol.Size; 394 | 395 | ulong numCacheLines = 0; 396 | var increment = 0; 397 | 398 | AddSymbolToGrid(_SelectedSymbol, ref prevCacheBoundaryOffset, ref numCacheLines, 0, increment); 399 | } 400 | 401 | private void RefreshSymbolGrid(int rowIndex) 402 | { 403 | var firstRow = dataGridViewSymbolInfo.FirstDisplayedScrollingRowIndex; 404 | UpdateSymbolGrid(); 405 | dataGridViewSymbolInfo.Rows[rowIndex].Selected = true; 406 | dataGridViewSymbolInfo.FirstDisplayedScrollingRowIndex = firstRow; 407 | } 408 | 409 | private void AddSymbolToGrid(SymbolInfo symbol, ref ulong prevCacheBoundaryOffset, ref ulong numCacheLines, 410 | ulong previousOffset, int increment) 411 | { 412 | var cacheLineSize = GetCacheLineSize(); 413 | 414 | var incrementText = ""; 415 | var incrementTextEmpty = ""; 416 | 417 | var whitespaceIncrementText = ""; 418 | for (var i = 0; i < increment; i++) 419 | { 420 | whitespaceIncrementText += " "; 421 | if (i == increment - 1) 422 | incrementText += " |__ "; 423 | else 424 | incrementText += " | "; 425 | incrementTextEmpty += " | "; 426 | } 427 | 428 | foreach (var member in symbol.Members) 429 | { 430 | var currentOffset = previousOffset + member.Offset; 431 | if (member.PaddingBefore > 0 && checkBoxPadding.Checked) 432 | { 433 | var paddingOffset = member.Offset - member.PaddingBefore; 434 | string[] paddingRow = 435 | { 436 | string.Empty, 437 | incrementTextEmpty, 438 | "Padding", 439 | paddingOffset.ToString(), 440 | string.Empty, 441 | member.PaddingBefore.ToString(), 442 | string.Empty 443 | }; 444 | dataGridViewSymbolInfo.Rows.Add(paddingRow); 445 | } 446 | 447 | if (checkBoxCacheLines.Checked) 448 | { 449 | var cacheLines = new List(); 450 | while (currentOffset >= cacheLineSize + prevCacheBoundaryOffset) 451 | { 452 | numCacheLines++; 453 | var cacheLineOffset = numCacheLines * cacheLineSize + _PrefetchStartOffset; 454 | cacheLines.Add(cacheLineOffset); 455 | prevCacheBoundaryOffset = cacheLineOffset; 456 | } 457 | 458 | if (cacheLines.Count > 3 && chkSmartCacheLines.Checked) 459 | { 460 | string[] firstBoundaryRow = 461 | { 462 | string.Empty, incrementTextEmpty, "Cacheline boundary", cacheLines[0].ToString(), 463 | string.Empty, string.Empty, string.Empty 464 | }; 465 | dataGridViewSymbolInfo.Rows.Add(firstBoundaryRow); 466 | string[] middleBoundariesRow = 467 | { 468 | string.Empty, incrementTextEmpty, "Cacheline boundaries (...)", string.Empty, 469 | string.Empty, string.Empty, string.Empty 470 | }; 471 | dataGridViewSymbolInfo.Rows.Add(middleBoundariesRow); 472 | string[] lastBoundaryRow = 473 | { 474 | string.Empty, incrementTextEmpty, "Cacheline boundary", 475 | cacheLines[cacheLines.Count - 1].ToString(), string.Empty, string.Empty, string.Empty 476 | }; 477 | dataGridViewSymbolInfo.Rows.Add(lastBoundaryRow); 478 | } 479 | else 480 | { 481 | cacheLines.ForEach(offset => 482 | { 483 | string[] boundaryRow = 484 | { 485 | string.Empty, 486 | incrementTextEmpty, 487 | "Cacheline boundary", 488 | offset.ToString(), 489 | string.Empty, 490 | string.Empty 491 | }; 492 | dataGridViewSymbolInfo.Rows.Add(boundaryRow); 493 | }); 494 | } 495 | } 496 | 497 | var baseInfo = _SymbolAnalyzer.FindSymbolInfo(member.TypeName); 498 | var expand = ""; 499 | if (member.Expanded) 500 | expand = whitespaceIncrementText + "- "; 501 | else if (member.IsExapandable) 502 | expand = whitespaceIncrementText + "+ "; 503 | 504 | object[] row = 505 | { 506 | expand, 507 | incrementText + member.DisplayName, 508 | member.TypeName, 509 | currentOffset.ToString(), 510 | (member.BitField ? member.BitPosition.ToString() : string.Empty), 511 | member.BitField ? (member.BitSize.ToString() + (member.BitSize == 1 ? " bit" : " bits")) : (member.Size.ToString() + (member.Size == 1 ? " byte" : " bytes")), 512 | baseInfo?.TotalPadding.ToString() ?? string.Empty 513 | }; 514 | dataGridViewSymbolInfo.Rows.Add(row); 515 | dataGridViewSymbolInfo.Rows[dataGridViewSymbolInfo.Rows.Count - 1].Tag = member; 516 | 517 | if (member.Expanded && baseInfo != null) 518 | AddSymbolToGrid(baseInfo, ref prevCacheBoundaryOffset, ref numCacheLines, currentOffset, 519 | increment + 1); 520 | 521 | if (member.BitField && member.BitPaddingAfter > 0 && checkBoxBitPadding.Checked) 522 | { 523 | var paddingOffset = member.BitPaddingAfter.ToString() + " bits"; 524 | string[] paddingRow = 525 | { 526 | string.Empty, 527 | incrementTextEmpty, 528 | "Bitfield padding", 529 | currentOffset.ToString(), 530 | (member.BitPosition + member.BitSize).ToString(), 531 | paddingOffset, 532 | string.Empty 533 | }; 534 | dataGridViewSymbolInfo.Rows.Add(paddingRow); 535 | } 536 | } 537 | 538 | // Final structure padding. 539 | if (symbol.EndPadding > 0 && checkBoxPadding.Checked) 540 | { 541 | var endPaddingOffset = symbol.Size - symbol.EndPadding; 542 | string[] paddingRow = 543 | { 544 | string.Empty, string.Empty, "Padding", endPaddingOffset.ToString(), symbol.EndPadding.ToString(), 545 | symbol.EndPadding.ToString() 546 | }; 547 | dataGridViewSymbolInfo.Rows.Add(paddingRow); 548 | } 549 | 550 | foreach (var function in symbol.Functions) 551 | { 552 | object[] row = 553 | { 554 | function.DisplayName, function.Virtual, function.IsPure, function.IsOverride, function.IsOverloaded, 555 | function.IsMasking 556 | }; 557 | dataGridViewFunctionsInfo.Rows.Add(row); 558 | dataGridViewFunctionsInfo.Rows[dataGridViewFunctionsInfo.Rows.Count - 1].Tag = function; 559 | } 560 | } 561 | 562 | private void dataGridSymbols_SortCompare(object sender, DataGridViewSortCompareEventArgs e) 563 | { 564 | if (e.Column.Index > 0) 565 | { 566 | e.Handled = true; 567 | TryParse(e.CellValue1.ToString(), out var val1); 568 | TryParse(e.CellValue2.ToString(), out var val2); 569 | e.SortResult = val1 - val2; 570 | } 571 | } 572 | 573 | private void dataGridViewSymbolInfo_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) 574 | { 575 | var row = dataGridViewSymbolInfo.Rows[e.RowIndex]; 576 | var nameCell = row.Cells[2]; 577 | 578 | if (row.Tag is SymbolMemberInfo info) 579 | { 580 | if (info.Category == SymbolMemberInfo.MemberCategory.VTable) 581 | e.CellStyle.BackColor = Color.LemonChiffon; 582 | else if (info.IsBase) 583 | e.CellStyle.BackColor = Color.LightGreen; 584 | 585 | else if (info.BitField) 586 | { 587 | if (e.ColumnIndex == 3 ) // Offset column 588 | { 589 | e.CellStyle.BackColor = Color.FromArgb((int)((info.Offset * 37) % 256), 192, 192); // random color not too dark 590 | row.Cells[e.ColumnIndex].ToolTipText = "Bitfield"; 591 | } 592 | } 593 | else if (info.AlignWithPrevious) 594 | if (e.ColumnIndex == 3) // Offset column 595 | { 596 | e.CellStyle.BackColor = Color.Salmon; 597 | row.Cells[e.ColumnIndex].ToolTipText = "Aligned with previous"; 598 | } 599 | 600 | if (info.Volatile) 601 | if (e.ColumnIndex == 0) // Name (Field) column 602 | { 603 | e.CellStyle.BackColor = Color.LightBlue; 604 | row.Cells[e.ColumnIndex].ToolTipText = "Volatile"; 605 | } 606 | } 607 | else if (nameCell.Value.ToString() == "Padding") 608 | { 609 | e.CellStyle.BackColor = Color.LightPink; 610 | if (e.ColumnIndex == 2) 611 | e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; 612 | } 613 | else if (nameCell.Value.ToString() == "Bitfield padding") 614 | { 615 | e.CellStyle.BackColor = Color.PaleVioletRed; 616 | if (e.ColumnIndex == 2) 617 | e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; 618 | } 619 | else if (nameCell.Value.ToString().StartsWith("Cacheline")) 620 | { 621 | e.CellStyle.BackColor = Color.LightGray; 622 | if (e.ColumnIndex == 2) 623 | e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; 624 | } 625 | } 626 | 627 | private void textBoxFilter_TextChanged(object sender, EventArgs e) 628 | { 629 | try 630 | { 631 | bindingSourceSymbols.Filter = GetFilterString(); 632 | textBoxFilter.BackColor = Color.Empty; 633 | textBoxFilter.ForeColor = Color.Empty; 634 | } 635 | catch (EvaluateException) 636 | { 637 | textBoxFilter.BackColor = Color.Red; 638 | textBoxFilter.ForeColor = Color.White; 639 | } 640 | } 641 | 642 | private void textBoxFilter_KeyUp(object sender, KeyEventArgs e) 643 | { 644 | if (e.KeyCode == Keys.Return && !backgroundWorker.IsBusy) 645 | { 646 | LoadPdb(_FileName, false); 647 | btnLoad.Text = "Cancel"; 648 | } 649 | } 650 | 651 | private void copyTypeLayoutToClipboardToolStripMenuItem_Click(object sender, EventArgs e) 652 | { 653 | var info = FindSelectedSymbolInfo(); 654 | if (info != null) Clipboard.SetText(info.ToString()); 655 | } 656 | 657 | private void textBoxCache_KeyPress(object sender, KeyPressEventArgs e) 658 | { 659 | if (e.KeyChar == (char) Keys.Enter || e.KeyChar == (char) Keys.Escape) ShowSelectedSymbolInfo(); 660 | OnKeyPress(e); 661 | } 662 | 663 | private void textBoxCache_Leave(object sender, EventArgs e) 664 | { 665 | ShowSelectedSymbolInfo(); 666 | } 667 | 668 | private void setPrefetchStartOffsetToolStripMenuItem_Click(object sender, EventArgs e) 669 | { 670 | if (dataGridViewSymbolInfo.SelectedRows.Count != 0) 671 | { 672 | var selectedRow = dataGridViewSymbolInfo.SelectedRows[0]; 673 | ulong symbolOffset = Convert.ToUInt32(selectedRow.Cells[3].Value); 674 | _PrefetchStartOffset = symbolOffset % GetCacheLineSize(); 675 | RefreshSymbolGrid(selectedRow.Index); 676 | } 677 | } 678 | 679 | 680 | private void checkBoxCacheLines_CheckedChanged(object sender, EventArgs e) 681 | { 682 | if (dataGridViewSymbolInfo.SelectedRows.Count != 0) 683 | { 684 | var selectedRow = dataGridViewSymbolInfo.SelectedRows[0]; 685 | RefreshSymbolGrid(selectedRow.Index); 686 | } 687 | else 688 | { 689 | RefreshSymbolGrid(0); 690 | } 691 | } 692 | 693 | private void dataGridViewSymbolInfo_MouseClick(object sender, MouseEventArgs e) 694 | { 695 | if (e.Button == MouseButtons.Right) 696 | { 697 | var currentMouseOverRow = dataGridViewSymbolInfo.HitTest(e.X, e.Y).RowIndex; 698 | if (currentMouseOverRow >= 0) 699 | { 700 | dataGridViewSymbolInfo.Rows[currentMouseOverRow].Selected = true; 701 | contextMenuStripMembers.Show(dataGridViewSymbolInfo, new Point(e.X, e.Y)); 702 | } 703 | } 704 | } 705 | 706 | private void dataGridViewSymbolInfo_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) 707 | { 708 | var selectedRow = dataGridViewSymbolInfo.Rows[e.RowIndex]; 709 | var currentSymbolInfo = selectedRow.Tag as SymbolMemberInfo; 710 | if (currentSymbolInfo != null && currentSymbolInfo.Category != SymbolMemberInfo.MemberCategory.Member) 711 | { 712 | var typeName = currentSymbolInfo.TypeName; 713 | if (typeName.Contains("[")) typeName = typeName.Substring(0, typeName.IndexOf("[")); 714 | 715 | if (currentSymbolInfo.Category == SymbolMemberInfo.MemberCategory.Pointer) 716 | { 717 | if (typeName.Contains("*")) typeName = typeName.Substring(0, typeName.IndexOf("*")); 718 | if (typeName.Contains("&")) typeName = typeName.Substring(0, typeName.IndexOf("&")); 719 | } 720 | 721 | var jumpToSymbolInfo = _SymbolAnalyzer.FindSymbolInfo(typeName, true); 722 | if (jumpToSymbolInfo != null) 723 | if (e.ColumnIndex == 0) 724 | { 725 | if (currentSymbolInfo.IsExapandable) 726 | { 727 | currentSymbolInfo.Expanded = !currentSymbolInfo.Expanded; 728 | RefreshSymbolGrid(e.RowIndex); 729 | } 730 | } 731 | else 732 | { 733 | ShowSymbolInfo(jumpToSymbolInfo, true); 734 | } 735 | } 736 | } 737 | 738 | private void chkShowTemplates_CheckedChanged(object sender, EventArgs e) 739 | { 740 | bindingSourceSymbols.Filter = GetFilterString(); 741 | } 742 | 743 | private void chkSmartCacheLines_CheckedChanged(object sender, EventArgs e) 744 | { 745 | ShowSelectedSymbolInfo(); 746 | } 747 | 748 | private void checkedListBoxNamespaces_ItemCheck(object sender, ItemCheckEventArgs e) 749 | { 750 | this.BeginInvoke(new Action(() => 751 | { 752 | bindingSourceSymbols.Filter = GetFilterString(); 753 | })); 754 | } 755 | 756 | private void dataGridViewSymbolInfo_KeyDown(object sender, KeyEventArgs e) 757 | { 758 | if (e.KeyCode == Keys.Back) 759 | if (_NavigationStack.Count > 0) 760 | { 761 | _SelectedSymbol = null; 762 | ShowSymbolInfo(_NavigationStack.Pop(), true); 763 | } 764 | 765 | OnKeyDown(e); 766 | } 767 | 768 | private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 769 | { 770 | toolStripProgressBar.Value = e.ProgressPercentage; 771 | toolStripStatusLabel.Text = e.UserState as String; 772 | } 773 | 774 | private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 775 | { 776 | btnLoad.Text = "Load"; 777 | 778 | if (e.Cancelled) 779 | { 780 | if (_CloseRequested) 781 | { 782 | Close(); 783 | } 784 | else 785 | { 786 | toolStripStatusLabel.Text = ""; 787 | toolStripProgressBar.Value = 0; 788 | } 789 | 790 | return; 791 | } 792 | 793 | var loadPdbSuccess = (bool) e.Result; 794 | 795 | if (!loadPdbSuccess) 796 | { 797 | MessageBox.Show(this, _SymbolAnalyzer.LastError); 798 | toolStripStatusLabel.Text = "Failed to load PDB."; 799 | return; 800 | } 801 | 802 | OnPDBLoaded(); 803 | } 804 | 805 | private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) 806 | { 807 | e.Result = _SymbolAnalyzer.LoadPdb(sender, e); 808 | } 809 | 810 | private void OnPDBLoaded() 811 | { 812 | compareWithPDBToolStripMenuItem.Enabled = true; 813 | loadInstanceCountToolStripMenuItem.Enabled = true; 814 | exportCsvToolStripMenuItem.Enabled = true; 815 | findUnusedVtablesToolStripMenuItem.Enabled = true; 816 | findMSVCExtraPaddingToolStripMenuItem.Enabled = true; 817 | findMSVCEmptyBaseClassToolStripMenuItem.Enabled = true; 818 | findUnusedInterfacesToolStripMenuItem.Enabled = true; 819 | findUnusedVirtualToolStripMenuItem.Enabled = true; 820 | findMaskingFunctionsToolStripMenuItem.Enabled = true; 821 | findRemovedInlineToolStripMenuItem.Enabled = true; 822 | 823 | // Temporarily clear the filter so, if current filter is invalid, we don't generate a ton of exceptions while populating the table 824 | var preExistingFilter = textBoxFilter.Text; 825 | textBoxFilter.Text = string.Empty; 826 | 827 | PopulateDataTable(); 828 | 829 | checkedListBoxNamespaces.Items.Clear(); 830 | foreach (var name in _SymbolAnalyzer.RootNamespaces) checkedListBoxNamespaces.Items.Add(name); 831 | 832 | // Sort by name by default (ascending) 833 | dataGridSymbols.Sort(dataGridSymbols.Columns[0], ListSortDirection.Ascending); 834 | bindingSourceSymbols.Filter = null; // "Symbol LIKE '*rde*'"; 835 | 836 | // Restore the filter now that the table is populated 837 | textBoxFilter.Text = preExistingFilter; 838 | bindingSourceSymbols.Filter = GetFilterString(); 839 | 840 | ShowSelectedSymbolInfo(); 841 | 842 | _NavigationStack.Clear(); 843 | } 844 | 845 | private void loadInstanceCountToolStripMenuItem_Click(object sender, EventArgs e) 846 | { 847 | if (backgroundWorker.IsBusy) return; 848 | if (openCsvDialog.ShowDialog() == DialogResult.OK) LoadCsv(openPdbDialog.FileName); 849 | } 850 | 851 | private void LoadCsv(string fileName) 852 | { 853 | Cursor.Current = Cursors.WaitCursor; 854 | 855 | if (_HasInstancesCount) 856 | foreach (var symbol in _SymbolAnalyzer.Symbols.Values) 857 | { 858 | symbol.NumInstances = 0; 859 | symbol.TotalCount = 0; 860 | } 861 | 862 | using (var sourceStream = 863 | File.Open(openCsvDialog.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 864 | { 865 | using (var reader = new StreamReader(sourceStream)) 866 | { 867 | var skipAllResolve = false; 868 | while (!reader.EndOfStream) 869 | { 870 | var line = reader.ReadLine(); 871 | var values = line.Split(','); 872 | var count = ulong.Parse(values[1]); 873 | if (count == 0) 874 | continue; 875 | 876 | var info = _SymbolAnalyzer.FindSymbolInfo(values[0]); 877 | if (info == null) 878 | { 879 | var validNamespaces = new List(); 880 | if (checkedListBoxNamespaces.CheckedItems.Count > 0) 881 | { 882 | foreach (var selected in checkedListBoxNamespaces.CheckedItems) 883 | { 884 | info = _SymbolAnalyzer.FindSymbolInfo(selected + "::" + values[0]); 885 | if (info != null) 886 | validNamespaces.Add(selected.ToString()); 887 | } 888 | 889 | if (validNamespaces.Count == 0) 890 | foreach (var selected in _SymbolAnalyzer.Namespaces) 891 | { 892 | info = _SymbolAnalyzer.FindSymbolInfo(selected + "::" + values[0]); 893 | if (info != null) 894 | validNamespaces.Add(selected); 895 | } 896 | } 897 | else 898 | { 899 | foreach (var selected in _SymbolAnalyzer.Namespaces) 900 | { 901 | info = _SymbolAnalyzer.FindSymbolInfo(selected + "::" + values[0]); 902 | if (info != null) 903 | validNamespaces.Add(selected); 904 | } 905 | } 906 | 907 | if (validNamespaces.Count == 0) continue; 908 | 909 | if (validNamespaces.Count == 1) 910 | { 911 | info = _SymbolAnalyzer.FindSymbolInfo(validNamespaces[0] + "::" + values[0]); 912 | } 913 | else if (!skipAllResolve) 914 | { 915 | var namespaceForm = new SelectNamespaceForm(); 916 | namespaceForm.SetName(values[0], count); 917 | namespaceForm.AddNamespaces(validNamespaces); 918 | namespaceForm.ShowDialog(); 919 | skipAllResolve = namespaceForm.SkipAllNamespaces; 920 | info = _SymbolAnalyzer.FindSymbolInfo(namespaceForm.GetNamespace() + "::" + values[0]); 921 | } 922 | } 923 | 924 | if (info != null) info.TotalCount = info.NumInstances = count; 925 | } 926 | } 927 | } 928 | 929 | foreach (var symbol in _SymbolAnalyzer.Symbols.Values) 930 | if (symbol.NumInstances > 0) 931 | symbol.UpdateTotalCount(_SymbolAnalyzer, symbol.NumInstances); 932 | 933 | Cursor.Current = Cursors.Default; 934 | AddInstancesCount(); 935 | PopulateDataTable(); 936 | } 937 | 938 | private void compareWithPDBToolStripMenuItem_Click(object sender, EventArgs e) 939 | { 940 | if (backgroundWorker.IsBusy) return; 941 | if (openPdbDialog.ShowDialog() == DialogResult.OK) 942 | { 943 | AddSecondPDB(); 944 | LoadPdb(openPdbDialog.FileName, true); 945 | } 946 | } 947 | 948 | private void exportCsvToolStripMenuItem_Click(object sender, EventArgs e) 949 | { 950 | if (backgroundWorker.IsBusy) return; 951 | if (saveCsvDialog.ShowDialog() == DialogResult.OK) 952 | using (var writer = new StreamWriter(saveCsvDialog.FileName)) 953 | { 954 | writer.WriteLine( 955 | "Name,Size,Padding,Padding zones,Total padding,Num Instances,Total size,Total waste,"); 956 | 957 | foreach (var symbolInfo in _SymbolAnalyzer.Symbols.Values) 958 | writer.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7},", symbolInfo.Name, symbolInfo.Size, 959 | symbolInfo.Padding, symbolInfo.PaddingZonesCount, symbolInfo.TotalPadding.Value, 960 | symbolInfo.NumInstances, symbolInfo.NumInstances * symbolInfo.Size, 961 | symbolInfo.NumInstances * symbolInfo.Padding); 962 | } 963 | } 964 | 965 | private void CruncherSharpForm_FormClosing(object sender, FormClosingEventArgs e) 966 | { 967 | if (backgroundWorker.IsBusy) 968 | { 969 | e.Cancel = true; // Can't close form while background worker is busy! 970 | backgroundWorker.CancelAsync(); 971 | _CloseRequested = true; 972 | } 973 | } 974 | 975 | private void findUnusedVtablesToolStripMenuItem_Click(object sender, EventArgs e) 976 | { 977 | SearchCategory = SearchType.UnusedVTables; 978 | 979 | PopulateDataTable(); 980 | } 981 | 982 | private void Reset_Click(object sender, EventArgs e) 983 | { 984 | chkShowTemplates.Checked = true; 985 | chkSmartCacheLines.Checked = true; 986 | for (var i = 0; i < checkedListBoxNamespaces.Items.Count; i++) 987 | checkedListBoxNamespaces.SetItemChecked(i, false); 988 | textBoxFilter.Clear(); 989 | SearchCategory = SearchType.None; 990 | PopulateDataTable(); 991 | } 992 | 993 | private void findMSVCExtraPaddingToolStripMenuItem_Click(object sender, EventArgs e) 994 | { 995 | SearchCategory = SearchType.MSVCExtraPadding; 996 | 997 | PopulateDataTable(); 998 | } 999 | 1000 | private void findMSVCEmptyBaseClassToolStripMenuItem_Click(object sender, EventArgs e) 1001 | { 1002 | SearchCategory = SearchType.MSVCEmptyBaseClass; 1003 | 1004 | PopulateDataTable(); 1005 | } 1006 | 1007 | private void findUnusedInterfacesToolStripMenuItem_Click(object sender, EventArgs e) 1008 | { 1009 | SearchCategory = SearchType.UnusedInterfaces; 1010 | 1011 | PopulateDataTable(); 1012 | } 1013 | 1014 | private void findUnusedVirtualToolStripMenuItem_Click(object sender, EventArgs e) 1015 | { 1016 | SearchCategory = SearchType.UnusedVirtual; 1017 | 1018 | PopulateDataTable(); 1019 | } 1020 | 1021 | private void dataGridViewFunctionsInfo_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) 1022 | { 1023 | var row = dataGridViewFunctionsInfo.Rows[e.RowIndex]; 1024 | var info = row.Tag as SymbolFunctionInfo; 1025 | if (_FunctionsToIgnore.Contains(info.Name)) 1026 | e.CellStyle.BackColor = Color.LightGray; 1027 | else 1028 | switch (info.Category) 1029 | { 1030 | case SymbolFunctionInfo.FunctionCategory.Constructor: 1031 | e.CellStyle.BackColor = Color.LightBlue; 1032 | break; 1033 | case SymbolFunctionInfo.FunctionCategory.Destructor: 1034 | e.CellStyle.BackColor = Color.LightGreen; 1035 | break; 1036 | case SymbolFunctionInfo.FunctionCategory.StaticFunction: 1037 | e.CellStyle.BackColor = Color.LightPink; 1038 | break; 1039 | default: 1040 | switch (SearchCategory) 1041 | { 1042 | case SearchType.UnusedVirtual: 1043 | if (info.UnusedVirtual) e.CellStyle.BackColor = Color.IndianRed; 1044 | 1045 | break; 1046 | case SearchType.MaskingFunction: 1047 | if (info.IsMasking) e.CellStyle.BackColor = Color.IndianRed; 1048 | 1049 | break; 1050 | case SearchType.RemovedInline: 1051 | if (info.WasInlineRemoved) e.CellStyle.BackColor = Color.IndianRed; 1052 | 1053 | break; 1054 | } 1055 | 1056 | break; 1057 | } 1058 | } 1059 | 1060 | private void findMaskingFunctionsToolStripMenuItem_Click(object sender, EventArgs e) 1061 | { 1062 | SearchCategory = SearchType.MaskingFunction; 1063 | 1064 | PopulateDataTable(); 1065 | } 1066 | 1067 | private void ignoreFunctionToolStripMenuItem_Click(object sender, EventArgs e) 1068 | { 1069 | if (dataGridViewFunctionsInfo.SelectedRows.Count != 0) 1070 | { 1071 | var selectedRow = dataGridViewFunctionsInfo.SelectedRows[0]; 1072 | var info = selectedRow.Tag as SymbolFunctionInfo; 1073 | if (!_FunctionsToIgnore.Contains(info.Name)) 1074 | { 1075 | _FunctionsToIgnore.Add(info.Name); 1076 | PopulateDataTable(); 1077 | } 1078 | } 1079 | } 1080 | 1081 | private void dataGridViewFunctionsInfo_MouseClick(object sender, MouseEventArgs e) 1082 | { 1083 | if (e.Button == MouseButtons.Right) 1084 | { 1085 | var currentMouseOverRow = dataGridViewFunctionsInfo.HitTest(e.X, e.Y).RowIndex; 1086 | if (currentMouseOverRow >= 0) 1087 | { 1088 | dataGridViewFunctionsInfo.Rows[currentMouseOverRow].Selected = true; 1089 | contextMenuStripFunctions.Show(dataGridViewFunctionsInfo, new Point(e.X, e.Y)); 1090 | } 1091 | } 1092 | } 1093 | 1094 | private void dataGridSymbols_MouseClick(object sender, MouseEventArgs e) 1095 | { 1096 | if (e.Button == MouseButtons.Right) 1097 | { 1098 | var currentMouseOverRow = dataGridSymbols.HitTest(e.X, e.Y).RowIndex; 1099 | if (currentMouseOverRow >= 0) 1100 | { 1101 | foreach (DataGridViewRow selectedRow in dataGridSymbols.SelectedRows) 1102 | selectedRow.Selected = false; 1103 | dataGridSymbols.Rows[currentMouseOverRow].Selected = true; 1104 | 1105 | toolStripMenuItemParentClasses.DropDownItems.Clear(); 1106 | var info = FindSelectedSymbolInfo(); 1107 | if (info != null) 1108 | { 1109 | if (info.DerivedClasses != null) 1110 | { 1111 | toolStripMenuItemParentClasses.Enabled = true; 1112 | foreach (var parent in info.DerivedClasses) 1113 | toolStripMenuItemParentClasses.DropDownItems.Add(parent.Name, null, 1114 | dataGridSymbols_ParentSelected); 1115 | } 1116 | else 1117 | { 1118 | toolStripMenuItemParentClasses.Enabled = false; 1119 | } 1120 | 1121 | ShowSymbolInfo(info, false); 1122 | } 1123 | 1124 | contextMenuStripClassInfo.Show(dataGridSymbols, new Point(e.X, e.Y)); 1125 | } 1126 | } 1127 | } 1128 | 1129 | private void findRemovedInlineToolStripMenuItem_Click(object sender, EventArgs e) 1130 | { 1131 | SearchCategory = SearchType.RemovedInline; 1132 | 1133 | PopulateDataTable(); 1134 | } 1135 | 1136 | private void UpdateCheckedOption() 1137 | { 1138 | findUnusedVtablesToolStripMenuItem.Checked = SearchCategory == SearchType.UnusedVTables; 1139 | findUnusedVirtualToolStripMenuItem.Checked = SearchCategory == SearchType.UnusedVirtual; 1140 | findMSVCExtraPaddingToolStripMenuItem.Checked = SearchCategory == SearchType.MSVCExtraPadding; 1141 | findMSVCEmptyBaseClassToolStripMenuItem.Checked = SearchCategory == SearchType.MSVCEmptyBaseClass; 1142 | findUnusedInterfacesToolStripMenuItem.Checked = SearchCategory == SearchType.UnusedInterfaces; 1143 | findMaskingFunctionsToolStripMenuItem.Checked = SearchCategory == SearchType.MaskingFunction; 1144 | findRemovedInlineToolStripMenuItem.Checked = SearchCategory == SearchType.RemovedInline; 1145 | } 1146 | 1147 | private void PopulateDataTable() 1148 | { 1149 | _Table.Rows.Clear(); 1150 | 1151 | _Table.BeginLoadData(); 1152 | 1153 | foreach (var symbolInfo in _SymbolAnalyzer.Symbols.Values) 1154 | switch (SearchCategory) 1155 | { 1156 | case SearchType.None: 1157 | AddSymbolToTable(symbolInfo); 1158 | break; 1159 | case SearchType.UnusedVTables: 1160 | if (symbolInfo.HasVtable && !symbolInfo.HasBaseClass && symbolInfo.DerivedClasses == null) 1161 | AddSymbolToTable(symbolInfo); 1162 | break; 1163 | case SearchType.MSVCExtraPadding: 1164 | if (symbolInfo.HasMSVCExtraPadding) AddSymbolToTable(symbolInfo); 1165 | break; 1166 | case SearchType.MSVCEmptyBaseClass: 1167 | if (symbolInfo.HasMSVCEmptyBaseClass) AddSymbolToTable(symbolInfo); 1168 | break; 1169 | case SearchType.UnusedInterfaces: 1170 | if (symbolInfo.IsAbstract && symbolInfo.DerivedClasses == null) AddSymbolToTable(symbolInfo); 1171 | break; 1172 | case SearchType.UnusedVirtual: 1173 | foreach (var function in symbolInfo.Functions) 1174 | if (function.UnusedVirtual) 1175 | if (!_FunctionsToIgnore.Contains(function.Name)) 1176 | { 1177 | AddSymbolToTable(symbolInfo); 1178 | break; 1179 | } 1180 | 1181 | break; 1182 | case SearchType.MaskingFunction: 1183 | foreach (var function in symbolInfo.Functions) 1184 | if (function.IsMasking) 1185 | if (!_FunctionsToIgnore.Contains(function.Name)) 1186 | { 1187 | AddSymbolToTable(symbolInfo); 1188 | break; 1189 | } 1190 | 1191 | break; 1192 | case SearchType.RemovedInline: 1193 | foreach (var function in symbolInfo.Functions) 1194 | if (function.WasInlineRemoved) 1195 | if (!_FunctionsToIgnore.Contains(function.Name)) 1196 | { 1197 | AddSymbolToTable(symbolInfo); 1198 | break; 1199 | } 1200 | 1201 | break; 1202 | } 1203 | 1204 | _Table.EndLoadData(); 1205 | } 1206 | 1207 | private void btnLoad_Click(object sender, EventArgs e) 1208 | { 1209 | if (backgroundWorker.IsBusy) 1210 | { 1211 | backgroundWorker.CancelAsync(); 1212 | toolStripStatusLabel.Text = "Cancelling..."; 1213 | } 1214 | else 1215 | { 1216 | LoadPdb(_FileName, false); 1217 | btnLoad.Text = "Cancel"; 1218 | } 1219 | } 1220 | 1221 | private void checkBoxMatchCase_CheckedChanged(object sender, EventArgs e) 1222 | { 1223 | _Table.CaseSensitive = checkBoxMatchCase.Checked; 1224 | bindingSourceSymbols.Filter = GetFilterString(); 1225 | } 1226 | 1227 | private void checkBoxMatchWholeExpression_CheckedChanged(object sender, EventArgs e) 1228 | { 1229 | bindingSourceSymbols.Filter = GetFilterString(); 1230 | } 1231 | 1232 | private void checkBoxRegularExpressions_CheckedChanged(object sender, EventArgs e) 1233 | { 1234 | if (checkBoxRegularExpressions.Checked) 1235 | { 1236 | checkBoxMatchWholeExpression.Checked = false; 1237 | checkBoxMatchWholeExpression.Enabled = false; 1238 | } 1239 | else 1240 | { 1241 | checkBoxMatchWholeExpression.Enabled = true; 1242 | } 1243 | 1244 | bindingSourceSymbols.Filter = GetFilterString(); 1245 | } 1246 | 1247 | private void checkBoxPadding_CheckedChanged(object sender, EventArgs e) 1248 | { 1249 | if (dataGridViewSymbolInfo.SelectedRows.Count != 0) 1250 | { 1251 | var selectedRow = dataGridViewSymbolInfo.SelectedRows[0]; 1252 | RefreshSymbolGrid(selectedRow.Index); 1253 | } 1254 | else 1255 | { 1256 | RefreshSymbolGrid(0); 1257 | } 1258 | } 1259 | 1260 | private void checkBoxBitPadding_CheckedChanged(object sender, EventArgs e) 1261 | { 1262 | if (dataGridViewSymbolInfo.SelectedRows.Count != 0) 1263 | { 1264 | var selectedRow = dataGridViewSymbolInfo.SelectedRows[0]; 1265 | RefreshSymbolGrid(selectedRow.Index); 1266 | } 1267 | else 1268 | { 1269 | RefreshSymbolGrid(0); 1270 | } 1271 | } 1272 | } 1273 | } -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) <2009-2015> Maciej Sinilo 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgement in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace CruncherSharp 5 | { 6 | static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | static void Main(string[] args) 13 | { 14 | Application.EnableVisualStyles(); 15 | Application.SetCompatibleTextRenderingDefault(false); 16 | var mainForm = new CruncherSharpForm(new SymbolAnalyzer()); 17 | if (args.Length > 0) 18 | { 19 | // Let's assume our arg is indeed the full path of a pdb file... 20 | mainForm.LoadPdb(args[0], false); 21 | } 22 | Application.Run(mainForm); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CruncherSharp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CruncherSharp")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6c1145d8-9bef-43e5-9629-f40de7d13e66")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CruncherSharp.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CruncherSharp.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CruncherSharp.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crunchersharp 2 | Program analyses debugger information file (PDB, so Microsoft Visual C++ only) and presents info about user defined structures (size, padding, cachelines, functions etc). 3 | 4 | - You can filter by namespace, search for a specific symbol 5 | - You can import a .csv with the instance count to get the total waste, the format should be "Class Name, Number of instances" 6 | - You can compare two PDBs 7 | - You search for useless vtables, useless virtual... 8 | 9 | Original blog post: http://msinilo.pl/blog/?p=425 10 | 11 | Note that you will need the `msdia` classes to be registered. To do this: 12 | 13 | 1) Find the msdia DLL corresponding to the version of the compiler you used to build the application. 14 | -- If you have Visual Studio installed, this DLL can be found in "C:\Program Files (x86)\Microsoft Visual Studio \Common7\IDE", where corresponds to your compiler version (e.g. "12.0" for Microsoft Visual Studio 2013) 15 | -- If you don't have the compiler installed, download the appropriate "Microsoft Visual C++ Redistributable Package" and install it. 16 | 17 | 2) Open an elevated (admin) command prompt in the directory containing msdia.dll. 18 | 19 | 3) Manually register the DLL by typing "regsvr32 msdia.dll" (e.g. "regsvr32 msdia12.dll" for Visual Studio 2013) 20 | 21 | ![Screenshot](Screenshot.png "Example screenshot") 22 | -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msinilo/crunchersharp/9345e140bfd6f5250336f83a9843baf88116d1e6/Screenshot.png -------------------------------------------------------------------------------- /SelectNamespace.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace CruncherSharp 2 | { 3 | partial class SelectNamespace 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.checkedListBoxNamespaces = new System.Windows.Forms.CheckedListBox(); 32 | this.buttonOK = new System.Windows.Forms.Button(); 33 | this.SuspendLayout(); 34 | // 35 | // checkedListBoxNamespaces 36 | // 37 | this.checkedListBoxNamespaces.FormattingEnabled = true; 38 | this.checkedListBoxNamespaces.Location = new System.Drawing.Point(-1, 0); 39 | this.checkedListBoxNamespaces.Name = "checkedListBoxNamespaces"; 40 | this.checkedListBoxNamespaces.Size = new System.Drawing.Size(344, 274); 41 | this.checkedListBoxNamespaces.TabIndex = 0; 42 | // 43 | // buttonOK 44 | // 45 | this.buttonOK.Location = new System.Drawing.Point(230, 292); 46 | this.buttonOK.Name = "buttonOK"; 47 | this.buttonOK.Size = new System.Drawing.Size(75, 23); 48 | this.buttonOK.TabIndex = 1; 49 | this.buttonOK.Text = "OK"; 50 | this.buttonOK.UseVisualStyleBackColor = true; 51 | // 52 | // SelectNamespace 53 | // 54 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 55 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 56 | this.ClientSize = new System.Drawing.Size(346, 327); 57 | this.Controls.Add(this.buttonOK); 58 | this.Controls.Add(this.checkedListBoxNamespaces); 59 | this.Name = "SelectNamespace"; 60 | this.Text = "Select namespace"; 61 | this.ResumeLayout(false); 62 | 63 | } 64 | 65 | #endregion 66 | 67 | private System.Windows.Forms.CheckedListBox checkedListBoxNamespaces; 68 | private System.Windows.Forms.Button buttonOK; 69 | } 70 | } -------------------------------------------------------------------------------- /SelectNamespace.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace CruncherSharp 12 | { 13 | public partial class SelectNamespace : Form 14 | { 15 | public SelectNamespace() 16 | { 17 | InitializeComponent(); 18 | } 19 | 20 | public void SetName(String className) 21 | { 22 | Name = "Select namespace for " + className; 23 | } 24 | 25 | public string GetNamespace() 26 | { 27 | if (checkedListBoxNamespaces.CheckedItems.Count == 0) 28 | return ""; 29 | return checkedListBoxNamespaces.CheckedItems[0].ToString(); 30 | } 31 | 32 | public void AddNamespaces(List namespaces) 33 | { 34 | foreach (var name in namespaces) 35 | checkedListBoxNamespaces.Items.Add(name); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SelectNamespaceForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace CruncherSharp 2 | { 3 | partial class SelectNamespaceForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.checkedListBoxNamespaces = new System.Windows.Forms.CheckedListBox(); 32 | this.buttonSkip = new System.Windows.Forms.Button(); 33 | this.buttonSkipAll = new System.Windows.Forms.Button(); 34 | this.labelSelectNamespace = new System.Windows.Forms.Label(); 35 | this.SuspendLayout(); 36 | // 37 | // checkedListBoxNamespaces 38 | // 39 | this.checkedListBoxNamespaces.FormattingEnabled = true; 40 | this.checkedListBoxNamespaces.Location = new System.Drawing.Point(11, 44); 41 | this.checkedListBoxNamespaces.Name = "checkedListBoxNamespaces"; 42 | this.checkedListBoxNamespaces.Size = new System.Drawing.Size(430, 229); 43 | this.checkedListBoxNamespaces.TabIndex = 0; 44 | this.checkedListBoxNamespaces.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.checkedListBoxNamespaces_ItemCheck); 45 | // 46 | // buttonSkip 47 | // 48 | this.buttonSkip.Location = new System.Drawing.Point(285, 300); 49 | this.buttonSkip.Name = "buttonSkip"; 50 | this.buttonSkip.Size = new System.Drawing.Size(75, 23); 51 | this.buttonSkip.TabIndex = 1; 52 | this.buttonSkip.Text = "Skip"; 53 | this.buttonSkip.UseVisualStyleBackColor = true; 54 | this.buttonSkip.Click += new System.EventHandler(this.buttonSkip_Click); 55 | // 56 | // buttonSkipAll 57 | // 58 | this.buttonSkipAll.Location = new System.Drawing.Point(366, 300); 59 | this.buttonSkipAll.Name = "buttonSkipAll"; 60 | this.buttonSkipAll.Size = new System.Drawing.Size(75, 23); 61 | this.buttonSkipAll.TabIndex = 2; 62 | this.buttonSkipAll.Text = "Skip all"; 63 | this.buttonSkipAll.UseVisualStyleBackColor = true; 64 | this.buttonSkipAll.Click += new System.EventHandler(this.buttonSkipAll_Click); 65 | // 66 | // labelSelectNamespace 67 | // 68 | this.labelSelectNamespace.AutoSize = true; 69 | this.labelSelectNamespace.Location = new System.Drawing.Point(13, 13); 70 | this.labelSelectNamespace.Name = "labelSelectNamespace"; 71 | this.labelSelectNamespace.Size = new System.Drawing.Size(95, 13); 72 | this.labelSelectNamespace.TabIndex = 3; 73 | this.labelSelectNamespace.Text = "Select namespace"; 74 | // 75 | // SelectNamespaceForm 76 | // 77 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 78 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 79 | this.AutoSize = true; 80 | this.ClientSize = new System.Drawing.Size(453, 334); 81 | this.Controls.Add(this.labelSelectNamespace); 82 | this.Controls.Add(this.buttonSkipAll); 83 | this.Controls.Add(this.buttonSkip); 84 | this.Controls.Add(this.checkedListBoxNamespaces); 85 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 86 | this.Name = "SelectNamespaceForm"; 87 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 88 | this.Text = "Resolve namespace"; 89 | this.ResumeLayout(false); 90 | this.PerformLayout(); 91 | 92 | } 93 | 94 | #endregion 95 | 96 | private System.Windows.Forms.CheckedListBox checkedListBoxNamespaces; 97 | private System.Windows.Forms.Button buttonSkip; 98 | private System.Windows.Forms.Button buttonSkipAll; 99 | private System.Windows.Forms.Label labelSelectNamespace; 100 | } 101 | } -------------------------------------------------------------------------------- /SelectNamespaceForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace CruncherSharp 12 | { 13 | public partial class SelectNamespaceForm : Form 14 | { 15 | public bool SkipAllNamespaces { get; set; } 16 | 17 | public SelectNamespaceForm() 18 | { 19 | InitializeComponent(); 20 | SkipAllNamespaces = false; 21 | } 22 | 23 | public void SetName(String className, ulong count) 24 | { 25 | this.labelSelectNamespace.Text = "Select namespace for " + className + " (" + count + " instances)"; 26 | } 27 | 28 | public string GetNamespace() 29 | { 30 | if (checkedListBoxNamespaces.CheckedItems.Count == 0) 31 | return ""; 32 | return checkedListBoxNamespaces.CheckedItems[0].ToString(); 33 | } 34 | 35 | public void AddNamespaces(List namespaces) 36 | { 37 | foreach (var name in namespaces) 38 | checkedListBoxNamespaces.Items.Add(name); 39 | } 40 | 41 | private void checkedListBoxNamespaces_ItemCheck(object sender, ItemCheckEventArgs e) 42 | { 43 | Close(); 44 | } 45 | 46 | private void buttonSkip_Click(object sender, EventArgs e) 47 | { 48 | Close(); 49 | } 50 | 51 | private void buttonSkipAll_Click(object sender, EventArgs e) 52 | { 53 | SkipAllNamespaces = true; 54 | Close(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SelectNamespaceForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /SymbolAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using Dia2Lib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | 6 | namespace CruncherSharp 7 | { 8 | public class LoadPDBTask 9 | { 10 | public string FileName { get; set; } 11 | public string Filter { get; set; } 12 | public bool SecondPDB { get; set; } 13 | public bool MatchCase { get; set; } 14 | public bool WholeExpression { get; set; } 15 | public bool UseRegularExpression { get; set; } 16 | } 17 | 18 | public class SymbolAnalyzer 19 | { 20 | public Dictionary Symbols { get; } 21 | public SortedSet RootNamespaces { get; } 22 | public SortedSet Namespaces { get; } 23 | public string LastError { get; private set; } 24 | public string FileName { get; set; } 25 | 26 | public SymbolAnalyzer() 27 | { 28 | Symbols = new Dictionary(); 29 | RootNamespaces = new SortedSet(); 30 | Namespaces = new SortedSet(); 31 | LastError = string.Empty; 32 | } 33 | 34 | public void Reset() 35 | { 36 | RootNamespaces.Clear(); 37 | Namespaces.Clear(); 38 | Symbols.Clear(); 39 | LastError = string.Empty; 40 | FileName = null; 41 | } 42 | 43 | public bool LoadPdb(object sender, DoWorkEventArgs e) 44 | { 45 | try 46 | { 47 | var task = e.Argument as LoadPDBTask; 48 | FileName = task.FileName; 49 | if (!LoadPdb(sender, task)) 50 | { 51 | e.Cancel = true; 52 | return false; 53 | } 54 | return true; 55 | } 56 | catch (System.Runtime.InteropServices.COMException exception) 57 | { 58 | LastError = exception.ToString(); 59 | return false; 60 | } 61 | } 62 | 63 | private bool LoadPdb(object sender, LoadPDBTask task) 64 | { 65 | IDiaDataSource source = new DiaSourceClass(); 66 | source.loadDataFromPdb(FileName); 67 | source.openSession(out IDiaSession session); 68 | if (!LoadSymbols(session, sender, task)) 69 | return false; 70 | RunAnalysis(); 71 | return true; 72 | } 73 | 74 | private bool LoadSymbols(IDiaSession session, object sender, LoadPDBTask task) 75 | { 76 | var worker = sender as BackgroundWorker; 77 | worker?.ReportProgress(0, "Finding symbols"); 78 | 79 | IDiaEnumSymbols allSymbols; 80 | 81 | if (task.Filter.Length > 0) 82 | { 83 | uint compareFlags = 0; 84 | if (!task.WholeExpression || task.UseRegularExpression) 85 | compareFlags |= 0x8; 86 | if (!task.MatchCase) 87 | compareFlags |= 0x2; 88 | else 89 | compareFlags |= 0x2; 90 | 91 | if (task.UseRegularExpression) 92 | session.findChildren(session.globalScope, SymTagEnum.SymTagUDT, @task.Filter, compareFlags, out allSymbols); 93 | else if (task.WholeExpression) 94 | session.findChildren(session.globalScope, SymTagEnum.SymTagUDT, task.Filter, compareFlags, out allSymbols); 95 | else 96 | { 97 | string filter = '*' + task.Filter + '*'; 98 | session.findChildren(session.globalScope, SymTagEnum.SymTagUDT, @filter, compareFlags, out allSymbols); 99 | } 100 | } 101 | else 102 | { 103 | session.findChildren(session.globalScope, SymTagEnum.SymTagUDT, null, 0, out allSymbols); 104 | } 105 | 106 | if (allSymbols == null) 107 | return false; 108 | 109 | worker?.ReportProgress(0, "Counting symbols"); 110 | 111 | var allSymbolsCount = worker != null ? allSymbols.count : 0; 112 | var i = 0; 113 | 114 | worker?.ReportProgress(0, "Adding symbols"); 115 | 116 | foreach (IDiaSymbol sym in allSymbols) 117 | { 118 | if (worker != null && worker.CancellationPending) 119 | { 120 | return false; 121 | } 122 | 123 | if ( task.SecondPDB) 124 | { 125 | SymbolInfo info = FindSymbolInfo(sym.name); 126 | if (info != null) 127 | { 128 | info.NewSize = sym.length; 129 | } 130 | } 131 | else 132 | { 133 | if (sym.length > 0 && !HasSymbolInfo(sym.name)) 134 | { 135 | var symbolInfo = new SymbolInfo(sym.name, sym.GetType().Name, sym.length); 136 | symbolInfo.ProcessChildren(sym); 137 | Symbols.Add(symbolInfo.Name, symbolInfo); 138 | 139 | if (symbolInfo.Name.Contains("::") && !symbolInfo.Name.Contains("<")) 140 | { 141 | RootNamespaces.Add(symbolInfo.Name.Substring(0, symbolInfo.Name.IndexOf("::"))); 142 | Namespaces.Add(symbolInfo.Name.Substring(0, symbolInfo.Name.LastIndexOf("::"))); 143 | } 144 | } 145 | } 146 | var percentProgress = (int)Math.Round((double)(100 * i++) / allSymbolsCount); 147 | percentProgress = Math.Max(Math.Min(percentProgress, 99), 1); 148 | worker?.ReportProgress(percentProgress, String.Format("Adding symbol {0} on {1}", i, allSymbolsCount)); 149 | } 150 | 151 | 152 | worker?.ReportProgress(100, String.Format("{0} symbols added", allSymbolsCount)); 153 | 154 | return true; 155 | } 156 | 157 | private void RunAnalysis() 158 | { 159 | foreach (var symbol in Symbols.Values) 160 | { 161 | symbol.ComputeTotalPadding(this); 162 | symbol.UpdateBaseClass(this); 163 | } 164 | 165 | foreach (var symbol in Symbols.Values) 166 | { 167 | symbol.CheckOverride(); 168 | symbol.CheckMasking(); 169 | } 170 | } 171 | 172 | 173 | public bool HasSymbolInfo(string name) 174 | { 175 | return Symbols.ContainsKey(name); 176 | } 177 | 178 | public SymbolInfo FindSymbolInfo(string name, bool loadMissingSymbol = false) 179 | { 180 | if (!HasSymbolInfo(name) && name.Length > 0) 181 | { 182 | if (!loadMissingSymbol) 183 | return null; 184 | var task = new LoadPDBTask 185 | { 186 | FileName = FileName, 187 | SecondPDB = false, 188 | Filter = name, 189 | MatchCase = true, 190 | WholeExpression = true, 191 | UseRegularExpression = false 192 | }; 193 | 194 | if (!LoadPdb(null, task)) 195 | return null; 196 | } 197 | 198 | Symbols.TryGetValue(name, out var symbolInfo); 199 | return symbolInfo; 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /SymbolFunctionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CruncherSharp 8 | { 9 | public class SymbolFunctionInfo 10 | { 11 | public enum FunctionCategory 12 | { 13 | Function, 14 | StaticFunction, 15 | Constructor, 16 | Destructor 17 | } 18 | 19 | public string Name { get; set; } 20 | public string DisplayName 21 | { 22 | get 23 | { 24 | switch (Category) 25 | { 26 | case FunctionCategory.StaticFunction: 27 | return $"static {Name}"; 28 | default: 29 | return Name + (IsConst ? " const" : ""); 30 | } 31 | } 32 | } 33 | 34 | public bool UnusedVirtual 35 | { 36 | get 37 | { 38 | return Category == FunctionCategory.Function && Virtual && !IsOverloaded && !IsOverride; 39 | } 40 | } 41 | 42 | public FunctionCategory Category { get; set; } 43 | 44 | public bool Virtual { get; set; } 45 | public bool IsPure { get; set; } 46 | public bool IsOverride { get; set; } 47 | public bool IsOverloaded { get; set; } 48 | public bool IsMasking { get; set; } 49 | public bool IsConst { get; set; } 50 | public bool WasInlineRemoved { get; set; } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SymbolInfo.cs: -------------------------------------------------------------------------------- 1 | using Dia2Lib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace CruncherSharp 8 | { 9 | public class SymbolInfo 10 | { 11 | public string Name { get; private set; } 12 | public string TypeName { get; set; } 13 | public ulong Size { get; set; } 14 | public ulong NewSize { get; set; } 15 | public ulong EndPadding { get; set; } 16 | public ulong Padding => (ulong)((long)EndPadding + Members.Sum(info => (long)info.PaddingBefore)); // This is the local (intrinsic) padding 17 | public ulong PaddingZonesCount => (ulong)((EndPadding > 0 ? 1 : 0) + Members.Sum(info => info.PaddingBefore > 0 ? 1 : 0)); 18 | public ulong? TotalPadding { get; set; } // Includes padding from base classes and members 19 | public ulong NumInstances { get; set; } 20 | public ulong TotalCount { get; set; } 21 | public bool IsAbstract { get; set; } 22 | public bool IsTemplate { get; set; } 23 | public List Members { get; set; } 24 | public List Functions { get; set; } 25 | public List DerivedClasses { get; set; } 26 | 27 | private const string PaddingMarker = "****Padding"; 28 | 29 | public SymbolInfo(string name, string typeName, ulong size) 30 | { 31 | Name = name; 32 | TypeName = typeName; 33 | Size = size; 34 | EndPadding = 0; 35 | TotalPadding = null; 36 | Members = new List(); 37 | Functions = new List(); 38 | IsAbstract = false; 39 | if (Name.Contains("<") && Name.Contains(">")) 40 | IsTemplate = true; 41 | } 42 | 43 | private void AddMember(SymbolMemberInfo member) 44 | { 45 | Members.Add(member); 46 | } 47 | 48 | private void AddFunction(SymbolFunctionInfo function) 49 | { 50 | Functions.Add(function); 51 | } 52 | 53 | private bool ComputeOffsetCollision(int index) 54 | { 55 | return index > 0 && Members[index].Offset == Members[index - 1].Offset; 56 | } 57 | 58 | public bool HasVtable 59 | { 60 | get 61 | { 62 | foreach (var member in Members) 63 | { 64 | if (member.Category == SymbolMemberInfo.MemberCategory.VTable) 65 | { 66 | return true; 67 | } 68 | } 69 | return false; 70 | } 71 | } 72 | 73 | public bool HasBaseClass 74 | { 75 | get 76 | { 77 | foreach (var member in Members) 78 | { 79 | if (member.Category == SymbolMemberInfo.MemberCategory.Base) 80 | { 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | } 87 | 88 | public bool HasMSVCExtraPadding 89 | { 90 | get 91 | { 92 | if (HasBaseClass) 93 | return false; 94 | if (!HasVtable) 95 | return false; 96 | if (Members.Count < 2) 97 | return false; 98 | return Members[1].Size == 8 && Members[1].Offset == 16; 99 | } 100 | } 101 | 102 | public bool HasMSVCEmptyBaseClass 103 | { 104 | get 105 | { 106 | var n = 1; 107 | while (n + 2 < Members.Count) 108 | { 109 | if (Members[n - 1].IsBase && Members[n].IsBase && Members[n].Size == 1 && Members[n].Offset > Members[n - 1].Offset && Members[n + 1].Offset > Members[n].Offset) 110 | return true; 111 | n++; 112 | } 113 | return false; 114 | 115 | } 116 | } 117 | 118 | private ulong ComputePadding(int index) 119 | { 120 | if (index < 1 || index > Members.Count) 121 | { 122 | return 0; 123 | } 124 | if (index < Members.Count && Members[index].AlignWithPrevious) 125 | { 126 | return 0; 127 | } 128 | int previousIndex = index - 1; 129 | ulong biggestSize = Members[previousIndex].Size; 130 | while (Members[previousIndex].AlignWithPrevious && previousIndex > 0) 131 | { 132 | previousIndex--; 133 | if (biggestSize < Members[previousIndex].Size) 134 | biggestSize = Members[previousIndex].Size; 135 | } 136 | 137 | ulong currentOffset = index > Members.Count - 1 ? Size : Members[index].Offset; 138 | ulong previousEnd = Members[previousIndex].Offset + biggestSize; 139 | return currentOffset > previousEnd ? currentOffset - previousEnd : 0; 140 | } 141 | 142 | private ulong ComputeBitPadding(int index) 143 | { 144 | if (index > Members.Count) 145 | { 146 | return 0; 147 | } 148 | if (!Members[index].BitField) 149 | { 150 | return 0; 151 | } 152 | if (index + 1 < Members.Count) 153 | { 154 | if (Members[index + 1].BitField && Members[index + 1].Offset == Members[index].Offset) 155 | return 0; 156 | } 157 | return (8 * Members[index].Size) - (Members[index].BitPosition + Members[index].BitSize); 158 | } 159 | 160 | private ulong ComputeEndPadding() 161 | { 162 | return ComputePadding(Members.Count); 163 | } 164 | 165 | public ulong ComputeTotalPadding(SymbolAnalyzer symbolAnalyzer) 166 | { 167 | if (TotalPadding.HasValue) 168 | { 169 | return TotalPadding.Value; 170 | } 171 | TotalPadding = (ulong)((long)Padding + Members.Sum(info => 172 | { 173 | if (info.AlignWithPrevious) 174 | return 0; 175 | if (info.Category == SymbolMemberInfo.MemberCategory.Member) 176 | return 0; 177 | if (info.TypeName == Name) 178 | return 0; // avoid infinite loops 179 | var referencedInfo = symbolAnalyzer.FindSymbolInfo(info.TypeName); 180 | if (referencedInfo == null) 181 | { 182 | return 0; 183 | } 184 | return (long)referencedInfo.ComputeTotalPadding(symbolAnalyzer); 185 | })); 186 | return TotalPadding.Value; 187 | } 188 | 189 | public void UpdateBaseClass(SymbolAnalyzer symbolAnalyzer) 190 | { 191 | foreach (var member in Members) 192 | { 193 | if (member.Category == SymbolMemberInfo.MemberCategory.Base) 194 | { 195 | var referencedInfo = symbolAnalyzer.FindSymbolInfo(member.Name); 196 | if (referencedInfo != null) 197 | { 198 | if (referencedInfo.DerivedClasses == null) 199 | referencedInfo.DerivedClasses = new List(); 200 | referencedInfo.DerivedClasses.Add(this); 201 | } 202 | } 203 | } 204 | } 205 | 206 | public void CheckOverride() 207 | { 208 | foreach (var function in Functions) 209 | { 210 | if (function.Virtual && function.Category == SymbolFunctionInfo.FunctionCategory.Function && DerivedClasses != null) 211 | { 212 | foreach(var derivedClass in DerivedClasses) 213 | { 214 | if (derivedClass.IsOverloadingFunction(function)) 215 | { 216 | function.IsOverloaded = true; 217 | break; 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | public void CheckMasking() 225 | { 226 | foreach (var function in Functions) 227 | { 228 | if (function.Virtual == false && function.Category == SymbolFunctionInfo.FunctionCategory.Function && DerivedClasses != null) 229 | { 230 | foreach (var derivedClass in DerivedClasses) 231 | { 232 | derivedClass.CheckMasking(function); 233 | } 234 | } 235 | } 236 | } 237 | 238 | private void CheckMasking(SymbolFunctionInfo func) 239 | { 240 | foreach (var function in Functions) 241 | { 242 | if (function.Virtual == false && function.Name == func.Name) 243 | { 244 | function.IsMasking = true; 245 | 246 | if (DerivedClasses != null) 247 | { 248 | foreach (var derivedClass in DerivedClasses) 249 | { 250 | derivedClass.CheckMasking(func); 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | private bool IsOverloadingFunction(SymbolFunctionInfo func) 258 | { 259 | foreach (var function in Functions) 260 | { 261 | if (function.Name == func.Name) 262 | return true; 263 | } 264 | if (DerivedClasses != null) 265 | { 266 | foreach (var derivedClass in DerivedClasses) 267 | { 268 | if (derivedClass.IsOverloadingFunction(func)) 269 | return true; 270 | } 271 | } 272 | return false; 273 | } 274 | 275 | public void UpdateTotalCount(SymbolAnalyzer symbolAnalyzer, ulong count) 276 | { 277 | foreach (var member in Members) 278 | { 279 | if (member.Category != SymbolMemberInfo.MemberCategory.VTable) 280 | { 281 | var referencedInfo = symbolAnalyzer.FindSymbolInfo(member.TypeName); 282 | if (referencedInfo != null) 283 | { 284 | referencedInfo.TotalCount += count; 285 | referencedInfo.UpdateTotalCount(symbolAnalyzer,count); 286 | } 287 | } 288 | } 289 | } 290 | 291 | public void ProcessChildren(IDiaSymbol symbol) 292 | { 293 | symbol.findChildren(SymTagEnum.SymTagNull, null, 0, out var children); 294 | if (children == null) 295 | return; 296 | foreach (IDiaSymbol child in children) 297 | { 298 | if (child.symTag == (uint)SymTagEnum.SymTagFunction) 299 | { 300 | var functionInfo = ProcessFunction(child); 301 | if (functionInfo != null) 302 | { 303 | AddFunction(functionInfo); 304 | if (functionInfo.IsPure) 305 | IsAbstract = true; 306 | } 307 | } 308 | else 309 | { 310 | var memberInfo = ProcessMember(child); 311 | if (memberInfo != null) 312 | { 313 | AddMember(memberInfo); 314 | } 315 | } 316 | } 317 | // Sort members by offset, recompute padding. 318 | // Sorting is usually not needed (for data fields), but sometimes base class order is wrong. 319 | Members.Sort(SymbolMemberInfo.CompareOffsets); 320 | for (int i = 0; i < Members.Count; ++i) 321 | { 322 | var member = Members[i]; 323 | member.AlignWithPrevious = ComputeOffsetCollision(i); 324 | member.PaddingBefore = ComputePadding(i); 325 | member.BitPaddingAfter = ComputeBitPadding(i); 326 | } 327 | EndPadding = ComputeEndPadding(); 328 | } 329 | 330 | public SymbolMemberInfo ProcessMember(IDiaSymbol symbol) 331 | { 332 | if (symbol.symTag == (uint)SymTagEnum.SymTagVTable) 333 | { 334 | return new SymbolMemberInfo(SymbolMemberInfo.MemberCategory.VTable, string.Empty, string.Empty, 8, 0, (ulong)symbol.offset, symbol.bitPosition); 335 | } 336 | 337 | if (symbol.isStatic != 0 || (symbol.symTag != (uint)SymTagEnum.SymTagData && symbol.symTag != (uint)SymTagEnum.SymTagBaseClass)) 338 | { 339 | return null; 340 | } 341 | 342 | // LocIsThisRel || LocIsNull || LocIsBitField 343 | if (symbol.locationType != 4 && symbol.locationType != 0 && symbol.locationType != 6) 344 | { 345 | return null; 346 | } 347 | 348 | var typeSymbol = symbol.type; 349 | 350 | var typeName = GetType(typeSymbol); 351 | 352 | var symbolName = symbol.name; 353 | var category = SymbolMemberInfo.MemberCategory.Member; 354 | 355 | if ((SymTagEnum)symbol.symTag == SymTagEnum.SymTagBaseClass) 356 | category = SymbolMemberInfo.MemberCategory.Base; 357 | else if ((SymTagEnum)typeSymbol.symTag == SymTagEnum.SymTagUDT) 358 | category = SymbolMemberInfo.MemberCategory.UDT; 359 | else if ((SymTagEnum)typeSymbol.symTag == SymTagEnum.SymTagPointerType) 360 | category = SymbolMemberInfo.MemberCategory.Pointer; 361 | 362 | var info = new SymbolMemberInfo(category, symbolName, typeName, typeSymbol.length, symbol.length, (ulong)symbol.offset, symbol.bitPosition); 363 | 364 | if (typeSymbol.volatileType == 1) 365 | info.Volatile = true; 366 | if (symbol.locationType == 6) 367 | info.BitField = true; 368 | 369 | return info; 370 | } 371 | 372 | public SymbolFunctionInfo ProcessFunction(IDiaSymbol symbol) 373 | { 374 | if (symbol.compilerGenerated > 0) 375 | return null; 376 | 377 | var info = new SymbolFunctionInfo(); 378 | if (symbol.isStatic > 0) 379 | { 380 | info.Category = SymbolFunctionInfo.FunctionCategory.StaticFunction; 381 | } 382 | else if (symbol.classParent.name.EndsWith(symbol.name)) 383 | { 384 | info.Category = SymbolFunctionInfo.FunctionCategory.Constructor; 385 | } 386 | else if (symbol.name.StartsWith("~")) 387 | { 388 | info.Category = SymbolFunctionInfo.FunctionCategory.Destructor; 389 | } 390 | else 391 | { 392 | info.Category = SymbolFunctionInfo.FunctionCategory.Function; 393 | } 394 | 395 | info.Virtual = (symbol.@virtual == 1); 396 | info.IsOverride = info.Virtual && (symbol.intro == 0); 397 | info.IsPure = info.Virtual && (symbol.pure != 0); 398 | info.IsConst = symbol.constType != 0; 399 | if (symbol.wasInlined == 0 && symbol.inlSpec != 0) 400 | info.WasInlineRemoved = true; 401 | 402 | info.Name = GetType(symbol.type.type) + " " + symbol.name; 403 | 404 | symbol.type.findChildren(SymTagEnum.SymTagFunctionArgType, null, 0, out var syms); 405 | if (syms.count == 0) 406 | { 407 | info.Name += "(void)"; 408 | } 409 | else 410 | { 411 | var parameters = new List(); 412 | foreach (IDiaSymbol argSym in syms) 413 | { 414 | parameters.Add(GetType(argSym.type)); 415 | } 416 | info.Name += "(" + string.Join(",", parameters) + ")"; 417 | } 418 | return info; 419 | } 420 | 421 | private string GetType(IDiaSymbol typeSymbol) 422 | { 423 | switch ((SymTagEnum)typeSymbol.symTag) 424 | { 425 | case SymTagEnum.SymTagFunctionType: 426 | var returnType = GetType(typeSymbol.type); 427 | 428 | typeSymbol.findChildren(SymTagEnum.SymTagFunctionArgType, null, 0, out var syms); 429 | if (syms.count == 0) 430 | { 431 | returnType += "(void)"; 432 | } 433 | else 434 | { 435 | var parameters = new List(); 436 | foreach (IDiaSymbol argSym in syms) 437 | { 438 | parameters.Add(GetType(argSym.type)); 439 | } 440 | returnType += "(" + string.Join(",",parameters) + ")"; 441 | } 442 | return returnType; 443 | case SymTagEnum.SymTagPointerType: 444 | return typeSymbol.reference != 0 ? $"{GetType(typeSymbol.type)}&" : $"{GetType(typeSymbol.type)}*"; 445 | case SymTagEnum.SymTagBaseType: 446 | if (typeSymbol.constType != 0) 447 | return "const " + SymbolMemberInfo.GetBaseType(typeSymbol); 448 | return SymbolMemberInfo.GetBaseType(typeSymbol); 449 | case SymTagEnum.SymTagArrayType: 450 | // get array dimension: 451 | var dimension = typeSymbol.count.ToString(); 452 | return $"{GetType(typeSymbol.type)}[{dimension}]"; 453 | case SymTagEnum.SymTagUDT: 454 | return typeSymbol.name; 455 | case SymTagEnum.SymTagEnum: 456 | return $"enum {typeSymbol.name}"; 457 | default: 458 | return string.Empty; 459 | } 460 | } 461 | 462 | public override string ToString() 463 | { 464 | var sw = new StringWriter(); 465 | 466 | sw.WriteLine($"Symbol: {Name}"); 467 | sw.WriteLine($"TypeName: {TypeName}"); 468 | sw.WriteLine($"Size: {Size}"); 469 | sw.WriteLine($"Padding: {Padding}"); 470 | sw.WriteLine($"Total padding: {TotalPadding}"); 471 | sw.WriteLine("Members:"); 472 | sw.WriteLine("-------"); 473 | 474 | foreach (var member in Members) 475 | { 476 | if (member.PaddingBefore > 0) 477 | { 478 | var paddingOffset = member.Offset - member.PaddingBefore; 479 | sw.WriteLine($"{PaddingMarker,-40} {paddingOffset,5} {member.PaddingBefore,5}"); 480 | } 481 | sw.WriteLine($"{member.DisplayName,-40} {member.Offset,5} {member.Size,5}"); 482 | } 483 | 484 | if (EndPadding > 0) 485 | { 486 | var endPaddingOffset = Size - EndPadding; 487 | sw.WriteLine($"{PaddingMarker,-40} {endPaddingOffset,5} {EndPadding,5}"); 488 | } 489 | 490 | return sw.ToString(); 491 | } 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /SymbolMemberInfo.cs: -------------------------------------------------------------------------------- 1 | using Dia2Lib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace CruncherSharp 8 | { 9 | public class SymbolMemberInfo 10 | { 11 | public enum MemberCategory 12 | { 13 | VTable = 0, 14 | Base = 1, 15 | Member = 2, 16 | UDT = 3, 17 | Pointer = 4 18 | } 19 | 20 | public MemberCategory Category { get; set; } 21 | public string Name { get; set; } 22 | public string DisplayName 23 | { 24 | get 25 | { 26 | switch (Category) 27 | { 28 | case MemberCategory.VTable: 29 | return "vtable"; 30 | case MemberCategory.Base: 31 | return $"Base: {Name}"; 32 | default: 33 | return Name; 34 | } 35 | } 36 | } 37 | public string TypeName { get; set; } 38 | public ulong Size { get; set; } 39 | public ulong BitSize { get; set; } 40 | public ulong Offset { get; set; } 41 | public uint BitPosition { get; set; } 42 | public ulong PaddingBefore { get; set; } 43 | public ulong BitPaddingAfter { get; set; } 44 | 45 | public bool AlignWithPrevious { get; set; } 46 | public bool BitField { get; set; } 47 | 48 | public bool Volatile { get; set; } 49 | public bool Expanded { get; set; } 50 | 51 | public SymbolMemberInfo(MemberCategory category, string name, string typeName, ulong size, ulong bitSize, ulong offset, uint bitPosition) 52 | { 53 | Category = category; 54 | Name = name; 55 | TypeName = typeName; 56 | Size = size; 57 | BitSize = bitSize; 58 | Offset = offset; 59 | BitPosition = bitPosition; 60 | AlignWithPrevious = false; 61 | PaddingBefore = 0; 62 | BitPaddingAfter = 0; 63 | BitField = false; 64 | Volatile = false; 65 | Expanded = false; 66 | } 67 | 68 | public bool IsBase => Category == MemberCategory.Base; 69 | 70 | public bool IsExapandable => (Category == MemberCategory.Base || Category == MemberCategory.UDT); 71 | 72 | public static int CompareOffsets(SymbolMemberInfo a, SymbolMemberInfo b) 73 | { 74 | if (a.Offset != b.Offset) 75 | { 76 | return a.Offset < b.Offset ? -1 : 1; 77 | } 78 | if (a.IsBase != b.IsBase) 79 | { 80 | return a.IsBase ? -1 : 1; 81 | } 82 | if (a.BitPosition != b.BitPosition) 83 | { 84 | return a.BitPosition < b.BitPosition ? -1 : 1; 85 | } 86 | if (a.Size != b.Size) 87 | { 88 | return a.Size > b.Size ? -1 : 1; 89 | } 90 | return 0; 91 | } 92 | 93 | public static string GetBaseType(IDiaSymbol typeSymbol) 94 | { 95 | //cf. https://msdn.microsoft.com/en-us/library/4szdtzc3.aspx 96 | switch (typeSymbol.baseType) 97 | { 98 | case 0: 99 | return string.Empty; 100 | case 1: 101 | return "void"; 102 | case 2: 103 | return "char"; 104 | case 3: 105 | return "wchar"; 106 | case 6: 107 | { 108 | switch (typeSymbol.length) 109 | { 110 | case 1: 111 | return "int8"; 112 | case 2: 113 | return "int16"; 114 | case 4: 115 | return "int32"; 116 | case 8: 117 | return "int64"; 118 | default: 119 | return "int"; 120 | } 121 | } 122 | case 7: 123 | switch (typeSymbol.length) 124 | { 125 | case 1: 126 | return "uint8"; 127 | case 2: 128 | return "uint16"; 129 | case 4: 130 | return "uint32"; 131 | case 8: 132 | return "uint64"; 133 | default: 134 | return "uint"; 135 | } 136 | case 8: 137 | return "float"; 138 | case 9: 139 | return "BCS"; 140 | case 10: 141 | return "bool"; 142 | case 13: 143 | return "int32"; 144 | case 14: 145 | return "uint32"; 146 | case 29: 147 | return "bit"; 148 | default: 149 | return $"Unhandled: {typeSymbol.baseType}"; 150 | } 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /crunchersharp.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msinilo/crunchersharp/9345e140bfd6f5250336f83a9843baf88116d1e6/crunchersharp.ico --------------------------------------------------------------------------------