Skip to content

Instantly share code, notes, and snippets.

@meziantou
Last active September 23, 2022 08:34
Show Gist options
  • Save meziantou/c39d5cc5bcc5f0b2e6d85b01a6a226e3 to your computer and use it in GitHub Desktop.
Save meziantou/c39d5cc5bcc5f0b2e6d85b01a6a226e3 to your computer and use it in GitHub Desktop.
Dump source files from a portable pdb
// Convert full pdb to portable pdb: https://github.com/dotnet/symreader-converter#pdb2pdb
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
var pdb = args[0];
var output = args[1];
Guid EmbeddedSourceId = new("0E8A571B-6926-466E-B4AD-8AB04611F5FE");
Guid SourceLinkId = new("CC110556-A091-4D38-9FEC-25AB9A351A6A");
HttpClient s_httpClient = new();
using var pdbStream = File.OpenRead(pdb);
var readerProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
var reader = readerProvider.GetMetadataReader();
var sourceLink = GetSourceLink(reader);
foreach (var assemblyHandle in reader.AssemblyFiles)
{
var assembly = reader.GetAssemblyFile(assemblyHandle);
var name = reader.GetString(assembly.Name);
var hash = reader.GetBlobBytes(assembly.HashValue);
}
foreach (var documentHandle in reader.Documents)
{
var document = reader.GetDocument(documentHandle);
if (document.Name.IsNil || document.Language.IsNil || document.Hash.IsNil || document.HashAlgorithm.IsNil)
continue;
var name = reader.GetString(document.Name);
var language = reader.GetGuid(document.Language);
var hashAlgorithm = reader.GetGuid(document.HashAlgorithm);
var hash = reader.GetBlobBytes(document.Hash);
var content = GetEmbeddedDocumentContent(reader, documentHandle);
if (content == null && sourceLink != null)
{
var url = sourceLink.GetUrl(name);
if (url != null)
{
content = await s_httpClient.GetByteArrayAsync(url);
}
}
if (content != null)
{
if (name.StartsWith("/_/", StringComparison.Ordinal))
{
name = name.Substring(3);
}
else if (name.StartsWith("/", StringComparison.Ordinal))
{
name = name.Substring(1);
}
var outputPath = Path.Combine(output, name);
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
await File.WriteAllBytesAsync(outputPath, content);
continue;
}
}
SourceLinkJson GetSourceLink(MetadataReader reader)
{
var blobHandle = default(BlobHandle);
foreach (var handle in reader.GetCustomDebugInformation(EntityHandle.ModuleDefinition))
{
var cdi = reader.GetCustomDebugInformation(handle);
if (reader.GetGuid(cdi.Kind) == SourceLinkId)
{
blobHandle = cdi.Value;
}
}
if (blobHandle.IsNil)
return null;
return JsonSerializer.Deserialize<SourceLinkJson>(reader.GetBlobBytes(blobHandle));
}
byte[] GetEmbeddedDocumentContent(MetadataReader reader, DocumentHandle documentHandle)
{
foreach (var cdih in reader.GetCustomDebugInformation(documentHandle))
{
var cdi = reader.GetCustomDebugInformation(cdih);
if (reader.GetGuid(cdi.Kind) == EmbeddedSourceId)
return reader.GetBlobBytes(cdi.Value);
}
return null;
}
class SourceLinkJson
{
[JsonPropertyName("documents")]
public IDictionary<string, string> Documents { get; set; }
public string GetUrl(string file)
{
foreach (var key in Documents.Keys)
{
if (key.Contains('*', StringComparison.Ordinal))
{
var pattern = Regex.Escape(key).Replace(@"\*", "(.+)");
var m = Regex.Match(file, pattern);
if (!m.Success)
continue;
var url = Documents[key];
var path = m.Groups[1].Value.Replace(@"\", "/", StringComparison.Ordinal);
return url.Replace("*", path);
}
else
{
if (!key.Equals(file, StringComparison.Ordinal))
continue;
return Documents[key];
}
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment