From: Mike McLaughlin Date: Thu, 22 Mar 2018 01:58:34 +0000 (-0700) Subject: Add the managed SOS code. X-Git-Tag: submit/tizen/20190813.035844~103^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ad99d3facd42707c6715bf4c1cfc18c346ec536d;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add the managed SOS code. Using the Roslyn repo tool set to build (for now). --- 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; + } + } + } +}