Skip to content

Commit

Permalink
Add Source Link support for Portable PDB symbols (#131)
Browse files Browse the repository at this point in the history
Add support for browser Source Link navigation in CIL View
  • Loading branch information
MSDN-WhiteKnight authored Aug 6, 2022
1 parent 3f4fbef commit db8778e
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 18 deletions.
20 changes: 19 additions & 1 deletion CilView.Core/Internal.Pdb.Readers/Portable/PortablePdb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace Internal.Pdb.Portable
{
static class PortablePdb
{
static readonly Guid GuidSourceLink = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A");

static string ReadDocumentPath(MetadataReader reader, DocumentHandle dh)
{
Document doc = reader.GetDocument(dh);
Expand Down Expand Up @@ -124,12 +126,27 @@ public static SourceLineData[] GetSourceLineData(string pdbPath, int methodToken
{
//stream is disposed by MetadataReaderProvider
MetadataReader reader = provider.GetMetadataReader();
string sourceLinkStr = string.Empty;

if (rowNumber > reader.MethodDebugInformation.Count)
{
throw new ArgumentOutOfRangeException("methodToken");
}

//read Source Link info
foreach (CustomDebugInformationHandle cdih in reader.CustomDebugInformation)
{
CustomDebugInformation cdi = reader.GetCustomDebugInformation(cdih);
Guid cdiKind = reader.GetGuid(cdi.Kind);

if (cdiKind.Equals(GuidSourceLink))
{
byte[] data = reader.GetBlobBytes(cdi.Value);
sourceLinkStr = Encoding.UTF8.GetString(data);
break;
}
}

MethodDebugInformation di = reader.GetMethodDebugInformation(hDebug);
string path = string.Empty;
Guid algo = Guid.Empty;
Expand Down Expand Up @@ -170,7 +187,8 @@ public static SourceLineData[] GetSourceLineData(string pdbPath, int methodToken
ColStart = sp.StartColumn,
ColEnd = sp.EndColumn,
Hash = h,
HashAlgorithm = a
HashAlgorithm = a,
SourceLinkData = sourceLinkStr
});
}
}//end using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ class SourceLineData
public int ColEnd { get; set; }
public Guid HashAlgorithm { get; set; }
public byte[] Hash { get; set; }
public string SourceLinkData { get; set; }
}
}
67 changes: 50 additions & 17 deletions CilView.Core/SourceCode/PdbCodeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ static SourceDocument GetSourceFromPortablePdb(MethodBase m)
ret.Method = m;

string filePath = string.Empty;
string sourceLinkStr = string.Empty;

for (int i = 0; i < linedata.Length; i++)
{
Expand All @@ -95,17 +96,10 @@ static SourceDocument GetSourceFromPortablePdb(MethodBase m)
if (string.IsNullOrEmpty(line.FilePath)) continue;

filePath = line.FilePath;

if (!File.Exists(filePath))
{
throw new SymbolsException("Source file not found: " + filePath);
}

bool isValid = PdbUtils.IsSourceValid(filePath, line.HashAlgorithm, line.Hash);

if (!isValid)

if (!string.IsNullOrEmpty(line.SourceLinkData))
{
throw new SymbolsException("Source file does not match PDB hash: " + filePath);
sourceLinkStr = line.SourceLinkData;
}

SourceFragment fragment = new SourceFragment();
Expand All @@ -128,11 +122,33 @@ static SourceDocument GetSourceFromPortablePdb(MethodBase m)
fragment.CilEnd = linedata[i + 1].CilOffset;
}

//считаем код фрагмента из исходников
string s = ReadSourceFromFile(filePath, (uint)fragment.LineStart, (ushort)colStart,
(uint)fragment.LineEnd, (ushort)colEnd, exact: true);
if (File.Exists(filePath))
{
bool isValid = PdbUtils.IsSourceValid(filePath, line.HashAlgorithm, line.Hash);

if (!isValid)
{
throw new SymbolsException("Source file does not match PDB hash: " + filePath);
}

//считаем код фрагмента из исходников
string s = ReadSourceFromFile(filePath, (uint)fragment.LineStart, (ushort)colStart,
(uint)fragment.LineEnd, (ushort)colEnd, exact: true);

fragment.Text = s;
}
else
{
if (string.IsNullOrEmpty(line.SourceLinkData))
{
throw new SymbolsException("Source file not found: " + filePath);
}
else
{
fragment.Text = string.Empty;
}
}

fragment.Text = s;
ret.AddFragment(fragment);
}//end for

Expand All @@ -150,14 +166,31 @@ static SourceDocument GetSourceFromPortablePdb(MethodBase m)
ret.LineEnd = end.LineEnd;
ret.ColEnd = end.ColEnd;

//считаем код метода из исходников
string methodSource = ReadSourceFromFile(filePath, (uint)ret.LineStart, (ushort)ret.ColStart,
if (File.Exists(filePath))
{
//считаем код метода из исходников
string methodSource = ReadSourceFromFile(filePath, (uint)ret.LineStart, (ushort)ret.ColStart,
(uint)ret.LineEnd, (ushort)ret.ColEnd, exact: false);

ret.Text = methodSource;
ret.Text = methodSource;
}
else
{
if (string.IsNullOrEmpty(sourceLinkStr))
{
throw new SymbolsException("Source file not found: " + filePath);
}
else
{
ret.Text = string.Empty;
}
}
}

ret.FilePath = filePath;

if (!string.IsNullOrEmpty(sourceLinkStr)) ret.SetAdditionalInfo("SourceLink", sourceLinkStr);

return ret;
}

Expand Down
5 changes: 5 additions & 0 deletions CilView/CilView.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
<SignManifests>false</SignManifests>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.7.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
Expand Down Expand Up @@ -119,6 +122,8 @@
<Compile Include="FileSystem\RuntimeDir.cs" />
<Compile Include="SearchResult.cs" />
<Compile Include="SourceCode\SourceCodeUI.cs" />
<Compile Include="SourceCode\SourceLinkEntry.cs" />
<Compile Include="SourceCode\SourceLinkMap.cs" />
<Compile Include="UI.Controls\CilBrowserPage.xaml.cs">
<DependentUpon>CilBrowserPage.xaml</DependentUpon>
</Compile>
Expand Down
86 changes: 86 additions & 0 deletions CilView/SourceCode/SourceCodeUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,37 @@ namespace CilView.SourceCode
{
static class SourceCodeUI
{
static HashSet<string> s_allowedRemoteServers = new HashSet<string>();

static string GetHost(string url)
{
try
{
Uri uri = new Uri(url);
return uri.Host.ToLower();
}
catch (UriFormatException)
{
return string.Empty;
}
}

static bool IsRemoteNavigationAllowed(string url)
{
string server = GetHost(url);

if (string.IsNullOrEmpty(server)) return false;

return s_allowedRemoteServers.Contains(server);
}

static void AllowRemoteNavigation(string url)
{
string server = GetHost(url);

if (!string.IsNullOrEmpty(server)) s_allowedRemoteServers.Add(server);
}

static void ShowDecompiledSource(MethodBase method)
{
//stub implementation that only works for abstract methods
Expand Down Expand Up @@ -67,7 +98,62 @@ public static void ShowSource(MethodBase method, uint byteOffset, bool wholeMeth
MessageBox.Show("Failed to get source code: line info not found for this method", "Error");
return;
}

if (string.IsNullOrEmpty(doc.Text))
{
//Local sources not available
string sourceLinkStr = doc.GetAdditionalInfo("SourceLink") as string;

if (string.IsNullOrEmpty(sourceLinkStr))
{
MessageBox.Show("Failed to get source code: Source file " + doc.FilePath +
" is not found or empty", "Error");
return;
}

//Source Link
SourceLinkMap map = SourceLinkMap.Read(sourceLinkStr);
string serverPath = map.GetServerPath(doc.FilePath);

if (string.IsNullOrEmpty(serverPath))
{
MessageBox.Show("Failed to map file path " + doc.FilePath +
"into a source link server path", "Error");
return;
}

bool shouldNavigate = false;

if (IsRemoteNavigationAllowed(serverPath))
{
shouldNavigate = true;
}
else
{
string msg = string.Format(
"The source code is located on the remote server:\r\n{0}\r\n\r\n" +
"Would you like to allow navigation to files on this server?",
serverPath);

MessageBoxResult res = MessageBox.Show(msg, "Source Link information",
MessageBoxButton.YesNo);

if (res == MessageBoxResult.Yes)
{
shouldNavigate = true;
AllowRemoteNavigation(serverPath);
}
}

if (shouldNavigate)
{
Utils.ShellExecute(serverPath, null, "Failed to open source code URL");
}

return;
}

//Local sources
if (wholeMethod)
{
string src = doc.Text;
Expand Down
50 changes: 50 additions & 0 deletions CilView/SourceCode/SourceLinkEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* CIL Tools
* Copyright (c) 2022, MSDN.WhiteKnight (https://github.com/MSDN-WhiteKnight)
* License: BSD 2.0 */
using System;
using System.Collections.Generic;
using System.Text;

namespace CilView.SourceCode
{
public class SourceLinkEntry
{
public string SymbolsPath { get; set; }
public string ServerPath { get; set; }

public string GetServerPath(string symbolsPath)
{
if (this.SymbolsPath.EndsWith("*", StringComparison.Ordinal))
{
//wildcard
string matchPrefix = this.SymbolsPath.Substring(0, this.SymbolsPath.Length - 1);

if (symbolsPath.StartsWith(matchPrefix, StringComparison.OrdinalIgnoreCase))
{
if(matchPrefix.Length >= symbolsPath.Length) return string.Empty;

string symbolsPathSuffix = symbolsPath.Substring(matchPrefix.Length);
string mappedPath = this.ServerPath.Replace("*", symbolsPathSuffix);
mappedPath = mappedPath.Replace("\\", "/");
return mappedPath;
}
else
{
return string.Empty;
}
}
else
{
//exact
if (this.SymbolsPath.Equals(symbolsPath, StringComparison.Ordinal))
{
return this.ServerPath;
}
else
{
return string.Empty;
}
}
}
}
}
Loading

0 comments on commit db8778e

Please sign in to comment.