Add the managed SOS code.
authorMike McLaughlin <mikem@microsoft.com>
Thu, 22 Mar 2018 01:58:34 +0000 (18:58 -0700)
committerMike McLaughlin <mikem@microsoft.com>
Thu, 26 Apr 2018 22:47:59 +0000 (15:47 -0700)
Using the Roslyn repo tool set to build (for now).

23 files changed:
.gitattributes [new file with mode: 0644]
.gitignore
Build.cmd [new file with mode: 0644]
Directory.Build.props [new file with mode: 0644]
NuGet.Config [new file with mode: 0644]
Restore.cmd [new file with mode: 0644]
Test.cmd [new file with mode: 0644]
build.sh [new file with mode: 0755]
diagnostics.sln [new file with mode: 0644]
eng/SignToolData.json [new file with mode: 0644]
eng/Versions.props [new file with mode: 0644]
eng/common/CIBuild.cmd [new file with mode: 0644]
eng/common/build.ps1 [new file with mode: 0644]
eng/common/build.sh [new file with mode: 0755]
eng/common/cibuild.sh [new file with mode: 0755]
global.json [new file with mode: 0644]
netci.groovy [new file with mode: 0644]
pack.cmd [new file with mode: 0644]
restore.sh [new file with mode: 0755]
src/Directory.Build.props [new file with mode: 0644]
src/Directory.Build.targets [new file with mode: 0644]
src/SOS/NETCore/SOS.NETCore.csproj [new file with mode: 0644]
src/SOS/NETCore/SymbolReader.cs [new file with mode: 0644]

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..e53d317
--- /dev/null
@@ -0,0 +1,49 @@
+###############################################################################
+# Set default behavior to:
+#   automatically normalize line endings on check-in, and
+#   convert to Windows-style line endings on check-out
+###############################################################################
+* text=auto encoding=UTF-8
+*.sh text eol=lf
+
+###############################################################################
+# Set file behavior to:
+#   treat as text, and
+#   diff as C# source code
+###############################################################################
+*.cs                   text            diff=csharp
+
+###############################################################################
+# Set file behavior to:
+#   treat as text
+###############################################################################
+*.cmd                  text
+*.config               text
+*.csproj               text
+*.groovy                       text
+*.json                 text
+*.md                   text
+*.nuspec                       text
+*.pkgdef                       text
+*.proj                         text
+*.projitems                    text
+*.props                text
+*.ps1                  text
+*.resx                         text
+*.ruleset                      text
+*.shproj                       text
+*.sln                  text
+*.targets              text
+*.vb                           text
+*.vbproj                       text
+*.vcxproj                      text
+*.vcxproj.filters      text
+*.vsct                         text
+*.vsixmanifest         text
+
+###############################################################################
+# Set file behavior to:
+#   treat as binary
+###############################################################################
+*.png                          binary
+*.snk                  binary
index 8c2f099f09302933c4d28aba1e00057906c73058..11d202635797f1b3fcf9cbb63348024b82485774 100644 (file)
@@ -1,60 +1,29 @@
-syntax: glob
-
-[Bb]inaries/
-
-# Build tools related files
-/[Tt]ools/
-
-### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
 
 # User-specific files
 *.suo
 *.user
-*.userosscache
 *.sln.docstates
-*.swp
+.vs/
+*.VC.db
 
 # Build results
+[Aa]rtifacts/
 [Dd]ebug/
-[Dd]ebugPublic/
 [Rr]elease/
-[Rr]eleases/
 x64/
-x86/
-build/
-bld/
 [Bb]in/
 [Oo]bj/
-msbuild.log
-
-# add back architecture directories ignored in 'Build results'
-!tests/x86
-!src/mscorlib/src/System/Runtime/Intrinsics/X86
-!tests/src/JIT/HardwareIntrinsics/X86
-
-# Visual Studio 2015
-.vs/
+.dotnet/
+.packages/
+.tools/
 
-# Visual Studio 2015 Pre-CTP6
-*.sln.ide
-*.ide/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-#NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
+# Per-user project properties
+launchSettings.json
 
 *_i.c
 *_p.c
-*_i.h
 *.ilk
 *.meta
 *.obj
@@ -62,7 +31,6 @@ dlldata.c
 *.pdb
 *.pgc
 *.pgd
-*.rsp
 *.sbr
 *.tlb
 *.tli
@@ -70,45 +38,34 @@ dlldata.c
 *.tmp
 *.tmp_proj
 *.log
-*.html
+*.wrn
 *.vspscc
 *.vssscc
 .builds
 *.pidb
-*.svclog
+*.log
 *.scc
 
-# Chutzpah Test files
-_Chutzpah*
-
 # Visual C++ cache files
 ipch/
 *.aps
 *.ncb
-*.opendb
 *.opensdf
 *.sdf
 *.cachefile
-*.VC.db
+*.VC.opendb
 
 # Visual Studio profiler
 *.psess
 *.vsp
 *.vspx
 
-# TFS 2012 Local Workspace
-$tf/
-
 # Guidance Automation Toolkit
 *.gpState
 
 # ReSharper is a .NET coding add-in
 _ReSharper*/
 *.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding addin-in
-.JustCode
 
 # TeamCity is a build add-in
 _TeamCity*
@@ -117,106 +74,28 @@ _TeamCity*
 *.dotCover
 
 # NCrunch
-_NCrunch_*
+*.ncrunch*
 .*crunch*.local.xml
 
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-*.pubxml
-*.publishproj
-
-# NuGet Packages
-*.nupkg
-*.nuget.g.props
-*.nuget.g.targets
-*.nuget.cache
-**/packages/*
-project.lock.json
-project.assets.json
-
-# Windows Azure Build Output
-csx/
-*.build.csdef
-
-# Windows Store app package directory
-AppPackages/
-
 # Others
 sql/
 *.Cache
 ClientBin/
 [Ss]tyle[Cc]op.*
 ~$*
+*~
 *.dbmdl
-*.dbproj.schemaview
+*.[Pp]ublish.xml
 *.pfx
 *.publishsettings
-node_modules/
-*.metaproj
-*.metaproj.tmp
-.atom-build.json
-tags
-TAGS
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
 
 # SQL Server files
-*.mdf
-*.ldf
+App_Data/*.mdf
+App_Data/*.ldf
 
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# C/C++ extension for Visual Studio Code
-browse.VC.db
-
-# Local settings folder for Visual Studio Code
-.vscode/
-
-### MonoDevelop ###
-
-*.pidb
-*.userprefs
-
-### Windows ###
+# =========================
+# Windows detritus
+# =========================
 
 # Windows image file caches
 Thumbs.db
@@ -228,95 +107,5 @@ Desktop.ini
 # Recycle Bin used on file shares
 $RECYCLE.BIN/
 
-# Windows Installer files
-*.cab
-*.msi
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-
-# Common binary extensions on Windows
-*.exe
-*.dll
-*.lib
-
-### Linux ###
-
-*~
-\#*\#
-
-# KDE directory preferences
-.directory
-
-### OSX ###
-
+# Mac desktop service store files
 .DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-# Thumbnails
-._*
-
-# Files that might appear on external disk
-.Spotlight-V100
-.Trashes
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-# We have some checked in prebuilt generated files
-!src/pal/prebuilt/idl/*_i.c
-
-# Valid 'debug' folder, that contains CLR debugging code
-!src/debug
-
-# Ignore folders created by the test build
-TestWrappers_x64_[d|D]ebug
-TestWrappers_x64_[c|C]hecked
-TestWrappers_x64_[r|R]elease
-TestWrappers_x86_[d|D]ebug
-TestWrappers_x86_[c|C]hecked
-TestWrappers_x86_[r|R]elease
-TestWrappers_arm_[d|D]ebug
-TestWrappers_arm_[c|C]hecked
-TestWrappers_arm_[r|R]elease
-TestWrappers_arm64_[d|D]ebug
-TestWrappers_arm64_[c|C]hecked
-TestWrappers_arm64_[r|R]elease
-tests/src/common/test_runtime/project.json
-
-Vagrantfile
-.vagrant
-
-# CMake files
-CMakeFiles/
-cmake_install.cmake
-CMakeCache.txt
-Makefile
-
-# Cross compilation
-cross/rootfs/*
-cross/android-rootfs/*
-# add x86 as it is ignored in 'Build results'
-!cross/x86
-
-#python import files
-*.pyc
-
-# JIT32 files
-src/jit32
-
-# performance testing sandbox
-sandbox
-
-#IL linker for testing
-linker
diff --git a/Build.cmd b/Build.cmd
new file mode 100644 (file)
index 0000000..7e77544
--- /dev/null
+++ b/Build.cmd
@@ -0,0 +1,3 @@
+@echo off
+powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -restore -build %*"
+exit /b %ErrorLevel%
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644 (file)
index 0000000..e8896ae
--- /dev/null
@@ -0,0 +1,12 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project>
+  <PropertyGroup>
+    <RepositoryUrl>https://github.com/dotnet/diagnostics.git</RepositoryUrl>
+    <RepositoryRawUrl>$(RepositoryUrl)</RepositoryRawUrl>
+    <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
+    <PackageLicenseUrl>http://go.microsoft.com/fwlink/?LinkId=529443</PackageLicenseUrl>
+    <PackageIconUrl>http://go.microsoft.com/fwlink/?LinkID=288859</PackageIconUrl>
+    <IsPublishable>false</IsPublishable>
+    <NoPackageAnalysis>true</NoPackageAnalysis>
+  </PropertyGroup>
+</Project>
diff --git a/NuGet.Config b/NuGet.Config
new file mode 100644 (file)
index 0000000..1b0fc7e
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <!-- Only specify feed for RepoToolset SDK (see https://github.com/Microsoft/msbuild/issues/2982) -->
+  <packageSources>
+    <clear />
+    <add key="roslyn-tools" value="https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json" />
+  </packageSources>
+</configuration>
diff --git a/Restore.cmd b/Restore.cmd
new file mode 100644 (file)
index 0000000..3fc17e8
--- /dev/null
@@ -0,0 +1,3 @@
+@echo off
+powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -restore %*"
+exit /b %ErrorLevel%
diff --git a/Test.cmd b/Test.cmd
new file mode 100644 (file)
index 0000000..8a841f6
--- /dev/null
+++ b/Test.cmd
@@ -0,0 +1,3 @@
+@echo off
+powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -test %*"
+exit /b %ErrorLevel%
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..1467a1d
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+source="${BASH_SOURCE[0]}"
+
+# resolve $SOURCE until the file is no longer a symlink
+while [[ -h $source ]]; do
+  scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+  source="$(readlink "$source")"
+
+  # if $source was a relative symlink, we need to resolve it relative to the path where the
+  # symlink file was located
+  [[ $source != /* ]] && source="$scriptroot/$source"
+done
+
+scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+"$scriptroot/eng/common/build.sh" --build --restore $@
\ No newline at end of file
diff --git a/diagnostics.sln b/diagnostics.sln
new file mode 100644 (file)
index 0000000..38e3017
--- /dev/null
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27004.2005
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SOS.NETCore", "src\SOS\NETCore\SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Any CPU = Debug|Any CPU
+               Debug|x64 = Debug|x64
+               Release|Any CPU = Release|Any CPU
+               Release|x64 = Release|x64
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.Build.0 = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+       GlobalSection(ExtensibilityGlobals) = postSolution
+               SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
+       EndGlobalSection
+EndGlobal
diff --git a/eng/SignToolData.json b/eng/SignToolData.json
new file mode 100644 (file)
index 0000000..6de1857
--- /dev/null
@@ -0,0 +1,25 @@
+{
+  "sign": [
+    {
+      "certificate": "MicrosoftSHA2",
+      "strongName": "MsSharedLib72",
+      "values": [
+        "bin/SOS.NETCore/netcoreapp1.0/SOS.NETCore.dll",
+      ]
+    },
+    {
+      "certificate": "NuGet",
+      "strongName": null,
+      "values": [
+        "packages/*.nupkg"
+      ]
+    }
+  ],
+  "exclude": [
+    "Microsoft.FileFormats.dll",
+    "Microsoft.SymbolStore.dll",
+    "System.Collections.Immutable.dll",
+    "System.Net.Http.dll",
+    "System.Reflection.Metadata.dll",
+  ]
+}
diff --git a/eng/Versions.props b/eng/Versions.props
new file mode 100644 (file)
index 0000000..18779ae
--- /dev/null
@@ -0,0 +1,25 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project>
+  <PropertyGroup>
+    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+
+    <!-- This repo version -->
+    <VersionPrefix>1.0.0</VersionPrefix>
+    <PreReleaseVersionLabel>beta</PreReleaseVersionLabel>
+  
+    <UsingToolNetFrameworkReferenceAssemblies>true</UsingToolNetFrameworkReferenceAssemblies>
+    <UsingToolXliff>false</UsingToolXliff>
+
+    <!-- CoreFX -->
+    <SystemReflectionMetadataVersion>1.6.0-preview2-26406-04</SystemReflectionMetadataVersion>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <RestoreSources>
+      $(RestoreSources);
+      https://dotnet.myget.org/F/symstore/api/v3/index.json;
+      https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json;
+      https://dotnet.myget.org/F/dotnet-core/api/v3/index.json
+    </RestoreSources>
+  </PropertyGroup>
+</Project>
diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd
new file mode 100644 (file)
index 0000000..42bb58b
--- /dev/null
@@ -0,0 +1,3 @@
+@echo off
+powershell -ExecutionPolicy ByPass -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -ci %*"
+exit /b %ErrorLevel%
diff --git a/eng/common/build.ps1 b/eng/common/build.ps1
new file mode 100644 (file)
index 0000000..7fe12a5
--- /dev/null
@@ -0,0 +1,252 @@
+[CmdletBinding(PositionalBinding=$false)]
+Param(
+  [string] $configuration = "Debug",
+  [string] $solution = "",
+  [string] $verbosity = "minimal",
+  [switch] $restore,
+  [switch] $deployDeps,
+  [switch] $build,
+  [switch] $rebuild,
+  [switch] $deploy,
+  [switch] $test,
+  [switch] $integrationTest,
+  [switch] $sign,
+  [switch] $pack,
+  [switch] $ci,
+  [switch] $prepareMachine,
+  [switch] $help,
+  [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties
+)
+
+set-strictmode -version 2.0
+$ErrorActionPreference = "Stop"
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+function Print-Usage() {
+    Write-Host "Common settings:"
+    Write-Host "  -configuration <value>  Build configuration Debug, Release"
+    Write-Host "  -verbosity <value>      Msbuild verbosity (q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic])"
+    Write-Host "  -help                   Print help and exit"
+    Write-Host ""
+
+    Write-Host "Actions:"
+    Write-Host "  -restore                Restore dependencies"
+    Write-Host "  -build                  Build solution"
+    Write-Host "  -rebuild                Rebuild solution"
+    Write-Host "  -deploy                 Deploy built VSIXes"
+    Write-Host "  -deployDeps             Deploy dependencies (e.g. VSIXes for integration tests)"
+    Write-Host "  -test                   Run all unit tests in the solution"
+    Write-Host "  -integrationTest        Run all integration tests in the solution"
+    Write-Host "  -sign                   Sign build outputs"
+    Write-Host "  -pack                   Package build outputs into NuGet packages and Willow components"
+    Write-Host ""
+
+    Write-Host "Advanced settings:"
+    Write-Host "  -solution <value>       Path to solution to build"
+    Write-Host "  -ci                     Set when running on CI server"
+    Write-Host "  -prepareMachine         Prepare machine for CI run"
+    Write-Host ""
+    Write-Host "Command line arguments not listed above are passed thru to msbuild."
+    Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)."
+}
+
+if ($help -or (($properties -ne $null) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) {
+  Print-Usage
+  exit 0
+}
+
+function Create-Directory([string[]] $path) {
+  if (!(Test-Path $path)) {
+    New-Item -path $path -force -itemType "Directory" | Out-Null
+  }
+}
+
+function InitializeDotNetCli {
+  # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism
+  $env:DOTNET_MULTILEVEL_LOOKUP=0
+  
+  # Disable first run since we do not need all ASP.NET packages restored.
+  $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+
+  # Source Build uses DotNetCoreSdkDir variable
+  if ($env:DotNetCoreSdkDir -ne $null) {
+    $env:DOTNET_INSTALL_DIR = $env:DotNetCoreSdkDir    
+  }
+
+  # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, 
+  # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues.
+  if (($env:DOTNET_INSTALL_DIR -ne $null) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$($GlobalJson.sdk.version)"))) {
+    $dotnetRoot = $env:DOTNET_INSTALL_DIR
+  } else {
+    $dotnetRoot = Join-Path $RepoRoot ".dotnet"
+    $env:DOTNET_INSTALL_DIR = $dotnetRoot
+    
+    if ($restore) {
+      InstallDotNetCli $dotnetRoot
+    }
+  }
+
+  $global:BuildDriver = Join-Path $dotnetRoot "dotnet.exe"    
+  $global:BuildArgs = "msbuild"
+}
+
+function InstallDotNetCli([string] $dotnetRoot) {
+  $installScript = "$dotnetRoot\dotnet-install.ps1"
+  if (!(Test-Path $installScript)) { 
+    Create-Directory $dotnetRoot
+    Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile $installScript
+  }
+  
+  & $installScript -Version $GlobalJson.sdk.version -InstallDir $dotnetRoot
+  if ($lastExitCode -ne 0) {
+    Write-Host "Failed to install dotnet cli (exit code '$lastExitCode')." -ForegroundColor Red
+    exit $lastExitCode
+  }
+}
+
+function InitializeVisualStudioBuild {
+  $inVSEnvironment = !($env:VS150COMNTOOLS -eq $null) -and (Test-Path $env:VS150COMNTOOLS)
+
+  if ($inVSEnvironment) {
+    $vsInstallDir = Join-Path $env:VS150COMNTOOLS "..\.."
+  } else {    
+    $vsInstallDir = LocateVisualStudio
+  
+    $env:VS150COMNTOOLS = Join-Path $vsInstallDir "Common7\Tools\"
+    $env:VSSDK150Install = Join-Path $vsInstallDir "VSSDK\"
+    $env:VSSDKInstall = Join-Path $vsInstallDir "VSSDK\"
+  }
+
+  $global:BuildDriver = Join-Path $vsInstallDir "MSBuild\15.0\Bin\msbuild.exe"
+  $global:BuildArgs = "/nodeReuse:$(!$ci)"
+}
+
+function LocateVisualStudio {
+  $vswhereVersion = $GlobalJson.vswhere.version
+  $toolsRoot = Join-Path $RepoRoot ".tools"
+  $vsWhereDir = Join-Path $toolsRoot "vswhere\$vswhereVersion"
+  $vsWhereExe = Join-Path $vsWhereDir "vswhere.exe"
+
+  if (!(Test-Path $vsWhereExe)) {
+    Create-Directory $vsWhereDir
+    Write-Host "Downloading vswhere"
+    Invoke-WebRequest "https://github.com/Microsoft/vswhere/releases/download/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe
+  }
+
+  $vsInstallDir = & $vsWhereExe -latest -prerelease -property installationPath -requires Microsoft.Component.MSBuild -requires Microsoft.VisualStudio.Component.VSSDK -requires Microsoft.Net.Component.4.6.TargetingPack -requires Microsoft.VisualStudio.Component.Roslyn.Compiler -requires Microsoft.VisualStudio.Component.VSSDK
+
+  if ($lastExitCode -ne 0) {
+    Write-Host "Failed to locate Visual Studio (exit code '$lastExitCode')." -ForegroundColor Red
+    exit $lastExitCode
+  }
+
+  return $vsInstallDir
+}
+
+function InitializeToolset {
+  $toolsetVersion = $GlobalJson.'msbuild-sdks'.'RoslynTools.RepoToolset'
+  $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt"
+
+  if (Test-Path $toolsetLocationFile) {
+    $path = Get-Content $toolsetLocationFile
+    if (Test-Path $path) {
+      $global:ToolsetBuildProj = $path
+      return
+    }
+  }
+    
+  if (-not $restore) {
+    Write-Host  "Toolset version $toolsetVersion has not been restored."
+    exit 1
+  }
+
+  $proj = Join-Path $ToolsetDir "restore.proj"  
+
+  '<Project Sdk="RoslynTools.RepoToolset"/>' | Set-Content $proj
+  & $BuildDriver $BuildArgs $proj /t:__WriteToolsetLocation /m /nologo /clp:None /warnaserror /bl:$ToolsetRestoreLog /v:$verbosity /p:__ToolsetLocationOutputFile=$toolsetLocationFile
+    
+  if ($lastExitCode -ne 0) {
+    Write-Host "Failed to restore toolset (exit code '$lastExitCode')." -Color Red
+    Write-Host "Build log: $ToolsetRestoreLog" -ForegroundColor DarkGray
+    exit $lastExitCode
+  }
+
+ $global:ToolsetBuildProj = Get-Content $toolsetLocationFile
+}
+
+function Build {
+  & $BuildDriver $BuildArgs $ToolsetBuildProj /m /nologo /clp:Summary /warnaserror /v:$verbosity /bl:$Log /p:Configuration=$configuration /p:Projects=$solution /p:RepoRoot=$RepoRoot /p:Restore=$restore /p:DeployDeps=$deployDeps /p:Build=$build /p:Rebuild=$rebuild /p:Deploy=$deploy /p:Test=$test /p:IntegrationTest=$integrationTest /p:Sign=$sign /p:Pack=$pack /p:CIBuild=$ci $properties
+  if ($lastExitCode -ne 0) {
+    Write-Host "Build log: $Log" -ForegroundColor DarkGray
+    exit $lastExitCode
+  }
+}
+
+function Stop-Processes() {
+  Write-Host "Killing running build processes..."
+  Get-Process -Name "msbuild" -ErrorAction SilentlyContinue | Stop-Process
+  Get-Process -Name "dotnet" -ErrorAction SilentlyContinue | Stop-Process
+  Get-Process -Name "vbcscompiler" -ErrorAction SilentlyContinue | Stop-Process
+}
+
+try {
+  $RepoRoot = Join-Path $PSScriptRoot "..\.."
+  $ArtifactsDir = Join-Path $RepoRoot "artifacts"
+  $ToolsetDir = Join-Path $ArtifactsDir "toolset"
+  $LogDir = Join-Path (Join-Path $ArtifactsDir $configuration) "log"
+  $Log = Join-Path $LogDir "Build.binlog"
+  $ToolsetRestoreLog = Join-Path $LogDir "ToolsetRestore.binlog"
+  $TempDir = Join-Path (Join-Path $ArtifactsDir $configuration) "tmp"
+  $GlobalJson = Get-Content(Join-Path $RepoRoot "global.json") | ConvertFrom-Json
+  
+  if ($solution -eq "") {
+    $solution = Join-Path $RepoRoot "*.sln"
+  }
+
+  if ($env:NUGET_PACKAGES -eq $null) {
+    # Use local cache on CI to ensure deterministic build,
+    # use global cache in dev builds to avoid cost of downloading packages.
+    $env:NUGET_PACKAGES = if ($ci) { Join-Path $RepoRoot ".packages" } 
+                          else { Join-Path $env:UserProfile ".nuget\packages" }
+  }
+
+  Create-Directory $ToolsetDir
+  Create-Directory $LogDir
+  
+  if ($ci) {
+    Create-Directory $TempDir
+    $env:TEMP = $TempDir
+    $env:TMP = $TempDir
+  }
+    
+  # Presence of vswhere.version indicates the repo needs to build using VS msbuild
+  if ((Get-Member -InputObject $GlobalJson -Name "vswhere") -ne $null) {    
+    InitializeVisualStudioBuild
+  } elseif ((Get-Member -InputObject $GlobalJson -Name "sdk") -ne $null) {  
+    InitializeDotNetCli
+  } else {
+    Write-Host "/global.json must either specify 'sdk.version' or 'vswhere.version'." -ForegroundColor Red
+    exit 1
+  }
+
+  if ($ci) {
+    Write-Host "Using $BuildDriver"
+  }
+
+  InitializeToolset
+
+  Build
+}
+catch {
+  Write-Host $_
+  Write-Host $_.Exception
+  Write-Host $_.ScriptStackTrace
+  exit 1
+}
+finally {
+  Pop-Location
+  if ($ci -and $prepareMachine) {
+    Stop-Processes
+  }
+}
+
diff --git a/eng/common/build.sh b/eng/common/build.sh
new file mode 100755 (executable)
index 0000000..f17bacb
--- /dev/null
@@ -0,0 +1,289 @@
+#!/usr/bin/env bash
+
+source="${BASH_SOURCE[0]}"
+
+# resolve $source until the file is no longer a symlink
+while [[ -h "$source" ]]; do
+  scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+  source="$(readlink "$source")"
+  # if $source was a relative symlink, we need to resolve it relative to the path where the
+  # symlink file was located
+  [[ $source != /* ]] && source="$scriptroot/$source"
+done
+scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+
+build=false
+ci=false
+configuration='Debug'
+help=false
+pack=false
+prepare_machine=false
+rebuild=false
+restore=false
+sign=false
+solution=''
+test=false
+verbosity='minimal'
+properties=''
+
+repo_root="$scriptroot/../.."
+artifacts_dir="$repo_root/artifacts"
+artifacts_configuration_dir="$artifacts_dir/$configuration"
+toolset_dir="$artifacts_dir/toolset"
+log_dir="$artifacts_configuration_dir/log"
+log="$log_dir/Build.binlog"
+toolset_restore_log="$log_dir/ToolsetRestore.binlog"
+temp_dir="$artifacts_configuration_dir/tmp"
+
+global_json_file="$repo_root/global.json"
+build_driver=""
+toolset_build_proj=""
+
+while (($# > 0)); do
+  lowerI="$(echo $1 | awk '{print tolower($0)}')"
+  case $lowerI in
+    --build)
+      build=true
+      shift 1
+      ;;
+    --ci)
+      ci=true
+      shift 1
+      ;;
+    --configuration)
+      configuration=$2
+      shift 2
+      ;;
+    --help)
+      echo "Common settings:"
+      echo "  --configuration <value>  Build configuration Debug, Release"
+      echo "  --verbosity <value>      Msbuild verbosity (q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic])"
+      echo "  --help                   Print help and exit"
+      echo ""
+      echo "Actions:"
+      echo "  --restore                Restore dependencies"
+      echo "  --build                  Build solution"
+      echo "  --rebuild                Rebuild solution"
+      echo "  --test                   Run all unit tests in the solution"
+      echo "  --sign                   Sign build outputs"
+      echo "  --pack                   Package build outputs into NuGet packages and Willow components"
+      echo ""
+      echo "Advanced settings:"
+      echo "  --solution <value>       Path to solution to build"
+      echo "  --ci                     Set when running on CI server"
+      echo "  --prepareMachine         Prepare machine for CI run"
+      echo ""
+      echo "Command line arguments not listed above are passed through to MSBuild."
+      exit 0
+      ;;
+    --pack)
+      pack=true
+      shift 1
+      ;;
+    --preparemachine)
+      prepare_machine=true
+      shift 1
+      ;;
+    --rebuild)
+      rebuild=true
+      shift 1
+      ;;
+    --restore)
+      restore=true
+      shift 1
+      ;;
+    --sign)
+      sign=true
+      shift 1
+      ;;
+    --solution)
+      solution=$2
+      shift 2
+      ;;
+    --test)
+      test=true
+      shift 1
+      ;;
+    --verbosity)
+      verbosity=$2
+      shift 2
+      ;;
+    *)
+      properties="$properties $1"
+      shift 1
+      ;;
+  esac
+done
+
+# ReadJson [filename] [json key]
+# Result: Sets 'readjsonvalue' to the value of the provided json key
+# Note: this method may return unexpected results if there are duplicate
+# keys in the json
+function ReadJson {
+  local file=$1
+  local key=$2
+
+  local unamestr="$(uname)"
+  local sedextended='-r'
+  if [[ "$unamestr" == 'Darwin' ]]; then
+    sedextended='-E'
+  fi;
+
+  readjsonvalue="$(grep -m 1 "\"$key\"" $file | sed $sedextended 's/^ *//;s/.*: *"//;s/",?//')"
+  if [[ ! "$readjsonvalue" ]]; then
+    echo "Error: Cannot find \"$key\" in $file" >&2;
+    ExitWithExitCode 1
+  fi;
+}
+
+function InitializeDotNetCli {
+  # Disable first run since we want to control all package sources
+  export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+
+  # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism
+  export DOTNET_MULTILEVEL_LOOKUP=0
+
+  # Source Build uses DotNetCoreSdkDir variable
+  if [[ -n "$DotNetCoreSdkDir" ]]; then
+    export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir"
+  fi
+
+  ReadJson "$global_json_file" "version"
+  local dotnet_sdk_version="$readjsonvalue"
+  local dotnet_root=""
+
+  # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, 
+  # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues.
+  if [[ -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then
+    dotnet_root="$DOTNET_INSTALL_DIR"
+  else
+    dotnet_root="$repo_root/.dotnet"
+    export DOTNET_INSTALL_DIR="$dotnet_root"
+
+    if [[ "$restore" == true ]]; then
+      InstallDotNetCli $dotnet_root $dotnet_sdk_version
+    fi
+  fi
+
+  build_driver="$dotnet_root/dotnet"
+}
+
+function InstallDotNetCli {
+  local dotnet_root=$1
+  local dotnet_sdk_version=$2
+  local dotnet_install_script="$dotnet_root/dotnet-install.sh"
+
+  if [[ ! -a "$dotnet_install_script" ]]; then
+    mkdir -p "$dotnet_root"
+
+    # Use curl if available, otherwise use wget
+    if command -v curl > /dev/null; then
+      curl "https://dot.net/v1/dotnet-install.sh" -sSL --retry 10 --create-dirs -o "$dotnet_install_script"
+    else
+      wget -q -O "$dotnet_install_script" "https://dot.net/v1/dotnet-install.sh"
+    fi
+  fi
+
+  bash "$dotnet_install_script" --version $dotnet_sdk_version --install-dir $dotnet_root
+  local lastexitcode=$?
+
+  if [[ $lastexitcode != 0 ]]; then
+    echo "Failed to install dotnet cli (exit code '$lastexitcode')."
+    ExitWithExitCode $lastexitcode
+  fi
+}
+
+function InitializeToolset {
+  ReadJson $global_json_file "RoslynTools.RepoToolset"
+  local toolset_version=$readjsonvalue
+  local toolset_location_file="$toolset_dir/$toolset_version.txt"
+
+  if [[ -a "$toolset_location_file" ]]; then
+    local path=`cat $toolset_location_file`
+    if [[ -a "$path" ]]; then
+      toolset_build_proj=$path
+      return
+    fi
+  fi  
+
+  if [[ "$restore" != true ]]; then
+    echo "Toolset version $toolsetVersion has not been restored."
+    ExitWithExitCode 2
+  fi
+  
+  local proj="$toolset_dir/restore.proj"
+
+  echo '<Project Sdk="RoslynTools.RepoToolset"/>' > $proj
+  "$build_driver" msbuild $proj /t:__WriteToolsetLocation /m /nologo /clp:None /warnaserror /bl:$toolset_restore_log /v:$verbosity /p:__ToolsetLocationOutputFile=$toolset_location_file 
+  local lastexitcode=$?
+
+  if [[ $lastexitcode != 0 ]]; then
+    echo "Failed to restore toolset (exit code '$lastexitcode'). See log: $toolset_restore_log"
+    ExitWithExitCode $lastexitcode
+  fi
+
+  toolset_build_proj=`cat $toolset_location_file`
+}
+
+function Build {
+  "$build_driver" msbuild $toolset_build_proj /m /nologo /clp:Summary /warnaserror \
+    /v:$verbosity /bl:$log /p:Configuration=$configuration /p:Projects=$solution /p:RepoRoot="$repo_root" \
+    /p:Restore=$restore /p:Build=$build /p:Rebuild=$rebuild /p:Deploy=$deploy /p:Test=$test /p:Sign=$sign /p:Pack=$pack /p:CIBuild=$ci \
+    $properties
+  local lastexitcode=$?
+
+  if [[ $lastexitcode != 0 ]]; then
+    echo "Failed to build $toolset_build_proj"
+    ExitWithExitCode $lastexitcode
+  fi
+}
+
+function ExitWithExitCode {
+  if [[ "$ci" == true && "$prepare_machine" == true ]]; then
+    StopProcesses
+  fi
+  exit $1
+}
+
+function StopProcesses {
+  echo "Killing running build processes..."
+  pkill -9 "dotnet"
+  pkill -9 "vbcscompiler"
+}
+
+function Main {
+  # HOME may not be defined in some scenarios, but it is required by NuGet
+  if [[ -z $HOME ]]; then
+    export HOME="$repo_root/artifacts/.home/"
+    mkdir -p "$HOME"
+  fi
+
+  if [[ -z $solution ]]; then
+    solution="$repo_root/*.sln"
+  fi
+
+  if [[ -z $NUGET_PACKAGES ]]; then
+    if [[ $ci ]]; then
+      export NUGET_PACKAGES="$repo_root/.packages"
+    else
+      export NUGET_PACKAGES="$HOME/.nuget/packages"
+    fi
+  fi
+
+  mkdir -p "$toolset_dir"
+  mkdir -p "$log_dir"
+  
+  if [[ $ci ]]; then
+    mkdir -p "$temp_dir"
+    export TEMP="$temp_dir"
+    export TMP="$temp_dir"
+  fi
+
+  InitializeDotNetCli
+  InitializeToolset
+
+  Build
+  ExitWithExitCode $?
+}
+
+Main
\ No newline at end of file
diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh
new file mode 100755 (executable)
index 0000000..b511253
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+source="${BASH_SOURCE[0]}"
+
+# resolve $SOURCE until the file is no longer a symlink
+while [[ -h $source ]]; do
+  scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+  source="$(readlink "$source")"
+
+  # if $source was a relative symlink, we need to resolve it relative to the path where 
+  # the symlink file was located
+  [[ $source != /* ]] && source="$scriptroot/$source"
+done
+scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+
+. "$scriptroot/build.sh" --restore --build --test --ci $@
\ No newline at end of file
diff --git a/global.json b/global.json
new file mode 100644 (file)
index 0000000..2ecbc05
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "sdk": {
+    "version": "2.1.101"
+  },
+  "msbuild-sdks": {
+    "RoslynTools.RepoToolset": "1.0.0-beta2-62810-01"
+  }
+}
diff --git a/netci.groovy b/netci.groovy
new file mode 100644 (file)
index 0000000..f841f85
--- /dev/null
@@ -0,0 +1,80 @@
+// Groovy Script: http://www.groovy-lang.org/syntax.html
+// Jenkins DSL: https://github.com/jenkinsci/job-dsl-plugin/wiki
+
+import jobs.generation.Utilities;
+
+static getJobName(def opsysName, def configName) {
+  return "${opsysName}_${configName}"
+}
+
+static addArchival(def job, def filesToArchive, def filesToExclude) {
+  def doNotFailIfNothingArchived = false
+  def archiveOnlyIfSuccessful = false
+
+  Utilities.addArchival(job, filesToArchive, filesToExclude, doNotFailIfNothingArchived, archiveOnlyIfSuccessful)
+}
+
+static addGithubPRTriggerForBranch(def job, def branchName, def jobName) {
+  def prContext = "prtest/${jobName.replace('_', '/')}"
+  def triggerPhrase = "(?i)^\\s*(@?dotnet-bot\\s+)?(re)?test\\s+(${prContext})(\\s+please)?\\s*\$"
+  def triggerOnPhraseOnly = false
+
+  Utilities.addGithubPRTriggerForBranch(job, branchName, prContext, triggerPhrase, triggerOnPhraseOnly)
+}
+
+static addXUnitDotNETResults(def job, def configName) {
+  def resultFilePattern = "**/artifacts/${configName}/TestResults/*.xml"
+  def skipIfNoTestFiles = false
+    
+  Utilities.addXUnitDotNETResults(job, resultFilePattern, skipIfNoTestFiles)
+}
+
+static addBuildSteps(def job, def projectName, def os, def configName, def isPR) {
+  def buildJobName = getJobName(os, configName)
+  def buildFullJobName = Utilities.getFullJobName(projectName, buildJobName, isPR)
+
+  job.with {
+    steps {
+      if (os == "Windows_NT") {
+        batchFile(""".\\eng\\common\\CIBuild.cmd -configuration ${configName} -prepareMachine""")
+      } else {
+        shell("./eng/common/cibuild.sh --configuration ${configName} --prepareMachine")
+      }
+    }
+  }
+}
+
+[true, false].each { isPR ->
+  ['Ubuntu16.04', 'Windows_NT'].each { os ->
+    ['Debug', 'Release'].each { configName ->
+      def projectName = GithubProject
+
+      def branchName = GithubBranchName
+
+      def filesToArchive = "**/artifacts/${configName}/**"
+
+      def jobName = getJobName(os, configName)
+      def fullJobName = Utilities.getFullJobName(projectName, jobName, isPR)
+      def myJob = job(fullJobName)
+
+      Utilities.standardJobSetup(myJob, projectName, isPR, "*/${branchName}")
+
+      if (isPR) {
+        addGithubPRTriggerForBranch(myJob, branchName, jobName)
+      } else {
+        Utilities.addGithubPushTrigger(myJob)
+      }
+      
+      addArchival(myJob, filesToArchive, "")
+      addXUnitDotNETResults(myJob, configName)
+
+      if (os == 'Windows_NT') {
+        Utilities.setMachineAffinity(myJob, os, 'latest-dev15-3')  
+      } else {
+        Utilities.setMachineAffinity(myJob, os, 'latest-or-auto')
+      }
+
+      addBuildSteps(myJob, projectName, os, configName, isPR)
+    }
+  }
+}
diff --git a/pack.cmd b/pack.cmd
new file mode 100644 (file)
index 0000000..59ccd0f
--- /dev/null
+++ b/pack.cmd
@@ -0,0 +1,3 @@
+@echo off
+powershell -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -pack %*"
+exit /b %ErrorLevel%
diff --git a/restore.sh b/restore.sh
new file mode 100755 (executable)
index 0000000..260efcf
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+source="${BASH_SOURCE[0]}"
+
+# resolve $SOURCE until the file is no longer a symlink
+while [[ -h $source ]]; do
+  scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+  source="$(readlink "$source")"
+
+  # if $source was a relative symlink, we need to resolve it relative to the path where the
+  # symlink file was located
+  [[ $source != /* ]] && source="$scriptroot/$source"
+done
+
+scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+"$scriptroot/eng/common/build.sh" --restore $@
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644 (file)
index 0000000..76b5681
--- /dev/null
@@ -0,0 +1,24 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project>
+  <Import Project="..\Directory.Build.props"/>
+
+  <PropertyGroup>
+    <LangVersion>Latest</LangVersion>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
+    <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)' == 'Release'">
+    <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(TargetFramework)' == 'net45'">
+    <DebugType>full</DebugType>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(TargetFramework)' != 'net45'">
+    <DebugType>portable</DebugType>
+  </PropertyGroup>
+</Project>
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
new file mode 100644 (file)
index 0000000..c709187
--- /dev/null
@@ -0,0 +1,7 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project>
+  <Target Name="PublishForPack" AfterTargets="PostBuildEvent" Condition="'$(NoPublishForPack)' != 'true' and '$(IsPackable)' == 'true'">
+    <Message Importance="High" Text="Executing Publish target on $(MSBuildProjectFullPath) for $(TargetFramework)" />
+    <MSBuild Targets="Publish" Projects="$(MSBuildProjectFullPath)" BuildInParallel="false" Properties="NoBuild=true;NoPublishForPack=true" />
+  </Target>
+</Project>
diff --git a/src/SOS/NETCore/SOS.NETCore.csproj b/src/SOS/NETCore/SOS.NETCore.csproj
new file mode 100644 (file)
index 0000000..a88c47e
--- /dev/null
@@ -0,0 +1,17 @@
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<Project Sdk="RoslynTools.RepoToolset">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp1.0</TargetFramework>
+    <AssemblyName>SOS.NETCore</AssemblyName>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <NoWarn>;1591;1701</NoWarn>
+    <IsPackable>true</IsPackable>
+    <Description>Managed SOS Services</Description>
+    <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+    <PackageTags>SOS</PackageTags>
+  </PropertyGroup>
+  
+  <ItemGroup>
+    <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
+  </ItemGroup>
+</Project>
diff --git a/src/SOS/NETCore/SymbolReader.cs b/src/SOS/NETCore/SymbolReader.cs
new file mode 100644 (file)
index 0000000..7a4bb52
--- /dev/null
@@ -0,0 +1,782 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using System.Runtime.InteropServices;
+
+namespace SOS
+{
+    internal class SymbolReader
+    {
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        internal struct DebugInfo
+        {
+            public int lineNumber;
+            public int ilOffset;
+            public string fileName;
+        }
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+        internal struct LocalVarInfo
+        {
+            public int startOffset;
+            public int endOffset;
+            public string name;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct MethodDebugInfo
+        {
+            public IntPtr points;
+            public int size;
+            public IntPtr locals;
+            public int localsSize;
+
+        }
+
+        /// <summary>
+        /// Read memory callback
+        /// </summary>
+        /// <returns>number of bytes read or 0 for error</returns>
+        internal unsafe delegate int ReadMemoryDelegate(ulong address, byte* buffer, int count);
+
+        private sealed class OpenedReader : IDisposable
+        {
+            public readonly MetadataReaderProvider Provider;
+            public readonly MetadataReader Reader;
+
+            public OpenedReader(MetadataReaderProvider provider, MetadataReader reader)
+            {
+                Debug.Assert(provider != null);
+                Debug.Assert(reader != null);
+
+                Provider = provider;
+                Reader = reader;
+            }
+
+            public void Dispose() => Provider.Dispose();
+        }
+
+        /// <summary>
+        /// Stream implementation to read debugger target memory for in-memory PDBs
+        /// </summary>
+        private class TargetStream : Stream
+        {
+            readonly ulong _address;
+            readonly ReadMemoryDelegate _readMemory;
+
+            public override long Position { get; set; }
+            public override long Length { get; }
+            public override bool CanSeek { get { return true; } }
+            public override bool CanRead { get { return true; } }
+            public override bool CanWrite { get { return false; } }
+
+            public TargetStream(ulong address, int size, ReadMemoryDelegate readMemory)
+                : base()
+            {
+                _address = address;
+                _readMemory = readMemory;
+                Length = size;
+                Position = 0;
+            }
+
+            public override int Read(byte[] buffer, int offset, int count)
+            {
+                if (Position + count > Length)
+                {
+                    throw new ArgumentOutOfRangeException();
+                }
+                unsafe
+                {
+                    fixed (byte* p = &buffer[offset])
+                    {
+                        int read  = _readMemory(_address + (ulong)Position, p, count);
+                        Position += read;
+                        return read;
+                    }
+                }
+            }
+
+            public override long Seek(long offset, SeekOrigin origin)
+            {
+                switch (origin)
+                {
+                    case SeekOrigin.Begin:
+                        Position = offset;
+                        break;
+                    case SeekOrigin.End:
+                        Position = Length + offset;
+                        break;
+                    case SeekOrigin.Current:
+                        Position += offset;
+                        break;
+                }
+                return Position;
+            }
+
+            public override void Flush()
+            {
+            }
+
+            public override void SetLength(long value)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override void Write(byte[] buffer, int offset, int count)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        /// <summary>
+        /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux
+        /// </summary>
+        /// <param name="pathName"> File path to be processed </param>
+        /// <returns>Last component of path</returns>
+        private static string GetFileName(string pathName)
+        {
+            int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'});
+            if (pos < 0)
+                return pathName;
+            return pathName.Substring(pos + 1);
+        }
+
+        /// <summary>
+        /// Checks availability of debugging information for given assembly.
+        /// </summary>
+        /// <param name="assemblyPath">
+        /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit)
+        /// </param>
+        /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param>
+        /// <param name="loadedPeAddress">
+        /// Loaded PE image address or zero if the module is dynamic (generated by Reflection.Emit). 
+        /// Dynamic modules have their PDBs (if any) generated to an in-memory stream 
+        /// (pointed to by <paramref name="inMemoryPdbAddress"/> and <paramref name="inMemoryPdbSize"/>).
+        /// </param>
+        /// <param name="loadedPeSize">loaded PE image size</param>
+        /// <param name="inMemoryPdbAddress">in memory PDB address or zero</param>
+        /// <param name="inMemoryPdbSize">in memory PDB size</param>
+        /// <returns>Symbol reader handle or zero if error</returns>
+        internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, ulong loadedPeAddress, int loadedPeSize, 
+            ulong inMemoryPdbAddress, int inMemoryPdbSize, ReadMemoryDelegate readMemory)
+        {
+            try
+            {
+                TargetStream peStream = null;
+                if (assemblyPath == null && loadedPeAddress != 0)
+                {
+                    peStream = new TargetStream(loadedPeAddress, loadedPeSize, readMemory);
+                }
+                TargetStream pdbStream = null;
+                if (inMemoryPdbAddress != 0)
+                {
+                    pdbStream = new TargetStream(inMemoryPdbAddress, inMemoryPdbSize, readMemory);
+                }
+                OpenedReader openedReader = GetReader(assemblyPath, isFileLayout, peStream, pdbStream);
+                if (openedReader != null)
+                {
+                    GCHandle gch = GCHandle.Alloc(openedReader);
+                    return GCHandle.ToIntPtr(gch);
+                }
+            }
+            catch
+            {
+            }
+            return IntPtr.Zero;
+        }
+
+        /// <summary>
+        /// Cleanup and dispose of symbol reader handle
+        /// </summary>
+        /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+        internal static void Dispose(IntPtr symbolReaderHandle)
+        {
+            Debug.Assert(symbolReaderHandle != IntPtr.Zero);
+            try
+            {
+                GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
+                ((OpenedReader)gch.Target).Dispose();
+                gch.Free();
+            }
+            catch
+            {
+            }
+        }
+
+        /// <summary>
+        /// Returns method token and IL offset for given source line number.
+        /// </summary>
+        /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+        /// <param name="filePath">source file name and path</param>
+        /// <param name="lineNumber">source line number</param>
+        /// <param name="methodToken">method token return</param>
+        /// <param name="ilOffset">IL offset return</param>
+        /// <returns> true if information is available</returns>
+        internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset)
+        {
+            Debug.Assert(symbolReaderHandle != IntPtr.Zero);
+            methodToken = 0;
+            ilOffset = 0;
+
+            GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
+            MetadataReader reader = ((OpenedReader)gch.Target).Reader;
+
+            try
+            {
+                string fileName = GetFileName(filePath);
+                foreach (MethodDebugInformationHandle methodDebugInformationHandle in reader.MethodDebugInformation)
+                {
+                    MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugInformationHandle);
+                    SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints();
+                    foreach (SequencePoint point in sequencePoints)
+                    {
+                        string sourceName = reader.GetString(reader.GetDocument(point.Document).Name);
+                        if (point.StartLine == lineNumber && GetFileName(sourceName) == fileName)
+                        {
+                            methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle());
+                            ilOffset = point.Offset;
+                            return true;
+                        }
+                    }
+                }
+            }
+            catch
+            {
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns source line number and source file name for given IL offset and method token.
+        /// </summary>
+        /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+        /// <param name="methodToken">method token</param>
+        /// <param name="ilOffset">IL offset</param>
+        /// <param name="lineNumber">source line number return</param>
+        /// <param name="fileName">source file name return</param>
+        /// <returns> true if information is available</returns>
+        internal static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName)
+        {
+            lineNumber = 0;
+            fileName = IntPtr.Zero;
+
+            string sourceFileName = null;
+
+            if (!GetSourceLineByILOffset(symbolReaderHandle, methodToken, ilOffset, out lineNumber, out sourceFileName))
+            {
+                return false;
+            }
+            fileName = Marshal.StringToBSTR(sourceFileName);
+            sourceFileName = null;
+            return true;
+        }
+
+        /// <summary>
+        /// Helper method to return source line number and source file name for given IL offset and method token.
+        /// </summary>
+        /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+        /// <param name="methodToken">method token</param>
+        /// <param name="ilOffset">IL offset</param>
+        /// <param name="lineNumber">source line number return</param>
+        /// <param name="fileName">source file name return</param>
+        /// <returns> true if information is available</returns>
+        private static bool GetSourceLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out string fileName)
+        {
+            Debug.Assert(symbolReaderHandle != IntPtr.Zero);
+            lineNumber = 0;
+            fileName = null;
+
+            GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
+            MetadataReader reader = ((OpenedReader)gch.Target).Reader;
+
+            try
+            {
+                Handle handle = MetadataTokens.Handle(methodToken);
+                if (handle.Kind != HandleKind.MethodDefinition)
+                    return false;
+
+                MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle();
+                if (methodDebugHandle.IsNil)
+                    return false;
+
+                MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugHandle);
+                SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints();
+
+                SequencePoint nearestPoint = sequencePoints.GetEnumerator().Current;
+                foreach (SequencePoint point in sequencePoints)
+                {
+                    if (point.Offset < ilOffset)
+                    {
+                        nearestPoint = point;
+                    }
+                    else
+                    {
+                        if (point.Offset == ilOffset)
+                            nearestPoint = point;
+
+                        if (nearestPoint.StartLine == 0 || nearestPoint.StartLine == SequencePoint.HiddenLine)
+                            return false;
+
+                        lineNumber = nearestPoint.StartLine;
+                        fileName = reader.GetString(reader.GetDocument(nearestPoint.Document).Name);
+                        return true;
+                    }
+                }
+            }
+            catch
+            {
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns local variable name for given local index and IL offset.
+        /// </summary>
+        /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+        /// <param name="methodToken">method token</param>
+        /// <param name="localIndex">local variable index</param>
+        /// <param name="localVarName">local variable name return</param>
+        /// <returns>true if name has been found</returns>
+        internal static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName)
+        {
+            localVarName = IntPtr.Zero;
+
+            string localVar = null;
+            if (!GetLocalVariableByIndex(symbolReaderHandle, methodToken, localIndex, out localVar))
+                return false;
+
+            localVarName = Marshal.StringToBSTR(localVar);
+            localVar = null;
+            return true;
+        }
+
+        /// <summary>
+        /// Helper method to return local variable name for given local index and IL offset.
+        /// </summary>
+        /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param>
+        /// <param name="methodToken">method token</param>
+        /// <param name="localIndex">local variable index</param>
+        /// <param name="localVarName">local variable name return</param>
+        /// <returns>true if name has been found</returns>
+        internal static bool GetLocalVariableByIndex(IntPtr symbolReaderHandle, int methodToken, int localIndex, out string localVarName)
+        {
+            Debug.Assert(symbolReaderHandle != IntPtr.Zero);
+            localVarName = null;
+
+            GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
+            MetadataReader reader = ((OpenedReader)gch.Target).Reader;
+
+            try
+            {
+                Handle handle = MetadataTokens.Handle(methodToken);
+                if (handle.Kind != HandleKind.MethodDefinition)
+                    return false;
+
+                MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle();
+                LocalScopeHandleCollection localScopes = reader.GetLocalScopes(methodDebugHandle);
+                foreach (LocalScopeHandle scopeHandle in localScopes)
+                {
+                    LocalScope scope = reader.GetLocalScope(scopeHandle);
+                    LocalVariableHandleCollection localVars = scope.GetLocalVariables();
+                    foreach (LocalVariableHandle varHandle in localVars)
+                    {
+                        LocalVariable localVar = reader.GetLocalVariable(varHandle);
+                        if (localVar.Index == localIndex)
+                        {
+                            if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden)
+                                return false;
+
+                            localVarName = reader.GetString(localVar.Name);
+                            return true;
+                        }
+                    }
+                }
+            }
+            catch
+            {
+            }
+            return false;
+        }
+        internal static bool GetLocalsInfoForMethod(string assemblyPath, int methodToken, out List<LocalVarInfo> locals)
+        {
+            locals = null;
+
+            OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null);
+            if (openedReader == null)
+                return false;
+
+            using (openedReader)
+            {
+                try
+                {
+                    Handle handle = MetadataTokens.Handle(methodToken);
+                    if (handle.Kind != HandleKind.MethodDefinition)
+                        return false;
+
+                    locals = new List<LocalVarInfo>();
+
+                    MethodDebugInformationHandle methodDebugHandle =
+                        ((MethodDefinitionHandle)handle).ToDebugInformationHandle();
+                    LocalScopeHandleCollection localScopes = openedReader.Reader.GetLocalScopes(methodDebugHandle);
+                    foreach (LocalScopeHandle scopeHandle in localScopes)
+                    {
+                        LocalScope scope = openedReader.Reader.GetLocalScope(scopeHandle);
+                        LocalVariableHandleCollection localVars = scope.GetLocalVariables();
+                        foreach (LocalVariableHandle varHandle in localVars)
+                        {
+                            LocalVariable localVar = openedReader.Reader.GetLocalVariable(varHandle);
+                            if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden)
+                                continue;
+                            LocalVarInfo info = new LocalVarInfo();
+                            info.startOffset = scope.StartOffset;
+                            info.endOffset = scope.EndOffset;
+                            info.name = openedReader.Reader.GetString(localVar.Name);
+                            locals.Add(info);
+                        }
+                    }
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+            return true;
+
+        }
+        /// <summary>
+        /// Returns source name, line numbers and IL offsets for given method token.
+        /// </summary>
+        /// <param name="assemblyPath">file path of the assembly</param>
+        /// <param name="methodToken">method token</param>
+        /// <param name="debugInfo">structure with debug information return</param>
+        /// <returns>true if information is available</returns>
+        /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks>
+        internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo)
+        {
+            try
+            {
+                List<DebugInfo> points = null;
+                List<LocalVarInfo> locals = null;
+
+                if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points))
+                {
+                    return false;
+                }
+
+                if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals))
+                {
+                    return false;
+                }
+                var structSize = Marshal.SizeOf<DebugInfo>();
+
+                debugInfo.size = points.Count;
+                var ptr = debugInfo.points;
+
+                foreach (var info in points)
+                {
+                    Marshal.StructureToPtr(info, ptr, false);
+                    ptr = (IntPtr)(ptr.ToInt64() + structSize);
+                }
+
+                structSize = Marshal.SizeOf<LocalVarInfo>();
+
+                debugInfo.localsSize = locals.Count;
+                ptr = debugInfo.locals;
+
+                foreach (var info in locals)
+                {
+                    Marshal.StructureToPtr(info, ptr, false);
+                    ptr = (IntPtr)(ptr.ToInt64() + structSize);
+                }
+
+                return true;
+            }
+            catch
+            {
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Helper method to return source name, line numbers and IL offsets for given method token.
+        /// </summary>
+        /// <param name="assemblyPath">file path of the assembly</param>
+        /// <param name="methodToken">method token</param>
+        /// <param name="points">list of debug information for each sequence point return</param>
+        /// <returns>true if information is available</returns>
+        /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks>
+        private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List<DebugInfo> points)
+        {
+            points = null;
+
+            OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null);
+            if (openedReader == null)
+                return false;
+
+            using (openedReader)
+            {
+                try
+                {
+                    Handle handle = MetadataTokens.Handle(methodToken);
+                    if (handle.Kind != HandleKind.MethodDefinition)
+                        return false;
+
+                    points = new List<DebugInfo>();
+                    MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle();
+                    MethodDebugInformation methodDebugInfo = openedReader.Reader.GetMethodDebugInformation(methodDebugHandle);
+                    SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints();
+
+                    foreach (SequencePoint point in sequencePoints)
+                    {
+
+                        DebugInfo debugInfo = new DebugInfo();
+                        debugInfo.lineNumber = point.StartLine;
+                        debugInfo.fileName = openedReader.Reader.GetString(openedReader.Reader.GetDocument(point.Document).Name);
+                        debugInfo.ilOffset = point.Offset;
+                        points.Add(debugInfo);
+                    }
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Returns the portable PDB reader for the assembly path
+        /// </summary>
+        /// <param name="assemblyPath">file path of the assembly or null if the module is in-memory or dynamic</param>
+        /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param>
+        /// <param name="peStream">optional in-memory PE stream</param>
+        /// <param name="pdbStream">optional in-memory PDB stream</param>
+        /// <returns>reader/provider wrapper instance</returns>
+        /// <remarks>
+        /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around.
+        /// </remarks>
+        private static OpenedReader GetReader(string assemblyPath, bool isFileLayout, Stream peStream, Stream pdbStream)
+        {
+            return (pdbStream != null) ? TryOpenReaderForInMemoryPdb(pdbStream) : TryOpenReaderFromAssembly(assemblyPath, isFileLayout, peStream);
+        }
+
+        private static OpenedReader TryOpenReaderForInMemoryPdb(Stream pdbStream)
+        {
+            Debug.Assert(pdbStream != null);
+
+            byte[] buffer = new byte[sizeof(uint)];
+            if (pdbStream.Read(buffer, 0, sizeof(uint)) != sizeof(uint))
+            {
+                return null;
+            }
+            uint signature = BitConverter.ToUInt32(buffer, 0);
+
+            // quick check to avoid throwing exceptions below in common cases:
+            const uint ManagedMetadataSignature = 0x424A5342;
+            if (signature != ManagedMetadataSignature)
+            {
+                // not a Portable PDB
+                return null;
+            }
+
+            OpenedReader result = null;
+            MetadataReaderProvider provider = null;
+            try
+            {
+                pdbStream.Position = 0;
+                provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
+                result = new OpenedReader(provider, provider.GetMetadataReader());
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                return null;
+            }
+            finally
+            {
+                if (result == null)
+                {
+                    provider?.Dispose();
+                }
+            }
+
+            return result;
+        }
+
+        private static OpenedReader TryOpenReaderFromAssembly(string assemblyPath, bool isFileLayout, Stream peStream)
+        {
+            if (assemblyPath == null && peStream == null)
+                return null;
+
+            PEStreamOptions options = isFileLayout ? PEStreamOptions.Default : PEStreamOptions.IsLoadedImage;
+            if (peStream == null)
+            {
+                peStream = TryOpenFile(assemblyPath);
+                if (peStream == null)
+                    return null;
+                
+                options = PEStreamOptions.Default;
+            }
+
+            try
+            {
+                using (var peReader = new PEReader(peStream, options))
+                {
+                    DebugDirectoryEntry codeViewEntry, embeddedPdbEntry;
+                    ReadPortableDebugTableEntries(peReader, out codeViewEntry, out embeddedPdbEntry);
+
+                    // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB
+                    // since embedded PDB needs decompression which is less efficient than memory-mapping the file).
+                    if (codeViewEntry.DataSize != 0)
+                    {
+                        var result = TryOpenReaderFromCodeView(peReader, codeViewEntry, assemblyPath);
+                        if (result != null)
+                        {
+                            return result;
+                        }
+                    }
+
+                    // if it failed try Embedded Portable PDB (if available):
+                    if (embeddedPdbEntry.DataSize != 0)
+                    {
+                        return TryOpenReaderFromEmbeddedPdb(peReader, embeddedPdbEntry);
+                    }
+                }
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                // nop
+            }
+
+            return null;
+        }
+
+        private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry)
+        {
+            // See spec: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md
+
+            codeViewEntry = default(DebugDirectoryEntry);
+            embeddedPdbEntry = default(DebugDirectoryEntry);
+
+            foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
+            {
+                if (entry.Type == DebugDirectoryEntryType.CodeView)
+                {
+                    const ushort PortableCodeViewVersionMagic = 0x504d;
+                    if (entry.MinorVersion != PortableCodeViewVersionMagic)
+                    {
+                        continue;
+                    }
+
+                    codeViewEntry = entry;
+                }
+                else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
+                {
+                    embeddedPdbEntry = entry;
+                }
+            }
+        }
+
+        private static OpenedReader TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath)
+        {
+            OpenedReader result = null;
+            MetadataReaderProvider provider = null;
+            try
+            {
+                var data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry);
+
+                string pdbPath = data.Path;
+                if (assemblyPath != null)
+                {
+                    try
+                    {
+                        pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), GetFileName(pdbPath));
+                    }
+                    catch
+                    {
+                        // invalid characters in CodeView path
+                        return null;
+                    }
+                }
+
+                var pdbStream = TryOpenFile(pdbPath);
+                if (pdbStream == null)
+                {
+                    return null;
+                }
+
+                provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
+                var reader = provider.GetMetadataReader();
+
+                // Validate that the PDB matches the assembly version
+                if (data.Age == 1 && new BlobContentId(reader.DebugMetadataHeader.Id) == new BlobContentId(data.Guid, codeViewEntry.Stamp))
+                {
+                    result = new OpenedReader(provider, reader);
+                }
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                return null;
+            }
+            finally
+            {
+                if (result == null)
+                {
+                    provider?.Dispose();
+                }
+            }
+
+            return result;
+        }
+
+        private static OpenedReader TryOpenReaderFromEmbeddedPdb(PEReader peReader, DebugDirectoryEntry embeddedPdbEntry)
+        {
+            OpenedReader result = null;
+            MetadataReaderProvider provider = null;
+
+            try
+            {
+                // TODO: We might want to cache this provider globally (across stack traces), 
+                // since decompressing embedded PDB takes some time.
+                provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry);
+                result = new OpenedReader(provider, provider.GetMetadataReader());
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                return null;
+            }
+            finally
+            {
+                if (result == null)
+                {
+                    provider?.Dispose();
+                }
+            }
+
+            return result;
+        }
+
+        private static Stream TryOpenFile(string path)
+        {
+            if (!File.Exists(path))
+            {
+                return null;
+            }
+            try
+            {
+                return File.OpenRead(path);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+    }
+}