|
|
|
@ -7,221 +7,261 @@
@@ -7,221 +7,261 @@
|
|
|
|
|
|
|
|
|
|
using System; |
|
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
|
|
|
|
using Debugger.Wrappers.CorDebug; |
|
|
|
|
using Debugger.Wrappers.CorSym; |
|
|
|
|
using System.IO; |
|
|
|
|
|
|
|
|
|
namespace Debugger |
|
|
|
|
{ |
|
|
|
|
[Serializable] |
|
|
|
|
public class SourcecodeSegment |
|
|
|
|
public class SourcecodeSegment: DebuggerObject |
|
|
|
|
{ |
|
|
|
|
string moduleFilename; |
|
|
|
|
string sourceFullFilename; |
|
|
|
|
Module module; |
|
|
|
|
|
|
|
|
|
string filename; |
|
|
|
|
byte[] checkSum; |
|
|
|
|
int startLine; |
|
|
|
|
int startColumn; |
|
|
|
|
int endLine; |
|
|
|
|
int endColumn; |
|
|
|
|
int ilOffset; |
|
|
|
|
|
|
|
|
|
ICorDebugFunction corFunction; |
|
|
|
|
int ilStart; |
|
|
|
|
int ilEnd; |
|
|
|
|
int[] stepRanges; |
|
|
|
|
|
|
|
|
|
internal SourcecodeSegment() |
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public SourcecodeSegment(string sourceFilename, int line) |
|
|
|
|
{ |
|
|
|
|
this.sourceFullFilename = sourceFilename; |
|
|
|
|
this.startLine = line; |
|
|
|
|
this.endLine = line; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public SourcecodeSegment(string sourceFilename, int line, int startColumn, int endColumn) |
|
|
|
|
{ |
|
|
|
|
this.sourceFullFilename = sourceFilename; |
|
|
|
|
this.startLine = line; |
|
|
|
|
this.endLine = line; |
|
|
|
|
this.startColumn = startColumn; |
|
|
|
|
this.endColumn = endColumn; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public SourcecodeSegment(string sourceFilename, int startLine, int endLine, int startColumn, int endColumn) |
|
|
|
|
{ |
|
|
|
|
this.sourceFullFilename = sourceFilename; |
|
|
|
|
this.startLine = startLine; |
|
|
|
|
this.endLine = endLine; |
|
|
|
|
this.startColumn = startColumn; |
|
|
|
|
this.endColumn = endColumn; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public string ModuleFilename { |
|
|
|
|
get { |
|
|
|
|
return moduleFilename; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
moduleFilename = value; |
|
|
|
|
} |
|
|
|
|
public Module Module { |
|
|
|
|
get { return module; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public string SourceFullFilename { |
|
|
|
|
get { |
|
|
|
|
return sourceFullFilename; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
sourceFullFilename = value; |
|
|
|
|
} |
|
|
|
|
public string Filename { |
|
|
|
|
get { return filename; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public string SourceFilename { |
|
|
|
|
get { |
|
|
|
|
return System.IO.Path.GetFileName(sourceFullFilename); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public byte[] CheckSum { |
|
|
|
|
get { return checkSum; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int StartLine { |
|
|
|
|
get { |
|
|
|
|
return startLine; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
startLine = value; |
|
|
|
|
} |
|
|
|
|
get { return startLine; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int StartColumn { |
|
|
|
|
get { |
|
|
|
|
return startColumn; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
startColumn = value; |
|
|
|
|
} |
|
|
|
|
get { return startColumn; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int EndLine { |
|
|
|
|
get { |
|
|
|
|
return endLine; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
endLine = value; |
|
|
|
|
} |
|
|
|
|
get { return endLine; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int EndColumn { |
|
|
|
|
get { |
|
|
|
|
return endColumn; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
endColumn = value; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int[] StepRanges { |
|
|
|
|
get { |
|
|
|
|
return stepRanges; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
stepRanges = value; |
|
|
|
|
} |
|
|
|
|
get { return endColumn; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int ILOffset { |
|
|
|
|
get { |
|
|
|
|
return ilOffset; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
ilOffset = value; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
internal ICorDebugFunction CorFunction { |
|
|
|
|
get { return corFunction; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public int ILStart { |
|
|
|
|
get { |
|
|
|
|
return ilStart; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
ilStart = value; |
|
|
|
|
} |
|
|
|
|
get { return ilStart; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public int ILEnd { |
|
|
|
|
get { |
|
|
|
|
return ilEnd; |
|
|
|
|
} |
|
|
|
|
set { |
|
|
|
|
ilEnd = value; |
|
|
|
|
} |
|
|
|
|
get { return ilEnd; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public int[] StepRanges { |
|
|
|
|
get { return stepRanges; } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Returns true if found
|
|
|
|
|
internal bool GetFunctionAndOffset(Module module, bool normailize, out ICorDebugFunction function, out int ilOffset) |
|
|
|
|
private SourcecodeSegment() |
|
|
|
|
{ |
|
|
|
|
function = null; |
|
|
|
|
ilOffset = 0; |
|
|
|
|
|
|
|
|
|
ISymUnmanagedReader symReader = module.SymReader; |
|
|
|
|
if (symReader == null) { |
|
|
|
|
return false; // No symbols
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Do not use ISymUnmanagedReader.GetDocument! It is broken if two files have the same name
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static ISymUnmanagedDocument GetSymDocument(Module module, string filename, byte[] checksum) |
|
|
|
|
{ |
|
|
|
|
if (filename == null) throw new ArgumentNullException("filename"); |
|
|
|
|
filename = filename.ToLower(); |
|
|
|
|
|
|
|
|
|
ISymUnmanagedDocument[] symDocs = module.SymDocuments; |
|
|
|
|
ISymUnmanagedDocument symDoc = null; |
|
|
|
|
foreach(ISymUnmanagedDocument d in symDocs) { |
|
|
|
|
if (d.URL.ToLower() == this.SourceFullFilename.ToLower()) { |
|
|
|
|
symDoc = d; |
|
|
|
|
|
|
|
|
|
// "c:\project\file.cs" N/A
|
|
|
|
|
if (Path.IsPathRooted(filename) && checksum == null) { |
|
|
|
|
foreach(ISymUnmanagedDocument symDoc in symDocs) { |
|
|
|
|
if (symDoc.URL.ToLower() == filename) return symDoc; |
|
|
|
|
} |
|
|
|
|
return null; // Not found
|
|
|
|
|
} |
|
|
|
|
if (symDoc == null) { |
|
|
|
|
return false; // Does not use source file
|
|
|
|
|
|
|
|
|
|
// "c:\project\file.cs" 0123456789
|
|
|
|
|
if (Path.IsPathRooted(filename) && checksum != null) { |
|
|
|
|
foreach(ISymUnmanagedDocument symDoc in symDocs) { |
|
|
|
|
if (symDoc.URL.ToLower() == filename) return symDoc; |
|
|
|
|
} |
|
|
|
|
// Not found - try to find using checksum
|
|
|
|
|
filename = Path.GetFileName(filename); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint validLine; |
|
|
|
|
try { |
|
|
|
|
validLine = symDoc.FindClosestLine((uint)StartLine); |
|
|
|
|
} catch { |
|
|
|
|
return false; // Not on a vaild point
|
|
|
|
|
// "file.cs" N/A
|
|
|
|
|
if (!Path.IsPathRooted(filename) && checksum == null) { |
|
|
|
|
if (!filename.StartsWith(@"\")) { |
|
|
|
|
filename = @"\" + filename; |
|
|
|
|
} |
|
|
|
|
foreach(ISymUnmanagedDocument symDoc in symDocs) { |
|
|
|
|
if (symDoc.URL.ToLower().EndsWith(filename)) return symDoc; |
|
|
|
|
} |
|
|
|
|
return null; // Not found
|
|
|
|
|
} |
|
|
|
|
if (validLine != StartLine && normailize) { |
|
|
|
|
StartLine = (int)validLine; |
|
|
|
|
EndLine = (int)validLine; |
|
|
|
|
StartColumn = 0; |
|
|
|
|
EndColumn = 0; |
|
|
|
|
|
|
|
|
|
// "file.cs" 0123456789
|
|
|
|
|
if (!Path.IsPathRooted(filename) && checksum != null) { |
|
|
|
|
if (!filename.StartsWith(@"\")) { |
|
|
|
|
filename = @"\" + filename; |
|
|
|
|
} |
|
|
|
|
foreach(ISymUnmanagedDocument symDoc in symDocs) { |
|
|
|
|
if (!symDoc.URL.ToLower().EndsWith(filename)) continue; |
|
|
|
|
byte[] symDocCheckSum = symDoc.CheckSum; |
|
|
|
|
if (symDocCheckSum.Length != checksum.Length) continue; |
|
|
|
|
for (int i = 0; i < checksum.Length; i++) { |
|
|
|
|
if (symDocCheckSum[i] != checksum[i]) continue; |
|
|
|
|
} |
|
|
|
|
return symDoc; |
|
|
|
|
} |
|
|
|
|
return null; // Not found
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static SourcecodeSegment Resolve(Module module, string fileName, byte[] checkSum, int line, int column) |
|
|
|
|
{ |
|
|
|
|
// Do not use ISymUnmanagedReader.GetDocument! It is broken if two files have the same name
|
|
|
|
|
// Do not use ISymUnmanagedMethod.GetOffset! It sometimes returns negative offset
|
|
|
|
|
|
|
|
|
|
ISymUnmanagedReader symReader = module.SymReader; |
|
|
|
|
if (symReader == null) return null; // No symbols
|
|
|
|
|
|
|
|
|
|
ISymUnmanagedDocument symDoc = GetSymDocument(module, fileName, checkSum); |
|
|
|
|
if (symDoc == null) return null; // Document not found
|
|
|
|
|
|
|
|
|
|
ISymUnmanagedMethod symMethod; |
|
|
|
|
try { |
|
|
|
|
symMethod = symReader.GetMethodFromDocumentPosition(symDoc, validLine, 0); |
|
|
|
|
//uint validLine = symDoc.FindClosestLine((uint)StartLine);
|
|
|
|
|
symMethod = symReader.GetMethodFromDocumentPosition(symDoc, (uint)line, (uint)column); |
|
|
|
|
} catch { |
|
|
|
|
return false; //Not found
|
|
|
|
|
return null; //Not found
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Do not use ISymUnmanagedMethod.GetOffset! It sometimes returns negative offset
|
|
|
|
|
|
|
|
|
|
SequencePoint[] seqPoints = symMethod.SequencePoints; |
|
|
|
|
Array.Sort(seqPoints); |
|
|
|
|
if (seqPoints.Length == 0) return false; |
|
|
|
|
if (this.StartLine < seqPoints[0].Line) return false; |
|
|
|
|
if (seqPoints.Length == 0) return null; |
|
|
|
|
if (line < seqPoints[0].Line) return null; |
|
|
|
|
foreach(SequencePoint sqPoint in seqPoints) { |
|
|
|
|
if (sqPoint.Line == 0xFEEFEE) continue; |
|
|
|
|
// If the desired breakpoint position is before the end of the sequence point
|
|
|
|
|
if (this.StartLine < sqPoint.EndLine || (this.StartLine == sqPoint.EndLine && this.StartColumn < sqPoint.EndColumn)) { |
|
|
|
|
function = module.CorModule.GetFunctionFromToken(symMethod.Token); |
|
|
|
|
ilOffset = (int)sqPoint.Offset; |
|
|
|
|
startLine = (int)sqPoint.Line; |
|
|
|
|
startColumn = (int)sqPoint.Column; |
|
|
|
|
endLine = (int)sqPoint.EndLine; |
|
|
|
|
endColumn = (int)sqPoint.EndColumn; |
|
|
|
|
return true; |
|
|
|
|
if (line < sqPoint.EndLine || (line == sqPoint.EndLine && column < sqPoint.EndColumn)) { |
|
|
|
|
SourcecodeSegment segment = new SourcecodeSegment(); |
|
|
|
|
segment.module = module; |
|
|
|
|
segment.filename = symDoc.URL; |
|
|
|
|
segment.checkSum = symDoc.CheckSum; |
|
|
|
|
segment.startLine = (int)sqPoint.Line; |
|
|
|
|
segment.startColumn = (int)sqPoint.Column; |
|
|
|
|
segment.endLine = (int)sqPoint.EndLine; |
|
|
|
|
segment.endColumn = (int)sqPoint.EndColumn; |
|
|
|
|
segment.corFunction = module.CorModule.GetFunctionFromToken(symMethod.Token); |
|
|
|
|
segment.ilStart = (int)sqPoint.Offset; |
|
|
|
|
segment.ilEnd = (int)sqPoint.Offset; |
|
|
|
|
segment.stepRanges = null; |
|
|
|
|
return segment; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 'ILStart <= ILOffset <= ILEnd' and this range includes at least
|
|
|
|
|
/// the returned area of source code. (May incude some extra compiler generated IL too)
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal static SourcecodeSegment Resolve(Module module, ICorDebugFunction corFunction, uint offset) |
|
|
|
|
{ |
|
|
|
|
ISymUnmanagedReader symReader = module.SymReader; |
|
|
|
|
if (symReader == null) return null; // No symbols
|
|
|
|
|
|
|
|
|
|
ISymUnmanagedMethod symMethod = symReader.GetMethod(corFunction.Token); |
|
|
|
|
if (symMethod == null) return null; |
|
|
|
|
|
|
|
|
|
uint sequencePointCount = symMethod.SequencePointCount; |
|
|
|
|
SequencePoint[] sequencePoints = symMethod.SequencePoints; |
|
|
|
|
|
|
|
|
|
// Get i for which: offsets[i] <= offset < offsets[i + 1]
|
|
|
|
|
// or fallback to first element if offset < offsets[0]
|
|
|
|
|
for (int i = (int)sequencePointCount - 1; i >= 0; i--) { // backwards
|
|
|
|
|
if (sequencePoints[i].Offset <= offset || i == 0) { |
|
|
|
|
// Set inforamtion about current IL range
|
|
|
|
|
int codeSize = (int)corFunction.ILCode.Size; |
|
|
|
|
|
|
|
|
|
int ilStart = (int)sequencePoints[i].Offset; |
|
|
|
|
int ilEnd = (i + 1 < sequencePointCount) ? (int)sequencePoints[i+1].Offset : codeSize; |
|
|
|
|
|
|
|
|
|
// 0xFeeFee means "code generated by compiler"
|
|
|
|
|
// If we are in generated sequence use to closest real one instead,
|
|
|
|
|
// extend the ILStart and ILEnd to include the 'real' sequence
|
|
|
|
|
|
|
|
|
|
// Look ahead for 'real' sequence
|
|
|
|
|
while (i + 1 < sequencePointCount && sequencePoints[i].Line == 0xFeeFee) { |
|
|
|
|
i++; |
|
|
|
|
ilEnd = (i + 1 < sequencePointCount) ? (int)sequencePoints[i+1].Offset : codeSize; |
|
|
|
|
} |
|
|
|
|
// Look back for 'real' sequence
|
|
|
|
|
while (i - 1 >= 0 && sequencePoints[i].Line == 0xFeeFee) { |
|
|
|
|
i--; |
|
|
|
|
ilStart = (int)sequencePoints[i].Offset; |
|
|
|
|
} |
|
|
|
|
// Wow, there are no 'real' sequences
|
|
|
|
|
if (sequencePoints[i].Line == 0xFeeFee) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
List<int> stepRanges = new List<int>(); |
|
|
|
|
for (int j = 0; j < sequencePointCount; j++) { |
|
|
|
|
// Step over compiler generated sequences and current statement
|
|
|
|
|
// 0xFeeFee means "code generated by compiler"
|
|
|
|
|
if (sequencePoints[j].Line == 0xFeeFee || j == i) { |
|
|
|
|
// Add start offset or remove last end (to connect two ranges into one)
|
|
|
|
|
if (stepRanges.Count > 0 && stepRanges[stepRanges.Count - 1] == sequencePoints[j].Offset) { |
|
|
|
|
stepRanges.RemoveAt(stepRanges.Count - 1); |
|
|
|
|
} else { |
|
|
|
|
stepRanges.Add((int)sequencePoints[j].Offset); |
|
|
|
|
} |
|
|
|
|
// Add end offset | handle last sequence point
|
|
|
|
|
if (j + 1 < sequencePointCount) { |
|
|
|
|
stepRanges.Add((int)sequencePoints[j+1].Offset); |
|
|
|
|
} else { |
|
|
|
|
stepRanges.Add(codeSize); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
SourcecodeSegment segment = new SourcecodeSegment(); |
|
|
|
|
segment.module = module; |
|
|
|
|
segment.filename = sequencePoints[i].Document.URL; |
|
|
|
|
segment.checkSum = sequencePoints[i].Document.CheckSum; |
|
|
|
|
segment.startLine = (int)sequencePoints[i].Line; |
|
|
|
|
segment.startColumn = (int)sequencePoints[i].Column; |
|
|
|
|
segment.endLine = (int)sequencePoints[i].EndLine; |
|
|
|
|
segment.endColumn = (int)sequencePoints[i].EndColumn; |
|
|
|
|
segment.corFunction = corFunction; |
|
|
|
|
segment.ilStart = ilStart; |
|
|
|
|
segment.ilEnd = ilEnd; |
|
|
|
|
segment.stepRanges = stepRanges.ToArray(); |
|
|
|
|
return segment; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public override string ToString() |
|
|
|
|
{ |
|
|
|
|
return string.Format("Start={0},{1} End={2},{3}", this.startLine, this.startColumn, this.endLine, this.endColumn); |
|
|
|
|
return string.Format("{0}:{1},{2}-{3},{4}", Path.GetFileName(this.Filename), this.startLine, this.startColumn, this.endLine, this.endColumn); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|