/*
* PERWAPI - An API for Reading and Writing PE Files
*
* Copyright (c) Diane Corney, Queensland University of Technology, 2004.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the PERWAPI Copyright as included with this
* distribution in the file PERWAPIcopyright.rtf.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY as is explained in the copyright notice.
*
* The author may be contacted at d.corney@qut.edu.au
*
* Version Date: 26/01/07
*/
// If the compilation requires the use of the original
// SimpleWriter.dll
// then define the following symbol
// #define SIMPLEWRITER
// without this symbol, the code requires SymbolRW.
using System;
using System.IO;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.SymbolStore;
using QSy = QUT.Symbols;
namespace QUT.PERWAPI
{
/**************************************************************************
* Classes related to PDB *
**************************************************************************/
#region PDB Classes
///
/// Writes PDB files
///
public class PDBWriter
{
private ArrayList _docWriters = new ArrayList();
private ArrayList methods = new ArrayList();
private Method currentMethod = null;
private Scope currentScope = null;
private string filename;
private byte[] debugInfo;
private SymbolToken entryPoint;
///
/// The name of the PE file this PDB file belongs to.
///
public string PEFilename
{
get { return filename; }
}
///
/// The name of the PDB file being written.
///
public string PDBFilename
{
get { return Path.ChangeExtension(filename, ".pdb"); }
}
///
/// Provide access to the debug info which needs to be written to the PE file.
/// This is only available after the call to WritePDBFile() has been made.
///
public byte[] DebugInfo
{
get
{
if (debugInfo == null) throw new Exception("DeugInfo is only available after calling WritePDBFile()");
return debugInfo;
}
}
///
/// Create a new instance of the PDB Writer
///
/// The name of the PE file we are writting the PDB file for.
public PDBWriter(string PEFilename)
{
filename = PEFilename;
}
///
/// Set the entry method of the applicaiton
///
/// The token for the entry method.
public void SetEntryPoint(int token)
{
entryPoint = new SymbolToken(token);
}
///
/// Open a new scope.
///
/// Offset as to where the scope should start.
public void OpenScope(int offset)
{
// Make sure we are in a method
if (currentMethod == null)
throw new Exception("You can not open a scope before opening a method.");
// Create and add the new scope
Scope scope = new Scope();
scope.OffsetStart = offset;
scope.ParentScope = currentScope;
// Check if this is the first/root scope or a child scope.
if (currentScope == null)
{
// Check to make sure we don't try to create two root scopes.
if (currentMethod.Scope != null)
throw new Exception("Only one top-most scope is permitted.");
currentMethod.Scope = scope;
}
else
{
currentScope.ChildScopes.Add(scope);
}
// Set the current scope
currentScope = scope;
}
///
/// Close the current scope at the given offset.
///
/// The offset of where to close the scope.
public void CloseScope(int offset)
{
// Make sure a scope is open
if (currentScope == null)
throw new Exception("You can not close a scope now, none are open.");
// Set the end offset for this scope and close it.
currentScope.OffsetEnd = offset;
currentScope = currentScope.ParentScope;
}
///
/// Bind a local to the current scope.
///
/// The name of the variable.
/// The index of the variable in the locals table.
/// The symbol token for the given variable.
/// The starting offset for the binding. Set to 0 to default to current scope.
/// The ending offset for the binding. Set to 0 to default to current scope.
public void BindLocal(string name, int idx, uint token, int startOffset, int endOffset)
{
// Check to make sure a scope is open
if (currentScope == null)
throw new Exception("You must have an open scope in order to bind locals.");
// Create the new local binding object
LocalBinding lb = new LocalBinding();
lb.Name = name;
lb.Index = idx;
lb.Token = new SymbolToken((int)token);
lb.OffsetStart = startOffset;
lb.OffsetEnd = endOffset;
// Add to the current scope
currentScope.Locals.Add(lb);
}
///
/// Adds a given ConstantBinding to the current scope.
///
/// The constant to add to this scope.
/* Not supported at this time. Doesn't work correctly. AKB 2007-02-03
public void BindConstant(ConstantBinding binding) {
// Check to make sure a scope is open
if (currentScope == null)
throw new Exception("You must have an open scope in order to bind a constant.");
// Add the constants to the current scope
currentScope.Constants.Add(binding);
}
*/
///
/// Add a new sequnce point.
///
/// The source file the sequence point is in.
/// The language of the source file.
/// The language vendor of the source file.
/// The document type.
/// The offset of the sequence point.
/// The starting line for the sequence point.
/// The starting column for the sequence point.
/// The ending line for the sequence point.
/// The ending column for the sequence point.
public void AddSequencePoint(string sourceFile, Guid docLanguage, Guid langVendor, Guid docType, int offset, int line, int col, int endLine, int endCol)
{
Document sourceDoc = null;
// Make sure we are in a method
if (currentMethod == null)
throw new Exception("You can not add sequence points before opening a method.");
// Check if a reference for this source document already exists
foreach (Document doc in _docWriters)
if (sourceFile == doc._file && docLanguage == doc._docLanguage && langVendor == doc._langVendor && docType == doc._docType)
{
sourceDoc = doc;
break;
}
// If no existing document, create a new one
if (sourceDoc == null)
{
sourceDoc = new Document();
sourceDoc._file = sourceFile;
sourceDoc._docLanguage = docLanguage;
sourceDoc._langVendor = langVendor;
sourceDoc._docType = docType;
_docWriters.Add(sourceDoc);
}
SequencePointList spList = (SequencePointList)currentMethod.SequencePointList[sourceDoc];
if (spList == null)
currentMethod.SequencePointList.Add(sourceDoc, spList = new SequencePointList());
spList.offsets.Add(offset);
spList.lines.Add(line);
spList.cols.Add(col);
spList.endLines.Add(endLine);
spList.endCols.Add(endCol);
}
///
/// Open a method. Scopes and sequence points will be added to this method.
///
/// The token for this method.
public void OpenMethod(int token)
{
// Add this new method to the list of methods
Method meth = new Method();
meth.Token = new SymbolToken(token);
methods.Add(meth);
// Set the current method
currentMethod = meth;
}
///
/// Close the current method.
///
public void CloseMethod()
{
// Make sure a method is open
if (currentMethod == null)
throw new Exception("No methods currently open.");
// Check to make sure all scopes have been closed.
if (currentScope != null)
throw new Exception("Can not close method until all scopes are closed. Method Token: " + currentMethod.Token.ToString());
// Change the current method to null
currentMethod = null;
}
///
/// Write the PDB file to disk.
///
public void WritePDBFile()
{
/*
* Write default template PDB file first
*
* NOTE: This is a dodgy hack so please feel free to change! AKB 06-01-2007
*
* For some reason if there isn't a PDB file to start with the
* debugger used to step through the resulting PDB file will
* jump all over the place. Resulting in incorrect step-throughs.
* I have not been able to work out why yet but I think it has
* something to do with the call to GetWriterForFile().
* Also, it doesn't happen on all PDB files.
* It is interesting to note that if it is writting a debug file
* to go with a PE file compiled by csc (MS Compiler) it works fine.
*/
// Get the blank PDB file from the resource assembly
System.Reflection.Assembly currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
Stream blankPDB = currentAssembly.GetManifestResourceStream("QUT.PERWAPI.Blank.pdb");
// Write the blank PDB file to the disk
using (FileStream fs = new FileStream(PDBFilename, FileMode.OpenOrCreate, FileAccess.Write))
{
BinaryWriter bw = new BinaryWriter(fs);
// Loop through the PDB file and write it to the disk
byte[] buffer = new byte[32768];
while (true)
{
int read = blankPDB.Read(buffer, 0, buffer.Length);
if (read <= 0) break;
bw.Write(buffer, 0, read);
}
// Close all of the streams we have opened
bw.Close();
fs.Close();
}
// Create the new Symbol Writer
QSy.SymbolWriter symWriter = new QSy.SymbolWriter(PEFilename, PDBFilename);
// Add each of the source documents
foreach (Document doc in _docWriters)
{
#if SIMPLEWRITER
doc._docWriter = symWriter.DefineDocument(
doc._file,
doc._docLanguage,
doc._langVendor,
doc._docType
);
// Set the entry point if it exists
if (entryPoint.GetToken() != 0)
symWriter.SetUserEntryPoint(entryPoint.GetToken());
#else
doc.docWriter = symWriter.DefineDocument(
doc._file,
ref doc._docLanguage,
ref doc._langVendor,
ref doc._docType
);
// Set the entry point if it exists
if (entryPoint.GetToken() != 0)
symWriter.SetUserEntryPoint(entryPoint);
#endif // SIMPLEWRITER
}
// Loop through and add each method
foreach (Method meth in methods)
{
#if SIMPLEWRITER
symWriter.OpenMethod(meth.Token.GetToken());
#else
symWriter.OpenMethod(meth.Token);
#endif
// Write the scope and the locals
if (meth.Scope != null) WriteScopeAndLocals(symWriter, meth.Scope);
// Add each of the sequence points
foreach (Document sourceDoc in meth.SequencePointList.Keys)
{
SequencePointList spList = (SequencePointList)meth.SequencePointList[sourceDoc];
#if SIMPLEWRITER
symWriter.DefineSequencePoints(sourceDoc._docWriter,
(uint[])spList.offsets.ToArray(typeof(int)),
(uint[])spList.lines.ToArray(typeof(int)),
(uint[])spList.cols.ToArray(typeof(int)),
(uint[])spList.endLines.ToArray(typeof(int)),
(uint[])spList.endCols.ToArray(typeof(int)));
#else
symWriter.DefineSequencePoints(sourceDoc.docWriter,
(int[])spList.offsets.ToArray(typeof(int)),
(int[])spList.lines.ToArray(typeof(int)),
(int[])spList.cols.ToArray(typeof(int)),
(int[])spList.endLines.ToArray(typeof(int)),
(int[])spList.endCols.ToArray(typeof(int)));
#endif // SIMPLEWRITER
}
symWriter.CloseMethod();
}
// Get the debug info
debugInfo = symWriter.GetDebugInfo();
// Close the PDB file
symWriter.Close();
}
///
/// Write out the scopes and the locals to the PDB file.
///
/// The symbol writer for this file.
/// The scope to write out.
private void WriteScopeAndLocals(QSy.SymbolWriter symWriter, Scope scope)
{
// Open the scope
symWriter.OpenScope(scope.OffsetStart);
// Add each local variable
foreach (LocalBinding lb in scope.Locals)
{
symWriter.DefineLocalVariable2(
lb.Name,
0,
#if SIMPLEWRITER
lb.Token.GetToken(),
#else
lb.Token,
#endif
1,
lb.Index,
0,
0,
lb.OffsetStart,
lb.OffsetEnd
);
}
// Add each constants
/* For now don't add constants. Doesn't work. AKB 09-01-2007
foreach (ConstantBinding cb in scope.Constants) {
symWriter.DefineConstant(
cb.Name,
cb.Value,
cb.GetSig()
);
}
*/
// Add any child scopes
foreach (Scope childScope in scope.ChildScopes)
WriteScopeAndLocals(symWriter, childScope);
// Close the scope
symWriter.CloseScope(scope.OffsetEnd);
}
///
/// A list of sequence points.
///
private class SequencePointList
{
internal ArrayList offsets = new ArrayList();
internal ArrayList lines = new ArrayList();
internal ArrayList cols = new ArrayList();
internal ArrayList endLines = new ArrayList();
internal ArrayList endCols = new ArrayList();
}
///
/// A source file document.
///
private class Document
{
internal string _file;
internal Guid _docLanguage, _langVendor, _docType;
#if SIMPLEWRITER
internal ulong _docWriter;
#else
internal object docWriter;
#endif
}
///
/// A method.
///
private class Method
{
internal SymbolToken Token;
internal Scope Scope = null;
internal Hashtable SequencePointList = new Hashtable();
}
///
/// A scope.
///
private class Scope
{
internal int OffsetStart;
internal int OffsetEnd;
internal Scope ParentScope = null;
internal ArrayList Locals = new ArrayList();
internal ArrayList Constants = new ArrayList();
internal ArrayList ChildScopes = new ArrayList();
}
///
/// A local binding.
///
private class LocalBinding
{
internal string Name;
internal int Index;
internal SymbolToken Token;
internal int OffsetStart;
internal int OffsetEnd;
}
}
///
/// Read a given PDB file.
///
public class PDBReader
{
//private static Guid IID_IMetaDataImport = new Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44");
//private static Guid CLSID_CorSymBinder = new Guid("AA544D41-28CB-11d3-BD22-0000F80849BD");
private ISymbolReader _reader;
private string _fileName;
///
/// Read the given PDB file by filename.
///
/// The filename and path to the PDB file.
public PDBReader(string fileName)
{
_reader = (ISymbolReader)(new QSy.SymbolReader(fileName));
_fileName = fileName;
}
///
/// Return a particular method.
///
/// The token to identify the method.
/// The method with the given token.
public PDBMethod GetMethod(int token)
{
try
{
ISymbolMethod method = _reader.GetMethod(new SymbolToken(token));
if (method != null)
return new PDBMethod(method);
else
return null;
}
catch
{
return null; // call fails on tokens which are not referenced
}
}
}
///
/// Defines debug information for a method.
///
public class PDBMethod
{
private ISymbolMethod _meth;
///
/// Create a new PDB method object from an ISymbolMethod object.
///
/// The ISymbolMethod object to wrap.
internal PDBMethod(ISymbolMethod meth)
{
_meth = meth;
}
///
/// The root scope of the method.
///
public PDBScope Scope
{
get
{
return new PDBScope(_meth.RootScope);
}
}
///
/// The sequence points in the method.
///
public PDBSequencePoint[] SequencePoints
{
get
{
int spCount = _meth.SequencePointCount;
int[] offsets = new int[spCount];
ISymbolDocument[] documents = new ISymbolDocument[spCount];
int[] lines = new int[spCount];
int[] cols = new int[spCount];
int[] endLines = new int[spCount];
int[] endCols = new int[spCount];
_meth.GetSequencePoints(offsets, documents, lines, cols, endLines, endCols);
PDBSequencePoint[] spList = new PDBSequencePoint[spCount];
for (int i = 0; i < spCount; i++)
spList[i] = new PDBSequencePoint(offsets[i], new PDBDocument(documents[i]), lines[i], cols[i], endLines[i], endCols[i]);
return spList;
}
}
}
///
/// Defines a scope in which local variables exist.
///
public class PDBScope
{
private ISymbolScope _scope;
///
/// Create a new scope from a ISymbolScope
///
///
internal PDBScope(ISymbolScope scope)
{
_scope = scope;
}
///
/// The starting index for the scope.
///
public int StartOffset
{
get
{
return _scope.StartOffset;
}
}
///
/// The end index for the scope.
///
public int EndOffset
{
get
{
return _scope.EndOffset;
}
}
///
/// The variables that exist in this scope.
///
public PDBVariable[] Variables
{
get
{
ArrayList vars = new ArrayList();
foreach (ISymbolVariable var in _scope.GetLocals())
vars.Add(new PDBVariable(var));
return (PDBVariable[])vars.ToArray(typeof(PDBVariable));
}
}
///
/// The sub-scopes within this scope.
///
public PDBScope[] Children
{
get
{
ArrayList children = new ArrayList();
foreach (ISymbolScope child in _scope.GetChildren())
children.Add(new PDBScope(child));
return (PDBScope[])children.ToArray(typeof(PDBScope));
}
}
}
///
/// Defines a reference to one section of code to be highlighted when
/// stepping through in debug mode. Typically one line of code.
///
public class PDBSequencePoint
{
internal PDBDocument _document;
internal int _offset;
internal int _line;
internal int _column;
internal int _endLine;
internal int _endColumn;
///
/// Create a new sequence point.
///
///
/// The source file.
/// The line the point begins on.
/// The column the point begins with.
/// The line the point ends on.
/// The column the point ends with.
internal PDBSequencePoint(int offset, PDBDocument doc, int line, int col, int endLine, int endCol)
{
_offset = offset;
_document = doc;
_line = line;
_column = col;
_endLine = endLine;
_endColumn = endCol;
}
///
/// The source file for this sequence point.
///
public PDBDocument Document
{
get
{
return _document;
}
}
///
///
///
public int Offset
{
get
{
return _offset;
}
}
///
/// The line this sequence point starts on.
///
public int Line
{
get
{
return _line;
}
}
///
/// The column this sequnce point starts with.
///
public int Column
{
get
{
return _column;
}
}
///
/// The line this sequence point ends with.
///
public int EndLine
{
get
{
return _endLine;
}
}
///
/// The column this sequence point ends with.
///
public int EndColumn
{
get
{
return _endColumn;
}
}
}
///
/// A PDB variable object. Stores debug information about a variable.
///
public class PDBVariable
{
private ISymbolVariable _var;
///
/// Create a new PDBVariable object from an ISymbolVariable object.
///
///
internal PDBVariable(ISymbolVariable var)
{
_var = var;
}
///
/// The name of the variable.
///
public string Name
{
get
{
return _var.Name;
}
}
///
/// The address or index of the variable.
///
public int Address
{
get
{
return _var.AddressField1;
}
}
}
///
/// A PDB document is a source file.
///
public class PDBDocument
{
private ISymbolDocument _doc;
///
/// Create a new document object from an existing document.
///
/// The ISymbolDocument to wrap.
internal PDBDocument(ISymbolDocument doc)
{
_doc = doc;
}
///
/// The language for this document.
///
public Guid Language
{
get
{
return _doc.Language; ;
}
}
///
/// The language vendor for this document.
///
public Guid LanguageVendor
{
get
{
return _doc.LanguageVendor;
}
}
///
/// The type for this document.
///
public Guid DocumentType
{
get
{
return _doc.DocumentType;
}
}
///
/// The path/url to the source file.
///
public string URL
{
get
{
return _doc.URL; ;
}
}
}
/**************************************************************************/
// Added to enable PDB reading
internal class MergeBuffer
{
private CILInstruction[] _buffer;
private ArrayList _debugBuffer;
private int _current;
public MergeBuffer(CILInstruction[] buffer)
{
_debugBuffer = new ArrayList();
_buffer = buffer;
}
public void Add(CILInstruction inst, uint offset)
{
while (_current < _buffer.Length && _buffer[_current].offset < offset)
_debugBuffer.Add(_buffer[_current++]);
if (_debugBuffer.Count > 0 && offset >= ((CILInstruction)_debugBuffer[_debugBuffer.Count - 1]).offset)
{
inst.offset = offset;
_debugBuffer.Add(inst);
}
else
{
int i;
for (i = 0; i < _debugBuffer.Count; i++)
if (((CILInstruction)_debugBuffer[i]).offset > offset)
break;
inst.offset = offset;
_debugBuffer.Insert((i > 0 ? i - 1 : i), inst);
}
}
///
/// Tests if Instructions begin and end with an OpenScope/CloseScope pair
///
/// True if there is a root scope
public bool hasRootScope()
{
return (_debugBuffer.Count > 0 && _debugBuffer[0] is OpenScope);
}
public CILInstruction[] Instructions
{
get
{
while (_current < _buffer.Length)
_debugBuffer.Add(_buffer[_current++]);
return (CILInstruction[])_debugBuffer.ToArray(typeof(CILInstruction));
}
}
}
#endregion
/**************************************************************************/
}