diff --git a/CrossPlatform.sln b/CrossPlatform.sln
index 4e69d25c2e3c8..c2fad84b9e8a5 100644
--- a/CrossPlatform.sln
+++ b/CrossPlatform.sln
@@ -92,6 +92,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vbc2", "src\Compilers\Visua
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vbc", "src\Compilers\VisualBasic\vbc\vbc.csproj", "{E58EE9D7-1239-4961-A0C1-F9EC3952C4C1}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Debugging", "Debugging", "{5EFE4D73-9608-4E19-83A5-963B02413164}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DiaSymReader.PortablePdb", "src\Debugging\Microsoft.DiaSymReader.PortablePdb\Microsoft.DiaSymReader.PortablePdb.csproj", "{F83343BA-B4EA-451C-B6DB-5D645E6171BC}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Compilers\Core\SharedCollections\SharedCollections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 4
@@ -640,6 +644,22 @@ Global
{E58EE9D7-1239-4961-A0C1-F9EC3952C4C1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E58EE9D7-1239-4961-A0C1-F9EC3952C4C1}.Release|x64.ActiveCfg = Release|x64
{E58EE9D7-1239-4961-A0C1-F9EC3952C4C1}.Release|x64.Build.0 = Release|x64
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|ARM.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x64.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|ARM.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x64.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -683,5 +703,6 @@ Global
{C545216D-8CBA-4460-810D-D3DC7EFF8373} = {32A48625-F0AD-419D-828B-A50BDABA38EA}
{21446697-E359-41D9-B39D-40ADA2B20823} = {C65C6143-BED3-46E6-869E-9F0BE6E84C37}
{E58EE9D7-1239-4961-A0C1-F9EC3952C4C1} = {C65C6143-BED3-46E6-869E-9F0BE6E84C37}
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC} = {5EFE4D73-9608-4E19-83A5-963B02413164}
EndGlobalSection
EndGlobal
diff --git a/Roslyn.sln b/Roslyn.sln
index 3894677141dea..d2b85fd05c294 100644
--- a/Roslyn.sln
+++ b/Roslyn.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.23025.0
+VisualStudioVersion = 14.0.23103.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeAnalysisTest", "src\Compilers\Core\CodeAnalysisTest\CodeAnalysisTest.csproj", "{A4C99B85-765C-4C65-9C2A-BB609AAB09E6}"
EndProject
@@ -341,6 +341,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{5CA5F70E
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repl", "Repl", "{81F048A1-B30A-4E74-9BD3-2655DA1DBEA6}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Debugging", "Debugging", "{62F787B2-1E8B-4A3C-BCC0-0EBAE50B42B7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DiaSymReader.PortablePdb", "src\Debugging\Microsoft.DiaSymReader.PortablePdb\Microsoft.DiaSymReader.PortablePdb.csproj", "{F83343BA-B4EA-451C-B6DB-5D645E6171BC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DiaSymReader.PortablePdb.UnitTests", "src\Debugging\Microsoft.DiaSymReader.PortablePdb.Tests\Microsoft.DiaSymReader.PortablePdb.UnitTests.csproj", "{DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 4
@@ -359,8 +365,8 @@ Global
src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{bf9dac1e-3a5e-4dc3-bb44-9a64e0d4e9d3}*SharedItemsImports = 4
src\Compilers\Core\SharedCollections\SharedCollections.projitems*{afde6bea-5038-4a4a-a88e-dbd2e4088eed}*SharedItemsImports = 4
src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{fa0e905d-ec46-466d-b7b2-3b5557f9428c}*SharedItemsImports = 4
- src\Compilers\Core\SharedCollections\SharedCollections.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 4
src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 4
+ src\Compilers\Core\SharedCollections\SharedCollections.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 4
src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 4
src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{bedc5a4a-809e-4017-9cfd-6c8d4e1847f0}*SharedItemsImports = 4
src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{b501a547-c911-4a05-ac6e-274a50dff30e}*SharedItemsImports = 4
@@ -5606,6 +5612,86 @@ Global
{06ECCF53-B9B8-4CC2-83C0-E308BF645F7F}.Release|x64.Build.0 = Release|Any CPU
{06ECCF53-B9B8-4CC2-83C0-E308BF645F7F}.Release|x86.ActiveCfg = Release|Any CPU
{06ECCF53-B9B8-4CC2-83C0-E308BF645F7F}.Release|x86.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|ARM.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x64.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x86.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|Any CPU.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|ARM.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|ARM.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|x64.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|x64.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|x86.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodDebug|x86.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|Any CPU.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|Any CPU.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|ARM.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|ARM.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|Mixed Platforms.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|x64.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|x64.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|x86.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.DogfoodRelease|x86.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|ARM.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x64.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x64.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x86.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x86.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|ARM.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|x64.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|x86.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|Any CPU.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|ARM.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|ARM.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|x64.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|x64.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|x86.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodDebug|x86.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|Any CPU.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|Any CPU.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|ARM.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|ARM.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|Mixed Platforms.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|x64.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|x64.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|x86.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.DogfoodRelease|x86.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|ARM.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|ARM.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|x64.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|x64.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|x86.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -5765,5 +5851,7 @@ Global
{2491A9B9-C0A8-49EE-9077-A32DE76E1E94} = {999FBDA2-33DA-4F74-B957-03AC72CCE5EC}
{5CA5F70E-0FDB-467B-B22C-3CD5994F0087} = {999FBDA2-33DA-4F74-B957-03AC72CCE5EC}
{81F048A1-B30A-4E74-9BD3-2655DA1DBEA6} = {999FBDA2-33DA-4F74-B957-03AC72CCE5EC}
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC} = {62F787B2-1E8B-4A3C-BCC0-0EBAE50B42B7}
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0} = {62F787B2-1E8B-4A3C-BCC0-0EBAE50B42B7}
EndGlobalSection
EndGlobal
diff --git a/build/Toolset.sln b/build/Toolset.sln
index 940b3dcf5b47d..3e1585959ee8f 100644
--- a/build/Toolset.sln
+++ b/build/Toolset.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.23025.0
+VisualStudioVersion = 14.0.23103.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeAnalysisTest", "..\src\Compilers\Core\CodeAnalysisTest\CodeAnalysisTest.csproj", "{A4C99B85-765C-4C65-9C2A-BB609AAB09E6}"
EndProject
@@ -116,6 +116,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vbc", "..\src\Compilers\Vis
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSBuildTaskTests", "..\src\Compilers\Core\MSBuildTaskTests\MSBuildTaskTests.csproj", "{1DFEA9C5-973C-4179-9B1B-0F32288E1EF2}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Debugging", "Debugging", "{0539DC23-F553-44C6-9D9D-B0CFEB4FFE9A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DiaSymReader.PortablePdb", "..\src\Debugging\Microsoft.DiaSymReader.PortablePdb\Microsoft.DiaSymReader.PortablePdb.csproj", "{F83343BA-B4EA-451C-B6DB-5D645E6171BC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DiaSymReader.PortablePdb.UnitTests", "..\src\Debugging\Microsoft.DiaSymReader.PortablePdb.Tests\Microsoft.DiaSymReader.PortablePdb.UnitTests.csproj", "{DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 4
@@ -843,6 +849,38 @@ Global
{1DFEA9C5-973C-4179-9B1B-0F32288E1EF2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1DFEA9C5-973C-4179-9B1B-0F32288E1EF2}.Release|x64.ActiveCfg = Release|x64
{1DFEA9C5-973C-4179-9B1B-0F32288E1EF2}.Release|x64.Build.0 = Release|x64
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|ARM.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Debug|x64.Build.0 = Debug|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|ARM.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x64.ActiveCfg = Release|Any CPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}.Release|x64.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|ARM.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Debug|x64.Build.0 = Debug|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|ARM.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|ARM.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|x64.ActiveCfg = Release|Any CPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -897,5 +935,7 @@ Global
{21446697-E359-41D9-B39D-40ADA2B20823} = {C65C6143-BED3-46E6-869E-9F0BE6E84C37}
{E58EE9D7-1239-4961-A0C1-F9EC3952C4C1} = {C65C6143-BED3-46E6-869E-9F0BE6E84C37}
{1DFEA9C5-973C-4179-9B1B-0F32288E1EF2} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC} = {0539DC23-F553-44C6-9D9D-B0CFEB4FFE9A}
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0} = {0539DC23-F553-44C6-9D9D-B0CFEB4FFE9A}
EndGlobalSection
EndGlobal
diff --git a/docs/specs/PortablePdb-Metadata.md b/docs/specs/PortablePdb-Metadata.md
new file mode 100644
index 0000000000000..e3727240df38c
--- /dev/null
+++ b/docs/specs/PortablePdb-Metadata.md
@@ -0,0 +1,510 @@
+#Portable PDB v0.1: Format Specification Draft
+
+## Portable PDB
+The Portable PDB (Program Database) format describes an encoding of debugging information produced by compilers of Common Language Infrastructure (CLI) languages and consumed by debuggers and other tools. The format is based on the ECMA-335 Partition II metadata standard. It extends its schema while using the same physical table and stream layouts and encodings. The schema of the debugging metadata is complementary to the ECMA-335 metadata schema, therefore, the debugging metadata can (but doesn’t need to) be stored in the same metadata section of the PE/COFF file as the type system metadata.
+
+## Debugging Metadata Format
+### Overview
+The format is based on the ECMA-335 Partition II metadata standard. The physical layout of the data is described in the ECMA-335-II Chapter 24 and the Portable PDB debugging metadata format introduces no changes to the fundamental structure.
+
+The ECMA-335-II standard is amended by an addition of the following tables to the “#~” metadata stream:
+
+* [Document](#DocumentTable)
+* [MethodBody](#MethodBodyTable)
+* [LocalScope](#LocalScopeTable)
+* [LocalVariable](#LocalVariableTable)
+* [LocalConstant](#LocalConstantTable)
+* [ImportScope](#ImportScopeTable)
+* [AsyncMethod](#AsyncMethodTable)
+* [CustomDebugInformation](#CustomDebugInformationTable)
+ * [StateMachineHoistedLocalScopes](#StateMachineHoistedLocalScopes)
+ * [DynamicLocalVariables](#DynamicLocalVariables)
+ * [DefaultNamespace](#DefaultNamespace)
+ * [EditAndContinueLocalSlotMap](#EditAndContinueLocalSlotMap)
+ * [EditAndContinueLambdaAndClosureMap](#EditAndContinueLambdaAndClosureMap)
+
+Debugging metadata tables may be embedded into type system metadata (and part of a PE file), or they may be stored separately in a metadata blob contained in a .pdb file. In the latter case additional information is included that connects the debugging metadata to the type system metadata.
+
+### Standalone debugging metadata
+
+When debugging metadata is generated to a separate data blob "#Pdb" and "#~" streams shall be present. The standalone debugging metadata may also include #Guid, #String and #Blob heaps, which have the same physical layout but are distict from the corresponding streams of the type system metadata.
+
+#### #Pdb stream
+
+The #Pdb stream has the following structure:
+
+| Offset | Size | Field | Description |
+|:-------|:-----|:---------------|----------------------------------------------------------------|
+| 0 | 4 | EntryPoint | Entry point MethodDef token, or 0 if not applicable. The same value as stored in CLI header of the PE file. See ECMA-335-II 15.4.1.2. |
+| 4 | 8 | ReferencedTypeSystemTables | Bit vector of referenced type system metadata tables, let n be the number of bits that are 1. |
+| 12 | 4*n | TypeSystemTableRows | Array of n 4-byte unsigned integers indicating the number of rows for each referenced type system metadata table. |
+
+#### #~ stream
+
+"#~" stream shall only contain debugging information tables defined above and a copy of the Module table from the type system metadata but no other type system metadata table. The Module table effectively links the debugging metadata to the corresponding type system metadata.
+
+References to heaps (strings, blobs, guids) are references to heaps of the debugging metadata. The sizes of references to type system tables are determined using the algorithm described in ECMA-335-II Chapter 24.2.6, except their respective row counts are found in _TypeSystemTableRows_ field of the #Pdb stream.
+
+### Document Table: 0x30
+
+The Document table has the following columns:
+* _Name_ (Blob heap index of [document name blob](#DocumentNameBlob))
+* _HashAlgorithm_ (Guid heap index)
+* _Hash_ (Blob heap index)
+* _Language_ (Guid heap index)
+
+The table is not required to be sorted.
+
+There shall be no duplicate rows in the _Document_ table, based upon document name.
+
+_Name_ shall not be nil. It can however encode an empty name string.
+
+The values for which field _Language_ has a defined meaning are listed in the following tables along with the corresponding interpretation:
+
+| _Language_ field value | language |
+|:-------------------------------------|:-------------|
+| 3f5162f8-07c6-11d3-9053-00c04fa302a1 | Visual C# |
+| 3a12d0b8-c26c-11d0-b442-00a0244a1dd2 | Visual Basic |
+| ab4f38c9-b6e6-43ba-be3b-58080b2ccce3 | Visual F# |
+
+The values for which _HashAlgorithm_ has defined meaning are listed in the following table along with the corresponding semantics of the _Hash_ value.
+
+| _HashAlgorithm_ field value | hash field semantics |
+|:-------------------------------------|:---------------------|
+| ff1816ec-aa5e-4d10-87f7-6f4963833460 | SHA-1 hash |
+| 8829d00f-11b8-4213-878b-770e8597ac16 | SHA-256 hash |
+
+Otherwise, the meaning of _Language_, _HashAlgorithm_ and _Hash_ values is undefined and the reader can interpret them arbitrarily.
+
+#### Document Name Blob
+
+Document name blob is a sequence:
+
+ Blob ::= separator part+
+
+where
+
+* _separator_ is a UTF8 encoded character, or byte 0 to represent an empty separator.
+* _part_ is a compressed integer into the #Blob heap, where the part is stored in UTF8 encoding (0 represents an empty string).
+
+The document name is a concatenation of the _parts_ separated by the _separator_.
+- - -
+**Note** Document names are usually normalized full paths, e.g. "C:\Source\file.cs" "/home/user/source/file.cs".
+The representation is optimized for an efficient deserialization of the name into a UTF8 encoded string while minimizing the overall storage space for document names.
+- - -
+
+### MethodBody Table: 0x31
+
+MethodBody table is either empty (missing) or has exactly as many rows as MethodDef table and the following column:
+
+* _SequencePoints_ (Blob heap index, 0 if the method doesn’t have a body, encoding: [sequence points blob](#SequencePointsBlob))
+
+The table is a logical extension of MethodDef table (adding a column to the table) and as such can be indexed by MethodDef row id.
+
+#### Method Body Blob
+Sequence point is a quintuple of integers and a document reference:
+
+* IL Offset
+* Start Line
+* Start Column
+* End Line
+* End Column
+* Document
+
+_Hidden sequence point_ is a sequence point whose Start Line = End Line = 0xfeefee and Start Column = End Column = 0.
+
+The values of non-hidden sequence point must satisfy the following constraints
+
+* IL Offset is within range [0, 0x20000000)
+* IL Offset of a sequence point is lesser than IL Offset of the subsequent sequence point.
+* Start Line is within range [0, 0x20000000) and not equal to 0xfeefee.
+* End Line is within range [0, 0x20000000) and not equal to 0xfeefee.
+* Start Column is within range [0, 0x10000)
+* End Column is within range [0, 0x10000)
+* End Line is greater or equal to Start Line.
+* If Start Line is equal to End Line then End Column is greater than Start Column.
+
+_Method body blob_ has the following structure:
+
+ Blob ::= header SequencePointRecord (SequencePointRecord | document-record)*
+ SequencePointRecord ::= sequence-point-record | hidden-sequence-point-record
+
+#####header
+| component | value stored | integer representation |
+|:-----------------|:------------------------------|:-----------------------|
+| _LocalSignature_ | StandAloneSig table row id | unsigned compressed |
+| _Document_ | Document table row id | unsigned compressed |
+
+_LocalSignature_ stores the row id of the local signature of the method. This information is somewhat redundant since it can be retrieved from the IL stream. However in some scenarios the IL stream is not available or loading it would unnecessary page in memory that might not otherwise be needed.
+
+#####sequence-point-record
+| component | value stored | integer representation |
+|:---------------|:-----------------------------------------------------|:--------------------------------------------|
+| _δILOffset_ | _ILOffset_ if this is the first sequence point | unsigned compressed |
+| | _ILOffset_ - _Previous_._ILOffset_ otherwise | unsigned compressed, non-zero |
+| _ΔLines_ | _EndLine_ - _StartLine_ | unsigned compressed |
+| _ΔColumns_ | _EndColumn_ - _StartColumn_ | _ΔLines_ = 0: unsigned compressed, non-zero |
+| | | _ΔLines_ > 0: signed compressed |
+| _δStartLine_ | _StartLine_ if this is the first non-hidden sequence point | unsigned compressed |
+| | _StartLine_ - _PreviousNonHidden_._StartLine_ otherwise | signed compressed |
+| _δStartColumn_ | _StartColumn_ if this is the first non-hidden sequence point | unsigned compressed |
+| | _StartColumn_ - _PreviousNonHidden_._StartColumn_ otherwise | signed compressed |
+
+#####hidden-sequence-point-record
+| component | value stored | integer representation |
+|:-------------|:-------------------------------------------------------|:--------------------------------|
+| _δILOffset_ | _ILOffset_ if this is the first sequence point | unsigned compressed |
+| | _ILOffset_ - _Previous_._ILOffset_ otherwise | unsigned compressed, non-zero |
+| _ΔLine_ | 0 | unsigned compressed |
+| _ΔColumn_ | 0 | unsigned compressed |
+
+#####document-record
+| component | value stored | integer representation |
+|:-------------|:-----------------------------------|:-------------------------------|
+| _δILOffset_ | 0 | unsigned compressed |
+| _Document_ | Document row id | unsigned compressed |
+
+Each _SequencePointRecord_ represents a single sequence point. The sequence point inherits the value of _Document_ property from the previous record (_SequencePointRecord_ or _document-record_) or from the _header_ if its the first sequence point. The value of _IL Offset_ is calculated using the value of the previous sequence point (if any) and the value stored in the record.
+
+The values of _Start Line_, _Start Column_, _End Line_ and _End Column_ of a non-hidden sequence point are calculated based upon the values of the previous non-hidden sequence point (if any) and the data stored in the record.
+
+### LocalScope Table: 0x32
+
+The LocalScope table has the following columns:
+
+* _Method_ (MethodDef row id)
+
+* _ImportScope_ (ImportScope row id)
+
+* _VariableList_ (LocalVariable row id)
+
+ An index into the LocalVariable table; it marks the first of a contiguous run of _LocalVariables_ owned by this LocalScope. The run continues to the smaller of:
+ * the last row of the _LocalVariable_ table
+ * the next run of _LocalVariables_, found by inspecting the _VariableList_ of the next row in this LocalScope table.
+
+* _ConstantList_ (LocalConstant row id)
+
+ An index into the LocalConstant table; it marks the first of a contiguous run of _LocalConstants_ owned by this LocalScope. The run continues to the smaller of:
+ * the last row of the _LocalConstant_ table
+ * the next run of _LocalConstants_, found by inspecting the _ConstantList_ of the next row in this LocalScope table.
+
+* _StartOffset_ (integer [0..0x80000000), encoding: uint32)
+
+ Starting IL offset of the scope.
+
+* _Length_ (integer (0..0x80000000), encoding: uint32)
+
+ The scope length in bytes.
+
+The table is required to be sorted first by _Method_ in ascending order, then by _StartOffset_ in ascending order, then by _Length_ in descending order.
+
+_StartOffset_ + _Length_ shall be in range (0..0x80000000).
+
+Each scope spans IL instructions in range [_StartOffset_, _StartOffset_ + _Length_).
+
+_StartOffset_ shall point to the starting byte of an instruction of the _Method_.
+
+_StartOffset_ + _Length_ shall point to the starting byte of an instruction of the _Method_ or be equal to the size of the IL stream of the _Method_.
+
+For each pair of scopes belonging to the same _Method_ the intersection of their respective ranges _R1_ and _R2_ shall be either _R1_ or _R2_ or empty.
+
+### LocalVariable Table: 0x33
+
+The LocalVariable table has the following columns:
+
+* _Attributes_ ([_LocalVariableAttributes_](#LocalVariableAttributes) value, encoding: uint16)
+* _Index_ (integer [0..0x10000), encoding: uint16)
+
+ Slot index in the local signature of the containing MethodDef.
+* _Name_ (String heap index)
+
+Conceptually, every row in the LocalVariable table is owned by one, and only one, row in the LocalScope table.
+
+There shall be no duplicate rows in the LocalVariable table, based upon owner and _Index_.
+
+There shall be no duplicate rows in the LocalVariable table, based upon owner and _Name_.
+
+#####LocalVariableAttributes
+| flag | value | description |
+|:------|:------|:------------|
+| DebuggerHidden | 0x0001 | Variable shouldn’t appear in the list of variables displayed by the debugger |
+
+###LocalConstant Table: 0x34
+
+The LocalConstant table has the following columns:
+
+* _Name_ (String heap index)
+* _Signature_ (Blob heap index, [LocalConstantSig blob](#LocalConstantSig))
+
+Conceptually, every row in the LocalConstant table is owned by one, and only one, row in the LocalScope table.
+
+There shall be no duplicate rows in the LocalConstant table, based upon owner and _Name_.
+
+####LocalConstantSig Blob
+
+The structure of the blob is
+
+ Blob ::= CustomMod* (PrimitiveConstant | EnumConstant | GeneralConstant)
+
+ PrimitiveConstant ::= PrimitiveTypeCode PrimitiveValue
+ PrimitiveTypeCode ::= BOOLEAN | CHAR | I1 | U1 | I2 | U2 | I4 | U4 | I8 | U8 | R4 | R8 | STRING
+
+ EnumConstant ::= EnumTypeCode EnumValue EnumType
+ EnumTypeCode ::= BOOLEAN | CHAR | I1 | U1 | I2 | U2 | I4 | U4 | I8 | U8
+ EnumType ::= TypeDefOrRefOrSpecEncoded
+
+ GeneralConstant ::= (CLASS | VALUETYPE) TypeDefOrRefOrSpecEncoded GeneralValue? |
+ OBJECT
+
+| component | description |
+|:----------------------------|:-----------------------------------------------------------|
+| _PrimitiveTypeCode_ | A 1-byte constant describing the structure of the _PrimitiveValue_. |
+| _PrimitiveValue_ | The value of the constant. |
+| _EnumTypeCode_ | A 1-byte constant describing the structure of the _EnumValue_. |
+| _EnumValue_ | The underlying value of the enum. |
+| _CustomMod_ | Custom modifier as specified in ECMA-335 §II.23.2.7 |
+| _TypeDefOrRefOrSpecEncoded_ | TypeDef, TypeRef or TypeSpec encoded as specified in ECMA-335 §II.23.2.8 |
+
+The encoding of the _PrimitiveValue_ and _EnumValue_ is determined based upon the value of _PrimitiveTypeCode_ and _EnumTypeCode_, respectively.
+
+| Type code | Value |
+|:--------------|:---------------------------|
+| ```BOOLEAN``` | uint8: 0 represents false, 1 represents true |
+| ```CHAR``` | uint16 |
+| ```I1``` | int8 |
+| ```U1``` | uint8 |
+| ```I2``` | int16 |
+| ```U2``` | uint16 |
+| ```I4``` | int32 |
+| ```U4``` | uint32 |
+| ```I8``` | int64 |
+| ```U8``` | uint64 |
+| ```R4``` | float32 |
+| ```R8``` | float64 |
+| ```STRING``` | A single byte 0xff (represents a null string reference), or a UTF-16 little-endian encoded string (possibly empty). |
+
+The numeric values of the type codes are defined by ECMA-335 §II.23.1.16.
+
+_EnumType_ must be an enum type as defined in ECMA-335 §II.14.3. The value of _EnumTypeCode_ must match the underlying type of the _EnumType_.
+
+The encoding of the _GeneralValue_ is determined based upon the type expressed by _TypeDefOrRefOrSpecEncoded_ specified in _GeneralConstant_. If the _GeneralValue_ is not present the value of the constant is the default value of the type. If the type is a reference type the value is a null reference, if the type is a pointer type the value is a null pointer, etc.
+
+| Namespace | Name | _GeneralValue_ encoding |
+|:--------------|:---------|:-------------------------|
+| System | Decimal | sign (highest bit), scale (bits 0..7), low (uint32), mid (uint32), high (uint32) |
+| System | DateTime | int64: ticks |
+
+###ImportScope Table: 0x35
+The ImportScope table has the following columns:
+
+* Parent (ImportScope row id or nil)
+* Imports (Blob index, encoding: [Imports blob](#ImportsBlob))
+
+####Imports Blob
+Imports blob represents all imports declared by an import scope.
+
+Imports blob has the following structure:
+
+ Blob ::= Import*
+ Import ::= kind alias? target-assembly? target-namespace? target-type?
+
+| terminal | value | description |
+|:--------------------|:-----------------------------|:-------------------------------------|
+| _kind_ | Compressed unsigned integer | Import kind. |
+| _alias_ | Compressed unsigned Blob heap index of a UTF8 string. | A name that can be used to refer to the target within the import scope. |
+| _target-assembly_ | Compressed unsigned integer. | Row id of the AssemblyRef table. |
+| _target-namespace_ | Compressed unsigned Blob heap index of a UTF8 string. | Fully qualified namespace name or XML namespace name. |
+| _target-type_ | Compressed unsigned integer. | TypeDef, TypeRef or TypeSpec encoded as TypeDefOrRefOrSpecEncoded (see section II.23.2.8 of the ECMA-335 Metadata specification). |
+
+| _kind_ | description |
+|:-------|:------------|
+| 1 | Imports members of _target-namespace_. |
+| 2 | Imports members of _target-namespace_ defined in assembly _target-assembly_.|
+| 3 | Imports members of _target-type_.|
+| 4 | Imports members of XML namespace _target-namespace_ with prefix _alias_.|
+| 5 | Imports assembly reference _alias_ defined in an ancestor scope.|
+| 6 | Defines an alias for assembly _target-assembly_.|
+| 7 | Defines an alias for the _target-namespace_.|
+| 8 | Defines an alias for the part of _target-namespace_ defined in assembly _target-assembly_.|
+| 9 | Defines an alias for the _target-type_.|
+
+The exact import semantics are language specific.
+
+The blob may be empty. An empty import scope may still be target of custom debug information record.
+
+### AsyncMethod Table: 0x36
+
+The AsyncMethod table has the following columns:
+
+* _KickoffMethod_ (MethodDef row id)
+
+* _CatchHandlerOffset_ (integer [0..0x80000000], encoding: uint32)
+
+ 0 if the handler is not present, otherwise IL offset + 1.
+
+* _Awaits_ (Blob heap index, encoding: [awaits blob](#AwaitsBlob))
+
+The table is required to be sorted by _KickoffMethod_ column.
+
+There shall be no duplicate rows in the AsyncMethod table, based upon _KickoffMethod_.
+
+#### Awaits Blob
+Structure:
+
+ Blob ::= Await+
+ Await ::= yield-offset resume-offset resume-method
+
+Each entry corresponds to an await expression in the async method.
+
+| terminal | encoding | description|
+|:--------------|:----------------------------|:-----------|
+| yield-offset | compressed unsigned integer | TODO|
+| resume-offset | compressed unsigned integer | TODO|
+| resume-method | compressed unsigned integer | TODO (MethodDef row id)|
+
+### CustomDebugInformation Table: 0x37
+The CustomDebugInformation table has the following columns:
+
+* _Parent_ ([HasCustomDebugInformation](#HasCustomDebugInformation) coded index)
+* _Kind_ (Guid heap index)
+* _Value_ (Blob heap index)
+
+Kind is an id defined by the tool producing the information.
+
+| HasCustomDebugInformation | tag (5 bits)|
+|:--------------------------|:------------|
+| MethodDef|0|
+| Field|1|
+| TypeRef|2|
+| TypeDef|3|
+| Param|4|
+| InterfaceImpl|5|
+| MemberRef|6|
+| Module|7|
+| DeclSecurity|8|
+| Property|9|
+| Event|10|
+| StandAloneSig|11|
+| ModuleRef|12|
+| TypeSpec|13|
+| Assembly|14|
+| AssemblyRef|15|
+| File|16|
+| ExportedType|17|
+| ManifestResource|18|
+| GenericParam|19|
+| GenericParamConstraint|20||
+| MethodSpec|21|
+| Document|22|
+| LocalScope|23|
+| LocalVariable|24|
+| LocalConstant|25|
+| ImportScope|26|
+
+#### Language Specific Custom Debug Information Records
+
+The following _Custom Debug Information_ records are currently produced by C#, VB and F# compilers. In future the compilers and other tools may define new records. Once specified they may not change. If a change is needed the owner has to define a new record with a new kind (GUID).
+
+##### State Machine Hoisted Local Scopes (C# & VB compilers)
+Parent: MethodDef
+
+Kind: {6DA9A61E-F8C7-4874-BE62-68BC5630DF71}
+
+Scopes of local variables hoisted to state machine fields.
+
+Structure:
+
+ Blob ::= Scope{hoisted-variable-count}
+ Scope::= start-offset length
+
+| terminal | encoding | description|
+|:---------------|:---------|:-----------|
+| _start-offset_ | uint32 | Start IL offset of the scope, a value in range [0..0x80000000).|
+| _length_ | uint32 | Length of the scope span, a value in range (0..0x80000000). |
+
+Each scope spans IL instructions in range [_start-offset_, _start-offset_ + _length_).
+
+_start-offset_ shall point to the starting byte of an instruction of the MoveNext method of the state machine type.
+
+_start-offset_ + _length_ shall point to the starting byte of an instruction or be equal to the size of the IL stream of the MoveNext method of the state machine type.
+
+##### Dynamic Local Variables (C# compiler)
+Parent: LocalVariables or LocalConstant
+
+Kind: {83C563C4-B4F3-47D5-B824-BA5441477EA8}
+
+Structure:
+
+ Blob ::= bit-sequence
+
+A sequence of bits for a local variable or constant whose type contains _dynamic_ type (e.g. dynamic, dynamic[], List etc.) that describes which System.Object types encoded in the metadata signature of the local type were specified as _dynamic_ in source code.
+
+Bits of the sequence are grouped by 8. If the sequence length is not a multiple of 8 it is padded by 0 bit to the closest multiple of 8. Each group of 8 bits is encoded as a byte whose least significant bit is the first bit of the group and the highest significant bit is the 8th bit of the group. The sequence is encoded as a sequence of bytes representing these groups. Trailing zero bytes may be omitted.
+
+TODO: Specify the meaning of the bits in the sequence.
+
+##### Default Namespace (VB compiler)
+Parent: Module
+
+Kind: {58b2eab6-209f-4e4e-a22c-b2d0f910c782}
+
+Structure:
+
+ Blob ::= namespace
+
+| terminal | encoding | description|
+|:---------|:---------|:-----------|
+| _namespace_ | UTF8 string | The default namespace for the module/project. |
+
+##### Edit and Continue Local Slot Map (C# and VB compilers)
+Parent: MethodDef
+
+Kind: {755F52A8-91C5-45BE-B4B8-209571E552BD}
+
+If _Parent_ is a kickoff method of a state machine (marked in metadata by a custom attribute derived from System.Runtime.CompilerServices.StateMachineAttribute) associates variables hoisted to fields of the state machine type with their syntax offsets. Otherwise, associates slots of the Parent method local signature with their syntax offsets.
+
+Syntax offset is an integer distance from the start of the method body (it may be negative). It is used by the compiler to map the slot to the syntax node that declares the corresponding variable.
+
+The blob has the following structure:
+
+ Blob ::= (has-syntax-offset-baseline syntax-offset-baseline)? SlotId{slot count}
+ SlotId ::= has-ordinal kind syntax-offset ordinal?
+
+| terminal | encoding | description |
+|:---------|:---------|:------------|
+| _has-syntax-offset-baseline_ | 8 bits or none | 0xff or not present.|
+| _syntax-offset-baseline_ | compressed unsigned integer | Negated syntax offset baseline. Only present if the minimal syntax offset stored in the slot map is less than -1. Defaults to -1 if not present.|
+| _has-ordinal_ | 1 bit (highest) | Set iff ordinal is present.|
+| _kind_ | 7 bits (lowest) | Implementation specific slot kind in range [0, 0x7f).|
+| _syntax-offset_ | compressed unsigned integer | The value of syntax-offset + syntax-offset-baseline is the distance of the syntax node that declares the corresponding variable from the start of the method body.|
+| _ordinal_ | compressed unsigned integer | Defines ordering of slots with the same syntax offset.|
+
+The exact algorithm used to calculate syntax offsets and the algorithm that maps slots to syntax nodes is language and implementation specific and may change in future versions of the compiler.
+
+##### Edit and Continue Lambda and Closure Map (C# and VB compilers)
+Parent: MethodDef
+
+Kind: {A643004C-0240-496F-A783-30D64F4979DE}
+
+Encodes information used by the compiler when mapping lambdas and closures declared in the Parent method to their implementing methods and types and to the syntax nodes that declare them.
+
+The blob has the following structure:
+
+ Blob ::= method-ordinal syntax-offset-baseline closure-count Closure{closure-count} Lambda*
+ Closure ::= syntax-offset
+ Lambda ::= syntax-offset closure-ordinal
+
+The number of lambda entries is determined by the size of the blob (the reader shall read lambda records until the end of the blob is reached).
+
+| terminal | encoding | description|
+|:---------|:---------|:-----------|
+| _method-ordinal_ | compressed unsigned integer | Implementation specific number derived from the source location of Parent method.|
+| _syntax-offset-baseline_ | compressed unsigned integer | Negated minimum of syntax offsets stored in the map and -1.|
+| _closure-count_ | compressed unsigned integer | The number of closure entries.|
+| _syntax-offset_ | compressed unsigned integer | The value of _syntax-offset_ + _syntax-offset-baseline_ is the distance of the syntax node that represents the lambda/closure in the source from the start of the method body.|
+| _closure-ordinal_ | compressed unsigned integer | 0 if the lambda doesn’t have a closure. Otherwise, 1-based index into the closure list.|
+
+The exact algorithm used to calculate syntax offsets and the algorithm that maps lambdas/closures to their implementing methods, types and syntax nodes is language and implementation specific and may change in future versions of the compiler.
+
+
+
diff --git a/src/Compilers/CSharp/Portable/CommandLine/CommandLineParser.cs b/src/Compilers/CSharp/Portable/CommandLine/CommandLineParser.cs
index 80480f74f273b..1d776e8c9717f 100644
--- a/src/Compilers/CSharp/Portable/CommandLine/CommandLineParser.cs
+++ b/src/Compilers/CSharp/Portable/CommandLine/CommandLineParser.cs
@@ -1022,12 +1022,20 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar
keyFileSearchPaths.Add(outputDirectory);
}
- if (!emitPdb)
+ var parsedFeatures = CompilerOptionParseUtilities.ParseFeatures(features);
+
+ var debugInfoFormat = DebugInformationFormat.Pdb;
+ string pdbFormatStr;
+ if (emitPdb && parsedFeatures.TryGetValue("pdb", out pdbFormatStr))
{
- if (pdbPath != null)
+ if (pdbFormatStr == "portable")
+ {
+ debugInfoFormat = DebugInformationFormat.PortablePdb;
+ }
+ else if (pdbFormatStr == "embedded")
{
- // Can't give a PDB file name and turn off debug information
- AddDiagnostic(diagnostics, ErrorCode.ERR_MissingDebugSwitch);
+ debugInfoFormat = DebugInformationFormat.Embedded;
+ emitPdb = false;
}
}
@@ -1040,7 +1048,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar
preprocessorSymbols: defines.ToImmutableAndFree(),
documentationMode: parseDocumentationComments ? DocumentationMode.Diagnose : DocumentationMode.None,
kind: SourceCodeKind.Regular,
- features: CompilerOptionParseUtilities.ParseFeatures(features)
+ features: parsedFeatures
);
var scriptParseOptions = parseOptions.WithKind(SourceCodeKind.Script);
@@ -1068,7 +1076,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar
var emitOptions = new EmitOptions
(
metadataOnly: false,
- debugInformationFormat: DebugInformationFormat.Pdb,
+ debugInformationFormat: debugInfoFormat,
pdbFilePath: null, // to be determined later
outputNameOverride: null, // to be determined later
baseAddress: baseAddress,
diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
index b491341b599fc..5bb52b23f8074 100644
--- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
+++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
@@ -1231,15 +1231,23 @@ public void Debug()
parsedArgs = DefaultParse(new[] { "/debug:pdbonly", "/debug:full", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
+ Assert.True(parsedArgs.EmitPdb);
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat);
parsedArgs = DefaultParse(new[] { "/debug:pdbonly", "/debug-", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
+ Assert.False(parsedArgs.EmitPdb);
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat);
parsedArgs = DefaultParse(new[] { "/debug:pdbonly", "/debug-", "/debug", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
+ Assert.True(parsedArgs.EmitPdb);
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat);
parsedArgs = DefaultParse(new[] { "/debug:pdbonly", "/debug-", "/debug+", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify();
+ Assert.True(parsedArgs.EmitPdb);
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat);
parsedArgs = DefaultParse(new[] { "/debug:", "a.cs" }, _baseDirectory);
parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_SwitchNeedsString).WithArguments("", "debug"));
@@ -5522,7 +5530,7 @@ private string GetDefaultResponseFilePath()
[Fact, WorkItem(530359, "DevDiv")]
public void NoStdLib02()
{
- #region "source"
+#region "source"
var source = @"
// A collection initializer can be declared with a user-defined IEnumerable that is declared in a user-defined System.Collections
using System.Collections;
@@ -5641,7 +5649,7 @@ public class DefaultMemberAttribute { }
}
}
";
- #endregion
+#endregion
var src = Temp.CreateFile("NoStdLib02.cs");
src.WriteAllText(source + mslib);
@@ -6215,7 +6223,7 @@ public static void Main() { }
using (var peFile = File.OpenRead(exe.Path))
{
- SharedCompilationUtils.ValidateDebugDirectory(peFile, pdb.Path);
+ SharedCompilationUtils.ValidateDebugDirectory(peFile, pdb.Path, isPortable: false);
}
Assert.True(new FileInfo(exe.Path).Length < oldSize);
@@ -6226,7 +6234,7 @@ public static void Main() { }
using (var peFile = File.OpenRead(exe.Path))
{
- SharedCompilationUtils.ValidateDebugDirectory(peFile, pdb.Path);
+ SharedCompilationUtils.ValidateDebugDirectory(peFile, pdb.Path, isPortable: false);
}
}
diff --git a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj
index 822325c95b251..e02803b288ee4 100644
--- a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj
+++ b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj
@@ -166,6 +166,7 @@
+
True
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncEHTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncEHTests.cs
index 328767952f510..f75f3651326f7 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncEHTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncEHTests.cs
@@ -472,6 +472,7 @@ public static void Main()
CompileAndVerify(source, expectedOutput: expected);
}
+
[Fact]
public void AsyncInFinally003()
{
diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs
index 2051dbf3f1a3f..fae57ed154e11 100644
--- a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs
@@ -85,7 +85,12 @@ public static void Main(string[] args) {}
[Fact]
public void CompareAllBytesEmitted_Release()
{
- foreach (var pdbFormat in new[] { DebugInformationFormat.Pdb })
+ foreach (var pdbFormat in new[]
+ {
+ DebugInformationFormat.Pdb,
+ DebugInformationFormat.PortablePdb,
+ DebugInformationFormat.Embedded
+ })
{
var source =
@"class Program
@@ -105,7 +110,12 @@ public static void Main(string[] args) {}
[Fact, WorkItem(926)]
public void CompareAllBytesEmitted_Debug()
{
- foreach (var pdbFormat in new[] { DebugInformationFormat.Pdb })
+ foreach (var pdbFormat in new[]
+ {
+ DebugInformationFormat.Pdb,
+ DebugInformationFormat.PortablePdb,
+ DebugInformationFormat.Embedded
+ })
{
var source =
@"class Program
diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs
index db83abfbd5f98..2fead2387c62f 100644
--- a/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBConstantTests.cs
@@ -292,7 +292,7 @@ this is a string constant that is too long to fit into the PDB
}
";
var c = CompileAndVerify(text, options: TestOptions.DebugDll);
-
+
c.VerifyPdb("C.M", @"
@@ -309,6 +309,21 @@ this is a string constant that is too long to fit into the PDB
", DebugInformationFormat.Pdb);
+
+ c.VerifyPdb("C.M", @"
+
+
+
+
+
+
+
+
+
+
+
+
+", DebugInformationFormat.PortablePdb);
}
[Fact]
diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs
index 8ff376e0add5f..68671310c0cdf 100644
--- a/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBTests.cs
@@ -3892,6 +3892,23 @@ public static void Main()
", DebugInformationFormat.Pdb);
+
+ c.VerifyPdb(@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+", DebugInformationFormat.PortablePdb);
}
[Fact, WorkItem(546862, "DevDiv")]
@@ -3932,6 +3949,21 @@ public static void Main()
", DebugInformationFormat.Pdb);
+
+ c.VerifyPdb(@"
+
+
+
+
+
+
+
+
+
+
+
+
+", DebugInformationFormat.PortablePdb);
}
[Fact]
@@ -4073,22 +4105,22 @@ public static void F()
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PortablePdbTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PortablePdbTests.cs
new file mode 100644
index 0000000000000..9783247a30923
--- /dev/null
+++ b/src/Compilers/CSharp/Test/Emit/PDB/PortablePdbTests.cs
@@ -0,0 +1,149 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using System.Text;
+using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.CSharp.UnitTests.PDB
+{
+ public class PortablePdbTests : CSharpPDBTestBase
+ {
+ [Fact]
+ public void SequencePointBlob()
+ {
+ string source = @"
+class C
+{
+ public static void Main()
+ {
+ if (F())
+ {
+ System.Console.WriteLine(1);
+ }
+ }
+
+ public static bool F() => false;
+}
+";
+ var c = CreateCompilationWithMscorlib(source, options: TestOptions.DebugDll);
+
+ var pdbStream = new MemoryStream();
+ var peBlob = c.EmitToArray(EmitOptions.Default.WithDebugInformationFormat(DebugInformationFormat.PortablePdb), pdbStream: pdbStream);
+
+ using (var peReader = new PEReader(peBlob))
+ using (var pdbMetadata = new PinnedMetadata(pdbStream.ToImmutable()))
+ {
+ var mdReader = peReader.GetMetadataReader();
+ var pdbReader = pdbMetadata.Reader;
+
+ foreach (var methodHandle in mdReader.MethodDefinitions)
+ {
+ var method = mdReader.GetMethodDefinition(methodHandle);
+ var methodBody = pdbReader.GetMethodBody(methodHandle);
+
+ var name = mdReader.GetString(method.Name);
+
+ var spReader = pdbReader.GetSequencePointsReader(methodBody.SequencePoints);
+
+ TextWriter writer = new StringWriter();
+ while (spReader.MoveNext())
+ {
+ var sp = spReader.Current;
+ if (sp.IsHidden)
+ {
+ writer.WriteLine($"{sp.Offset}: ");
+ }
+ else
+ {
+ writer.WriteLine($"{sp.Offset}: ({sp.StartLine},{sp.StartColumn})-({sp.EndLine},{sp.EndColumn})");
+ }
+ }
+
+ var spString = writer.ToString();
+ var spBlob = pdbReader.GetBlobBytes(methodBody.SequencePoints);
+
+ switch (name)
+ {
+ case "Main":
+ AssertEx.AssertEqualToleratingWhitespaceDifferences(@"
+0: (5,5)-(5,6)
+1: (6,9)-(6,17)
+7:
+10: (7,9)-(7,10)
+11: (8,13)-(8,41)
+18: (9,9)-(9,10)
+19: (10,5)-(10,6)
+", spString);
+ AssertEx.Equal(new byte[]
+ {
+ 0x01, // local signature
+ 0x01, // document
+
+ 0x00, // IL offset
+ 0x00, // Delta Lines
+ 0x01, // Delta Columns
+ 0x05, // Start Line
+ 0x05, // Start Column
+
+ 0x01, // delta IL offset
+ 0x00, // Delta Lines
+ 0x08, // Delta Columns
+ 0x02, // delta Start Line (signed compressed)
+ 0x08, // delta Start Column (signed compressed)
+
+ 0x06, // delta IL offset
+ 0x00, // hidden
+ 0x00, // hidden
+
+ 0x03, // delta IL offset
+ 0x00, // Delta Lines
+ 0x01, // Delta Columns
+ 0x02, // delta Start Line (signed compressed)
+ 0x00, // delta Start Column (signed compressed)
+
+ 0x01, // delta IL offset
+ 0x00, // Delta Lines
+ 0x1C, // Delta Columns
+ 0x02, // delta Start Line (signed compressed)
+ 0x08, // delta Start Column (signed compressed)
+
+ 0x07, // delta IL offset
+ 0x00, // Delta Lines
+ 0x01, // Delta Columns
+ 0x02, // delta Start Line (signed compressed)
+ 0x79, // delta Start Column (signed compressed)
+
+ 0x01, // delta IL offset
+ 0x00, // Delta Lines
+ 0x01, // Delta Columns
+ 0x02, // delta Start Line (signed compressed)
+ 0x79, // delta Start Column (signed compressed)
+ }, spBlob);
+ break;
+
+ case "F":
+ AssertEx.AssertEqualToleratingWhitespaceDifferences("0: (12,31)-(12,36)", spString);
+ AssertEx.Equal(new byte[]
+ {
+ 0x00, // local signature
+ 0x01, // document
+
+ 0x00, // delta IL offset
+ 0x00, // Delta Lines
+ 0x05, // Delta Columns
+ 0x0C, // Start Line
+ 0x1F // Start Column
+ }, spBlob);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj
index 444f90775c9a3..fb77db4b03bc1 100644
--- a/src/Compilers/Core/Portable/CodeAnalysis.csproj
+++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj
@@ -344,6 +344,7 @@
+
@@ -375,6 +376,7 @@
+
@@ -712,9 +714,11 @@
PreserveNewest
+ false
PreserveNewest
+ false
diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs
index cf206901af794..afda6c41bf477 100644
--- a/src/Compilers/Core/Portable/Compilation/Compilation.cs
+++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs
@@ -1773,10 +1773,7 @@ internal bool SerializeToPeStream(
Stream peTempStream = null;
bool deterministic = this.Feature("deterministic")?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false;
-
- // Portable PDBs not supported yet:
- bool emitPortablePdb = false;
-
+ bool emitPortablePdb = moduleBeingBuilt.EmitOptions.DebugInformationFormat == DebugInformationFormat.PortablePdb;
string pdbPath = (pdbStreamProvider != null) ? (moduleBeingBuilt.EmitOptions.PdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb")) : null;
try
diff --git a/src/Compilers/Core/Portable/Emit/DebugInformationFormat.cs b/src/Compilers/Core/Portable/Emit/DebugInformationFormat.cs
index da7526f3b497d..88a7e0edcb557 100644
--- a/src/Compilers/Core/Portable/Emit/DebugInformationFormat.cs
+++ b/src/Compilers/Core/Portable/Emit/DebugInformationFormat.cs
@@ -5,13 +5,15 @@ namespace Microsoft.CodeAnalysis.Emit
public enum DebugInformationFormat
{
Pdb = 1,
+ PortablePdb = 2,
+ Embedded = 3,
}
internal static partial class DebugInformationFormatExtensions
{
internal static bool IsValid(this DebugInformationFormat value)
{
- return value == DebugInformationFormat.Pdb;
+ return value >= DebugInformationFormat.Pdb && value <= DebugInformationFormat.Embedded;
}
}
}
diff --git a/src/Compilers/Core/Portable/MetadataReader/PortableCustomDebugInfoKinds.cs b/src/Compilers/Core/Portable/MetadataReader/PortableCustomDebugInfoKinds.cs
new file mode 100644
index 0000000000000..437d4d1b52340
--- /dev/null
+++ b/src/Compilers/Core/Portable/MetadataReader/PortableCustomDebugInfoKinds.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.CodeAnalysis
+{
+ internal static class PortableCustomDebugInfoKinds
+ {
+ public static readonly Guid AsyncMethodSteppingInformationBlob = new Guid("54FD2AC5-E925-401A-9C2A-F94F171072F8");
+ public static readonly Guid StateMachineHoistedLocalScopes = new Guid("6DA9A61E-F8C7-4874-BE62-68BC5630DF71");
+ public static readonly Guid DynamicLocalVariables = new Guid("83C563C4-B4F3-47D5-B824-BA5441477EA8");
+ public static readonly Guid DefaultNamespace = new Guid("58b2eab6-209f-4e4e-a22c-b2d0f910c782");
+ public static readonly Guid EncLocalSlotMap = new Guid("755F52A8-91C5-45BE-B4B8-209571E552BD");
+ public static readonly Guid EncLambdaAndClosureMap = new Guid("A643004C-0240-496F-A783-30D64F4979DE");
+ }
+}
diff --git a/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs
index 0a1a726ab5a96..00d5b52f8f644 100644
--- a/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs
+++ b/src/Compilers/Core/Portable/PEWriter/FullMetadataWriter.cs
@@ -41,9 +41,21 @@ public static MetadataWriter Create(
CancellationToken cancellationToken)
{
var heaps = new MetadataHeapsBuilder();
+ MetadataHeapsBuilder debugHeapsOpt;
+ switch (context.ModuleBuilder.EmitOptions.DebugInformationFormat)
+ {
+ case DebugInformationFormat.PortablePdb:
+ debugHeapsOpt = hasPdbStream ? new MetadataHeapsBuilder() : null;
+ break;
+
+ case DebugInformationFormat.Embedded:
+ debugHeapsOpt = heaps;
+ break;
- // Portable PDBs not supported yet:
- MetadataHeapsBuilder debugHeapsOpt = null;
+ default:
+ debugHeapsOpt = null;
+ break;
+ }
return new FullMetadataWriter(context, heaps, debugHeapsOpt, messageProvider, allowMissingMethodBodies, deterministic, cancellationToken);
}
diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataHeapsBuilder.cs b/src/Compilers/Core/Portable/PEWriter/MetadataHeapsBuilder.cs
index 2a60a1663dbed..5106a0915d610 100644
--- a/src/Compilers/Core/Portable/PEWriter/MetadataHeapsBuilder.cs
+++ b/src/Compilers/Core/Portable/PEWriter/MetadataHeapsBuilder.cs
@@ -192,6 +192,11 @@ public BlobIdx GetBlobIndex(string str)
return this.GetBlobIndex(ImmutableArray.Create(byteArray));
}
+ public BlobIdx GetBlobIndexUtf8(string str)
+ {
+ return GetBlobIndex(ImmutableArray.Create(Encoding.UTF8.GetBytes(str)));
+ }
+
public int GetGuidIndex(Guid guid)
{
if (guid == Guid.Empty)
diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataSizes.cs b/src/Compilers/Core/Portable/PEWriter/MetadataSizes.cs
index e57aa51da43b6..71a8a25236398 100644
--- a/src/Compilers/Core/Portable/PEWriter/MetadataSizes.cs
+++ b/src/Compilers/Core/Portable/PEWriter/MetadataSizes.cs
@@ -11,6 +11,16 @@ internal sealed class MetadataSizes
{
private const int StreamAlignment = 4;
+ public const ulong DebugMetadataTablesMask =
+ 1UL << (int)TableIndex.Document |
+ 1UL << (int)TableIndex.MethodBody |
+ 1UL << (int)TableIndex.LocalScope |
+ 1UL << (int)TableIndex.LocalVariable |
+ 1UL << (int)TableIndex.LocalConstant |
+ 1UL << (int)TableIndex.ImportScope |
+ 1UL << (int)TableIndex.StateMachineMethod |
+ 1UL << (int)TableIndex.CustomDebugInformation;
+
public readonly bool IsMinimalDelta;
// EnC delta tables are stored as uncompressed metadata table stream
@@ -41,6 +51,11 @@ internal sealed class MetadataSizes
public readonly byte TypeDefOrRefCodedIndexSize;
public readonly byte TypeOrMethodDefCodedIndexSize;
+ public readonly byte LocalVariableIndexSize;
+ public readonly byte LocalConstantIndexSize;
+ public readonly byte ImportScopeIndexSize;
+ public readonly byte HasCustomDebugInformationSize;
+
///
/// Table row counts.
///
@@ -51,13 +66,18 @@ internal sealed class MetadataSizes
///
public readonly ulong PresentTablesMask;
+ ///
+ /// Non-empty tables stored in an external metadata table stream that might be referenced from the metadata table stream being emitted.
+ ///
+ public readonly ulong ExternalTablesMask;
+
///
/// Exact (unaligned) heap sizes.
///
public readonly ImmutableArray HeapSizes;
///
- /// Overall size of metadata stream storage (stream headers, streams: heaps + tables).
+ /// Overall size of metadata stream storage (stream headers, table stream, heaps, additional streams).
/// Aligned to .
///
public readonly int MetadataStreamStorageSize;
@@ -68,6 +88,11 @@ internal sealed class MetadataSizes
///
public readonly int MetadataTableStreamSize;
+ ///
+ /// The size of #Pdb stream. Aligned.
+ ///
+ public readonly int StandalonePdbStreamSize;
+
///
/// The size of IL stream.
///
@@ -97,10 +122,13 @@ public MetadataSizes(
int mappedFieldDataSize,
int resourceDataSize,
int strongNameSignatureSize,
- bool isMinimalDelta)
+ bool isMinimalDelta,
+ bool emitStandaloneDebugMetadata,
+ bool isStandaloneDebugMetadata)
{
Debug.Assert(rowCounts.Length == MetadataTokens.TableCount);
Debug.Assert(heapSizes.Length == MetadataTokens.HeapCount);
+ Debug.Assert(!isStandaloneDebugMetadata || emitStandaloneDebugMetadata);
const byte large = 4;
const byte small = 2;
@@ -117,7 +145,25 @@ public MetadataSizes(
this.StringIndexSize = (isMinimalDelta || heapSizes[(int)HeapIndex.String] > ushort.MaxValue) ? large : small;
this.GuidIndexSize = (isMinimalDelta || heapSizes[(int)HeapIndex.Guid] > ushort.MaxValue) ? large : small;
- PresentTablesMask = ComputeNonEmptyTableMask(rowCounts);
+ ulong allTables = ComputeNonEmptyTableMask(rowCounts);
+ if (!emitStandaloneDebugMetadata)
+ {
+ // all tables
+ PresentTablesMask = allTables;
+ ExternalTablesMask = 0;
+ }
+ else if (isStandaloneDebugMetadata)
+ {
+ // debug tables:
+ PresentTablesMask = allTables & DebugMetadataTablesMask;
+ ExternalTablesMask = allTables & ~DebugMetadataTablesMask;
+ }
+ else
+ {
+ // type-system tables only:
+ PresentTablesMask = allTables & ~DebugMetadataTablesMask;
+ ExternalTablesMask = 0;
+ }
this.CustomAttributeTypeCodedIndexSize = this.GetReferenceByteSize(3, TableIndex.MethodDef, TableIndex.MemberRef);
this.DeclSecurityCodedIndexSize = this.GetReferenceByteSize(2, TableIndex.MethodDef, TableIndex.TypeDef);
@@ -165,6 +211,39 @@ public MetadataSizes(
this.TypeDefOrRefCodedIndexSize = this.GetReferenceByteSize(2, TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec);
this.TypeOrMethodDefCodedIndexSize = this.GetReferenceByteSize(1, TableIndex.TypeDef, TableIndex.MethodDef);
+ this.LocalVariableIndexSize = this.GetReferenceByteSize(0, TableIndex.LocalVariable);
+ this.LocalConstantIndexSize = this.GetReferenceByteSize(0, TableIndex.LocalConstant);
+ this.ImportScopeIndexSize = this.GetReferenceByteSize(0, TableIndex.ImportScope);
+
+ this.HasCustomDebugInformationSize = this.GetReferenceByteSize(5,
+ TableIndex.MethodDef,
+ TableIndex.Field,
+ TableIndex.TypeRef,
+ TableIndex.TypeDef,
+ TableIndex.Param,
+ TableIndex.InterfaceImpl,
+ TableIndex.MemberRef,
+ TableIndex.Module,
+ TableIndex.DeclSecurity,
+ TableIndex.Property,
+ TableIndex.Event,
+ TableIndex.StandAloneSig,
+ TableIndex.ModuleRef,
+ TableIndex.TypeSpec,
+ TableIndex.Assembly,
+ TableIndex.AssemblyRef,
+ TableIndex.File,
+ TableIndex.ExportedType,
+ TableIndex.ManifestResource,
+ TableIndex.GenericParam,
+ TableIndex.GenericParamConstraint,
+ TableIndex.MethodSpec,
+ TableIndex.Document,
+ TableIndex.LocalScope,
+ TableIndex.LocalVariable,
+ TableIndex.LocalConstant,
+ TableIndex.ImportScope);
+
int size = this.CalculateTableStreamHeaderSize();
size += GetTableSize(TableIndex.Module, 2 + 3 * this.GuidIndexSize + this.StringIndexSize);
@@ -213,6 +292,15 @@ public MetadataSizes(
size += GetTableSize(TableIndex.MethodSpec, this.MethodDefOrRefCodedIndexSize + this.BlobIndexSize);
size += GetTableSize(TableIndex.GenericParamConstraint, this.GenericParamIndexSize + this.TypeDefOrRefCodedIndexSize);
+ size += GetTableSize(TableIndex.Document, this.BlobIndexSize + this.GuidIndexSize + this.BlobIndexSize + this.GuidIndexSize);
+ size += GetTableSize(TableIndex.MethodBody, this.BlobIndexSize);
+ size += GetTableSize(TableIndex.LocalScope, this.MethodDefIndexSize + this.ImportScopeIndexSize + this.LocalVariableIndexSize + this.LocalConstantIndexSize + 4 + 4);
+ size += GetTableSize(TableIndex.LocalVariable, 2 + 2 + this.StringIndexSize);
+ size += GetTableSize(TableIndex.LocalConstant, this.StringIndexSize + this.BlobIndexSize);
+ size += GetTableSize(TableIndex.ImportScope, this.ImportScopeIndexSize + this.BlobIndexSize);
+ size += GetTableSize(TableIndex.StateMachineMethod, this.MethodDefIndexSize + this.MethodDefIndexSize);
+ size += GetTableSize(TableIndex.CustomDebugInformation, this.HasCustomDebugInformationSize + this.GuidIndexSize + this.BlobIndexSize);
+
// +1 for terminating 0 byte
size = BitArithmeticUtilities.Align(size + 1, StreamAlignment);
@@ -223,9 +311,14 @@ public MetadataSizes(
size += GetAlignedHeapSize(HeapIndex.Guid);
size += GetAlignedHeapSize(HeapIndex.Blob);
+ this.StandalonePdbStreamSize = isStandaloneDebugMetadata ? CalculateStandalonePdbStreamSize() : 0;
+ size += this.StandalonePdbStreamSize;
+
this.MetadataStreamStorageSize = size;
}
+ public bool IsStandaloneDebugMetadata => StandalonePdbStreamSize > 0;
+
public bool IsPresent(TableIndex table) => (PresentTablesMask & (1UL << (int)table)) != 0;
///
@@ -241,6 +334,7 @@ public int MetadataHeaderSize
{
const int RegularStreamHeaderSizes = 76;
const int MinimalDeltaMarkerStreamSize = 16;
+ const int StandalonePdbStreamSize = 16;
Debug.Assert(RegularStreamHeaderSizes ==
GetMetadataStreamHeaderSize("#~") +
@@ -250,6 +344,7 @@ public int MetadataHeaderSize
GetMetadataStreamHeaderSize("#Blob"));
Debug.Assert(MinimalDeltaMarkerStreamSize == GetMetadataStreamHeaderSize("#JTD"));
+ Debug.Assert(StandalonePdbStreamSize == GetMetadataStreamHeaderSize("#Pdb"));
return
sizeof(uint) + // signature
@@ -261,7 +356,8 @@ public int MetadataHeaderSize
sizeof(ushort) + // storage header: reserved
sizeof(ushort) + // stream count
RegularStreamHeaderSizes +
- (IsMinimalDelta ? MinimalDeltaMarkerStreamSize : 0);
+ (IsMinimalDelta ? MinimalDeltaMarkerStreamSize : 0) +
+ (IsStandaloneDebugMetadata ? StandalonePdbStreamSize : 0);
}
}
@@ -307,6 +403,24 @@ internal int CalculateTableStreamHeaderSize()
return result;
}
+ internal int CalculateStandalonePdbStreamSize()
+ {
+ int result = sizeof(int) + // EntryPoint
+ sizeof(long); // ReferencedTypeSystemTables
+
+ // external table row counts
+ for (int i = 0; i < RowCounts.Length; i++)
+ {
+ if (((1UL << i) & ExternalTablesMask) != 0)
+ {
+ result += sizeof(int);
+ }
+ }
+
+ Debug.Assert(result % StreamAlignment == 0);
+ return result;
+ }
+
private static ulong ComputeNonEmptyTableMask(ImmutableArray rowCounts)
{
ulong mask = 0;
diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs
new file mode 100644
index 0000000000000..662e9e01340df
--- /dev/null
+++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.PortablePdb.cs
@@ -0,0 +1,848 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection.Metadata;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Cci
+{
+ partial class MetadataWriter
+ {
+ private struct DocumentRow
+ {
+ public BlobIdx Name;
+ public uint HashAlgorithm; // Guid
+ public BlobIdx Hash;
+ public uint Language; // Guid
+ }
+
+ private struct MethodBodyRow
+ {
+ public BlobIdx SequencePoints;
+ }
+
+ private struct LocalScopeRow
+ {
+ public uint Method; // MethodRid
+ public uint ImportScope; // ImportScopeRid
+ public uint VariableList; // LocalVariableRid
+ public uint ConstantList; // LocalConstantRid
+ public uint StartOffset;
+ public uint Length;
+ }
+
+ private struct LocalVariableRow
+ {
+ public ushort Attributes;
+ public ushort Index;
+ public StringIdx Name;
+ }
+
+ private struct LocalConstantRow
+ {
+ public StringIdx Name;
+ public BlobIdx Signature;
+ }
+
+ private struct ImportScopeRow
+ {
+ public uint Parent; // ImportScopeRid
+ public BlobIdx Imports;
+ }
+
+ private struct StateMachineMethodRow
+ {
+ public uint MoveNextMethod; // MethodRid
+ public uint KickoffMethod; // MethodRid
+ }
+
+ private struct CustomDebugInformationRow
+ {
+ public uint Parent; // HasCustomDebugInformation coded index
+ public uint Kind; // Guid
+ public BlobIdx Value;
+ }
+
+ private readonly List _documentTable = new List();
+ private readonly List _methodBodyTable = new List();
+ private readonly List _localScopeTable = new List();
+ private readonly List _localVariableTable = new List();
+ private readonly List _localConstantTable = new List();
+ private readonly List _importScopeTable = new List();
+ private readonly List _stateMachineMethodTable = new List();
+ private readonly List _customDebugInformationTable = new List();
+
+ private readonly Dictionary _documentIndex = new Dictionary();
+ private readonly Dictionary _scopeIndex = new Dictionary();
+
+ private void SerializeMethodDebugInfo(IMethodBody bodyOpt, int methodRid, int localSignatureRowId)
+ {
+ if (bodyOpt == null)
+ {
+ _methodBodyTable.Add(default(MethodBodyRow));
+ return;
+ }
+
+ bool isIterator = bodyOpt.StateMachineTypeName != null;
+ bool emitDebugInfo = isIterator || bodyOpt.HasAnySequencePoints;
+
+ if (!emitDebugInfo)
+ {
+ _methodBodyTable.Add(default(MethodBodyRow));
+ return;
+ }
+
+ var bodyImportScope = bodyOpt.ImportScope;
+ int importScopeRid = (bodyImportScope != null) ? GetImportScopeIndex(bodyImportScope, _scopeIndex) : 0;
+
+ // documents & sequence points:
+ BlobIdx sequencePointsBlob = SerializeSequencePoints(localSignatureRowId, bodyOpt.GetSequencePoints(), _documentIndex);
+ _methodBodyTable.Add(new MethodBodyRow { SequencePoints = sequencePointsBlob });
+
+ // Unlike native PDB we don't emit an empty root scope.
+ // scopes are already ordered by StartOffset ascending then by EndOffset descending (the longest scope first).
+
+ if (bodyOpt.LocalScopes.Length == 0)
+ {
+ // TODO: the compiler should produce a scope for each debuggable method
+ _localScopeTable.Add(new LocalScopeRow
+ {
+ Method = (uint)methodRid,
+ ImportScope = (uint)importScopeRid,
+ VariableList = (uint)_localVariableTable.Count + 1,
+ ConstantList = (uint)_localConstantTable.Count + 1,
+ StartOffset = 0,
+ Length = (uint)bodyOpt.IL.Length
+ });
+ }
+ else
+ {
+ foreach (LocalScope scope in bodyOpt.LocalScopes)
+ {
+ _localScopeTable.Add(new LocalScopeRow
+ {
+ Method = (uint)methodRid,
+ ImportScope = (uint)importScopeRid,
+ VariableList = (uint)_localVariableTable.Count + 1,
+ ConstantList = (uint)_localConstantTable.Count + 1,
+ StartOffset = (uint)scope.StartOffset,
+ Length = (uint)scope.Length
+ });
+
+ foreach (ILocalDefinition local in scope.Variables)
+ {
+ Debug.Assert(local.SlotIndex >= 0);
+
+ _localVariableTable.Add(new LocalVariableRow
+ {
+ Attributes = (ushort)local.PdbAttributes,
+ Index = (ushort)local.SlotIndex,
+ Name = _debugHeapsOpt.GetStringIndex(local.Name)
+ });
+
+ SerializeDynamicLocalInfo(local, rowId: _localVariableTable.Count, isConstant: false);
+ }
+
+ foreach (ILocalDefinition constant in scope.Constants)
+ {
+ var mdConstant = constant.CompileTimeValue;
+ Debug.Assert(mdConstant != null);
+
+ _localConstantTable.Add(new LocalConstantRow
+ {
+ Name = _debugHeapsOpt.GetStringIndex(constant.Name),
+ Signature = SerializeLocalConstantSignature(constant)
+ });
+
+ SerializeDynamicLocalInfo(constant, rowId: _localConstantTable.Count, isConstant: true);
+ }
+ }
+ }
+
+ var asyncDebugInfo = bodyOpt.AsyncDebugInfo;
+ if (asyncDebugInfo != null)
+ {
+ _stateMachineMethodTable.Add(new StateMachineMethodRow
+ {
+ MoveNextMethod = (uint)methodRid,
+ KickoffMethod = (uint)GetMethodDefIndex(asyncDebugInfo.KickoffMethod)
+ });
+
+ SerializeAsyncMethodSteppingInfo(asyncDebugInfo, methodRid);
+ }
+
+ SerializeStateMachineLocalScopes(bodyOpt, methodRid);
+
+ // delta doesn't need this information - we use information recorded by previous generation emit
+ if (Context.ModuleBuilder.CommonCompilation.Options.EnableEditAndContinue && !IsFullMetadata)
+ {
+ SerializeEncMethodDebugInformation(bodyOpt, methodRid);
+ }
+ }
+
+ private BlobIdx SerializeLocalConstantSignature(ILocalDefinition localConstant)
+ {
+ var writer = new BlobWriter();
+
+ // CustomMod*
+ SerializeCustomModifiers(localConstant.CustomModifiers, writer);
+
+ var type = localConstant.Type;
+ var typeCode = type.TypeCode(Context);
+
+ object value = localConstant.CompileTimeValue.Value;
+
+ // PrimitiveConstant or EnumConstant
+ if (value is decimal)
+ {
+ writer.WriteByte(0x11);
+ writer.WriteCompressedUInt(GetTypeDefOrRefCodedIndex(type, treatRefAsPotentialTypeSpec: true));
+
+ writer.WriteDecimal((decimal)value);
+ }
+ else if (value is DateTime)
+ {
+ writer.WriteByte(0x11);
+ writer.WriteCompressedUInt(GetTypeDefOrRefCodedIndex(type, treatRefAsPotentialTypeSpec: true));
+
+ writer.WriteDateTime((DateTime)value);
+ }
+ else if (typeCode == PrimitiveTypeCode.String)
+ {
+ writer.WriteByte((byte)ConstantTypeCode.String);
+ if (value == null)
+ {
+ writer.WriteByte(0xff);
+ }
+ else
+ {
+ writer.WriteUTF16((string)value);
+ }
+ }
+ else if (value != null)
+ {
+ // TypeCode
+ writer.WriteByte((byte)GetConstantTypeCode(value));
+
+ // Value
+ writer.WriteConstant(value);
+
+ // EnumType
+ if (type.IsEnum)
+ {
+ writer.WriteCompressedUInt(GetTypeDefOrRefCodedIndex(type, treatRefAsPotentialTypeSpec: true));
+ }
+ }
+ else if (this.module.IsPlatformType(type, PlatformType.SystemObject))
+ {
+ writer.WriteByte(0x1c);
+ }
+ else
+ {
+ writer.WriteByte((byte)(type.IsValueType ? 0x11 : 0x12));
+ writer.WriteCompressedUInt(GetTypeDefOrRefCodedIndex(type, treatRefAsPotentialTypeSpec: true));
+ }
+
+ return _debugHeapsOpt.GetBlobIndex(writer);
+ }
+
+ private static uint HasCustomDebugInformation(HasCustomDebugInformationTag tag, int rowId)
+ {
+ return (uint)(rowId << 5) | (uint)tag;
+ }
+
+ private enum HasCustomDebugInformationTag
+ {
+ MethodDef = 0,
+ Module = 7,
+ Assembly = 14,
+ LocalVariable = 24,
+ LocalConstant = 25,
+ }
+
+ #region ImportScope
+
+ private const int ModuleImportScopeRid = 1;
+
+ private void SerializeImport(BlobWriter writer, AssemblyReferenceAlias alias)
+ {
+ // ::= AliasAssemblyReference
+ writer.WriteByte((byte)ImportDefinitionKind.AliasAssemblyReference);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(alias.Name)));
+ writer.WriteCompressedUInt((uint)GetOrAddAssemblyRefIndex(alias.Assembly));
+ }
+
+ private void SerializeImport(BlobWriter writer, UsedNamespaceOrType import)
+ {
+ if (import.TargetXmlNamespaceOpt != null)
+ {
+ Debug.Assert(import.TargetNamespaceOpt == null);
+ Debug.Assert(import.TargetAssemblyOpt == null);
+ Debug.Assert(import.TargetTypeOpt == null);
+
+ // ::= ImportXmlNamespace
+ writer.WriteByte((byte)ImportDefinitionKind.ImportXmlNamespace);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(import.AliasOpt)));
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(import.TargetXmlNamespaceOpt)));
+ }
+ else if (import.TargetTypeOpt != null)
+ {
+ Debug.Assert(import.TargetNamespaceOpt == null);
+ Debug.Assert(import.TargetAssemblyOpt == null);
+
+ if (import.AliasOpt != null)
+ {
+ // ::= AliasType
+ writer.WriteByte((byte)ImportDefinitionKind.AliasType);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(import.AliasOpt)));
+ }
+ else
+ {
+ // ::= ImportType
+ writer.WriteByte((byte)ImportDefinitionKind.ImportType);
+ }
+
+ writer.WriteCompressedUInt(GetTypeDefOrRefCodedIndex(import.TargetTypeOpt, treatRefAsPotentialTypeSpec: true)); // TODO: index in release build
+ }
+ else if (import.TargetNamespaceOpt != null)
+ {
+ if (import.TargetAssemblyOpt != null)
+ {
+ if (import.AliasOpt != null)
+ {
+ // ::= AliasAssemblyNamespace
+ writer.WriteByte((byte)ImportDefinitionKind.AliasAssemblyNamespace);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(import.AliasOpt)));
+ }
+ else
+ {
+ // ::= ImportAssemblyNamespace
+ writer.WriteByte((byte)ImportDefinitionKind.ImportAssemblyNamespace);
+ }
+
+ writer.WriteCompressedUInt((uint)GetAssemblyRefIndex(import.TargetAssemblyOpt));
+ }
+ else
+ {
+ if (import.AliasOpt != null)
+ {
+ // ::= AliasNamespace
+ writer.WriteByte((byte)ImportDefinitionKind.AliasNamespace);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(import.AliasOpt)));
+ }
+ else
+ {
+ // ::= ImportNamespace
+ writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace);
+ }
+ }
+
+ // TODO: cache?
+ string namespaceName = TypeNameSerializer.BuildQualifiedNamespaceName(import.TargetNamespaceOpt);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(namespaceName)));
+ }
+ else
+ {
+ // ::= ImportReferenceAlias
+ Debug.Assert(import.AliasOpt != null);
+ Debug.Assert(import.TargetAssemblyOpt == null);
+
+ writer.WriteByte((byte)ImportDefinitionKind.ImportAssemblyReferenceAlias);
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(_debugHeapsOpt.GetBlobIndexUtf8(import.AliasOpt)));
+ }
+ }
+
+ private void DefineModuleImportScope()
+ {
+ // module-level import scope:
+ var writer = new BlobWriter();
+
+ SerializeModuleDefaultNamespace();
+
+ foreach (AssemblyReferenceAlias alias in module.GetAssemblyReferenceAliases(Context))
+ {
+ SerializeImport(writer, alias);
+ }
+
+ foreach (UsedNamespaceOrType import in module.GetImports())
+ {
+ SerializeImport(writer, import);
+ }
+
+ _importScopeTable.Add(new ImportScopeRow
+ {
+ Parent = 0,
+ Imports = _debugHeapsOpt.GetBlobIndex(writer)
+ });
+
+ Debug.Assert(_importScopeTable.Count == ModuleImportScopeRid);
+ }
+
+ private int GetImportScopeIndex(IImportScope scope, Dictionary scopeIndex)
+ {
+ int scopeRid;
+ if (scopeIndex.TryGetValue(scope, out scopeRid))
+ {
+ // scoep is already indexed:
+ return scopeRid;
+ }
+
+ var parent = scope.Parent;
+ int parentScopeRid = (parent != null) ? GetImportScopeIndex(scope.Parent, scopeIndex) : ModuleImportScopeRid;
+
+ _importScopeTable.Add(new ImportScopeRow
+ {
+ Parent = (uint)parentScopeRid,
+ Imports = SerializeImportsBlob(scope)
+ });
+
+ var rid = _importScopeTable.Count;
+ scopeIndex.Add(scope, rid);
+ return rid;
+ }
+
+ private BlobIdx SerializeImportsBlob(IImportScope scope)
+ {
+ var writer = new BlobWriter();
+
+ foreach (UsedNamespaceOrType import in scope.GetUsedNamespaces())
+ {
+ SerializeImport(writer, import);
+ }
+
+ return _debugHeapsOpt.GetBlobIndex(writer);
+ }
+
+ private void SerializeModuleDefaultNamespace()
+ {
+ // C#: DefaultNamespace is null.
+ // VB: DefaultNamespace is non-null.
+ if (module.DefaultNamespace == null)
+ {
+ return;
+ }
+
+ _customDebugInformationTable.Add(new CustomDebugInformationRow
+ {
+ Parent = HasCustomDebugInformation(HasCustomDebugInformationTag.Module, 1),
+ Kind = (uint)_debugHeapsOpt.GetGuidIndex(PortableCustomDebugInfoKinds.DefaultNamespace),
+ Value = _debugHeapsOpt.GetBlobIndexUtf8(module.DefaultNamespace)
+ });
+ }
+
+ #endregion
+
+ #region Locals
+
+ private void SerializeDynamicLocalInfo(ILocalDefinition local, int rowId, bool isConstant)
+ {
+ var dynamicFlags = local.DynamicTransformFlags;
+ if (dynamicFlags.IsDefault)
+ {
+ return;
+ }
+
+ var value = SerializeBitVector(dynamicFlags);
+
+ var tag = isConstant ? HasCustomDebugInformationTag.LocalConstant : HasCustomDebugInformationTag.LocalVariable;
+
+ _customDebugInformationTable.Add(new CustomDebugInformationRow
+ {
+ Parent = HasCustomDebugInformation(tag, rowId),
+ Kind = (uint)_debugHeapsOpt.GetGuidIndex(PortableCustomDebugInfoKinds.DynamicLocalVariables),
+ Value = _debugHeapsOpt.GetBlobIndex(value),
+ });
+ }
+
+ private static ImmutableArray SerializeBitVector(ImmutableArray vector)
+ {
+ var builder = ArrayBuilder.GetInstance();
+
+ int b = 0;
+ int shift = 0;
+ for (int i = 0; i < vector.Length; i++)
+ {
+ if ((bool)vector[i].Value)
+ {
+ b |= 1 << shift;
+ }
+
+ if (shift == 7)
+ {
+ builder.Add((byte)b);
+ b = 0;
+ shift = 0;
+ }
+ else
+ {
+ shift++;
+ }
+ }
+
+ if (b != 0)
+ {
+ builder.Add((byte)b);
+ }
+ else
+ {
+ // trim trailing zeros:
+ int lastNonZero = builder.Count - 1;
+ while (builder[lastNonZero] == 0)
+ {
+ lastNonZero--;
+ }
+
+ builder.Clip(lastNonZero + 1);
+ }
+
+ return builder.ToImmutableAndFree();
+ }
+
+ #endregion
+
+ #region State Machines
+
+ private void SerializeAsyncMethodSteppingInfo(AsyncMethodBodyDebugInfo asyncInfo, int moveNextMethodRid)
+ {
+ Debug.Assert(asyncInfo.ResumeOffsets.Length == asyncInfo.YieldOffsets.Length);
+ Debug.Assert(asyncInfo.CatchHandlerOffset >= -1);
+
+ var writer = new BlobWriter();
+
+ writer.WriteUint((uint)((long)asyncInfo.CatchHandlerOffset + 1));
+
+ for (int i = 0; i < asyncInfo.ResumeOffsets.Length; i++)
+ {
+ writer.WriteUint((uint)asyncInfo.YieldOffsets[i]);
+ writer.WriteUint((uint)asyncInfo.ResumeOffsets[i]);
+ writer.WriteCompressedUInt((uint)moveNextMethodRid);
+ }
+
+ _customDebugInformationTable.Add(new CustomDebugInformationRow
+ {
+ Parent = HasCustomDebugInformation(HasCustomDebugInformationTag.MethodDef, moveNextMethodRid),
+ Kind = (uint)_debugHeapsOpt.GetGuidIndex(PortableCustomDebugInfoKinds.AsyncMethodSteppingInformationBlob),
+ Value = _debugHeapsOpt.GetBlobIndex(writer),
+ });
+ }
+
+ private void SerializeStateMachineLocalScopes(IMethodBody methodBody, int methodRowId)
+ {
+ var scopes = methodBody.StateMachineHoistedLocalScopes;
+ if (scopes.IsDefaultOrEmpty)
+ {
+ return;
+ }
+
+ var writer = new BlobWriter();
+
+ foreach (var scope in scopes)
+ {
+ writer.WriteUint((uint)scope.StartOffset);
+ writer.WriteUint((uint)scope.Length);
+ }
+
+ _customDebugInformationTable.Add(new CustomDebugInformationRow
+ {
+ Parent = HasCustomDebugInformation(HasCustomDebugInformationTag.MethodDef, methodRowId),
+ Kind = (uint)_debugHeapsOpt.GetGuidIndex(PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes),
+ Value = _debugHeapsOpt.GetBlobIndex(writer),
+ });
+ }
+
+ #endregion
+
+ #region Sequence Points
+
+ private BlobIdx SerializeSequencePoints(int localSignatureRowId, ImmutableArray sequencePoints, Dictionary documentIndex)
+ {
+ if (sequencePoints.Length == 0)
+ {
+ return default(BlobIdx);
+ }
+
+ var writer = new BlobWriter();
+
+ int previousNonHiddenStartLine = -1;
+ int previousNonHiddenStartColumn = -1;
+
+ uint currentDocumentRowId = GetOrAddDocument(sequencePoints[0].Document, documentIndex);
+
+ // header:
+ writer.WriteCompressedUInt((uint)localSignatureRowId);
+ writer.WriteCompressedUInt(currentDocumentRowId);
+
+ for (int i = 0; i < sequencePoints.Length; i++)
+ {
+ uint documentRowId = GetOrAddDocument(sequencePoints[i].Document, documentIndex);
+ if (documentRowId != currentDocumentRowId)
+ {
+ // document record:
+ writer.WriteCompressedUInt(0);
+ writer.WriteCompressedUInt(documentRowId);
+ currentDocumentRowId = documentRowId;
+ }
+
+ // delta IL offset:
+ if (i > 0)
+ {
+ writer.WriteCompressedUInt((uint)(sequencePoints[i].Offset - sequencePoints[i - 1].Offset));
+ }
+ else
+ {
+ writer.WriteCompressedUInt((uint)sequencePoints[i].Offset);
+ }
+
+ if (sequencePoints[i].IsHidden)
+ {
+ writer.WriteShort(0);
+ continue;
+ }
+
+ // Delta Lines & Columns:
+ SerializeDeltaLinesAndColumns(writer, sequencePoints[i]);
+
+ // delta Start Lines & Columns:
+ if (previousNonHiddenStartLine < 0)
+ {
+ Debug.Assert(previousNonHiddenStartColumn < 0);
+ writer.WriteCompressedUInt((uint)sequencePoints[i].StartLine);
+ writer.WriteCompressedUInt((uint)sequencePoints[i].StartColumn);
+ }
+ else
+ {
+ writer.WriteCompressedSignedInteger(sequencePoints[i].StartLine - previousNonHiddenStartLine);
+ writer.WriteCompressedSignedInteger(sequencePoints[i].StartColumn - previousNonHiddenStartColumn);
+ }
+
+ previousNonHiddenStartLine = sequencePoints[i].StartLine;
+ previousNonHiddenStartColumn = sequencePoints[i].StartColumn;
+ }
+
+ return _debugHeapsOpt.GetBlobIndex(writer);
+ }
+
+ private void SerializeDeltaLinesAndColumns(BlobWriter writer, SequencePoint sequencePoint)
+ {
+ int deltaLines = sequencePoint.EndLine - sequencePoint.StartLine;
+ int deltaColumns = sequencePoint.EndColumn - sequencePoint.StartColumn;
+
+ // only hidden sequence points have zero width
+ Debug.Assert(deltaLines != 0 || deltaColumns != 0 || sequencePoint.IsHidden);
+
+ writer.WriteCompressedUInt((uint)deltaLines);
+
+ if (deltaLines == 0)
+ {
+ writer.WriteCompressedUInt((uint)deltaColumns);
+ }
+ else
+ {
+ writer.WriteCompressedSignedInteger(deltaColumns);
+ }
+ }
+
+ #endregion
+
+ #region Documents
+
+ private uint GetOrAddDocument(DebugSourceDocument document, Dictionary index)
+ {
+ int documentRowId;
+ if (!index.TryGetValue(document, out documentRowId))
+ {
+ documentRowId = _documentTable.Count + 1;
+ index.Add(document, documentRowId);
+
+ var checksumAndAlgorithm = document.ChecksumAndAlgorithm;
+ _documentTable.Add(new DocumentRow
+ {
+ Name = SerializeDocumentName(document.Location),
+ HashAlgorithm = (uint)(checksumAndAlgorithm.Item1.IsDefault ? 0 : _debugHeapsOpt.GetGuidIndex(checksumAndAlgorithm.Item2)),
+ Hash = (checksumAndAlgorithm.Item1.IsDefault) ? new BlobIdx(0) : _debugHeapsOpt.GetBlobIndex(checksumAndAlgorithm.Item1),
+ Language = (uint)_debugHeapsOpt.GetGuidIndex(document.Language),
+ });
+ }
+
+ return (uint)documentRowId;
+ }
+
+ private static readonly char[] Separator1 = { '/' };
+ private static readonly char[] Separator2 = { '\\' };
+
+ private BlobIdx SerializeDocumentName(string name)
+ {
+ Debug.Assert(name != null);
+
+ var writer = new BlobWriter();
+
+ int c1 = Count(name, Separator1[0]);
+ int c2 = Count(name, Separator2[0]);
+ char[] separator = (c1 >= c2) ? Separator1 : Separator2;
+
+ writer.WriteByte((byte)separator[0]);
+
+ // TODO: avoid allocations
+ foreach (var part in name.Split(separator))
+ {
+ BlobIdx partIndex = _debugHeapsOpt.GetBlobIndex(ImmutableArray.Create(s_utf8Encoding.GetBytes(part)));
+ writer.WriteCompressedUInt((uint)_debugHeapsOpt.ResolveBlobIndex(partIndex));
+ }
+
+ return _debugHeapsOpt.GetBlobIndex(writer);
+ }
+
+ private static int Count(string str, char c)
+ {
+ int count = 0;
+ for (int i = 0; i < str.Length; i++)
+ {
+ if (str[i] == c)
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Edit and Continue
+
+ private void SerializeEncMethodDebugInformation(IMethodBody methodBody, int methodRowId)
+ {
+ var encInfo = GetEncMethodDebugInfo(methodBody);
+
+ if (!encInfo.LocalSlots.IsDefaultOrEmpty)
+ {
+ var writer = new BlobWriter();
+
+ encInfo.SerializeLocalSlots(writer);
+
+ _customDebugInformationTable.Add(new CustomDebugInformationRow
+ {
+ Parent = HasCustomDebugInformation(HasCustomDebugInformationTag.MethodDef, methodRowId),
+ Kind = (uint)_debugHeapsOpt.GetGuidIndex(PortableCustomDebugInfoKinds.EncLocalSlotMap),
+ Value = _debugHeapsOpt.GetBlobIndex(writer),
+ });
+ }
+
+ if (!encInfo.Lambdas.IsDefaultOrEmpty)
+ {
+ var writer = new BlobWriter();
+
+ encInfo.SerializeLambdaMap(writer);
+
+ _customDebugInformationTable.Add(new CustomDebugInformationRow
+ {
+ Parent = HasCustomDebugInformation(HasCustomDebugInformationTag.MethodDef, methodRowId),
+ Kind = (uint)_debugHeapsOpt.GetGuidIndex(PortableCustomDebugInfoKinds.EncLambdaAndClosureMap),
+ Value = _debugHeapsOpt.GetBlobIndex(writer),
+ });
+ }
+ }
+
+ #endregion
+
+ #region Table Serialization
+
+ private void SerializeDocumentTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _documentTable)
+ {
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveBlobIndex(row.Name), metadataSizes.BlobIndexSize);
+ writer.WriteReference(row.HashAlgorithm, metadataSizes.GuidIndexSize);
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveBlobIndex(row.Hash), metadataSizes.BlobIndexSize);
+ writer.WriteReference(row.Language, metadataSizes.GuidIndexSize);
+ }
+ }
+
+ private void SerializeMethodBodyTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _methodBodyTable)
+ {
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveBlobIndex(row.SequencePoints), metadataSizes.BlobIndexSize);
+ }
+ }
+
+ private void SerializeLocalScopeTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _localScopeTable)
+ {
+ writer.WriteReference(row.Method, metadataSizes.MethodDefIndexSize);
+ writer.WriteReference(row.ImportScope, metadataSizes.ImportScopeIndexSize);
+ writer.WriteReference(row.VariableList, metadataSizes.LocalVariableIndexSize);
+ writer.WriteReference(row.ConstantList, metadataSizes.LocalConstantIndexSize);
+ writer.WriteUint(row.StartOffset);
+ writer.WriteUint(row.Length);
+ }
+ }
+
+ private void SerializeLocalVariableTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _localVariableTable)
+ {
+ writer.WriteUshort(row.Attributes);
+ writer.WriteUshort(row.Index);
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveStringIndex(row.Name), metadataSizes.StringIndexSize);
+ }
+ }
+
+ private void SerializeLocalConstantTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _localConstantTable)
+ {
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveStringIndex(row.Name), metadataSizes.StringIndexSize);
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveBlobIndex(row.Signature), metadataSizes.BlobIndexSize);
+ }
+ }
+
+ private void SerializeImportScopeTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _importScopeTable)
+ {
+ writer.WriteReference(row.Parent, metadataSizes.ImportScopeIndexSize);
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveBlobIndex(row.Imports), metadataSizes.BlobIndexSize);
+ }
+ }
+
+ private void SerializeStateMachineMethodTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ foreach (var row in _stateMachineMethodTable)
+ {
+ writer.WriteReference(row.MoveNextMethod, metadataSizes.MethodDefIndexSize);
+ writer.WriteReference(row.KickoffMethod, metadataSizes.MethodDefIndexSize);
+ }
+ }
+
+ private void SerializeCustomDebugInformationTable(BlobWriter writer, MetadataSizes metadataSizes)
+ {
+ // sort by Parent, Kind
+ _customDebugInformationTable.Sort(CustomDebugInformationRowComparer.Instance);
+
+ foreach (var row in _customDebugInformationTable)
+ {
+ writer.WriteReference(row.Parent, metadataSizes.HasCustomDebugInformationSize);
+ writer.WriteReference(row.Kind, metadataSizes.GuidIndexSize);
+ writer.WriteReference((uint)_debugHeapsOpt.ResolveBlobIndex(row.Value), metadataSizes.BlobIndexSize);
+ }
+ }
+
+ private class CustomDebugInformationRowComparer : Comparer
+ {
+ public static readonly CustomDebugInformationRowComparer Instance = new CustomDebugInformationRowComparer();
+
+ public override int Compare(CustomDebugInformationRow x, CustomDebugInformationRow y)
+ {
+ int result = (int)x.Parent - (int)y.Parent;
+ return (result != 0) ? result : (int)x.Kind - (int)y.Kind;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs
index bac95bd5ec178..80306d3cceea7 100644
--- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs
+++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs
@@ -23,7 +23,7 @@
namespace Microsoft.Cci
{
- internal abstract class MetadataWriter
+ internal abstract partial class MetadataWriter
{
private static readonly Encoding s_utf8Encoding = Encoding.UTF8;
@@ -97,6 +97,7 @@ protected MetadataWriter(
_cancellationToken = cancellationToken;
this.heaps = heaps;
+ _debugHeapsOpt = debugHeapsOpt;
_smallMethodBodies = new Dictionary(ByteSequenceComparer.Instance);
}
@@ -419,6 +420,14 @@ private bool IsMinimalDelta
private ReferenceIndexer _referenceVisitor;
protected readonly MetadataHeapsBuilder heaps;
+
+ // A heap builder distinct from heaps if we are emitting debug information into a separate Portable PDB stream.
+ // Shared heap builder (reference equals heaps) if we are embedding Portable PDB into the metadata stream.
+ // Null otherwise.
+ private readonly MetadataHeapsBuilder _debugHeapsOpt;
+
+ private bool EmitStandaloneDebugMetadata => _debugHeapsOpt != null && heaps != _debugHeapsOpt;
+
private readonly Dictionary _customAttributeSignatureIndex = new Dictionary();
private readonly Dictionary _typeSpecSignatureIndex = new Dictionary();
private readonly Dictionary _exportedTypeIndex;
@@ -487,6 +496,15 @@ private ImmutableArray GetRowCounts()
rowCounts[(int)TableIndex.TypeRef] = _typeRefTable.Count;
rowCounts[(int)TableIndex.TypeSpec] = _typeSpecTable.Count;
+ rowCounts[(int)TableIndex.Document] = _documentTable.Count;
+ rowCounts[(int)TableIndex.MethodBody] = _methodBodyTable.Count;
+ rowCounts[(int)TableIndex.LocalScope] = _localScopeTable.Count;
+ rowCounts[(int)TableIndex.LocalVariable] = _localVariableTable.Count;
+ rowCounts[(int)TableIndex.LocalConstant] = _localConstantTable.Count;
+ rowCounts[(int)TableIndex.StateMachineMethod] = _stateMachineMethodTable.Count;
+ rowCounts[(int)TableIndex.ImportScope] = _importScopeTable.Count;
+ rowCounts[(int)TableIndex.CustomDebugInformation] = _customDebugInformationTable.Count;
+
return ImmutableArray.CreateRange(rowCounts);
}
@@ -1951,7 +1969,7 @@ private void SerializeMetadataHeader(BlobWriter writer, MetadataSizes metadataSi
// metadata version length
writer.WriteUint(MetadataSizes.MetadataVersionPaddedLength);
- string targetRuntimeVersion = module.Properties.TargetRuntimeVersion;
+ string targetRuntimeVersion = metadataSizes.IsStandaloneDebugMetadata ? "PDB v0.1" : module.Properties.TargetRuntimeVersion;
int n = Math.Min(MetadataSizes.MetadataVersionPaddedLength, targetRuntimeVersion.Length);
for (int i = 0; i < n; i++)
@@ -1968,7 +1986,7 @@ private void SerializeMetadataHeader(BlobWriter writer, MetadataSizes metadataSi
writer.WriteUshort(0);
// number of streams
- writer.WriteUshort((ushort)(5 + (metadataSizes.IsMinimalDelta ? 1 : 0)));
+ writer.WriteUshort((ushort)(5 + (metadataSizes.IsMinimalDelta ? 1 : 0) + (metadataSizes.IsStandaloneDebugMetadata ? 1 : 0)));
// stream headers
int offsetFromStartOfMetadata = metadataSizes.MetadataHeaderSize;
@@ -1989,6 +2007,11 @@ private void SerializeMetadataHeader(BlobWriter writer, MetadataSizes metadataSi
SerializeStreamHeader(ref offsetFromStartOfMetadata, 0, "#JTD", writer);
}
+ if (metadataSizes.IsStandaloneDebugMetadata)
+ {
+ SerializeStreamHeader(ref offsetFromStartOfMetadata, metadataSizes.StandalonePdbStreamSize, "#Pdb", writer);
+ }
+
int endOffset = writer.Position;
Debug.Assert(endOffset - startOffset == metadataSizes.MetadataHeaderSize);
}
@@ -2073,6 +2096,11 @@ public void SerializeMetadataAndIL(
// Extract information from object model into tables, indices and streams
CreateIndices();
+ if (_debugHeapsOpt != null)
+ {
+ DefineModuleImportScope();
+ }
+
int[] methodBodyRvas = SerializeMethodBodies(ilWriter, nativePdbWriterOpt);
_cancellationToken.ThrowIfCancellationRequested();
@@ -2110,13 +2138,38 @@ public void SerializeMetadataAndIL(
mappedFieldDataSize: mappedFieldDataWriter.Length,
resourceDataSize: managedResourceDataWriter.Length,
strongNameSignatureSize: CalculateStrongNameSignatureSize(module),
- isMinimalDelta: IsMinimalDelta);
+ isMinimalDelta: IsMinimalDelta,
+ emitStandaloneDebugMetadata: EmitStandaloneDebugMetadata,
+ isStandaloneDebugMetadata: false);
int mappedFieldDataStreamRva = calculateMappedFieldDataStreamRva(metadataSizes);
int guidHeapStartOffset;
SerializeMetadata(metadataWriter, metadataSizes, methodBodyStreamRva, mappedFieldDataStreamRva, entryPointToken, out guidHeapStartOffset);
moduleVersionIdOffsetInMetadataStream = GetModuleVersionGuidOffsetInMetadataStream(guidHeapStartOffset);
+
+ if (!EmitStandaloneDebugMetadata)
+ {
+ return;
+ }
+
+ // serialize debug metadata stream
+
+ Debug.Assert(_debugHeapsOpt != null);
+ _debugHeapsOpt.Complete();
+
+ var debugMetadataSizes = new MetadataSizes(
+ rowCounts: tableRowCounts,
+ heapSizes: _debugHeapsOpt.GetHeapSizes(),
+ ilStreamSize: 0,
+ mappedFieldDataSize: 0,
+ resourceDataSize: 0,
+ strongNameSignatureSize: 0,
+ isMinimalDelta: IsMinimalDelta,
+ emitStandaloneDebugMetadata: true,
+ isStandaloneDebugMetadata: true);
+
+ SerializeMetadata(debugMetadataWriterOpt, debugMetadataSizes, 0, 0, entryPointToken, out guidHeapStartOffset);
}
private static int CalculateStrongNameSignatureSize(IModule module)
@@ -2161,7 +2214,13 @@ private void SerializeMetadata(
SerializeMetadataTables(metadataWriter, metadataSizes, methodBodyStreamRva, mappedFieldDataStreamRva);
// #Strings, #US, #Guid and #Blob streams:
- heaps.WriteTo(metadataWriter, out guidHeapStartOffset);
+ (metadataSizes.IsStandaloneDebugMetadata ? _debugHeapsOpt : heaps).WriteTo(metadataWriter, out guidHeapStartOffset);
+
+ // #Pdb stream
+ if (metadataSizes.IsStandaloneDebugMetadata)
+ {
+ SerializeStandalonePdbStream(metadataWriter, metadataSizes, entryPointToken);
+ }
int metadataSize = metadataWriter.Position;
@@ -2185,8 +2244,8 @@ private int GetModuleVersionGuidOffsetInMetadataStream(int guidHeapOffsetInMetad
private void SerializeMetadataTables(
BlobWriter writer,
- MetadataSizes metadataSizes,
- int methodBodyStreamRva,
+ MetadataSizes metadataSizes,
+ int methodBodyStreamRva,
int mappedFieldDataStreamRva)
{
int startPosition = writer.Position;
@@ -2373,6 +2432,47 @@ private void SerializeMetadataTables(
this.SerializeGenericParamConstraintTable(writer, metadataSizes);
}
+ // debug tables
+ if (metadataSizes.IsPresent(TableIndex.Document))
+ {
+ this.SerializeDocumentTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.MethodBody))
+ {
+ this.SerializeMethodBodyTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.LocalScope))
+ {
+ this.SerializeLocalScopeTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.LocalVariable))
+ {
+ this.SerializeLocalVariableTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.LocalConstant))
+ {
+ this.SerializeLocalConstantTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.ImportScope))
+ {
+ this.SerializeImportScopeTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.StateMachineMethod))
+ {
+ this.SerializeStateMachineMethodTable(writer, metadataSizes);
+ }
+
+ if (metadataSizes.IsPresent(TableIndex.CustomDebugInformation))
+ {
+ this.SerializeCustomDebugInformationTable(writer, metadataSizes);
+ }
+
writer.WriteByte(0);
writer.Align(4);
@@ -3680,6 +3780,19 @@ private void SerializeTablesHeader(BlobWriter writer, MetadataSizes metadataSize
Debug.Assert(metadataSizes.CalculateTableStreamHeaderSize() == endPosition - startPosition);
}
+ private static void SerializeStandalonePdbStream(BlobWriter writer, MetadataSizes metadataSizes, int entryPointToken)
+ {
+ int startPosition = writer.Position;
+
+ writer.WriteUint((uint)entryPointToken);
+
+ writer.WriteUlong(metadataSizes.ExternalTablesMask);
+ SerializeRowCounts(writer, metadataSizes.RowCounts, metadataSizes.ExternalTablesMask);
+
+ int endPosition = writer.Position;
+ Debug.Assert(metadataSizes.CalculateStandalonePdbStreamSize() == endPosition - startPosition);
+ }
+
private static void SerializeRowCounts(BlobWriter writer, ImmutableArray rowCounts, ulong includeTables)
{
for (int i = 0; i < rowCounts.Length; i++)
@@ -4126,6 +4239,11 @@ private int[] SerializeMethodBodies(BlobWriter writer, PdbWriter pdbWriterOpt)
localSignatureRid = 0;
}
+ if (_debugHeapsOpt != null)
+ {
+ SerializeMethodDebugInfo(body, methodRid, localSignatureRid);
+ }
+
rvas[methodRid - 1] = rva;
methodRid++;
diff --git a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs
index ffc36e45ac26a..c412a0ef2d960 100644
--- a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs
+++ b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs
@@ -159,6 +159,27 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream,
{
nativePdbContentId = default(ContentId);
}
+
+ // write to Portable PDB stream:
+ ContentId portablePdbContentId;
+ Stream portablePdbStream = getPortablePdbStreamOpt?.Invoke();
+ if (portablePdbStream != null)
+ {
+ debugMetadataWriterOpt.WriteTo(portablePdbStream);
+
+ if (_deterministic)
+ {
+ portablePdbContentId = ContentId.FromHash(CryptographicHashProvider.ComputeSha1(portablePdbStream));
+ }
+ else
+ {
+ portablePdbContentId = new ContentId(Guid.NewGuid().ToByteArray(), BitConverter.GetBytes(_timeStamp));
+ }
+ }
+ else
+ {
+ portablePdbContentId = default(ContentId);
+ }
// Only the size of the fixed part of the debug table goes here.
DirectoryEntry debugDirectory = default(DirectoryEntry);
@@ -209,6 +230,7 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream,
managedResourceWriter,
metadataSizes,
nativePdbContentId,
+ portablePdbContentId,
out metadataPosition);
var resourceSection = sectionHeaders.FirstOrDefault(s => s.Name == ResourceSectionName);
@@ -1148,7 +1170,8 @@ private void WriteTextSection(
BlobWriter mappedFieldDataWriter,
BlobWriter managedResourceWriter,
MetadataSizes metadataSizes,
- ContentId pdbContentId,
+ ContentId nativePdbContentId,
+ ContentId portablePdbContentId,
out long metadataPosition)
{
// TODO: zero out all bytes:
@@ -1180,7 +1203,7 @@ private void WriteTextSection(
if (EmitPdb)
{
- WriteDebugTable(peStream, textSection, pdbContentId, metadataSizes);
+ WriteDebugTable(peStream, textSection, nativePdbContentId, portablePdbContentId, metadataSizes);
}
if (_properties.RequiresStartupStub)
@@ -1317,19 +1340,27 @@ private static void WriteSpaceForHash(Stream peStream, int strongNameSignatureSi
}
}
- private void WriteDebugTable(Stream peStream, SectionHeader textSection, ContentId nativePdbContentId, MetadataSizes metadataSizes)
+ private void WriteDebugTable(Stream peStream, SectionHeader textSection, ContentId nativePdbContentId, ContentId portablePdbContentId, MetadataSizes metadataSizes)
{
+ Debug.Assert(nativePdbContentId.IsDefault ^ portablePdbContentId.IsDefault);
+
var writer = new BlobWriter();
// characteristics:
writer.WriteUint(0);
- // PDB stamp
- writer.WriteBytes(nativePdbContentId.Stamp);
-
- // version
- writer.WriteUint(0);
-
+ // PDB stamp & version
+ if (portablePdbContentId.IsDefault)
+ {
+ writer.WriteBytes(nativePdbContentId.Stamp);
+ writer.WriteUint(0);
+ }
+ else
+ {
+ writer.WriteBytes(portablePdbContentId.Stamp);
+ writer.WriteUint('P' << 24 | 'M' << 16 | 0x00 << 8 | 0x01);
+ }
+
// type:
const int ImageDebugTypeCodeView = 2;
writer.WriteUint(ImageDebugTypeCodeView);
@@ -1351,7 +1382,7 @@ private void WriteDebugTable(Stream peStream, SectionHeader textSection, Content
writer.WriteByte((byte)'S');
// PDB id:
- writer.WriteBytes(nativePdbContentId.Guid);
+ writer.WriteBytes(nativePdbContentId.Guid ?? portablePdbContentId.Guid);
// age
writer.WriteUint(PdbWriter.Age);
diff --git a/src/Compilers/Core/Portable/PublicAPI.txt b/src/Compilers/Core/Portable/PublicAPI.txt
index c8dc9f09f8a52..ddc974508547d 100644
--- a/src/Compilers/Core/Portable/PublicAPI.txt
+++ b/src/Compilers/Core/Portable/PublicAPI.txt
@@ -371,7 +371,9 @@ Microsoft.CodeAnalysis.DocumentationMode.Parse = 1 -> Microsoft.CodeAnalysis.Doc
Microsoft.CodeAnalysis.DocumentationProvider
Microsoft.CodeAnalysis.DocumentationProvider.DocumentationProvider() -> void
Microsoft.CodeAnalysis.Emit.DebugInformationFormat
+Microsoft.CodeAnalysis.Emit.DebugInformationFormat.Embedded = 3 -> Microsoft.CodeAnalysis.Emit.DebugInformationFormat
Microsoft.CodeAnalysis.Emit.DebugInformationFormat.Pdb = 1 -> Microsoft.CodeAnalysis.Emit.DebugInformationFormat
+Microsoft.CodeAnalysis.Emit.DebugInformationFormat.PortablePdb = 2 -> Microsoft.CodeAnalysis.Emit.DebugInformationFormat
Microsoft.CodeAnalysis.Emit.EditAndContinueMethodDebugInformation
Microsoft.CodeAnalysis.Emit.EmitBaseline
Microsoft.CodeAnalysis.Emit.EmitBaseline.OriginalMetadata.get -> Microsoft.CodeAnalysis.ModuleMetadata
diff --git a/src/Compilers/VisualBasic/Portable/CommandLine/CommandLineParser.vb b/src/Compilers/VisualBasic/Portable/CommandLine/CommandLineParser.vb
index 46a9ad4091b03..b13320dc58270 100644
--- a/src/Compilers/VisualBasic/Portable/CommandLine/CommandLineParser.vb
+++ b/src/Compilers/VisualBasic/Portable/CommandLine/CommandLineParser.vb
@@ -88,7 +88,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim optimize As Boolean = False
Dim checkOverflow As Boolean = True
Dim concurrentBuild As Boolean = True
- Dim emitPdb As Boolean = False
+ Dim emitPdb As Boolean
Dim noStdLib As Boolean = False
Dim utf8output As Boolean = False
Dim outputFileName As String = Nothing
@@ -576,7 +576,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
If String.IsNullOrEmpty(value) Then
AddDiagnostic(diagnostics, ERRID.ERR_ArgumentRequired, "debug", ":pdbonly|full")
ElseIf Not String.Equals(value, "full", StringComparison.OrdinalIgnoreCase) AndAlso
- Not String.Equals(value, "pdbonly", StringComparison.OrdinalIgnoreCase) Then
+ Not String.Equals(value, "pdbonly", StringComparison.OrdinalIgnoreCase) Then
AddDiagnostic(diagnostics, ERRID.ERR_InvalidSwitchValue, "debug", value)
End If
End If
@@ -1122,59 +1122,73 @@ lVbRuntimePlus:
keyFileSearchPaths.Add(outputDirectory)
End If
+ Dim parsedFeatures = CompilerOptionParseUtilities.ParseFeatures(features)
+
+ Dim debugInfoFormat = DebugInformationFormat.Pdb
+ Dim pdbFormatStr As String = Nothing
+ If emitPdb AndAlso parsedFeatures.TryGetValue("pdb", pdbFormatStr) Then
+ If StringComparer.Ordinal.Equals(pdbFormatStr, "portable") Then
+ debugInfoFormat = DebugInformationFormat.PortablePdb
+ ElseIf StringComparer.Ordinal.Equals(pdbFormatStr, "embedded") Then
+ debugInfoFormat = DebugInformationFormat.Embedded
+ emitPdb = False
+ End If
+ End If
+
Dim compilationName As String = Nothing
GetCompilationAndModuleNames(diagnostics, outputKind, sourceFiles, moduleAssemblyName, outputFileName, moduleName, compilationName)
If Not IsInteractive AndAlso
- Not hasSourceFiles AndAlso
- Not managedResources.IsEmpty() AndAlso
- outputFileName = Nothing AndAlso
- Not flattenedArgs.IsEmpty() Then
+ Not hasSourceFiles AndAlso
+ Not managedResources.IsEmpty() AndAlso
+ outputFileName = Nothing AndAlso
+ Not flattenedArgs.IsEmpty() Then
+
AddDiagnostic(diagnostics, ERRID.ERR_NoSourcesOut)
End If
Dim parseOptions = New VisualBasicParseOptions(
- languageVersion:=languageVersion,
- documentationMode:=If(parseDocumentationComments, DocumentationMode.Diagnose, DocumentationMode.None),
- kind:=SourceCodeKind.Regular,
- preprocessorSymbols:=AddPredefinedPreprocessorSymbols(outputKind, defines.AsImmutableOrEmpty()),
- features:=CompilerOptionParseUtilities.ParseFeatures(features))
+ languageVersion:=languageVersion,
+ documentationMode:=If(parseDocumentationComments, DocumentationMode.Diagnose, DocumentationMode.None),
+ kind:=SourceCodeKind.Regular,
+ preprocessorSymbols:=AddPredefinedPreprocessorSymbols(outputKind, defines.AsImmutableOrEmpty()),
+ features:=parsedFeatures)
Dim scriptParseOptions = parseOptions.WithKind(SourceCodeKind.Script)
Dim options = New VisualBasicCompilationOptions(
- outputKind:=outputKind,
- moduleName:=moduleName,
- mainTypeName:=mainTypeName,
- scriptClassName:=WellKnownMemberNames.DefaultScriptClassName,
- globalImports:=globalImports,
- rootNamespace:=rootNamespace,
- optionStrict:=optionStrict,
- optionInfer:=optionInfer,
- optionExplicit:=optionExplicit,
- optionCompareText:=optionCompareText,
- embedVbCoreRuntime:=embedVbCoreRuntime,
- checkOverflow:=checkOverflow,
- concurrentBuild:=concurrentBuild,
- cryptoKeyContainer:=keyContainerSetting,
- cryptoKeyFile:=keyFileSetting,
- delaySign:=delaySignSetting,
- platform:=platform,
- generalDiagnosticOption:=generalDiagnosticOption,
- specificDiagnosticOptions:=specificDiagnosticOptions,
- optimizationLevel:=If(optimize, OptimizationLevel.Release, OptimizationLevel.Debug),
- parseOptions:=parseOptions)
+ outputKind:=outputKind,
+ moduleName:=moduleName,
+ mainTypeName:=mainTypeName,
+ scriptClassName:=WellKnownMemberNames.DefaultScriptClassName,
+ globalImports:=globalImports,
+ rootNamespace:=rootNamespace,
+ optionStrict:=optionStrict,
+ optionInfer:=optionInfer,
+ optionExplicit:=optionExplicit,
+ optionCompareText:=optionCompareText,
+ embedVbCoreRuntime:=embedVbCoreRuntime,
+ checkOverflow:=checkOverflow,
+ concurrentBuild:=concurrentBuild,
+ cryptoKeyContainer:=keyContainerSetting,
+ cryptoKeyFile:=keyFileSetting,
+ delaySign:=delaySignSetting,
+ platform:=platform,
+ generalDiagnosticOption:=generalDiagnosticOption,
+ specificDiagnosticOptions:=specificDiagnosticOptions,
+ optimizationLevel:=If(optimize, OptimizationLevel.Release, OptimizationLevel.Debug),
+ parseOptions:=parseOptions)
Dim emitOptions = New EmitOptions(
- metadataOnly:=False,
- debugInformationFormat:=DebugInformationFormat.Pdb,
- pdbFilePath:=Nothing, ' to be determined later
- outputNameOverride:=Nothing, ' to be determined later
- fileAlignment:=fileAlignment,
- baseAddress:=baseAddress,
- highEntropyVirtualAddressSpace:=highEntropyVA,
- subsystemVersion:=ssVersion,
- runtimeMetadataVersion:=Nothing)
+ metadataOnly:=False,
+ debugInformationFormat:=debugInfoFormat,
+ pdbFilePath:=Nothing, ' to be determined later
+ outputNameOverride:=Nothing, ' to be determined later
+ fileAlignment:=fileAlignment,
+ baseAddress:=baseAddress,
+ highEntropyVirtualAddressSpace:=highEntropyVA,
+ subsystemVersion:=ssVersion,
+ runtimeMetadataVersion:=Nothing)
' add option incompatibility errors if any
diagnostics.AddRange(options.Errors)
@@ -1185,43 +1199,43 @@ lVbRuntimePlus:
End If
Return New VisualBasicCommandLineArguments With
- {
- .IsInteractive = IsInteractive,
- .BaseDirectory = baseDirectory,
- .Errors = diagnostics.AsImmutable(),
- .Utf8Output = utf8output,
- .CompilationName = compilationName,
- .OutputFileName = outputFileName,
- .OutputDirectory = outputDirectory,
- .DocumentationPath = documentationPath,
- .ErrorLogPath = errorLogPath,
- .SourceFiles = sourceFiles.AsImmutable(),
- .Encoding = codepage,
- .ChecksumAlgorithm = checksumAlgorithm,
- .MetadataReferences = metadataReferences.AsImmutable(),
- .AnalyzerReferences = analyzers.AsImmutable(),
- .AdditionalFiles = additionalFiles.AsImmutable(),
- .ReferencePaths = searchPaths,
- .KeyFileSearchPaths = keyFileSearchPaths.AsImmutable(),
- .Win32ResourceFile = win32ResourceFile,
- .Win32Icon = win32IconFile,
- .Win32Manifest = win32ManifestFile,
- .NoWin32Manifest = noWin32Manifest,
- .DisplayLogo = displayLogo,
- .DisplayHelp = displayHelp,
- .ManifestResources = managedResources.AsImmutable(),
- .CompilationOptions = options,
- .ParseOptions = If(IsInteractive, scriptParseOptions, parseOptions),
- .EmitOptions = emitOptions,
- .ScriptArguments = scriptArgs.AsImmutableOrEmpty(),
- .TouchedFilesPath = touchedFilesPath,
- .OutputLevel = outputLevel,
- .EmitPdb = emitPdb,
- .DefaultCoreLibraryReference = defaultCoreLibraryReference,
- .PreferredUILang = preferredUILang,
- .SqmSessionGuid = sqmsessionguid,
- .ReportAnalyzer = reportAnalyzer
- }
+ {
+ .IsInteractive = IsInteractive,
+ .BaseDirectory = baseDirectory,
+ .Errors = diagnostics.AsImmutable(),
+ .Utf8Output = utf8output,
+ .CompilationName = compilationName,
+ .OutputFileName = outputFileName,
+ .OutputDirectory = outputDirectory,
+ .DocumentationPath = documentationPath,
+ .ErrorLogPath = errorLogPath,
+ .SourceFiles = sourceFiles.AsImmutable(),
+ .Encoding = codepage,
+ .ChecksumAlgorithm = checksumAlgorithm,
+ .MetadataReferences = metadataReferences.AsImmutable(),
+ .AnalyzerReferences = analyzers.AsImmutable(),
+ .AdditionalFiles = additionalFiles.AsImmutable(),
+ .ReferencePaths = searchPaths,
+ .KeyFileSearchPaths = keyFileSearchPaths.AsImmutable(),
+ .Win32ResourceFile = win32ResourceFile,
+ .Win32Icon = win32IconFile,
+ .Win32Manifest = win32ManifestFile,
+ .NoWin32Manifest = noWin32Manifest,
+ .DisplayLogo = displayLogo,
+ .DisplayHelp = displayHelp,
+ .ManifestResources = managedResources.AsImmutable(),
+ .CompilationOptions = options,
+ .ParseOptions = If(IsInteractive, scriptParseOptions, parseOptions),
+ .EmitOptions = emitOptions,
+ .ScriptArguments = scriptArgs.AsImmutableOrEmpty(),
+ .TouchedFilesPath = touchedFilesPath,
+ .OutputLevel = outputLevel,
+ .EmitPdb = emitPdb,
+ .DefaultCoreLibraryReference = defaultCoreLibraryReference,
+ .PreferredUILang = preferredUILang,
+ .SqmSessionGuid = sqmsessionguid,
+ .ReportAnalyzer = reportAnalyzer
+ }
End Function
Private Function LoadCoreLibraryReference(sdkPaths As List(Of String), baseDirectory As String, sdkDirectory As String) As CommandLineReference?
diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb
index 164040570ebc4..eba62084d2504 100644
--- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb
+++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb
@@ -2201,15 +2201,23 @@ a.vb
parsedArgs = DefaultParse({"/debug:pdbonly", "/debug:full", "a.vb"}, _baseDirectory)
parsedArgs.Errors.Verify()
+ Assert.True(parsedArgs.EmitPdb)
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat)
parsedArgs = DefaultParse({"/debug:pdbonly", "/debug-", "a.vb"}, _baseDirectory)
parsedArgs.Errors.Verify()
+ Assert.False(parsedArgs.EmitPdb)
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat)
parsedArgs = DefaultParse({"/debug:pdbonly", "/debug-", "/debug", "a.vb"}, _baseDirectory)
parsedArgs.Errors.Verify()
+ Assert.True(parsedArgs.EmitPdb)
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat)
parsedArgs = DefaultParse({"/debug:pdbonly", "/debug-", "/debug+", "a.vb"}, _baseDirectory)
parsedArgs.Errors.Verify()
+ Assert.True(parsedArgs.EmitPdb)
+ Assert.Equal(DebugInformationFormat.Pdb, parsedArgs.EmitOptions.DebugInformationFormat)
parsedArgs = DefaultParse({"/debug:", "a.vb"}, _baseDirectory)
parsedArgs.Errors.Verify(Diagnostic(ERRID.ERR_ArgumentRequired).WithArguments("debug", ":pdbonly|full"))
diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBExternalSourceDirectiveTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBExternalSourceDirectiveTests.vb
index 8e8f3d6168261..4bade82863d4c 100644
--- a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBExternalSourceDirectiveTests.vb
+++ b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBExternalSourceDirectiveTests.vb
@@ -1,5 +1,6 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+Imports Microsoft.CodeAnalysis.Emit
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Roslyn.Test.Utilities
@@ -812,7 +813,7 @@ End Module
-)
+, DebugInformationFormat.Pdb)
End Sub
diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb
index 1374db90372b7..dcdfbf3ffe85d 100644
--- a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb
+++ b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBTests.vb
@@ -4053,14 +4053,14 @@ End Class
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/Compilers/VisualBasic/Test/Semantic/Compilation/MyTemplateTests.vb b/src/Compilers/VisualBasic/Test/Semantic/Compilation/MyTemplateTests.vb
index 89eabcc98f59f..8a4615eb25bb6 100644
--- a/src/Compilers/VisualBasic/Test/Semantic/Compilation/MyTemplateTests.vb
+++ b/src/Compilers/VisualBasic/Test/Semantic/Compilation/MyTemplateTests.vb
@@ -215,7 +215,6 @@ End Class
compilation.VerifyDiagnostics()
CompileAndVerify(compilation, expectedOutput:="HelloWinform")
-
End Sub
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.vcxproj b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.vcxproj
new file mode 100644
index 0000000000000..a9a1d9c92dde8
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.vcxproj
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ Debug
+ AnyCPU
+ {690CACA9-9F32-47DA-B61D-55231257CBA3}
+ SAK
+ SAK
+ SAK
+ SAK
+ Win32Proj
+ NativeClientTests
+
+
+
+ DynamicLibrary
+ true
+ Unicode
+ false
+
+
+ DynamicLibrary
+ false
+ true
+ Unicode
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ Microsoft.DiaSymReader.PortablePdb.Native.UnitTests
+
+
+ false
+ Microsoft.DiaSymReader.PortablePdb.Native.UnitTests
+
+
+
+ NotUsing
+ Level3
+ Disabled
+ WIN32;_DEBUG;%(PreprocessorDefinitions)
+ true
+ $(IntermediateOutputPath);$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ true
+ MultiThreadedDebug
+ ProgramDatabase
+
+
+ Windows
+ true
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+ /ignore:4099 %(AdditionalOptions)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ WIN32;NDEBUG;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+
+
+ Windows
+ true
+ true
+ true
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+ /ignore:4099 %(AdditionalOptions)
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.vcxproj.filters b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.vcxproj.filters
new file mode 100644
index 0000000000000..706a0546522be
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.vcxproj.filters
@@ -0,0 +1,28 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+
+
+
+
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/SymReaderTests.cpp b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/SymReaderTests.cpp
new file mode 100644
index 0000000000000..525209a8de611
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.NativeTests/SymReaderTests.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+#if TODO
+#pragma warning( push )
+#pragma warning( disable: 4499 )
+#include "CppUnitTest.h"
+#pragma warning (pop)
+
+#include
+#include
+#include
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+namespace Microsoft_DiaSymReader_PortablePdb_UnitTests
+{
+ // {E4B18DEF-3B78-46AE-8F50-E67E421BDF70}
+ static const GUID CLSID_Factory = { 0xE4B18DEF, 0x3B78, 0x46AE, { 0x8F, 0x50, 0xE6, 0x7E, 0x42, 0x1B, 0xDF, 0x70 } };
+
+ // {AA544D42-28CB-11d3-BD22-0000F80849BD}
+ static const GUID IID_ISymUnmanagedBinder = { 0xAA544D42, 0x28CB, 0x11d3, { 0xBD, 0x22, 0x00, 0x00, 0xF8, 0x08, 0x49, 0xBD } };
+
+ // To run these test from command line
+ // vstest.console.exe Microsoft.DiaSymReader.PortablePdb.Native.UnitTests.dll
+ TEST_CLASS(SymReaderTests)
+ {
+ public:
+ TEST_METHOD(Instantiation)
+ {
+ HRESULT hr;
+
+ hr = CoInitialize(nullptr);
+ Assert::IsTrue(hr == S_OK || hr == S_FALSE, L"CoInitialize");
+
+ LPVOID factory;
+ hr = CoCreateInstance(CLSID_Factory, nullptr, CLSCTX_INPROC_SERVER, IID_ISymUnmanagedBinder, &factory);
+ Assert::AreEqual(S_OK, hr, L"CoCreateInstance");
+
+ Assert::AreEqual(1, 1);
+ }
+ };
+
+}
+#endif
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Microsoft.DiaSymReader.PortablePdb.UnitTests.csproj b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Microsoft.DiaSymReader.PortablePdb.UnitTests.csproj
new file mode 100644
index 0000000000000..a9c02d661b850
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Microsoft.DiaSymReader.PortablePdb.UnitTests.csproj
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+ true
+ Debug
+ AnyCPU
+ {DEB3D675-5A3C-46DA-8945-F2EFAB135EA0}
+ Library
+ Microsoft.DiaSymReader.PortablePdb.UnitTests
+ Microsoft.DiaSymReader.PortablePdb.UnitTests
+ true
+ ..\..\..\..\
+ true
+ v4.5
+ Profile7
+ .NETPortable
+ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ False
+ true
+
+
+
+
+
+
+ False
+ ..\..\..\..\packages\System.Collections.Immutable.$(SystemCollectionsImmutableVersion)\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+
+
+ ..\..\..\..\packages\System.Reflection.Metadata.$(SystemReflectionMetadataVersion)\lib\portable-net45+win8\System.Reflection.Metadata.dll
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ {f83343ba-b4ea-451c-b6db-5d645e6171bc}
+ Microsoft.DiaSymReader.PortablePdb
+
+
+ False
+ ..\..\..\packages\xunit.assert.2.1.0-beta2-build2981\lib\portable-net45+dnxcore50+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll
+
+
+ False
+ ..\..\..\packages\xunit.extensibility.core.2.1.0-beta2-build2981\lib\portable-net45+dnxcore50+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll
+
+
+
+
+ PreserveNewest
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.cs
new file mode 100644
index 0000000000000..1b573a36dfcc9
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.cs
@@ -0,0 +1,21 @@
+#line 1 "C:\Async.cs"
+#pragma checksum "C:\Async.cs" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "DBEB2A067B2F0E0D678A002C587A2806056C3DCE"
+
+using System.Threading.Tasks;
+
+public class C
+{
+ public async Task M1()
+ {
+ await Task.FromResult(0);
+ await Task.FromResult(1);
+ await Task.FromResult(2);
+
+ return 1;
+ }
+
+ public async void M2()
+ {
+ await Task.FromResult(0);
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.dll b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.dll
new file mode 100644
index 0000000000000..50523c8be2885
Binary files /dev/null and b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.dll differ
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.pdbx b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.pdbx
new file mode 100644
index 0000000000000..dae328103ece3
Binary files /dev/null and b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Async.pdbx differ
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.cs
new file mode 100644
index 0000000000000..ffe85cbd5b690
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.cs
@@ -0,0 +1,34 @@
+#line 1 "C:\Documents.cs"
+#pragma checksum "C:\Documents.cs" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "DBEB2A067B2F0E0D678A002C587A2806056C3DCE"
+using System;
+
+class C
+{
+ public void M()
+ {
+#line 10 "C:\a\b\c\d\1.cs"
+ Console.WriteLine(1);
+#line 20 "C:\a\b\c\D\2.cs"
+ Console.WriteLine(2);
+#line 30 "C:\a\b\C\d\3.cs"
+ Console.WriteLine(3);
+#line 40
+ Console.WriteLine(4);
+#line hidden
+ Console.WriteLine();
+#line 50 "C:\a\b\c\d\x.cs"
+ Console.WriteLine(5);
+#line 60 "C:\A\b\c\x.cs"
+ Console.WriteLine(5);
+#line 70 "C:\a\b\x.cs"
+ Console.WriteLine(5);
+#line 80 "C:\a\B\3.cs"
+ Console.WriteLine(3);
+#line 90 "C:\a\B\c/4.cs"
+ Console.WriteLine(4);
+#line 100 "C:\*\5.cs"
+ Console.WriteLine(5);
+#line 110 ":6.cs"
+ Console.WriteLine(6);
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.dll b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.dll
new file mode 100644
index 0000000000000..ae6dee54c4800
Binary files /dev/null and b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.dll differ
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.pdbx b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.pdbx
new file mode 100644
index 0000000000000..153612612271c
Binary files /dev/null and b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Documents.pdbx differ
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.cs
new file mode 100644
index 0000000000000..4ac1b858884cd
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.cs
@@ -0,0 +1,75 @@
+#line 1 "C:\Scopes.cs"
+#pragma checksum "C:\Scopes.cs" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "DBEB2A067B2F0E0D678A002C587A2806056C3DCE"
+#pragma warning disable 219 // unused const
+
+using System;
+using System.Collections.Generic;
+
+class X { }
+
+public class C
+{
+ enum EnumI1 : sbyte { A = 1 }
+ enum EnumU1 : byte { A = 2 }
+ enum EnumI2 : short { A = 3 }
+ enum EnumU2 : ushort { A = 4 }
+ enum EnumI4 : int { A = 5 }
+ enum EnumU4 : uint { A = 6 }
+ enum EnumI8 : long { A = 7 }
+ enum EnumU8 : ulong { A = 8 }
+
+ public static void F()
+ {
+ const bool B = false;
+ const char C = '\0';
+ const sbyte I1 = 1;
+ const byte U1 = 2;
+ const short I2 = 3;
+ const ushort U2 = 4;
+ const int I4 = 5;
+ const uint U4 = 6;
+ const long I8 = 7;
+ const ulong U8 = 8;
+ const float R4 = (float)9.1;
+ const double R8 = 10.2;
+
+ const C.EnumI1 EI1 = C.EnumI1.A;
+ const C.EnumU1 EU1 = C.EnumU1.A;
+ const C.EnumI2 EI2 = C.EnumI2.A;
+ const C.EnumU2 EU2 = C.EnumU2.A;
+ const C.EnumI4 EI4 = C.EnumI4.A;
+ const C.EnumU4 EU4 = C.EnumU4.A;
+ const C.EnumI8 EI8 = C.EnumI8.A;
+ const C.EnumU8 EU8 = C.EnumU8.A;
+
+ const string StrWithNul = "\0";
+ const string EmptyStr = "";
+ const string NullStr = null;
+ const object NullObject = null;
+ const dynamic NullDynamic = null;
+ const X NullTypeDef = null;
+ const Action NullTypeRef = null;
+ const Func>, dynamic, T, List> NullTypeSpec = null;
+
+ const decimal D = 123456.78M;
+ }
+
+ public static void NestedScopes()
+ {
+ int x0 = 0;
+ {
+ const int c1 = 11;
+ int x1 = 1;
+ }
+
+ int y0 = 0;
+ {
+ int y1 = 1;
+ {
+ const string c2 = "c2";
+ const string d2 = "d2";
+ int y2 = 2;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.dll b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.dll
new file mode 100644
index 0000000000000..aa016a71460f4
Binary files /dev/null and b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.dll differ
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.pdbx b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.pdbx
new file mode 100644
index 0000000000000..9ca830855de8c
Binary files /dev/null and b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/Scopes.pdbx differ
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/TestResources.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/TestResources.cs
new file mode 100644
index 0000000000000..302708caa7c38
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/TestResources.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO;
+using System.Reflection;
+
+namespace TestResources
+{
+ internal static class Documents
+ {
+ public static readonly byte[] dll = ResourceHelper.GetResource("Documents.dll");
+ public static readonly byte[] pdb = ResourceHelper.GetResource("Documents.pdbx");
+ }
+
+ internal static class ResourceHelper
+ {
+ public static Stream GetResourceStream(string name)
+ {
+ string fullName = $"{nameof(Microsoft)}.{nameof(Microsoft.DiaSymReader)}.{nameof(Microsoft.DiaSymReader.PortablePdb)}.{nameof(Microsoft.DiaSymReader.PortablePdb.UnitTests)}.Resources." + name;
+ return typeof(ResourceHelper).GetTypeInfo().Assembly.GetManifestResourceStream(fullName);
+ }
+
+ public static byte[] GetResource(string name)
+ {
+ using (var stream = GetResourceStream(name))
+ {
+ var bytes = new byte[stream.Length];
+ using (var memoryStream = new MemoryStream(bytes))
+ {
+ stream.CopyTo(memoryStream);
+ }
+
+ return bytes;
+ }
+ }
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/build.cmd b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/build.cmd
new file mode 100644
index 0000000000000..2dcef694a0daf
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Resources/build.cmd
@@ -0,0 +1,12 @@
+csc /target:library /debug:portable /optimize- /features:deterministic Scopes.cs
+copy /y Scopes.pdb Scopes.pdbx
+del Scopes.pdb
+
+csc /target:library /debug:portable /optimize- /features:deterministic Documents.cs
+copy /y Documents.pdb Documents.pdbx
+del Documents.pdb
+
+csc /target:library /debug:portable /optimize- /features:deterministic Async.cs
+copy /y Async.pdb Async.pdbx
+del Async.pdb
+
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/SymMetadataImport.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/SymMetadataImport.cs
new file mode 100644
index 0000000000000..c31c5028b12d4
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/SymMetadataImport.cs
@@ -0,0 +1,409 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.DiaSymReader.PortablePdb.UnitTests
+{
+ internal sealed class SymMetadataImport : IMetadataImport, IDisposable
+ {
+ private readonly PEReader _peReader;
+ public readonly MetadataReader MetadataReader;
+
+ public SymMetadataImport(Stream peStream)
+ {
+ _peReader = new PEReader(peStream);
+ MetadataReader = _peReader.GetMetadataReader();
+ }
+
+ public void Dispose()
+ {
+ _peReader.Dispose();
+ }
+
+ public void GetTypeDefProps(
+ int typeDefinition,
+ [MarshalAs(UnmanagedType.LPWStr), Out]StringBuilder qualifiedName,
+ int qualifiedNameBufferLength,
+ out int qualifiedNameLength,
+ [MarshalAs(UnmanagedType.U4)]out TypeAttributes attributes,
+ out int baseType)
+ {
+ var handle = (TypeDefinitionHandle)MetadataTokens.Handle(typeDefinition);
+ var typeDef = MetadataReader.GetTypeDefinition(handle);
+
+ if (qualifiedName != null)
+ {
+ qualifiedName.Clear();
+
+ if (!typeDef.Namespace.IsNil)
+ {
+ qualifiedName.Append(MetadataReader.GetString(typeDef.Namespace));
+ qualifiedName.Append('.');
+ }
+
+ qualifiedName.Append(MetadataReader.GetString(typeDef.Name));
+ qualifiedNameLength = qualifiedName.Length;
+ }
+ else
+ {
+ qualifiedNameLength =
+ (typeDef.Namespace.IsNil ? 0 : MetadataReader.GetString(typeDef.Namespace).Length + 1) +
+ MetadataReader.GetString(typeDef.Name).Length;
+ }
+
+ baseType = MetadataTokens.GetToken(typeDef.BaseType);
+ attributes = typeDef.Attributes;
+ }
+
+ public void GetTypeRefProps(
+ int typeReference,
+ out int resolutionScope,
+ [MarshalAs(UnmanagedType.LPWStr), Out]StringBuilder qualifiedName,
+ int qualifiedNameBufferLength,
+ out int qualifiedNameLength)
+ {
+ var handle = (TypeReferenceHandle)MetadataTokens.Handle(typeReference);
+ var typeRef = MetadataReader.GetTypeReference(handle);
+
+ if (qualifiedName != null)
+ {
+ qualifiedName.Clear();
+
+ if (!typeRef.Namespace.IsNil)
+ {
+ qualifiedName.Append(MetadataReader.GetString(typeRef.Namespace));
+ qualifiedName.Append('.');
+ }
+
+ qualifiedName.Append(MetadataReader.GetString(typeRef.Name));
+ qualifiedNameLength = qualifiedName.Length;
+ }
+ else
+ {
+ qualifiedNameLength =
+ (typeRef.Namespace.IsNil ? 0 : MetadataReader.GetString(typeRef.Namespace).Length + 1) +
+ MetadataReader.GetString(typeRef.Name).Length;
+ }
+
+ resolutionScope = MetadataTokens.GetToken(typeRef.ResolutionScope);
+ }
+
+ public unsafe int GetSigFromToken(int tkSignature, out byte* ppvSig, out int pcbSig)
+ {
+ var signatureHandle = (StandaloneSignatureHandle)MetadataTokens.Handle(tkSignature);
+ var bytes = MetadataReader.GetBlobBytes(MetadataReader.GetStandaloneSignature(signatureHandle).Signature);
+
+ var pinned = GCHandle.Alloc(bytes, GCHandleType.Pinned);
+ ppvSig = (byte*)pinned.AddrOfPinnedObject();
+ pcbSig = bytes.Length;
+ return HResult.S_OK;
+ }
+
+ #region Not Implemented
+
+ public void CloseEnum(uint handleEnum)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint CountEnum(uint handleEnum)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumCustomAttributes(ref uint handlePointerEnum, uint tk, uint tokenType, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayCustomAttributes, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint EnumEvents(ref uint handlePointerEnum, uint td, uint* arrayEvents, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint EnumFields(ref uint handlePointerEnum, uint cl, uint* arrayFields, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumFieldsWithName(ref uint handlePointerEnum, uint cl, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayFields, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumInterfaceImpls(ref uint handlePointerEnum, uint td, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]uint[] arrayImpls, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumMemberRefs(ref uint handlePointerEnum, uint tokenParent, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]uint[] arrayMemberRefs, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumMembers(ref uint handlePointerEnum, uint cl, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]uint[] arrayMembers, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumMembersWithName(ref uint handlePointerEnum, uint cl, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayMembers, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumMethodImpls(ref uint handlePointerEnum, uint td, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayMethodBody, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayMethodDecl, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint EnumMethods(ref uint handlePointerEnum, uint cl, uint* arrayMethods, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumMethodSemantics(ref uint handlePointerEnum, uint mb, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]uint[] arrayEventProp, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumMethodsWithName(ref uint handlePointerEnum, uint cl, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayMethods, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumModuleRefs(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arrayModuleRefs, uint cmax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumParams(ref uint handlePointerEnum, uint mb, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]uint[] arrayParams, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumPermissionSets(ref uint handlePointerEnum, uint tk, uint dwordActions, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]uint[] arrayPermission, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint EnumProperties(ref uint handlePointerEnum, uint td, uint* arrayProperties, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumSignatures(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arraySignatures, uint cmax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumTypeDefs(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arrayTypeDefs, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumTypeRefs(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arrayTypeRefs, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumTypeSpecs(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arrayTypeSpecs, uint cmax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumUnresolvedMethods(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arrayMethods, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint EnumUserStrings(ref uint handlePointerEnum, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]uint[] arrayStrings, uint cmax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint FindField(uint td, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]byte[] voidPointerSigBlob, uint byteCountSigBlob)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint FindMember(uint td, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]byte[] voidPointerSigBlob, uint byteCountSigBlob)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint FindMemberRef(uint td, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]byte[] voidPointerSigBlob, uint byteCountSigBlob)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint FindMethod(uint td, string stringName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]byte[] voidPointerSigBlob, uint byteCountSigBlob)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint FindTypeDefByName(string stringTypeDef, uint tokenEnclosingClass)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint FindTypeRef(uint tokenResolutionScope, string stringName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetClassLayout(uint td, out uint pdwPackSize, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]ulong[] arrayFieldOffset, uint countMax, out uint countPointerFieldOffset)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetCustomAttributeByName(uint tokenObj, string stringName, out void* ppData)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetCustomAttributeProps(uint cv, out uint ptkObj, out uint ptkType, out void* ppBlob)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetEventProps(uint ev, out uint pointerClass, StringBuilder stringEvent, uint cchEvent, out uint pchEvent, out uint pdwEventFlags, out uint ptkEventType, out uint pmdAddOn, out uint pmdRemoveOn, out uint pmdFire, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 11)]uint[] rmdOtherMethod, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetFieldMarshal(uint tk, out byte* ppvNativeType)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetFieldProps(uint mb, out uint pointerClass, StringBuilder stringField, uint cchField, out uint pchField, out uint pdwAttr, out byte* ppvSigBlob, out uint pcbSigBlob, out uint pdwCPlusTypeFlag, out void* ppValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetInterfaceImplProps(uint impl, out uint pointerClass)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetMemberProps(uint mb, out uint pointerClass, StringBuilder stringMember, uint cchMember, out uint pchMember, out uint pdwAttr, out byte* ppvSigBlob, out uint pcbSigBlob, out uint pulCodeRVA, out uint pdwImplFlags, out uint pdwCPlusTypeFlag, out void* ppValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetMemberRefProps(uint mr, ref uint ptk, StringBuilder stringMember, uint cchMember, out uint pchMember, out byte* ppvSigBlob)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetMethodProps(uint mb, out uint pointerClass, IntPtr stringMethod, uint cchMethod, out uint pchMethod, IntPtr pdwAttr, IntPtr ppvSigBlob, IntPtr pcbSigBlob, IntPtr pulCodeRVA)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetMethodSemantics(uint mb, uint tokenEventProp)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetModuleFromScope()
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetModuleRefProps(uint mur, StringBuilder stringName, uint cchName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetNameFromToken(uint tk)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetNativeCallConvFromSig(void* voidPointerSig, uint byteCountSig)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetNestedClassProps(uint typeDefNestedClass)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int GetParamForMethodIndex(uint md, uint ulongParamSeq, out uint pointerParam)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetParamProps(uint tk, out uint pmd, out uint pulSequence, StringBuilder stringName, uint cchName, out uint pchName, out uint pdwAttr, out uint pdwCPlusTypeFlag, out void* ppValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetPermissionSetProps(uint pm, out uint pdwAction, out void* ppvPermission)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetPinvokeMap(uint tk, out uint pdwMappingFlags, StringBuilder stringImportName, uint cchImportName, out uint pchImportName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetPropertyProps(uint prop, out uint pointerClass, StringBuilder stringProperty, uint cchProperty, out uint pchProperty, out uint pdwPropFlags, out byte* ppvSig, out uint bytePointerSig, out uint pdwCPlusTypeFlag, out void* ppDefaultValue, out uint pcchDefaultValue, out uint pmdSetter, out uint pmdGetter, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 14)]uint[] rmdOtherMethod, uint countMax)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetRVA(uint tk, out uint pulCodeRVA)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Guid GetScopeProps(StringBuilder stringName, uint cchName, out uint pchName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public unsafe uint GetTypeSpecFromToken(uint typespec, out byte* ppvSig)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint GetUserString(uint stk, StringBuilder stringString, uint cchString)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int IsGlobal(uint pd)
+ {
+ throw new NotImplementedException();
+ }
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public bool IsValidToken(uint tk)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ResetEnum(uint handleEnum, uint ulongPos)
+ {
+ throw new NotImplementedException();
+ }
+
+ public uint ResolveTypeRef(uint tr, [In]ref Guid riid, [MarshalAs(UnmanagedType.Interface)]out object ppIScope)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/SymReaderTests.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/SymReaderTests.cs
new file mode 100644
index 0000000000000..8954c38883a3a
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/SymReaderTests.cs
@@ -0,0 +1,554 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using Roslyn.Test.Utilities;
+using System;
+using System.Reflection.Metadata;
+using Xunit;
+
+namespace Microsoft.DiaSymReader.PortablePdb.UnitTests
+{
+ public class SymReaderTests
+ {
+ private static SymReader CreateSymReaderFromResource(string name)
+ {
+ MetadataReader mdReader;
+ return CreateSymReaderFromResource(name, out mdReader);
+ }
+
+ private static SymReader CreateSymReaderFromResource(string name, out MetadataReader mdReader)
+ {
+ var importer = new SymMetadataImport(TestResources.ResourceHelper.GetResourceStream(name + ".dll"));
+ mdReader = importer.MetadataReader;
+ return new SymReader(new PortablePdbReader(TestResources.ResourceHelper.GetResourceStream(name + ".pdbx"), importer));
+ }
+
+ private void ValidateDocumentUrl(ISymUnmanagedDocument document, string url)
+ {
+ int actualCount, actualCount2;
+ Assert.Equal(HResult.S_OK, document.GetUrl(0, out actualCount, null));
+
+ char[] actualUrl = new char[actualCount];
+ Assert.Equal(HResult.S_OK, document.GetUrl(actualCount, out actualCount2, actualUrl));
+ Assert.Equal(url, new string(actualUrl, 0, actualUrl.Length - 1));
+ }
+
+ private void ValidateDocument(ISymUnmanagedDocument document, string url, string algorithmId, byte[] checksum)
+ {
+ ValidateDocumentUrl(document, url);
+
+ int actualCount, actualCount2;
+
+ if (checksum != null)
+ {
+ Assert.Equal(HResult.S_OK, document.GetChecksum(0, out actualCount, null));
+ byte[] actualChecksum = new byte[actualCount];
+ Assert.Equal(HResult.S_OK, document.GetChecksum(actualCount, out actualCount2, actualChecksum));
+ Assert.Equal(actualCount, actualCount2);
+ AssertEx.Equal(checksum, actualChecksum);
+ }
+ else
+ {
+ Assert.Equal(HResult.S_FALSE, document.GetChecksum(0, out actualCount, null));
+ Assert.Equal(0, actualCount);
+ }
+
+ var guid = Guid.NewGuid();
+
+ Assert.Equal(HResult.S_OK, document.GetChecksumAlgorithmId(ref guid));
+ Assert.Equal(algorithmId != null ? new Guid(algorithmId) : default(Guid), guid);
+
+ guid = Guid.NewGuid();
+ Assert.Equal(HResult.S_OK, document.GetLanguageVendor(ref guid));
+ Assert.Equal(new Guid("994b45c4-e6e9-11d2-903f-00c04fa302a1"), guid);
+
+ guid = Guid.NewGuid();
+ Assert.Equal(HResult.S_OK, document.GetDocumentType(ref guid));
+ Assert.Equal(new Guid("5a869d0b-6611-11d3-bd2a-0000f80849bd"), guid);
+ }
+
+ private void ValidateRange(ISymUnmanagedScope scope, int expectedStartOffset, int expectedLength)
+ {
+ int actualOffset;
+ Assert.Equal(HResult.S_OK, scope.GetStartOffset(out actualOffset));
+ Assert.Equal(expectedStartOffset, actualOffset);
+
+ Assert.Equal(HResult.S_OK, scope.GetEndOffset(out actualOffset));
+ Assert.Equal(expectedStartOffset + expectedLength, 2);
+ }
+
+ private void ValidateConstant(ISymUnmanagedConstant constant, string name, object value, byte[] signature)
+ {
+ int length, length2;
+
+ // name:
+ Assert.Equal(HResult.S_OK, constant.GetName(0, out length, null));
+ Assert.Equal(name.Length + 1, length);
+ var actualName = new char[length];
+ Assert.Equal(HResult.S_OK, constant.GetName(length, out length2, actualName));
+ Assert.Equal(length, length2);
+ Assert.Equal(name + "\0", new string(actualName));
+
+ // value:
+ object actualValue;
+ Assert.Equal(HResult.S_OK, constant.GetValue(out actualValue));
+ Assert.Equal(value, actualValue);
+
+ // signature:
+ Assert.Equal(HResult.S_OK, constant.GetSignature(0, out length, null));
+ var actualSignature = new byte[length];
+ Assert.Equal(HResult.S_OK, constant.GetSignature(length, out length2, actualSignature));
+ Assert.Equal(length, length2);
+ AssertEx.Equal(signature, actualSignature);
+ }
+
+ private void ValidateVariable(ISymUnmanagedVariable variable, string name, int slot, LocalVariableAttributes attributes, byte[] signature)
+ {
+ int length, length2;
+
+ // name:
+ Assert.Equal(HResult.S_OK, variable.GetName(0, out length, null));
+ Assert.Equal(name.Length + 1, length);
+ var actualName = new char[length];
+ Assert.Equal(HResult.S_OK, variable.GetName(length, out length2, actualName));
+ Assert.Equal(length, length2);
+ Assert.Equal(name + "\0", new string(actualName));
+
+ int value;
+ Assert.Equal(HResult.S_OK, variable.GetAddressField1(out value));
+ Assert.Equal(slot, value);
+
+ Assert.Equal(HResult.E_NOTIMPL, variable.GetAddressField2(out value));
+ Assert.Equal(HResult.E_NOTIMPL, variable.GetAddressField3(out value));
+ Assert.Equal(HResult.E_NOTIMPL, variable.GetStartOffset(out value));
+ Assert.Equal(HResult.E_NOTIMPL, variable.GetEndOffset(out value));
+
+ Assert.Equal(HResult.S_OK, variable.GetAttributes(out value));
+ Assert.Equal(attributes, (LocalVariableAttributes)value);
+
+ Assert.Equal(HResult.S_OK, variable.GetAddressKind(out value));
+ Assert.Equal(1, value);
+
+ Assert.Equal(HResult.S_OK, variable.GetSignature(0, out length, null));
+ var actualSignature = new byte[length];
+ Assert.Equal(HResult.S_OK, variable.GetSignature(length, out length2, actualSignature));
+ Assert.Equal(length, length2);
+ AssertEx.Equal(signature, actualSignature);
+ }
+
+ private void ValidateRootScope(ISymUnmanagedScope scope)
+ {
+ int count;
+ Assert.Equal(HResult.S_OK, scope.GetLocalCount(out count));
+ Assert.Equal(0, count);
+
+ Assert.Equal(HResult.S_OK, ((ISymUnmanagedScope2)scope).GetConstantCount(out count));
+ Assert.Equal(0, count);
+
+ Assert.Equal(HResult.S_OK, ((ISymUnmanagedScope2)scope).GetNamespaces(0, out count, null));
+ Assert.Equal(0, count);
+
+ ISymUnmanagedScope parent;
+ Assert.Equal(HResult.S_OK, scope.GetParent(out parent));
+ Assert.Null(parent);
+ }
+
+ private ISymUnmanagedScope[] GetAndValidateChildScopes(ISymUnmanagedScope scope, int expectedCount)
+ {
+ int count, count2;
+ Assert.Equal(HResult.S_OK, scope.GetChildren(0, out count, null));
+ Assert.Equal(expectedCount, count);
+ var children = new ISymUnmanagedScope[count];
+ Assert.Equal(HResult.S_OK, scope.GetChildren(count, out count2, children));
+ Assert.Equal(count, count2);
+ return children;
+ }
+
+ private ISymUnmanagedConstant[] GetAndValidateConstants(ISymUnmanagedScope scope, int expectedCount)
+ {
+ int count, count2;
+ Assert.Equal(HResult.S_OK, ((ISymUnmanagedScope2)scope).GetConstants(0, out count, null));
+ Assert.Equal(expectedCount, count);
+ var constants = new ISymUnmanagedConstant[count];
+ Assert.Equal(HResult.S_OK, ((ISymUnmanagedScope2)scope).GetConstants(count, out count2, constants));
+ Assert.Equal(count, count2);
+ return constants;
+ }
+
+ private ISymUnmanagedVariable[] GetAndValidateVariables(ISymUnmanagedScope scope, int expectedCount)
+ {
+ int count, count2, count3;
+ Assert.Equal(HResult.S_OK, scope.GetLocalCount(out count));
+ Assert.Equal(expectedCount, count);
+ Assert.Equal(HResult.S_OK, scope.GetLocals(0, out count2, null));
+ Assert.Equal(expectedCount, count2);
+ var variables = new ISymUnmanagedVariable[count];
+ Assert.Equal(HResult.S_OK, scope.GetLocals(count, out count3, variables));
+ Assert.Equal(count, count3);
+ return variables;
+ }
+
+ private void ValidateAsyncMethod(ISymUnmanagedReader symReader, int moveNextMethodToken, int kickoffMethodToken, int catchHandlerOffset, int[] yieldOffsets, int[] resumeOffsets)
+ {
+ ISymUnmanagedMethod method;
+ Assert.Equal(HResult.S_OK, symReader.GetMethod(moveNextMethodToken, out method));
+
+ var asyncMethod = (ISymUnmanagedAsyncMethod)method;
+
+ bool isAsync;
+ Assert.Equal(HResult.S_OK, asyncMethod.IsAsyncMethod(out isAsync));
+ Assert.True(isAsync);
+
+ int actualKickoffMethodToken;
+ Assert.Equal(HResult.S_OK, asyncMethod.GetKickoffMethod(out actualKickoffMethodToken));
+ Assert.Equal(kickoffMethodToken, actualKickoffMethodToken);
+
+ bool hasCatchHandlerILOffset;
+ Assert.Equal(HResult.S_OK, asyncMethod.HasCatchHandlerILOffset(out hasCatchHandlerILOffset));
+ Assert.Equal(catchHandlerOffset >= 0, hasCatchHandlerILOffset);
+
+ int actualCatchHandlerOffset;
+ if (hasCatchHandlerILOffset)
+ {
+ Assert.Equal(HResult.S_OK, asyncMethod.GetCatchHandlerILOffset(out actualCatchHandlerOffset));
+ Assert.Equal(catchHandlerOffset, actualCatchHandlerOffset);
+ }
+ else
+ {
+ Assert.Equal(HResult.E_UNEXPECTED, asyncMethod.GetCatchHandlerILOffset(out actualCatchHandlerOffset));
+ }
+
+ int count, count2;
+ Assert.Equal(HResult.S_OK, asyncMethod.GetAsyncStepInfoCount(out count));
+ Assert.Equal(yieldOffsets.Length, count);
+ Assert.Equal(resumeOffsets.Length, count);
+
+ var actualYieldOffsets = new int[count];
+ var actualResumeOffsets = new int[count];
+ var actualResumeMethods = new int[count];
+
+ Assert.Equal(HResult.S_OK, asyncMethod.GetAsyncStepInfo(count, out count2, actualYieldOffsets, actualResumeOffsets, actualResumeMethods));
+
+ AssertEx.Equal(yieldOffsets, actualYieldOffsets);
+ AssertEx.Equal(resumeOffsets, actualResumeOffsets);
+
+ foreach (int actualResumeMethod in actualResumeMethods)
+ {
+ Assert.Equal(moveNextMethodToken, actualResumeMethod);
+ }
+ }
+
+ [Fact]
+ public unsafe void TestMetadataHeaders1()
+ {
+ fixed (byte* pdbPtr = TestResources.Documents.pdb)
+ {
+ var pdbReader = new MetadataReader(pdbPtr, TestResources.Documents.pdb.Length);
+ Assert.Equal("PDB v0.1", pdbReader.MetadataVersion);
+ Assert.Equal(MetadataKind.Ecma335, pdbReader.MetadataKind);
+ Assert.False(pdbReader.IsAssembly);
+ Assert.True(pdbReader.DebugMetadataHeader.EntryPoint.IsNil);
+ }
+ }
+
+ [Fact]
+ public void TestGetDocuments1()
+ {
+ var symReader = CreateSymReaderFromResource("Documents");
+
+ int actualCount;
+ Assert.Equal(HResult.S_OK, symReader.GetDocuments(0, out actualCount, null));
+ Assert.Equal(11, actualCount);
+
+ var actualDocuments = new ISymUnmanagedDocument[actualCount];
+ int actualCount2;
+ Assert.Equal(HResult.S_OK, symReader.GetDocuments(actualCount, out actualCount2, actualDocuments));
+ Assert.Equal(11, actualCount2);
+
+ ValidateDocument(actualDocuments[0],
+ url: @"C:\Documents.cs",
+ algorithmId: "ff1816ec-aa5e-4d10-87f7-6f4963833460",
+ checksum: new byte[] { 0xDB, 0xEB, 0x2A, 0x06, 0x7B, 0x2F, 0x0E, 0x0D, 0x67, 0x8A, 0x00, 0x2C, 0x58, 0x7A, 0x28, 0x06, 0x05, 0x6C, 0x3D, 0xCE });
+
+ ValidateDocument(actualDocuments[1], url: @"C:\a\b\c\d\1.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[2], url: @"C:\a\b\c\D\2.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[3], url: @"C:\a\b\C\d\3.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[4], url: @"C:\a\b\c\d\x.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[5], url: @"C:\A\b\c\x.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[6], url: @"C:\a\b\x.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[7], url: @"C:\a\B\3.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[8], url: @"C:\a\B\c\4.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[9], url: @"C:\*\5.cs", algorithmId: null, checksum: null);
+ ValidateDocument(actualDocuments[10], url: @":6.cs", algorithmId: null, checksum: null);
+ }
+
+ [Fact]
+ public void TestGetDocument1()
+ {
+ var symReader = CreateSymReaderFromResource("Documents");
+ TestGetDocument(symReader, @"x.cs", expectedUrl: @"C:\a\b\c\d\x.cs");
+ TestGetDocument(symReader, @"X.CS", expectedUrl: @"C:\a\b\c\d\x.cs");
+ TestGetDocument(symReader, @"1.cs", expectedUrl: @"C:\a\b\c\d\1.cs");
+ TestGetDocument(symReader, @"2.cs", expectedUrl: @"C:\a\b\c\D\2.cs");
+ TestGetDocument(symReader, @"3.cs", expectedUrl: @"C:\a\b\C\d\3.cs");
+ TestGetDocument(symReader, @"C:\A\b\c\x.cs", expectedUrl: @"C:\A\b\c\x.cs");
+ TestGetDocument(symReader, @"C:\a\b\x.cs", expectedUrl: @"C:\a\b\x.cs");
+ TestGetDocument(symReader, @"C:\*\5.cs", expectedUrl: @"C:\*\5.cs");
+ TestGetDocument(symReader, @"5.cs", expectedUrl: @"C:\*\5.cs");
+ TestGetDocument(symReader, @":6.cs", expectedUrl: @":6.cs");
+ }
+
+ private void TestGetDocument(SymReader symReader, string name, string expectedUrl)
+ {
+ ISymUnmanagedDocument document;
+ if (expectedUrl != null)
+ {
+ // guids are ignored
+ Assert.Equal(HResult.S_OK, symReader.GetDocument(name, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), out document));
+ ValidateDocumentUrl(document, expectedUrl);
+ }
+ else
+ {
+ // guids are ignored
+ Assert.Equal(HResult.S_FALSE, symReader.GetDocument(name, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), out document));
+ Assert.Null(document);
+ }
+ }
+
+ [Fact]
+ public void TestSymGetAttribute()
+ {
+ var symReader = CreateSymReaderFromResource("Documents");
+
+ int actualCount;
+ int actualCount2;
+ Assert.Equal(HResult.S_FALSE, symReader.GetSymAttribute(0, "", 0, out actualCount, null));
+
+ byte[] image = new byte[actualCount];
+ Assert.Equal(HResult.S_OK, symReader.GetSymAttribute(0, "", actualCount, out actualCount2, image));
+ Assert.Equal(actualCount, actualCount2);
+
+ AssertEx.Equal(TestResources.Documents.pdb, image);
+ }
+
+ [Fact]
+ public void TestMethods1()
+ {
+ var symReader = CreateSymReaderFromResource("Scopes");
+ int count;
+
+ //
+ // C.F
+ //
+
+ ISymUnmanagedMethod mF;
+ Assert.Equal(HResult.S_OK, symReader.GetMethod(0x06000002, out mF));
+
+ // root scope:
+ ISymUnmanagedScope rootScope, rootScopeCopy;
+ Assert.Equal(HResult.S_OK, mF.GetRootScope(out rootScope));
+ Assert.Equal(HResult.S_OK, mF.GetRootScope(out rootScopeCopy));
+ Assert.NotSame(rootScope, rootScopeCopy);
+
+ ValidateRange(rootScope, 0, 2);
+ ValidateRootScope(rootScope);
+
+ // child scope:
+ var children = GetAndValidateChildScopes(rootScope, expectedCount: 1);
+
+ var child = children[0];
+ Assert.Equal(HResult.S_OK, child.GetLocals(0, out count, null));
+ Assert.Equal(0, count);
+
+ ISymUnmanagedScope parent;
+ Assert.Equal(HResult.S_OK, child.GetParent(out parent));
+ Assert.NotSame(rootScope, parent); // a new instance should be created
+ ValidateRootScope(parent);
+ ValidateRange(parent, 0, 2);
+
+ var constants = GetAndValidateConstants(child, expectedCount: 29);
+
+ ValidateConstant(constants[0], "B", (short)0, new byte[] { 0x02 });
+ ValidateConstant(constants[1], "C", (ushort)'\0', new byte[] { 0x03 });
+ ValidateConstant(constants[2], "I1", (short)1, new byte[] { 0x04 });
+ ValidateConstant(constants[3], "U1", (short)2, new byte[] { 0x05 });
+ ValidateConstant(constants[4], "I2", (short)3, new byte[] { 0x06 });
+ ValidateConstant(constants[5], "U2", (ushort)4, new byte[] { 0x07 });
+ ValidateConstant(constants[6], "I4", 5, new byte[] { 0x08 });
+ ValidateConstant(constants[7], "U4", (uint)6, new byte[] { 0x09 });
+ ValidateConstant(constants[8], "I8", (long)7, new byte[] { 0x0A });
+ ValidateConstant(constants[9], "U8", (ulong)8, new byte[] { 0x0B });
+ ValidateConstant(constants[10], "R4", (float)9.1, new byte[] { 0x0C });
+ ValidateConstant(constants[11], "R8", 10.2, new byte[] { 0x0D });
+
+ ValidateConstant(constants[12], "EI1", (short)1, new byte[] { 0x11, 0x06 });
+ ValidateConstant(constants[13], "EU1", (short)2, new byte[] { 0x11, 0x0A });
+ ValidateConstant(constants[14], "EI2", (short)3, new byte[] { 0x11, 0x0E });
+ ValidateConstant(constants[15], "EU2", (ushort)4, new byte[] { 0x11, 0x12 });
+ ValidateConstant(constants[16], "EI4", 5, new byte[] { 0x11, 0x16 });
+ ValidateConstant(constants[17], "EU4", (uint)6, new byte[] { 0x11, 0x1A });
+ ValidateConstant(constants[18], "EI8", (long)7, new byte[] { 0x11, 0x1E });
+ ValidateConstant(constants[19], "EU8", (ulong)8, new byte[] { 0x11, 0x22 });
+
+ ValidateConstant(constants[20], "StrWithNul", "\0", new byte[] { 0x0e });
+ ValidateConstant(constants[21], "EmptyStr", "", new byte[] { 0x0e });
+ ValidateConstant(constants[22], "NullStr", 0, new byte[] { 0x0e });
+ ValidateConstant(constants[23], "NullObject", 0, new byte[] { 0x1c });
+ ValidateConstant(constants[24], "NullDynamic", 0, new byte[] { 0x1c });
+
+ // Note: Natvie PDBs produce expanded form of the signature stored as StandAloneSig.
+ // In Portable PDBs we produce a TypeSpec. Since a StandAlongSig can also contain a TypeSpec
+ // the consumers should be able to resolve it. If we find a case where that's not true we can
+ // potentially expand the TypeSpec signature in ISymUnmanagedConstant.GetValue.
+ ValidateConstant(constants[25], "NullTypeDef", 0, new byte[] { 0x12, 0x08 });
+ ValidateConstant(constants[26], "NullTypeRef", 0, new byte[] { 0x12, 0x1D });
+ ValidateConstant(constants[27], "NullTypeSpec", 0, new byte[] { 0x12, 0x26 });
+
+ ValidateConstant(constants[28], "D", 123456.78M, new byte[] { 0x11, 0x2D });
+
+ //
+ // C.NestedScopes
+ //
+
+ ISymUnmanagedMethod mNestedScopes;
+ Assert.Equal(HResult.S_OK, symReader.GetMethod(0x06000003, out mNestedScopes));
+
+ // root scope:
+ Assert.Equal(HResult.S_OK, mNestedScopes.GetRootScope(out rootScope));
+ ValidateRootScope(rootScope);
+
+ var main = GetAndValidateChildScopes(rootScope, expectedCount: 1)[0];
+ constants = GetAndValidateConstants(main, expectedCount: 0);
+ var variables = GetAndValidateVariables(main, expectedCount: 2);
+
+ ValidateVariable(variables[0], "x0", 0, LocalVariableAttributes.None, new byte[] { 0x08 });
+ ValidateVariable(variables[1], "y0", 1, LocalVariableAttributes.None, new byte[] { 0x08 });
+
+ children = GetAndValidateChildScopes(main, expectedCount: 2);
+ var first = children[0];
+ GetAndValidateChildScopes(first, expectedCount: 0);
+ var second = children[1];
+ var third = GetAndValidateChildScopes(second, expectedCount: 1)[0];
+ GetAndValidateChildScopes(third, expectedCount: 0);
+
+ constants = GetAndValidateConstants(first, expectedCount: 1);
+ variables = GetAndValidateVariables(first, expectedCount: 1);
+ ValidateConstant(constants[0], "c1", 11, new byte[] { 0x08 });
+ ValidateVariable(variables[0], "x1", 2, LocalVariableAttributes.None, new byte[] { 0x08 });
+
+ constants = GetAndValidateConstants(second, expectedCount: 0);
+ variables = GetAndValidateVariables(second, expectedCount: 1);
+ ValidateVariable(variables[0], "y1", 3, LocalVariableAttributes.None, new byte[] { 0x08 });
+
+ constants = GetAndValidateConstants(third, expectedCount: 2);
+ variables = GetAndValidateVariables(third, expectedCount: 1);
+ ValidateConstant(constants[0], "c2", "c2", new byte[] { 0x0e });
+ ValidateConstant(constants[1], "d2", "d2", new byte[] { 0x0e });
+ ValidateVariable(variables[0], "y2", 4, LocalVariableAttributes.None, new byte[] { 0x08 });
+
+ // TODO:
+ // f.GetOffset();
+ // f.GetRanges();
+
+ ISymUnmanagedNamespace ns;
+ ISymUnmanagedVariable[] ps = null;
+ Assert.Equal(HResult.E_NOTIMPL, mF.GetNamespace(out ns));
+ Assert.Equal(HResult.E_NOTIMPL, mF.GetParameters(0, out count, ps));
+ // TODO:
+ // f.GetScopeFromOffset()
+ }
+
+ [Fact]
+ public void TestAsyncMethods()
+ {
+ var symReader = CreateSymReaderFromResource("Async");
+
+ ValidateAsyncMethod(
+ symReader,
+ moveNextMethodToken: 0x06000005,
+ kickoffMethodToken: 0x06000001,
+ catchHandlerOffset: -1,
+ yieldOffsets: new[] { 0x46, 0xAF, 0x11A },
+ resumeOffsets: new[] { 0x64, 0xCE, 0x136 });
+
+ ValidateAsyncMethod(
+ symReader,
+ moveNextMethodToken: 0x06000008,
+ kickoffMethodToken: 0x06000002,
+ catchHandlerOffset: 0x76,
+ yieldOffsets: new[] { 0x2D },
+ resumeOffsets: new[] { 0x48 });
+ }
+
+ [Fact]
+ public void TestAsyncMethods_GetAsyncStepInfo()
+ {
+ var symReader = CreateSymReaderFromResource("Async");
+
+ ISymUnmanagedMethod method;
+ Assert.Equal(HResult.S_OK, symReader.GetMethod(0x06000005, out method));
+
+ var asyncMethod = (ISymUnmanagedAsyncMethod)method;
+
+ var actualYieldOffsets = new int[1];
+ var actualResumeOffsets = new int[1];
+ var actualResumeMethods = new int[1];
+
+ int count2;
+ Assert.Equal(HResult.S_OK, asyncMethod.GetAsyncStepInfo(1, out count2, actualYieldOffsets, actualResumeOffsets, actualResumeMethods));
+
+ Assert.Equal(1, count2);
+ Assert.NotEqual(0, actualYieldOffsets[0]);
+ Assert.NotEqual(0, actualResumeOffsets[0]);
+ Assert.NotEqual(0, actualResumeMethods[0]);
+
+ actualYieldOffsets = new int[5];
+ actualResumeOffsets = new int[5];
+ actualResumeMethods = new int[5];
+
+ Assert.Equal(HResult.S_OK, asyncMethod.GetAsyncStepInfo(4, out count2, actualYieldOffsets, actualResumeOffsets, actualResumeMethods));
+
+ Assert.Equal(3, count2);
+
+ for (int i = 0; i < 3; i++)
+ {
+ Assert.NotEqual(0, actualYieldOffsets[i]);
+ Assert.NotEqual(0, actualResumeOffsets[i]);
+ Assert.NotEqual(0, actualResumeMethods[i]);
+ }
+
+ for (int i = 3; i < 5; i++)
+ {
+ Assert.Equal(0, actualYieldOffsets[i]);
+ Assert.Equal(0, actualResumeOffsets[i]);
+ Assert.Equal(0, actualResumeMethods[i]);
+ }
+ }
+
+ [Fact]
+ public void TestAsyncMethods_Errors()
+ {
+ var symReader = CreateSymReaderFromResource("Scopes");
+
+ ISymUnmanagedMethod method;
+ Assert.Equal(HResult.S_OK, symReader.GetMethod(0x06000002, out method));
+
+ var asyncMethod = (ISymUnmanagedAsyncMethod)method;
+
+ bool isAsync;
+ Assert.Equal(HResult.S_OK, asyncMethod.IsAsyncMethod(out isAsync));
+ Assert.False(isAsync);
+
+ int actualKickoffMethodToken;
+ Assert.Equal(HResult.E_UNEXPECTED, asyncMethod.GetKickoffMethod(out actualKickoffMethodToken));
+
+ bool hasCatchHandlerILOffset;
+ Assert.Equal(HResult.E_UNEXPECTED, asyncMethod.HasCatchHandlerILOffset(out hasCatchHandlerILOffset));
+
+ int actualCatchHandlerOffset;
+ Assert.Equal(HResult.E_UNEXPECTED, asyncMethod.GetCatchHandlerILOffset(out actualCatchHandlerOffset));
+
+ int count, count2;
+ Assert.Equal(HResult.E_UNEXPECTED, asyncMethod.GetAsyncStepInfoCount(out count));
+ Assert.Equal(HResult.E_UNEXPECTED, asyncMethod.GetAsyncStepInfo(count, out count2, null, null, null));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/TestHelpers/AssertEx.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/TestHelpers/AssertEx.cs
new file mode 100644
index 0000000000000..8efa424097987
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/TestHelpers/AssertEx.cs
@@ -0,0 +1,508 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Xunit;
+
+namespace Roslyn.Test.Utilities
+{
+ ///
+ /// Assert style type to deal with the lack of features in xUnit's Assert type
+ ///
+ public static class AssertEx
+ {
+ #region AssertEqualityComparer
+
+ private class AssertEqualityComparer : IEqualityComparer
+ {
+ private static readonly IEqualityComparer s_instance = new AssertEqualityComparer();
+
+ private static bool CanBeNull()
+ {
+ var type = typeof(T);
+ return !type.GetTypeInfo().IsValueType ||
+ (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
+ }
+
+ public static bool IsNull(T @object)
+ {
+ if (!CanBeNull())
+ {
+ return false;
+ }
+
+ return object.Equals(@object, default(T));
+ }
+
+ public static bool Equals(T left, T right)
+ {
+ return s_instance.Equals(left, right);
+ }
+
+ bool IEqualityComparer.Equals(T x, T y)
+ {
+ if (CanBeNull())
+ {
+ if (object.Equals(x, default(T)))
+ {
+ return object.Equals(y, default(T));
+ }
+
+ if (object.Equals(y, default(T)))
+ {
+ return false;
+ }
+ }
+
+ if (x.GetType() != y.GetType())
+ {
+ return false;
+ }
+
+ var equatable = x as IEquatable;
+ if (equatable != null)
+ {
+ return equatable.Equals(y);
+ }
+
+ var comparableT = x as IComparable;
+ if (comparableT != null)
+ {
+ return comparableT.CompareTo(y) == 0;
+ }
+
+ var comparable = x as IComparable;
+ if (comparable != null)
+ {
+ return comparable.CompareTo(y) == 0;
+ }
+
+ var enumerableX = x as IEnumerable;
+ var enumerableY = y as IEnumerable;
+
+ if (enumerableX != null && enumerableY != null)
+ {
+ var enumeratorX = enumerableX.GetEnumerator();
+ var enumeratorY = enumerableY.GetEnumerator();
+
+ while (true)
+ {
+ bool hasNextX = enumeratorX.MoveNext();
+ bool hasNextY = enumeratorY.MoveNext();
+
+ if (!hasNextX || !hasNextY)
+ {
+ return hasNextX == hasNextY;
+ }
+
+ if (!Equals(enumeratorX.Current, enumeratorY.Current))
+ {
+ return false;
+ }
+ }
+ }
+
+ return object.Equals(x, y);
+ }
+
+ int IEqualityComparer.GetHashCode(T obj)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ public static void AreEqual(T expected, T actual, string message = null, IEqualityComparer comparer = null)
+ {
+ if (ReferenceEquals(expected, actual))
+ {
+ return;
+ }
+
+ if (expected == null)
+ {
+ Fail("expected was null, but actual wasn't\r\n" + message);
+ }
+ else if (actual == null)
+ {
+ Fail("actual was null, but expected wasn't\r\n" + message);
+ }
+ else
+ {
+ if (!(comparer != null ?
+ comparer.Equals(expected, actual) :
+ AssertEqualityComparer.Equals(expected, actual)))
+ {
+ Fail("Expected and actual were different.\r\n" +
+ "Expected: " + expected + "\r\n" +
+ "Actual: " + actual + "\r\n" +
+ message);
+ }
+ }
+ }
+
+ public static void Equal(ImmutableArray expected, IEnumerable actual, IEqualityComparer comparer = null, string message = null)
+ {
+ if (actual == null || expected.IsDefault)
+ {
+ Assert.True((actual == null) == expected.IsDefault, message);
+ }
+ else
+ {
+ Equal((IEnumerable)expected, actual, comparer, message);
+ }
+ }
+
+ public static void Equal(IEnumerable expected, ImmutableArray actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = null)
+ {
+ if (expected == null || actual.IsDefault)
+ {
+ Assert.True((expected == null) == actual.IsDefault, message);
+ }
+ else
+ {
+ Equal(expected, (IEnumerable)actual, comparer, message, itemSeparator);
+ }
+ }
+
+ public static void Equal(ImmutableArray expected, ImmutableArray actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = null)
+ {
+ Equal(expected, (IEnumerable)actual, comparer, message, itemSeparator);
+ }
+
+ public static void Equal(IEnumerable expected, IEnumerable actual, IEqualityComparer comparer = null, string message = null,
+ string itemSeparator = null, Func itemInspector = null)
+ {
+ if (ReferenceEquals(expected, actual))
+ {
+ return;
+ }
+
+ if (expected == null)
+ {
+ Fail("expected was null, but actual wasn't\r\n" + message);
+ }
+ else if (actual == null)
+ {
+ Fail("actual was null, but expected wasn't\r\n" + message);
+ }
+ else if (!SequenceEqual(expected, actual, comparer))
+ {
+ string assertMessage = GetAssertMessage(expected, actual, comparer, itemInspector, itemSeparator);
+
+ if (message != null)
+ {
+ assertMessage = message + "\r\n" + assertMessage;
+ }
+
+ Assert.True(false, assertMessage);
+ }
+ }
+
+ private static bool SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer comparer = null)
+ {
+ var enumerator1 = expected.GetEnumerator();
+ var enumerator2 = actual.GetEnumerator();
+
+ while (true)
+ {
+ var hasNext1 = enumerator1.MoveNext();
+ var hasNext2 = enumerator2.MoveNext();
+
+ if (hasNext1 != hasNext2)
+ {
+ return false;
+ }
+
+ if (!hasNext1)
+ {
+ break;
+ }
+
+ var value1 = enumerator1.Current;
+ var value2 = enumerator2.Current;
+
+ if (!(comparer != null ? comparer.Equals(value1, value2) : AssertEqualityComparer.Equals(value1, value2)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static void SetEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer comparer = null, string message = null, string itemSeparator = "\r\n")
+ {
+ var expectedSet = new HashSet(expected, comparer);
+ var result = expected.Count() == actual.Count() && expectedSet.SetEquals(actual);
+ if (!result)
+ {
+ if (string.IsNullOrEmpty(message))
+ {
+ message = GetAssertMessage(
+ ToString(expected, itemSeparator),
+ ToString(actual, itemSeparator));
+ }
+
+ Assert.True(result, message);
+ }
+ }
+
+ public static void SetEqual(IEnumerable actual, params T[] expected)
+ {
+ var expectedSet = new HashSet(expected);
+ Assert.True(expectedSet.SetEquals(actual), string.Format("Expected: {0}\nActual: {1}", ToString(expected), ToString(actual)));
+ }
+
+ public static void None(IEnumerable actual, Func predicate)
+ {
+ var none = !actual.Any(predicate);
+ if (!none)
+ {
+ Assert.True(none, string.Format(
+ "Unexpected item found among existing items: {0}\nExisting items: {1}",
+ ToString(actual.First(predicate)),
+ ToString(actual)));
+ }
+ }
+
+ public static void Any(IEnumerable actual, Func predicate)
+ {
+ var any = actual.Any(predicate);
+ Assert.True(any, string.Format("No expected item was found.\nExisting items: {0}", ToString(actual)));
+ }
+
+ public static void All(IEnumerable actual, Func predicate)
+ {
+ var all = actual.All(predicate);
+ if (!all)
+ {
+ Assert.True(all, string.Format(
+ "Not all items satisfy condition:\n{0}",
+ ToString(actual.Where(i => !predicate(i)))));
+ }
+ }
+
+ public static string ToString(object o)
+ {
+ return Convert.ToString(o);
+ }
+
+ public static string ToString(IEnumerable list, string separator = ", ", Func itemInspector = null)
+ {
+ if (itemInspector == null)
+ {
+ itemInspector = i => Convert.ToString(i);
+ }
+
+ return string.Join(separator, list.Select(itemInspector));
+ }
+
+ public static void Fail(string message)
+ {
+ Assert.False(true, message);
+ }
+
+ public static void Fail(string format, params object[] args)
+ {
+ Assert.False(true, string.Format(format, args));
+ }
+
+ public static void Null(T @object, string message = null)
+ {
+ Assert.True(AssertEqualityComparer.IsNull(@object), message);
+ }
+
+ public static void NotNull(T @object, string message = null)
+ {
+ Assert.False(AssertEqualityComparer.IsNull(@object), message);
+ }
+
+ public static void ThrowsArgumentNull(string parameterName, Action del)
+ {
+ try
+ {
+ del();
+ }
+ catch (ArgumentNullException e)
+ {
+ Assert.Equal(parameterName, e.ParamName);
+ }
+ }
+
+ public static void ThrowsArgumentException(string parameterName, Action del)
+ {
+ try
+ {
+ del();
+ }
+ catch (ArgumentException e)
+ {
+ Assert.Equal(parameterName, e.ParamName);
+ }
+ }
+
+ public static T Throws(Action del, bool allowDerived = false) where T : Exception
+ {
+ try
+ {
+ del();
+ }
+ catch (Exception ex)
+ {
+ var type = ex.GetType();
+ if (type.Equals(typeof(T)))
+ {
+ // We got exactly the type we wanted
+ return (T)ex;
+ }
+
+ if (allowDerived && typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
+ {
+ // We got a derived type
+ return (T)ex;
+ }
+
+ // We got some other type. We know that type != typeof(T), and so we'll use Assert.Equal since Xunit
+ // will give a nice Expected/Actual output for this
+ Assert.Equal(typeof(T), type);
+ }
+
+ throw new Exception("No exception was thrown.");
+ }
+
+ public static void AssertEqualToleratingWhitespaceDifferences(
+ string expected,
+ string actual,
+ bool escapeQuotes = true,
+ [CallerFilePath]string expectedValueSourcePath = null,
+ [CallerLineNumber]int expectedValueSourceLine = 0)
+ {
+ var normalizedExpected = NormalizeWhitespace(expected);
+ var normalizedActual = NormalizeWhitespace(actual);
+
+ if (normalizedExpected != normalizedActual)
+ {
+ Assert.True(false, GetAssertMessage(expected, actual, escapeQuotes, expectedValueSourcePath, expectedValueSourceLine));
+ }
+ }
+
+ public static void AssertContainsToleratingWhitespaceDifferences(string expectedSubString, string actualString)
+ {
+ expectedSubString = NormalizeWhitespace(expectedSubString);
+ actualString = NormalizeWhitespace(actualString);
+ Assert.Contains(expectedSubString, actualString, StringComparison.Ordinal);
+ }
+
+ internal static string NormalizeWhitespace(string input)
+ {
+ var output = new StringBuilder();
+ var inputLines = input.Split('\n', '\r');
+ foreach (var line in inputLines)
+ {
+ var trimmedLine = line.Trim();
+ if (trimmedLine.Length > 0)
+ {
+ if (!(trimmedLine[0] == '{' || trimmedLine[0] == '}'))
+ {
+ output.Append(" ");
+ }
+
+ output.AppendLine(trimmedLine);
+ }
+ }
+
+ return output.ToString();
+ }
+
+ public static string GetAssertMessage(IEnumerable expected, IEnumerable actual, bool escapeQuotes, string expectedValueSourcePath = null, int expectedValueSourceLine = 0)
+ {
+ Func itemInspector = escapeQuotes ? new Func(t => t.ToString().Replace("\"", "\"\"")) : null;
+ return GetAssertMessage(expected, actual, itemInspector: itemInspector, itemSeparator: "\r\n", expectedValueSourcePath: expectedValueSourcePath, expectedValueSourceLine: expectedValueSourceLine);
+ }
+
+ private static readonly string s_diffToolPath = Environment.GetEnvironmentVariable("ROSLYN_DIFFTOOL");
+
+ public static string GetAssertMessage(
+ IEnumerable expected,
+ IEnumerable actual,
+ IEqualityComparer comparer = null,
+ Func itemInspector = null,
+ string itemSeparator = null,
+ string expectedValueSourcePath = null,
+ int expectedValueSourceLine = 0)
+ {
+ if (itemInspector == null)
+ {
+ if (expected is IEnumerable)
+ {
+ itemInspector = b => $"0x{b:X2}";
+ }
+ else
+ {
+ itemInspector = new Func(obj => (obj != null) ? obj.ToString() : "");
+ }
+ }
+
+ if (itemSeparator == null)
+ {
+ if (expected is IEnumerable)
+ {
+ itemSeparator = ", ";
+ }
+ else
+ {
+ itemSeparator = ",\r\n";
+ }
+ }
+
+ var actualString = string.Join(itemSeparator, actual.Select(itemInspector));
+
+ var message = new StringBuilder();
+ message.AppendLine();
+ message.AppendLine("Actual:");
+ message.AppendLine(actualString);
+
+ string link;
+ if (TryGenerateExpectedSourceFielAndGetDiffLink(actualString, expected.Count(), expectedValueSourcePath, expectedValueSourceLine, out link))
+ {
+ message.AppendLine(link);
+ }
+
+ return message.ToString();
+ }
+
+ internal static bool TryGenerateExpectedSourceFielAndGetDiffLink(string actualString, int expectedLineCount, string expectedValueSourcePath, int expectedValueSourceLine, out string link)
+ {
+ // add a link to a .cmd file that opens a diff tool:
+ if (!string.IsNullOrEmpty(s_diffToolPath) && expectedValueSourcePath != null && expectedValueSourceLine != 0)
+ {
+ var actualFile = Path.GetTempFileName();
+ var testFileLines = File.ReadLines(expectedValueSourcePath);
+
+ File.WriteAllLines(actualFile, testFileLines.Take(expectedValueSourceLine));
+ File.AppendAllText(actualFile, actualString);
+ File.AppendAllLines(actualFile, testFileLines.Skip(expectedValueSourceLine + expectedLineCount));
+
+ var compareCmd = Path.GetTempFileName() + ".cmd";
+ File.WriteAllText(compareCmd, string.Format("\"{0}\" \"{1}\" \"{2}\"", s_diffToolPath, actualFile, expectedValueSourcePath));
+
+ link = "file://" + compareCmd;
+
+ return true;
+ }
+
+ link = null;
+ return false;
+ }
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Utilities/EnumerableHelpersTests.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Utilities/EnumerableHelpersTests.cs
new file mode 100644
index 0000000000000..3ef021826ed4e
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/Utilities/EnumerableHelpersTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.DiaSymReader.PortablePdb.UnitTests
+{
+ public class EnumerableHelpersTests
+ {
+ [Fact]
+ public void GroupBy1()
+ {
+ var pairs = new[]
+ {
+ KeyValuePair.Create("A", 1),
+ KeyValuePair.Create("B", 2),
+ KeyValuePair.Create("C", 3),
+ KeyValuePair.Create("a", 4),
+ KeyValuePair.Create("B", 5),
+ KeyValuePair.Create("A", 6),
+ KeyValuePair.Create("d", 7),
+ };
+
+ var groups = pairs.GroupBy(StringComparer.OrdinalIgnoreCase);
+ AssertEx.SetEqual(new[] { "A", "B", "C", "d" }, groups.Keys);
+
+ Assert.Equal(0, groups["A"].Key);
+ AssertEx.Equal(new[] { 1, 4, 6 }, groups["A"].Value);
+
+ Assert.Equal(0, groups["B"].Key);
+ AssertEx.Equal(new[] { 2, 5 }, groups["B"].Value);
+
+ Assert.Equal(3, groups["C"].Key);
+ Assert.True(groups["C"].Value.IsDefault);
+
+ Assert.Equal(7, groups["d"].Key);
+ Assert.True(groups["d"].Value.IsDefault);
+ }
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/packages.config b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/packages.config
new file mode 100644
index 0000000000000..5dba29bdd7d65
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb.Tests/packages.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/AsyncMethodData.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/AsyncMethodData.cs
new file mode 100644
index 0000000000000..058a611f41b6b
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/AsyncMethodData.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ internal sealed class AsyncMethodData
+ {
+ public static readonly AsyncMethodData None = new AsyncMethodData();
+
+ public readonly MethodDefinitionHandle KickoffMethod;
+ public readonly int CatchHandlerOffset;
+ public readonly ImmutableArray YieldOffsets;
+ public readonly ImmutableArray ResumeOffsets;
+ public readonly ImmutableArray ResumeMethods;
+
+ private AsyncMethodData()
+ {
+ }
+
+ public AsyncMethodData(
+ MethodDefinitionHandle kickoffMethod,
+ int catchHandlerOffset,
+ ImmutableArray yieldOffsets,
+ ImmutableArray resumeOffsets,
+ ImmutableArray resumeMethods)
+ {
+ Debug.Assert(!kickoffMethod.IsNil);
+ Debug.Assert(catchHandlerOffset >= -1);
+
+ Debug.Assert(yieldOffsets.Length == resumeOffsets.Length);
+ Debug.Assert(yieldOffsets.Length == resumeMethods.Length);
+
+ KickoffMethod = kickoffMethod;
+ CatchHandlerOffset = catchHandlerOffset;
+ YieldOffsets = yieldOffsets;
+ ResumeOffsets = resumeOffsets;
+ ResumeMethods = resumeMethods;
+ }
+
+ public bool IsNone => ReferenceEquals(this, None);
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/ChildScopeData.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/ChildScopeData.cs
new file mode 100644
index 0000000000000..5b41e15b36649
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/ChildScopeData.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ internal sealed class ChildScopeData : ScopeData
+ {
+ private readonly LocalScopeHandle _handle;
+ private readonly ScopeData _parent;
+
+ internal ChildScopeData(SymMethod symMethod, ScopeData parent, LocalScopeHandle handle)
+ : base(symMethod)
+ {
+ Debug.Assert(parent != null);
+ Debug.Assert(!handle.IsNil);
+
+ _handle = handle;
+ _parent = parent;
+ }
+
+ internal override ScopeData Parent => _parent;
+
+ internal override int StartOffset
+ {
+ get
+ {
+ return SymMethod.MetadataReader.GetLocalScope(_handle).StartOffset;
+ }
+ }
+
+ internal override int EndOffset
+ {
+ get
+ {
+ return AdjustEndOffset(SymMethod.MetadataReader.GetLocalScope(_handle).EndOffset);
+ }
+ }
+
+ protected override ImmutableArray CreateChildren()
+ {
+ // TODO: pool?
+ var builder = ImmutableArray.CreateBuilder();
+
+ var children = SymMethod.MetadataReader.GetLocalScope(_handle).GetChildren();
+ while (children.MoveNext())
+ {
+ builder.Add(new ChildScopeData(SymMethod, this, children.Current));
+ }
+
+ return builder.ToImmutable();
+ }
+
+ internal override int GetConstants(int bufferLength, out int count, ISymUnmanagedConstant[] constants)
+ {
+ var symReader = SymMethod.SymReader;
+ var mdReader = symReader.MetadataReader;
+ var scope = mdReader.GetLocalScope(_handle);
+
+ var handles = scope.GetLocalConstants();
+
+ int i = 0;
+ foreach (var handle in handles)
+ {
+ if (i >= bufferLength)
+ {
+ break;
+ }
+
+ constants[i++] = new SymConstant(symReader, handle);
+ }
+
+ count = (bufferLength == 0) ? handles.Count : i;
+ return HResult.S_OK;
+ }
+
+ internal override int GetLocals(int bufferLength, out int count, ISymUnmanagedVariable[] locals)
+ {
+ var mdReader = SymMethod.MetadataReader;
+ var scope = mdReader.GetLocalScope(_handle);
+
+ var handles = scope.GetLocalVariables();
+
+ int i = 0;
+ foreach (var handle in handles)
+ {
+ if (i >= bufferLength)
+ {
+ break;
+ }
+
+ locals[i++] = new SymVariable(SymMethod, handle);
+ }
+
+ count = (bufferLength == 0) ? handles.Count : i;
+ return HResult.S_OK;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/DocumentMap.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/DocumentMap.cs
new file mode 100644
index 0000000000000..cf4df12c4660b
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/DocumentMap.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Text;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ internal sealed class DocumentMap
+ {
+ private readonly MetadataReader _reader;
+
+ // { last part of document name -> one or many document handles that have the part in commmon }
+ private readonly IReadOnlyDictionary>> _map;
+
+ public DocumentMap(MetadataReader reader)
+ {
+ _reader = reader;
+ _map = GetDocumentsByFileName(reader).GroupBy(StringComparer.OrdinalIgnoreCase);
+ }
+
+ private static IEnumerable> GetDocumentsByFileName(MetadataReader reader)
+ {
+ foreach (var documentHandle in reader.Documents)
+ {
+ string fileName = GetFileName(reader, documentHandle);
+
+ // invalid metadata: document doesn't have a name
+ if (fileName == null)
+ {
+ continue;
+ }
+
+ yield return new KeyValuePair(fileName, documentHandle);
+ }
+ }
+
+ private static string GetFileName(MetadataReader reader, DocumentHandle documentHandle)
+ {
+ var document = reader.GetDocument(documentHandle);
+
+ if (document.Name.IsNil)
+ {
+ return null;
+ }
+
+ var nameReader = reader.GetBlobReader(document.Name);
+
+ int separator = nameReader.ReadByte();
+ if (!FileNameUtilities.IsDirectorySeparator((char)separator))
+ {
+ return FileNameUtilities.GetFileName(reader.GetString(document.Name));
+ }
+
+ // find the last part handle:
+ BlobHandle partHandle = default(BlobHandle);
+ while (nameReader.RemainingBytes > 0)
+ {
+ partHandle = nameReader.ReadBlobHandle();
+ }
+
+ if (partHandle.IsNil)
+ {
+ return string.Empty;
+ }
+
+ var partReader = reader.GetBlobReader(partHandle);
+ var part = partReader.ReadUTF8(partReader.Length);
+ if (part.IndexOf('\0') >= 0)
+ {
+ // bad metadata
+ return null;
+ }
+
+ // it is valid to encode document name so that the parts contain directory separators:
+ return FileNameUtilities.GetFileName(part);
+ }
+
+ internal bool TryGetDocument(string fullPath, out DocumentHandle documentHandle)
+ {
+ var fileName = FileNameUtilities.GetFileName(fullPath);
+
+ KeyValuePair> documents;
+ if (!_map.TryGetValue(fileName, out documents))
+ {
+ documentHandle = default(DocumentHandle);
+ return false;
+ }
+
+ // SymReader first attempts to find the document by the full path, then by file name with extension.
+
+ if (!documents.Key.IsNil)
+ {
+ // There is only one document with the specified file name.
+ // SymReader returns the document regardless of whether the path matches the name.
+ documentHandle = documents.Key;
+ return true;
+ }
+
+ Debug.Assert(documents.Value.Length > 1);
+
+ // We have multiple candidates with the same file name. Find the one whose name matches the specified full path.
+ // If none does return the first one. It will be the one with the smallest handle, due to the multi-map construction implementation.
+
+ foreach (DocumentHandle candidateHandle in documents.Value)
+ {
+ if (_reader.StringComparer.Equals(_reader.GetDocument(candidateHandle).Name, fullPath, ignoreCase: true))
+ {
+ documentHandle = candidateHandle;
+ return true;
+ }
+ }
+
+ documentHandle = documents.Value[0];
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Test/PdbUtilities/Shared/IMetadataImport.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/IMetadataImport.cs
similarity index 99%
rename from src/Test/PdbUtilities/Shared/IMetadataImport.cs
rename to src/Debugging/Microsoft.DiaSymReader.PortablePdb/IMetadataImport.cs
index 1ed5c1866a837..f53cd65933152 100644
--- a/src/Test/PdbUtilities/Shared/IMetadataImport.cs
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/IMetadataImport.cs
@@ -7,7 +7,7 @@
using System.Runtime.InteropServices;
using System.Text;
-namespace Roslyn.Test.PdbUtilities
+namespace Microsoft.DiaSymReader.PortablePdb
{
internal static class IMetadataImportExtensions
{
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.csproj b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.csproj
new file mode 100644
index 0000000000000..eebac360f0ee5
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.csproj
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+ Debug
+ AnyCPU
+ {F83343BA-B4EA-451C-B6DB-5D645E6171BC}
+ Library
+ Microsoft.DiaSymReader.PortablePdb
+ Microsoft.DiaSymReader.PortablePdb
+ true
+ ..\..\..\
+ v4.5
+ Profile7
+ .NETPortable
+ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+
+
+
+
+
+ ..\..\..\packages\System.Reflection.Metadata.$(SystemReflectionMetadataVersion)\lib\portable-net45+win8\System.Reflection.Metadata.dll
+
+
+ False
+ ..\..\..\packages\System.Collections.Immutable.$(SystemCollectionsImmutableVersion)\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+
+
+
+ False
+
+
+ False
+
+
+
+
+ PreserveNewest
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.nuget.proj b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.nuget.proj
new file mode 100644
index 0000000000000..cd34a9b339574
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.nuget.proj
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+ Shipping
+
+
+
+
+ -prop binaries=$(OutDir) -prop currentVersion="$(NuGetVersion)"
+ $(OutDir)NuGet\%(NuSpec.Publishing)\$(NuGetVersionType)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.nuspec b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.nuspec
new file mode 100644
index 0000000000000..e728b88053ab8
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/Microsoft.DiaSymReader.PortablePdb.nuspec
@@ -0,0 +1,29 @@
+
+
+
+ Microsoft.DiaSymReader.PortablePdb
+ Implementation of Microsoft DiaSymReader interfaces that reads debug information from Portable PDB format
+
+ Implementation of Microsoft DiaSymReader interfaces that reads debug information from Portable PDB format.
+Supported Platforms:
+- .NET Framework 4.5
+
+
+
+
+
+
+ en-US
+ true
+ $currentVersion$
+ Microsoft
+ http://go.microsoft.com/fwlink/?LinkId=394369
+ http://msdn.com/roslyn
+ Preview of DiaSymReader interop library
+ DiaSymReader ISymUnmanagedReader PortablePDB COM interop debugging
+
+
+
+
+
+
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/PortablePdbReader.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/PortablePdbReader.cs
new file mode 100644
index 0000000000000..c06ebb26a1b69
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/PortablePdbReader.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ public sealed class PortablePdbReader : IDisposable
+ {
+ private readonly Stream _stream;
+ private readonly GCHandle _pinnedMetadata;
+ private readonly MetadataReader _metadataReader;
+ private IMetadataImport _metadataImporter;
+
+ // TODO: add GetContent to MetadataReader?
+ private readonly byte[] _image;
+
+ public unsafe PortablePdbReader(Stream stream, object metadataImporter)
+ {
+ _stream = stream;
+ _metadataImporter = (IMetadataImport)metadataImporter;
+
+ // TODO:
+ stream.Seek(0, SeekOrigin.Begin);
+ _image = new byte[stream.Length];
+ stream.Read(_image, 0, _image.Length);
+
+ _pinnedMetadata = GCHandle.Alloc(_image, GCHandleType.Pinned);
+ _metadataReader = new MetadataReader((byte*)_pinnedMetadata.AddrOfPinnedObject(), _image.Length);
+ }
+
+ internal MetadataReader MetadataReader => _metadataReader;
+ internal IMetadataImport MetadataImport => _metadataImporter;
+ internal byte[] Image => _image;
+
+ public void Dispose()
+ {
+ _stream.Dispose();
+ _pinnedMetadata.Free();
+ _metadataImporter = null;
+ }
+ }
+}
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/RootScopeData.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/RootScopeData.cs
new file mode 100644
index 0000000000000..38e9fd1e44b36
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/RootScopeData.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Collections.Immutable;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ internal sealed class RootScopeData : ScopeData
+ {
+ internal RootScopeData(SymMethod symMethod)
+ : base(symMethod)
+ {
+ }
+
+ internal override ScopeData Parent => null;
+ internal override int StartOffset => 0;
+
+ internal override int EndOffset
+ {
+ get
+ {
+ var mdReader = SymMethod.MetadataReader;
+ var allScopes = mdReader.GetLocalScopes(SymMethod.Handle);
+
+ foreach (var handle in allScopes)
+ {
+ // the first scope spans the entire body
+ return AdjustEndOffset(mdReader.GetLocalScope(handle).EndOffset);
+ }
+
+ // method has no body
+ return 0;
+ }
+ }
+
+ protected override ImmutableArray CreateChildren()
+ {
+ foreach (var handle in SymMethod.MetadataReader.GetLocalScopes(SymMethod.Handle))
+ {
+ // The root scope has only a single child scope,
+ // which is the first scope in the scopes belonging to the method:
+ return ImmutableArray.Create(new ChildScopeData(SymMethod, this, handle));
+ }
+
+ // method has no body
+ return ImmutableArray.Empty;
+ }
+
+ internal override int GetConstants(int bufferLength, out int count, ISymUnmanagedConstant[] constants)
+ {
+ // C# and VB never define any constants in the root scope
+ count = 0;
+ return HResult.S_OK;
+ }
+
+ internal override int GetLocals(int bufferLength, out int count, ISymUnmanagedVariable[] locals)
+ {
+ // C# and VB never define any locals in the root scope
+ count = 0;
+ return HResult.S_OK;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/ScopeData.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/ScopeData.cs
new file mode 100644
index 0000000000000..e93ffc9043474
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/ScopeData.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ internal abstract class ScopeData
+ {
+ internal readonly SymMethod SymMethod;
+
+ private ImmutableArray _lazyChildren;
+
+ internal ScopeData(SymMethod symMethod)
+ {
+ Debug.Assert(symMethod != null);
+ SymMethod = symMethod;
+ }
+
+ internal ImmutableArray GetChildren()
+ {
+ if (_lazyChildren.IsDefault)
+ {
+ _lazyChildren = CreateChildren();
+ }
+
+ return _lazyChildren;
+ }
+
+ public int AdjustEndOffset(int value)
+ {
+ // Portable PDB uses edge-exclusive semantics like C#.
+ // VB end offset is inclusive.
+ return SymMethod.SymReader.VbSemantics.Value && !(Parent is RootScopeData) ? value - 1 : value;
+ }
+
+ protected abstract ImmutableArray CreateChildren();
+
+ internal abstract int StartOffset { get; }
+ internal abstract int EndOffset { get; }
+ internal abstract ScopeData Parent { get; }
+ internal abstract int GetConstants(int bufferLength, out int count, ISymUnmanagedConstant[] constants);
+ internal abstract int GetLocals(int bufferLength, out int count, ISymUnmanagedVariable[] locals);
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymBinder.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymBinder.cs
new file mode 100644
index 0000000000000..49ea99a1a5576
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymBinder.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+[assembly: Guid("CA89ACD1-A1D5-43DE-890A-5FDF50BC1F93")]
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ [Guid("E4B18DEF-3B78-46AE-8F50-E67E421BDF70")]
+ [ComVisible(true)]
+ public class SymBinder : ISymUnmanagedBinder
+ {
+ [PreserveSig]
+ public int GetReaderForFile(
+ [MarshalAs(UnmanagedType.Interface)]object importer,
+ [MarshalAs(UnmanagedType.LPWStr)]string fileName,
+ [MarshalAs(UnmanagedType.LPWStr)]string searchPath,
+ [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedReader reader)
+ {
+ var pdbReader = new PortablePdbReader(File.OpenRead(fileName), importer);
+ reader = new SymReader(pdbReader);
+ return HResult.S_OK;
+ }
+
+ [PreserveSig]
+ public int GetReaderFromStream(
+ [MarshalAs(UnmanagedType.Interface)]object importer,
+ [MarshalAs(UnmanagedType.Interface)]object pstream,
+ [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedReader reader)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+ }
+}
+
+// regasm /codebase C:\R0\Binaries\Debug\Microsoft.DiaSymReader.PortablePdb.dll
+// tlbexp C:\R0\Binaries\Debug\Microsoft.DiaSymReader.PortablePdb.dll
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymConstant.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymConstant.cs
new file mode 100644
index 0000000000000..82dec09125761
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymConstant.cs
@@ -0,0 +1,234 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ [ComVisible(false)]
+ public sealed class SymConstant : ISymUnmanagedConstant
+ {
+ private readonly SymReader _symReader;
+ private readonly LocalConstantHandle _handle;
+
+ private object _lazyValue = Uninitialized;
+ private byte[] _lazySignature;
+
+ private static readonly object NullReferenceValue = 0;
+ private static readonly object Uninitialized = new object();
+
+ internal SymConstant(SymReader symReader, LocalConstantHandle handle)
+ {
+ Debug.Assert(symReader != null);
+ _symReader = symReader;
+ _handle = handle;
+ }
+
+ public int GetName(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]char[] name)
+ {
+ var mdReader = _symReader.MetadataReader;
+ var constant = mdReader.GetLocalConstant(_handle);
+
+ var str = mdReader.GetString(constant.Name);
+ return InteropUtilities.StringToBuffer(str, bufferLength, out count, name);
+ }
+
+ public int GetSignature(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]byte[] signature)
+ {
+ if (_lazySignature == null)
+ {
+ InitializeValueAndSignature();
+ }
+
+ return InteropUtilities.BytesToBuffer(_lazySignature, bufferLength, out count, signature);
+ }
+
+ public int GetValue(out object value)
+ {
+ if (_lazyValue == Uninitialized)
+ {
+ InitializeValueAndSignature();
+ }
+
+ value = _lazyValue;
+ return HResult.S_OK;
+ }
+
+ private void InitializeValueAndSignature()
+ {
+ var mdReader = _symReader.MetadataReader;
+ var constant = mdReader.GetLocalConstant(_handle);
+
+ var sigReader = mdReader.GetBlobReader(constant.Signature);
+ var sigWriter = new BlobWriter(sigReader.Length);
+
+ // custom modifiers:
+ int rawTypeCode;
+ while (true)
+ {
+ rawTypeCode = sigReader.ReadCompressedInteger();
+ if (rawTypeCode == (int)SignatureTypeCode.OptionalModifier || rawTypeCode == (int)SignatureTypeCode.RequiredModifier)
+ {
+ sigReader.ReadCompressedInteger();
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ int customModifiersLength = sigReader.Offset - 1;
+ if (customModifiersLength > 0)
+ {
+ sigWriter.Write(mdReader.GetBlobBytes(constant.Signature), 0, customModifiersLength);
+ }
+
+ object translatedValue;
+ if (rawTypeCode == (int)MetadataUtilities.SignatureTypeCode_ValueType ||
+ rawTypeCode == (int)MetadataUtilities.SignatureTypeCode_Class)
+ {
+ var typeHandle = sigReader.ReadTypeHandle();
+ if (sigReader.RemainingBytes == 0)
+ {
+ // null reference is returned as a boxed integer 0:
+ translatedValue = NullReferenceValue;
+ }
+ else
+ {
+ string qualifiedName = _symReader.PdbReader.MetadataImport.GetQualifiedTypeName(typeHandle);
+ if (qualifiedName == "System.Decimal")
+ {
+ translatedValue = sigReader.ReadDecimal();
+ }
+ else if (qualifiedName == "System.DateTime")
+ {
+ translatedValue = BitConverter.Int64BitsToDouble(sigReader.ReadDateTime().Ticks);
+ }
+ else
+ {
+ // unknown (not produced by C# or VB)
+ translatedValue = null;
+ }
+ }
+
+ sigWriter.Write((byte)rawTypeCode);
+ sigWriter.WriteCompressedInteger(MetadataUtilities.GetTypeDefOrRefOrSpecCodedIndex(typeHandle));
+ }
+ else
+ {
+ bool isEnumTypeCode;
+ translatedValue = ReadAndTranslateValue(ref sigReader, (SignatureTypeCode)rawTypeCode, out isEnumTypeCode);
+
+ if (sigReader.RemainingBytes == 0)
+ {
+ // primitive type code:
+ sigWriter.Write((byte)rawTypeCode);
+ }
+ else if (isEnumTypeCode)
+ {
+ var enumTypeHandle = sigReader.ReadTypeHandle();
+
+ // enum type signature:
+ sigWriter.Write((byte)MetadataUtilities.SignatureTypeCode_ValueType);
+ sigWriter.WriteCompressedInteger(MetadataUtilities.GetTypeDefOrRefOrSpecCodedIndex(enumTypeHandle));
+ }
+ else
+ {
+ throw new BadImageFormatException();
+ }
+ }
+
+ _lazyValue = translatedValue;
+ _lazySignature = sigWriter.ToArray();
+ }
+
+ private object ReadAndTranslateValue(ref BlobReader sigReader, SignatureTypeCode typeCode, out bool isEnumTypeCode)
+ {
+ switch (typeCode)
+ {
+ case SignatureTypeCode.Boolean:
+ isEnumTypeCode = true;
+ return (short)(sigReader.ReadBoolean() ? 1 : 0);
+
+ case SignatureTypeCode.Char:
+ isEnumTypeCode = true;
+ return (ushort)sigReader.ReadChar();
+
+ case SignatureTypeCode.SByte:
+ isEnumTypeCode = true;
+ return (short)sigReader.ReadSByte();
+
+ case SignatureTypeCode.Byte:
+ isEnumTypeCode = true;
+ return (short)sigReader.ReadByte();
+
+ case SignatureTypeCode.Int16:
+ isEnumTypeCode = true;
+ return sigReader.ReadInt16();
+
+ case SignatureTypeCode.UInt16:
+ isEnumTypeCode = true;
+ return sigReader.ReadUInt16();
+
+ case SignatureTypeCode.Int32:
+ isEnumTypeCode = true;
+ return sigReader.ReadInt32();
+
+ case SignatureTypeCode.UInt32:
+ isEnumTypeCode = true;
+ return sigReader.ReadUInt32();
+
+ case SignatureTypeCode.Int64:
+ isEnumTypeCode = true;
+ return sigReader.ReadInt64();
+
+ case SignatureTypeCode.UInt64:
+ isEnumTypeCode = true;
+ return sigReader.ReadUInt64();
+
+ case SignatureTypeCode.Single:
+ isEnumTypeCode = false;
+ return sigReader.ReadSingle();
+
+ case SignatureTypeCode.Double:
+ isEnumTypeCode = false;
+ return sigReader.ReadDouble();
+
+ case SignatureTypeCode.String:
+ isEnumTypeCode = false;
+
+ if (sigReader.RemainingBytes == 1)
+ {
+ if (sigReader.ReadByte() != 0xff)
+ {
+ throw new BadImageFormatException();
+ }
+
+ return NullReferenceValue;
+ }
+
+ if (sigReader.RemainingBytes % 2 != 0)
+ {
+ throw new BadImageFormatException();
+ }
+
+ return sigReader.ReadUTF16(sigReader.RemainingBytes);
+
+ case SignatureTypeCode.Object:
+ // null reference
+ isEnumTypeCode = false;
+ return NullReferenceValue;
+
+ default:
+ throw new BadImageFormatException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymDocument.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymDocument.cs
new file mode 100644
index 0000000000000..c8f0743b118c1
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymDocument.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ [ComVisible(false)]
+ public sealed class SymDocument : ISymUnmanagedDocument
+ {
+ private static Guid CSharpGuid = new Guid("3f5162f8-07c6-11d3-9053-00c04fa302a1");
+ private static Guid VisualBasicGuid = new Guid("3a12d0b8-c26c-11d0-b442-00a0244a1dd2");
+ private static Guid FSharpGuid = new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3");
+ private static Guid Sha1Guid = new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460");
+ private static Guid Sha256Guid = new Guid("8829d00f-11b8-4213-878b-770e8597ac16");
+
+ private static Guid VendorMicrosoftGuid = new Guid("994b45c4-e6e9-11d2-903f-00c04fa302a1");
+ private static Guid DocumentTypeGuid = new Guid("5a869d0b-6611-11d3-bd2a-0000f80849bd");
+
+ private readonly DocumentHandle _handle;
+ private readonly SymReader _symReader;
+
+ internal SymDocument(SymReader symReader, DocumentHandle documentHandle)
+ {
+ Debug.Assert(symReader != null);
+ _symReader = symReader;
+ _handle = documentHandle;
+ }
+
+ internal DocumentHandle Handle => _handle;
+
+ public int FindClosestLine(int line, out int closestLine)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetChecksum(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]byte[] checksum)
+ {
+ var document = _symReader.MetadataReader.GetDocument(_handle);
+ if (document.Hash.IsNil)
+ {
+ count = 0;
+ return HResult.S_FALSE;
+ }
+
+ var hash = _symReader.MetadataReader.GetBlobBytes(document.Hash);
+ return InteropUtilities.BytesToBuffer(hash, bufferLength, out count, checksum);
+ }
+
+ public int GetChecksumAlgorithmId(ref Guid algorithm)
+ {
+ var document = _symReader.MetadataReader.GetDocument(_handle);
+ algorithm = _symReader.MetadataReader.GetGuid(document.HashAlgorithm);
+ return HResult.S_OK;
+ }
+
+ public int GetDocumentType(ref Guid documentType)
+ {
+ documentType = DocumentTypeGuid;
+ return HResult.S_OK;
+ }
+
+ public int GetLanguage(ref Guid language)
+ {
+ var document = _symReader.MetadataReader.GetDocument(_handle);
+ language = _symReader.MetadataReader.GetGuid(document.Language);
+ return HResult.S_OK;
+ }
+
+ public int GetLanguageVendor(ref Guid vendor)
+ {
+ var document = _symReader.MetadataReader.GetDocument(_handle);
+ Guid languageId = _symReader.MetadataReader.GetGuid(document.Language);
+ vendor = VendorMicrosoftGuid;
+ return HResult.S_OK;
+ }
+
+ public int GetSourceLength(out int length)
+ {
+ // SymReader doesn't support embedded source.
+ length = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetSourceRange(
+ int startLine,
+ int startColumn,
+ int endLine,
+ int endColumn,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4), Out]byte[] source)
+ {
+ // SymReader doesn't support embedded source.
+ count = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetUrl(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]char[] url)
+ {
+ string name = _symReader.MetadataReader.GetString(_symReader.MetadataReader.GetDocument(_handle).Name);
+ return InteropUtilities.StringToBuffer(name, bufferLength, out count, url);
+ }
+
+ public int HasEmbeddedSource(out bool value)
+ {
+ // SymReader doesn't support embedded source.
+ value = false;
+ return HResult.E_NOTIMPL;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymMethod.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymMethod.cs
new file mode 100644
index 0000000000000..670c31a8b7976
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymMethod.cs
@@ -0,0 +1,342 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ [ComVisible(false)]
+ public sealed class SymMethod : ISymUnmanagedMethod, ISymUnmanagedAsyncMethod
+ {
+ private readonly MethodDefinitionHandle _handle;
+ private readonly SymReader _symReader;
+ private RootScopeData _lazyRootScopeData;
+ private AsyncMethodData _lazyAsyncMethodData;
+
+ internal SymMethod(SymReader symReader, MethodDefinitionHandle handle)
+ {
+ Debug.Assert(symReader != null);
+ _symReader = symReader;
+ _handle = handle;
+ }
+
+ internal SymReader SymReader => _symReader;
+ internal MetadataReader MetadataReader => _symReader.MetadataReader;
+ internal MethodDefinitionHandle Handle => _handle;
+
+ #region ISymUnmanagedMethod
+
+ public int GetNamespace([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedNamespace @namespace)
+ {
+ // SymReader doesn't support namspaces
+ @namespace = null;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetOffset(ISymUnmanagedDocument document, int line, int column, out int offset)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetParameters(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedVariable[] parameters)
+ {
+ // SymReader doesn't support parameter access.
+ count = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetRanges(
+ ISymUnmanagedDocument document,
+ int line,
+ int column,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), Out]int[] ranges)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetRootScope([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedScope scope)
+ {
+ if (_lazyRootScopeData == null)
+ {
+ _lazyRootScopeData = new RootScopeData(this);
+ }
+
+ // SymReader always creates a new scope instance
+ scope = new SymScope(_lazyRootScopeData);
+ return HResult.S_OK;
+ }
+
+ public int GetScopeFromOffset(int offset, [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedScope scope)
+ {
+ // SymReader doesn't support.
+ scope = null;
+ return HResult.S_OK;
+ }
+
+ public int GetSequencePointCount(out int count)
+ {
+ return GetSequencePoints(0, out count, null, null, null, null, null, null);
+ }
+
+ public int GetSequencePoints(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] offsets,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedDocument[] documents,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] startLines,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] startColumns,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] endLines,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] endColumns)
+ {
+ // TODO: cache
+
+ var mdReader = _symReader.MetadataReader;
+
+ var body = mdReader.GetMethodBody(_handle);
+ var spReader = mdReader.GetSequencePointsReader(body.SequencePoints);
+
+ SymDocument currentDocument = null;
+
+ int i = 0;
+ while (spReader.MoveNext())
+ {
+ if (bufferLength != 0 && i >= bufferLength)
+ {
+ break;
+ }
+
+ var sp = spReader.Current;
+
+ if (offsets != null)
+ {
+ offsets[i] = sp.Offset;
+ }
+
+ if (startLines != null)
+ {
+ startLines[i] = sp.StartLine;
+ }
+
+ if (startColumns != null)
+ {
+ startColumns[i] = sp.StartColumn;
+ }
+
+ if (endLines != null)
+ {
+ endLines[i] = sp.EndLine;
+ }
+
+ if (endColumns != null)
+ {
+ endColumns[i] = sp.EndColumn;
+ }
+
+ if (documents != null)
+ {
+ if (currentDocument == null || currentDocument.Handle != sp.Document)
+ {
+ currentDocument = new SymDocument(_symReader, sp.Document);
+ }
+
+ documents[i] = currentDocument;
+ }
+
+ i++;
+ }
+
+ count = i;
+ return HResult.S_OK;
+ }
+
+ public int GetSourceStartEnd(
+ ISymUnmanagedDocument[] documents,
+ [In, MarshalAs(UnmanagedType.LPArray), Out]int[] lines,
+ [In, MarshalAs(UnmanagedType.LPArray), Out]int[] columns,
+ out bool defined)
+ {
+ // This symbol reader doesn't support source start/end for methods.
+ defined = false;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetToken(out int methodToken)
+ {
+ methodToken = MetadataTokens.GetToken(_handle);
+ return HResult.S_OK;
+ }
+
+ #endregion
+
+ #region ISymUnmanagedAsyncMethod
+
+ private AsyncMethodData AsyncMethodData
+ {
+ get
+ {
+ if (_lazyAsyncMethodData == null)
+ {
+ _lazyAsyncMethodData = ReadAsyncMethodData();
+ }
+
+ return _lazyAsyncMethodData;
+ }
+ }
+
+ private AsyncMethodData ReadAsyncMethodData()
+ {
+ var reader = MetadataReader;
+ var body = reader.GetMethodBody(_handle);
+ var kickoffMethod = body.GetStateMachineKickoffMethod();
+
+ if (kickoffMethod.IsNil)
+ {
+ return AsyncMethodData.None;
+ }
+
+ var value = reader.GetCustomDebugInformation(_handle, MetadataUtilities.MethodSteppingInformationBlobId);
+ if (value.IsNil)
+ {
+ return AsyncMethodData.None;
+ }
+
+ var blobReader = reader.GetBlobReader(value);
+
+ long catchHandlerOffset = blobReader.ReadUInt32();
+ if (catchHandlerOffset > (uint)int.MaxValue + 1)
+ {
+ throw new BadImageFormatException();
+ }
+
+ var yieldOffsets = ImmutableArray.CreateBuilder();
+ var resultOffsets = ImmutableArray.CreateBuilder();
+ var resumeMethods = ImmutableArray.CreateBuilder();
+
+ while (blobReader.RemainingBytes > 0)
+ {
+ uint yieldOffset = blobReader.ReadUInt32();
+ if (yieldOffset > int.MaxValue)
+ {
+ throw new BadImageFormatException();
+ }
+
+ uint resultOffset = blobReader.ReadUInt32();
+ if (resultOffset > int.MaxValue)
+ {
+ throw new BadImageFormatException();
+ }
+
+ yieldOffsets.Add((int)yieldOffset);
+ resultOffsets.Add((int)resultOffset);
+ resumeMethods.Add(MetadataUtilities.MethodDefToken(blobReader.ReadCompressedInteger()));
+ }
+
+ return new AsyncMethodData(
+ kickoffMethod,
+ (int)(catchHandlerOffset - 1),
+ yieldOffsets.ToImmutable(),
+ resultOffsets.ToImmutable(),
+ resumeMethods.ToImmutable());
+ }
+
+ public int IsAsyncMethod(out bool value)
+ {
+ value = !AsyncMethodData.IsNone;
+ return HResult.S_OK;
+ }
+
+ public int GetKickoffMethod(out int kickoffMethodToken)
+ {
+ if (AsyncMethodData.IsNone)
+ {
+ kickoffMethodToken = 0;
+ return HResult.E_UNEXPECTED;
+ }
+
+ kickoffMethodToken = MetadataTokens.GetToken(AsyncMethodData.KickoffMethod);
+ return HResult.S_OK;
+ }
+
+ public int HasCatchHandlerILOffset(out bool value)
+ {
+ if (AsyncMethodData.IsNone)
+ {
+ value = false;
+ return HResult.E_UNEXPECTED;
+ }
+
+ value = AsyncMethodData.CatchHandlerOffset >= 0;
+ return HResult.S_OK;
+ }
+
+ public int GetCatchHandlerILOffset(out int offset)
+ {
+ if (AsyncMethodData.IsNone || AsyncMethodData.CatchHandlerOffset < 0)
+ {
+ offset = 0;
+ return HResult.E_UNEXPECTED;
+ }
+
+ offset = AsyncMethodData.CatchHandlerOffset;
+ return HResult.S_OK;
+ }
+
+ public int GetAsyncStepInfoCount(out int count)
+ {
+ if (AsyncMethodData.IsNone)
+ {
+ count = 0;
+ return HResult.E_UNEXPECTED;
+ }
+
+ count = AsyncMethodData.YieldOffsets.Length;
+ return HResult.S_OK;
+ }
+
+ public int GetAsyncStepInfo(
+ int bufferLength,
+ out int count,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]int[] yieldOffsets,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]int[] breakpointOffsets,
+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]int[] breakpointMethods)
+ {
+ if (AsyncMethodData.IsNone)
+ {
+ count = 0;
+ return HResult.E_UNEXPECTED;
+ }
+
+ int length = Math.Min(bufferLength, AsyncMethodData.YieldOffsets.Length);
+
+ if (yieldOffsets != null)
+ {
+ AsyncMethodData.YieldOffsets.CopyTo(0, yieldOffsets, 0, length);
+ }
+
+ if (breakpointOffsets != null)
+ {
+ AsyncMethodData.ResumeOffsets.CopyTo(0, breakpointOffsets, 0, length);
+ }
+
+ if (breakpointMethods != null)
+ {
+ AsyncMethodData.ResumeMethods.CopyTo(0, breakpointMethods, 0, length);
+ }
+
+ count = length;
+ return HResult.S_OK;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymReader.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymReader.cs
new file mode 100644
index 0000000000000..c676001bcfc9c
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymReader.cs
@@ -0,0 +1,331 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ // TODO:
+ // ISymUnmanagedReaderSymbolSearchInfo?
+ // ISymUnmanagedSourceServerModule?
+
+ [ComVisible(false)]
+ public sealed class SymReader : ISymUnmanagedReader3, ISymUnmanagedDispose
+ {
+ private readonly PortablePdbReader _pdbReader;
+ private readonly Lazy _lazyDocumentMap;
+ private readonly Lazy _lazyVbSemantics;
+
+ private int _version;
+
+ ///
+ /// Creates .
+ ///
+ ///
+ ///
+ /// Takes ownership of .
+ ///
+ public SymReader(PortablePdbReader pdbReader)
+ {
+ Debug.Assert(pdbReader != null);
+
+ _pdbReader = pdbReader;
+ _version = 1;
+
+ _lazyDocumentMap = new Lazy(() => new DocumentMap(MetadataReader));
+ _lazyVbSemantics = new Lazy(() => IsVisualBasicAssembly());
+ }
+
+ internal MetadataReader MetadataReader => _pdbReader.MetadataReader;
+ internal PortablePdbReader PdbReader => _pdbReader;
+ internal Lazy VbSemantics => _lazyVbSemantics;
+
+ public int Destroy()
+ {
+ _pdbReader.Dispose();
+ return HResult.S_OK;
+ }
+
+ private bool IsVisualBasicAssembly()
+ {
+ var reader = MetadataReader;
+
+ foreach (var cdiHandle in reader.GetCustomDebugInformation(Handle.ModuleDefinition))
+ {
+ if (reader.GetGuid(reader.GetCustomDebugInformation(cdiHandle).Kind) == MetadataUtilities.VbDefaultNamespaceId)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public int GetDocument(
+ [MarshalAs(UnmanagedType.LPWStr)]string url,
+ Guid language,
+ Guid languageVendor,
+ Guid documentType,
+ [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedDocument document)
+ {
+ DocumentHandle documentHandle;
+
+ // SymReader: language, vendor and type parameters are ignored.
+
+ if (_lazyDocumentMap.Value.TryGetDocument(url, out documentHandle))
+ {
+ document = new SymDocument(this, documentHandle);
+ return HResult.S_OK;
+ }
+
+ document = null;
+ return HResult.S_FALSE;
+ }
+
+ public int GetDocuments(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedDocument[] documents)
+ {
+ count = MetadataReader.Documents.Count;
+
+ if (bufferLength == 0)
+ {
+ return HResult.S_OK;
+ }
+
+ int i = 0;
+ foreach (var documentHandle in MetadataReader.Documents)
+ {
+ if (i >= bufferLength)
+ {
+ break;
+ }
+
+ documents[i++] = new SymDocument(this, documentHandle);
+ }
+
+ return HResult.S_OK;
+ }
+
+ public int GetDocumentVersion(ISymUnmanagedDocument document, out int version, out bool isCurrent)
+ {
+ // SymReader always returns the same values
+ version = 1;
+ isCurrent = true;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetGlobalVariables(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedVariable[] variables)
+ {
+ // SymReader doesn't support.
+ count = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetMethod(int methodToken, [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedMethod method)
+ {
+ return GetMethodByVersion(methodToken, _version, out method);
+ }
+
+ public int GetMethodByVersion(
+ int methodToken,
+ int version,
+ [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedMethod method)
+ {
+ if (version != _version)
+ {
+ method = null;
+ return HResult.E_INVALIDARG;
+ }
+
+ var handle = MetadataTokens.Handle(methodToken);
+ if (handle.Kind != HandleKind.MethodDefinition)
+ {
+ method = null;
+ return HResult.E_INVALIDARG;
+ }
+
+ var methodDefHandle = (MethodDefinitionHandle)handle;
+
+ var methodBody = MetadataReader.GetMethodBody(methodDefHandle);
+ if (methodBody.SequencePoints.IsNil)
+ {
+ // no debug info for the method
+ method = null;
+ return HResult.E_FAIL;
+ }
+
+ method = new SymMethod(this, methodDefHandle);
+ return HResult.S_OK;
+ }
+
+ public int GetMethodByVersionPreRemap(int methodToken, int version, [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedMethod method)
+ {
+ // TODO:
+ throw new NotSupportedException();
+ }
+
+ public int GetMethodFromDocumentPosition(ISymUnmanagedDocument document, int line, int column, [MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedMethod method)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetMethodsFromDocumentPosition(ISymUnmanagedDocument document, int line, int column, int bufferLength, out int count, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), Out]ISymUnmanagedMethod[] methods)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetMethodsInDocument(ISymUnmanagedDocument document, int bufferLength, out int count, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), Out]ISymUnmanagedMethod[] methods)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetMethodVersion(ISymUnmanagedMethod method, out int version)
+ {
+ version = _version;
+ return HResult.S_OK;
+ }
+
+ public int GetNamespaces(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedNamespace[] namespaces)
+ {
+ // SymReader doesn't support
+ count = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetSymAttribute(int methodToken,
+ [MarshalAs(UnmanagedType.LPWStr)]string name,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2), Out]byte[] customDebugInformation)
+ {
+ return GetSymAttributeByVersion(methodToken, 1, name, bufferLength, out count, customDebugInformation);
+ }
+
+ public int GetSymAttributeByVersion(
+ int methodToken,
+ int version,
+ [MarshalAs(UnmanagedType.LPWStr)]string name,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), Out]byte[] customDebugInformation)
+ {
+ if ((bufferLength != 0) != (customDebugInformation != null))
+ {
+ count = 0;
+ return HResult.E_INVALIDARG;
+ }
+
+ if (version != _version)
+ {
+ count = 0;
+ return HResult.E_INVALIDARG;
+ }
+
+ if (name == "")
+ {
+ count = _pdbReader.Image.Length;
+
+ if (bufferLength == 0)
+ {
+ return HResult.S_FALSE;
+ }
+
+ Buffer.BlockCopy(_pdbReader.Image, 0, customDebugInformation, 0, bufferLength);
+ return HResult.S_OK;
+ }
+
+ count = 0;
+ return HResult.S_FALSE;
+ }
+
+ public int GetSymAttributePreRemap(
+ int methodToken,
+ [MarshalAs(UnmanagedType.LPWStr)]string name,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2), Out]byte[] customDebugInformation)
+ {
+ // TODO:
+ throw new NotSupportedException();
+ }
+
+ public int GetSymAttributeByVersionPreRemap(
+ int methodToken,
+ int version,
+ [MarshalAs(UnmanagedType.LPWStr)]string name,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), Out]byte[] customDebugInformation)
+ {
+ // TODO:
+ throw new NotSupportedException();
+ }
+
+ public int GetSymbolStoreFileName(int bufferLength, out int count, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]char[] name)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int GetUserEntryPoint(out int methodToken)
+ {
+ var handle = MetadataReader.DebugMetadataHeader.EntryPoint;
+ if (!handle.IsNil)
+ {
+ methodToken = MetadataTokens.GetToken(handle);
+ return HResult.S_OK;
+ }
+
+ methodToken = 0;
+ return HResult.E_FAIL;
+ }
+
+ public int GetVariables(
+ int methodToken,
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), Out]ISymUnmanagedVariable[] variables)
+ {
+ // SymReader doesn't support non-local variables.
+ count = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int Initialize(
+ [MarshalAs(UnmanagedType.Interface)]object metadataImporter,
+ [MarshalAs(UnmanagedType.LPWStr)]string fileName,
+ [MarshalAs(UnmanagedType.LPWStr)]string searchPath,
+ IStream stream)
+ {
+ return HResult.S_OK;
+ }
+
+ public int ReplaceSymbolStore([MarshalAs(UnmanagedType.LPWStr)]string fileName, IStream stream)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+
+ public int UpdateSymbolStore([MarshalAs(UnmanagedType.LPWStr)]string fileName, IStream stream)
+ {
+ // TODO:
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymScope.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymScope.cs
new file mode 100644
index 0000000000000..06611111117e7
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymScope.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ [ComVisible(false)]
+ public sealed class SymScope : ISymUnmanagedScope2
+ {
+ internal readonly ScopeData _data;
+
+ internal SymScope(ScopeData data)
+ {
+ Debug.Assert(data != null);
+ _data = data;
+ }
+
+ public int GetChildren(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedScope[] children)
+ {
+ var childrenData = _data.GetChildren();
+
+ int i = 0;
+ foreach (var childData in childrenData)
+ {
+ if (i >= bufferLength)
+ {
+ break;
+ }
+
+ children[i++] = new SymScope(childData);
+ }
+
+ count = (bufferLength == 0) ? childrenData.Length : i;
+ return HResult.S_OK;
+ }
+
+ public int GetConstantCount(out int count)
+ {
+ return GetConstants(0, out count, null);
+ }
+
+ public int GetConstants(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedConstant[] constants)
+ {
+ return _data.GetConstants(bufferLength, out count, constants);
+ }
+
+ public int GetLocalCount(out int count)
+ {
+ return GetLocals(0, out count, null);
+ }
+
+ public int GetLocals(int bufferLength, out int count, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedVariable[] locals)
+ {
+ return _data.GetLocals(bufferLength, out count, locals);
+ }
+
+ public int GetMethod([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedMethod method)
+ {
+ method = _data.SymMethod;
+ return HResult.S_OK;
+ }
+
+ public int GetParent([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedScope scope)
+ {
+ var parentData = _data.Parent;
+ scope = (parentData != null) ? new SymScope(parentData) : null;
+ return HResult.S_OK;
+ }
+
+ public int GetStartOffset(out int offset)
+ {
+ offset = _data.StartOffset;
+ return HResult.S_OK;
+ }
+
+ public int GetEndOffset(out int offset)
+ {
+ offset = _data.EndOffset;
+ return HResult.S_OK;
+ }
+
+ public int GetNamespaces(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]ISymUnmanagedNamespace[] namespaces)
+ {
+ // Language specific, the client has to use Portable PDB reader directly to access the data.
+ // Pretend there are no namespace scopes.
+ count = 0;
+ return HResult.S_OK;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymVariable.cs b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymVariable.cs
new file mode 100644
index 0000000000000..face653357be9
--- /dev/null
+++ b/src/Debugging/Microsoft.DiaSymReader.PortablePdb/SymVariable.cs
@@ -0,0 +1,167 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Decoding;
+using System.Reflection.Metadata.Ecma335;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.DiaSymReader.PortablePdb
+{
+ [ComVisible(false)]
+ public sealed class SymVariable : ISymUnmanagedVariable
+ {
+ private const int ADDR_IL_OFFSET = 1;
+
+ private readonly SymMethod _symMethod;
+ private readonly LocalVariableHandle _handle;
+
+ internal SymVariable(SymMethod symMethod, LocalVariableHandle handle)
+ {
+ Debug.Assert(symMethod != null);
+ _symMethod = symMethod;
+ _handle = handle;
+ }
+
+ private MetadataReader MetadataReader => _symMethod.MetadataReader;
+
+ public int GetAttributes(out int attributes)
+ {
+ var variable = MetadataReader.GetLocalVariable(_handle);
+ attributes = (int)variable.Attributes;
+ return HResult.S_OK;
+ }
+
+ public int GetAddressField1(out int value)
+ {
+ var variable = MetadataReader.GetLocalVariable(_handle);
+ value = variable.Index;
+ return HResult.S_OK;
+ }
+
+ public int GetAddressField2(out int value)
+ {
+ // not implemented by DiaSymReader
+ value = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetAddressField3(out int value)
+ {
+ // not implemented by DiaSymReader
+ value = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetStartOffset(out int offset)
+ {
+ // not implemented by DiaSymReader
+ offset = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetEndOffset(out int offset)
+ {
+ // not implemented by DiaSymReader
+ offset = 0;
+ return HResult.E_NOTIMPL;
+ }
+
+ public int GetAddressKind(out int kind)
+ {
+ kind = ADDR_IL_OFFSET;
+ return HResult.S_OK;
+ }
+
+ public int GetName(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]char[] name)
+ {
+ var variable = MetadataReader.GetLocalVariable(_handle);
+ var str = MetadataReader.GetString(variable.Name);
+ return InteropUtilities.StringToBuffer(str, bufferLength, out count, name);
+ }
+
+ public unsafe int GetSignature(
+ int bufferLength,
+ out int count,
+ [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]byte[] signature)
+ {
+ var localSignatureHandle = _symMethod.MetadataReader.GetMethodBody(_symMethod.Handle).LocalSignature;
+ var metadataImport = _symMethod.SymReader.PdbReader.MetadataImport;
+ var local = _symMethod.MetadataReader.GetLocalVariable(_handle);
+
+ byte* signaturePtr;
+ int signatureLength;
+ int hr = metadataImport.GetSigFromToken(MetadataTokens.GetToken(localSignatureHandle), out signaturePtr, out signatureLength);
+ if (hr != HResult.S_OK)
+ {
+ count = 0;
+ return hr;
+ }
+
+ var signatureReader = new BlobReader(signaturePtr, signatureLength);
+
+ SignatureHeader header = signatureReader.ReadSignatureHeader();
+ if (header.Kind != SignatureKind.LocalVariables)
+ {
+ count = 0;
+ return HResult.E_FAIL;
+ }
+
+ int slotCount = signatureReader.ReadCompressedInteger();
+ int slotIndex = local.Index;
+ if (slotIndex >= slotCount)
+ {
+ count = 0;
+ return HResult.E_FAIL;
+ }
+
+ var typeProvider = new DummyTypeProvider(_symMethod.MetadataReader);
+
+ for (int i = 0; i < slotIndex - 1; i++)
+ {
+ SignatureDecoder.DecodeType(ref signatureReader, typeProvider);
+ }
+
+ int localSlotStart = signatureReader.Offset;
+ SignatureDecoder.DecodeType(ref signatureReader, typeProvider);
+ int localSlotLength = signatureReader.Offset - localSlotStart;
+
+ if (localSlotLength <= bufferLength)
+ {
+ Marshal.Copy((IntPtr)(signaturePtr + localSlotStart), signature, 0, localSlotLength);
+ }
+
+ count = localSlotLength;
+ return HResult.S_OK;
+ }
+
+ private sealed class DummyTypeProvider : ISignatureTypeProvider