From ad99d3facd42707c6715bf4c1cfc18c346ec536d Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Wed, 21 Mar 2018 18:58:34 -0700 Subject: [PATCH] Add the managed SOS code. Using the Roslyn repo tool set to build (for now). --- .gitattributes | 49 ++ .gitignore | 255 +--------- Build.cmd | 3 + Directory.Build.props | 12 + NuGet.Config | 8 + Restore.cmd | 3 + Test.cmd | 3 + build.sh | 16 + diagnostics.sln | 27 + eng/SignToolData.json | 25 + eng/Versions.props | 25 + eng/common/CIBuild.cmd | 3 + eng/common/build.ps1 | 252 ++++++++++ eng/common/build.sh | 289 +++++++++++ eng/common/cibuild.sh | 16 + global.json | 8 + netci.groovy | 80 +++ pack.cmd | 3 + restore.sh | 16 + src/Directory.Build.props | 24 + src/Directory.Build.targets | 7 + src/SOS/NETCore/SOS.NETCore.csproj | 17 + src/SOS/NETCore/SymbolReader.cs | 782 +++++++++++++++++++++++++++++ 23 files changed, 1690 insertions(+), 233 deletions(-) create mode 100644 .gitattributes create mode 100644 Build.cmd create mode 100644 Directory.Build.props create mode 100644 NuGet.Config create mode 100644 Restore.cmd create mode 100644 Test.cmd create mode 100755 build.sh create mode 100644 diagnostics.sln create mode 100644 eng/SignToolData.json create mode 100644 eng/Versions.props create mode 100644 eng/common/CIBuild.cmd create mode 100644 eng/common/build.ps1 create mode 100755 eng/common/build.sh create mode 100755 eng/common/cibuild.sh create mode 100644 global.json create mode 100644 netci.groovy create mode 100644 pack.cmd create mode 100755 restore.sh create mode 100644 src/Directory.Build.props create mode 100644 src/Directory.Build.targets create mode 100644 src/SOS/NETCore/SOS.NETCore.csproj create mode 100644 src/SOS/NETCore/SymbolReader.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..e53d31783 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/.gitignore b/.gitignore index 8c2f099f0..11d202635 100644 --- a/.gitignore +++ b/.gitignore @@ -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 index 000000000..7e7754462 --- /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 index 000000000..e8896aefc --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + + https://github.com/dotnet/diagnostics.git + $(RepositoryUrl) + $(RepositoryUrl) + http://go.microsoft.com/fwlink/?LinkId=529443 + http://go.microsoft.com/fwlink/?LinkID=288859 + false + true + + diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..1b0fc7e28 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Restore.cmd b/Restore.cmd new file mode 100644 index 000000000..3fc17e8f7 --- /dev/null +++ b/Restore.cmd @@ -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 index 000000000..8a841f681 --- /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 index 000000000..1467a1daa --- /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 index 000000000..38e3017f2 --- /dev/null +++ b/diagnostics.sln @@ -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 index 000000000..6de1857c5 --- /dev/null +++ b/eng/SignToolData.json @@ -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 index 000000000..18779ae02 --- /dev/null +++ b/eng/Versions.props @@ -0,0 +1,25 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + 1.0.0 + beta + + true + false + + + 1.6.0-preview2-26406-04 + + + + + $(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 + + + diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd new file mode 100644 index 000000000..42bb58ba2 --- /dev/null +++ b/eng/common/CIBuild.cmd @@ -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 index 000000000..7fe12a5bd --- /dev/null +++ b/eng/common/build.ps1 @@ -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 Build configuration Debug, Release" + Write-Host " -verbosity 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 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" + + '' | 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 index 000000000..f17bacb48 --- /dev/null +++ b/eng/common/build.sh @@ -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 Build configuration Debug, Release" + echo " --verbosity 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 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 '' > $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 index 000000000..b5112539e --- /dev/null +++ b/eng/common/cibuild.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/build.sh" --restore --build --test --ci $@ \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 000000000..2ecbc0560 --- /dev/null +++ b/global.json @@ -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 index 000000000..f841f85dc --- /dev/null +++ b/netci.groovy @@ -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 index 000000000..59ccd0fdf --- /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 index 000000000..260efcf5a --- /dev/null +++ b/restore.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" --restore $@ \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..76b56816c --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,24 @@ + + + + + + Latest + + + + false + + + + false + + + + full + + + + portable + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 000000000..c709187cf --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/SOS/NETCore/SOS.NETCore.csproj b/src/SOS/NETCore/SOS.NETCore.csproj new file mode 100644 index 000000000..a88c47e8b --- /dev/null +++ b/src/SOS/NETCore/SOS.NETCore.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp1.0 + SOS.NETCore + true + ;1591;1701 + true + Managed SOS Services + $(Description) + SOS + + + + + + diff --git a/src/SOS/NETCore/SymbolReader.cs b/src/SOS/NETCore/SymbolReader.cs new file mode 100644 index 000000000..7a4bb5210 --- /dev/null +++ b/src/SOS/NETCore/SymbolReader.cs @@ -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; + + } + + /// + /// Read memory callback + /// + /// number of bytes read or 0 for error + 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(); + } + + /// + /// Stream implementation to read debugger target memory for in-memory PDBs + /// + 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(); + } + } + + /// + /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux + /// + /// File path to be processed + /// Last component of path + private static string GetFileName(string pathName) + { + int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'}); + if (pos < 0) + return pathName; + return pathName.Substring(pos + 1); + } + + /// + /// Checks availability of debugging information for given assembly. + /// + /// + /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit) + /// + /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout + /// + /// 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 and ). + /// + /// loaded PE image size + /// in memory PDB address or zero + /// in memory PDB size + /// Symbol reader handle or zero if error + 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; + } + + /// + /// Cleanup and dispose of symbol reader handle + /// + /// symbol reader handle returned by LoadSymbolsForModule + internal static void Dispose(IntPtr symbolReaderHandle) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + try + { + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + ((OpenedReader)gch.Target).Dispose(); + gch.Free(); + } + catch + { + } + } + + /// + /// Returns method token and IL offset for given source line number. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// source file name and path + /// source line number + /// method token return + /// IL offset return + /// true if information is available + 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; + } + + /// + /// Returns source line number and source file name for given IL offset and method token. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// IL offset + /// source line number return + /// source file name return + /// true if information is available + 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; + } + + /// + /// Helper method to return source line number and source file name for given IL offset and method token. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// IL offset + /// source line number return + /// source file name return + /// true if information is available + 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; + } + + /// + /// Returns local variable name for given local index and IL offset. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// local variable index + /// local variable name return + /// true if name has been found + 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; + } + + /// + /// Helper method to return local variable name for given local index and IL offset. + /// + /// symbol reader handle returned by LoadSymbolsForModule + /// method token + /// local variable index + /// local variable name return + /// true if name has been found + 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 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(); + + 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; + + } + /// + /// Returns source name, line numbers and IL offsets for given method token. + /// + /// file path of the assembly + /// method token + /// structure with debug information return + /// true if information is available + /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs + internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo) + { + try + { + List points = null; + List locals = null; + + if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points)) + { + return false; + } + + if (!GetLocalsInfoForMethod(assemblyPath, methodToken, out locals)) + { + return false; + } + var structSize = Marshal.SizeOf(); + + 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(); + + 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; + } + + /// + /// Helper method to return source name, line numbers and IL offsets for given method token. + /// + /// file path of the assembly + /// method token + /// list of debug information for each sequence point return + /// true if information is available + /// used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs + private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List 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(); + 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; + } + + /// + /// Returns the portable PDB reader for the assembly path + /// + /// file path of the assembly or null if the module is in-memory or dynamic + /// type of in-memory PE layout, if true, file based layout otherwise, loaded layout + /// optional in-memory PE stream + /// optional in-memory PDB stream + /// reader/provider wrapper instance + /// + /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around. + /// + 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; + } + } + } +} -- 2.34.1