diff --git a/eng/pipelines/templates/stages/vmr-build.yml b/eng/pipelines/templates/stages/vmr-build.yml index 5d30418704d7..5ecb5a334802 100644 --- a/eng/pipelines/templates/stages/vmr-build.yml +++ b/eng/pipelines/templates/stages/vmr-build.yml @@ -105,7 +105,7 @@ stages: useMonoRuntime: false # 🚫 withPreviousSDK: false # 🚫 - - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + - ${{ if ne(variables['Build.Reason'], 'fixme') }}: # CI - Stage 1 x64 legs ------------------------------------ @@ -217,23 +217,24 @@ stages: useMonoRuntime: false # 🚫 withPreviousSDK: false # 🚫 - # CI - Stage 1 arm64 Legs ------------------------------------ + - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + # CI - Stage 1 arm64 Legs ------------------------------------ - - template: ../jobs/vmr-build.yml - parameters: - # Changing the build name requires updating the referenced name in the source-build-sdk-diff-tests.yml pipeline - buildName: Ubuntu2204Arm64_Offline_MsftSdk - isBuiltFromVmr: ${{ parameters.isBuiltFromVmr }} - vmrBranch: ${{ variables.VmrBranch }} - architecture: arm64 - pool: ${{ parameters.poolInternalArm64 }} - container: ${{ parameters.ubuntu2204ArmContainer }} - buildFromArchive: false # 🚫 - enablePoison: false # 🚫 - excludeOmniSharpTests: false # 🚫 - runOnline: false # 🚫 - useMonoRuntime: false # 🚫 - withPreviousSDK: false # 🚫 + - template: ../jobs/vmr-build.yml + parameters: + # Changing the build name requires updating the referenced name in the source-build-sdk-diff-tests.yml pipeline + buildName: Ubuntu2204Arm64_Offline_MsftSdk + isBuiltFromVmr: ${{ parameters.isBuiltFromVmr }} + vmrBranch: ${{ variables.VmrBranch }} + architecture: arm64 + pool: ${{ parameters.poolInternalArm64 }} + container: ${{ parameters.ubuntu2204ArmContainer }} + buildFromArchive: false # 🚫 + enablePoison: false # 🚫 + excludeOmniSharpTests: false # 🚫 + runOnline: false # 🚫 + useMonoRuntime: false # 🚫 + withPreviousSDK: false # 🚫 # CI - Stage 2 x64 Legs ------------------------------------ diff --git a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/DebugTests.cs b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/DebugTests.cs new file mode 100644 index 000000000000..939339207f62 --- /dev/null +++ b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/DebugTests.cs @@ -0,0 +1,122 @@ +// 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.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.DotNet.SourceBuild.SmokeTests; + +public class DebugTests : SmokeTests +{ + private record ScanResult(string FileName, bool HasDebugInfo, bool HasDebugAbbrevs, bool HasFileSymbols, bool HasGnuDebugLink); + + public DebugTests(ITestOutputHelper outputHelper) : base(outputHelper) { } + + /// + /// Verifies that all generated native files include native debug symbols. + /// + [Fact] + public void SourceBuiltSdkContainsNativeDebugSymbols() + { + + var fileNames = Directory.EnumerateFiles(Config.DotNetDirectory, "*", SearchOption.AllDirectories); + var foundIssue = false; + StringBuilder issueDetails = new(); + foreach (var fileName in fileNames) + { + if (!IsElfFile(fileName)) + { + continue; + } + + var result = ScanFile(fileName); + + string newLine = Environment.NewLine; + + if (!result.HasDebugInfo) + { + foundIssue = true; + issueDetails.Append($"missing .debug_info section in {fileName}{newLine}"); + } + if (!result.HasDebugAbbrevs) + { + foundIssue = true; + issueDetails.Append($"missing .debug_abbrev section in {fileName}{newLine}"); + } + if (!result.HasFileSymbols) + { + foundIssue = true; + issueDetails.Append($"missing FILE symbols in {fileName}{newLine}"); + } + if (result.HasGnuDebugLink) + { + foundIssue = true; + issueDetails.Append($"unexpected .gnu_debuglink section in {fileName}{newLine}"); + } + } + + Assert.False(foundIssue, issueDetails.ToString()); + } + + private bool IsElfFile(string fileName) + { + string fileStdOut = ExecuteHelper.ExecuteProcessValidateExitCode("file", $"{fileName}", OutputHelper); + return Regex.IsMatch(fileStdOut, @"ELF 64-bit [LM]SB (?:pie )?(?:executable|shared object)"); + } + + private ScanResult ScanFile(string fileName) + { + string readelfSStdOut = ExecuteHelper.ExecuteProcessValidateExitCode("eu-readelf", $"-S {fileName}", OutputHelper); + + // Test for .debug_* sections in the shared object. This is the main test. + // Stripped objects will not contain these. + + bool hasDebugInfo = readelfSStdOut + .Split("\n") + .Where(line => line.Contains("] .debug_info")) + .Any(); + + bool hasDebugAbbrev = readelfSStdOut.Split("\n") + .Where(line => line.Contains("] .debug_abbrev")) + .Any(); + + string readelfsStdOut = ExecuteHelper.ExecuteProcessValidateExitCode("eu-readelf", $"-s {fileName}", OutputHelper); + + // Test FILE symbols. These will most likely be removed by anyting that + // manipulates symbol tables because it's generally useless. So a nice test + // that nothing has messed with symbols. + bool hasFileSymbols = readelfsStdOut.Split("\n").Where(ContainsFileSymbols).Any(); + + // Test that there are no .gnu_debuglink sections pointing to another + // debuginfo file. There shouldn't be any debuginfo files, so the link makes + // no sense either. + bool hasGnuDebuglink = readelfsStdOut.Split("\n").Where(line => line.Contains("] .gnu_debuglink")).Any(); + + return new ScanResult(fileName, hasDebugInfo, hasDebugAbbrev, hasFileSymbols, hasGnuDebuglink); + } + + private bool ContainsFileSymbols(string line) + { + // Try matching against output like this: + // 10: 0000000000000000 0 FILE LOCAL DEFAULT ABS coreclr_resolver.cpp + // 779: 0000000000000000 0 FILE LOCAL DEFAULT ABS header.cpp + + var parts = new Regex(@"[ \t\n\r]+").Split(line); + int expectedNumberOfParts = 9; + + if (parts.Length < expectedNumberOfParts) + { + return false; + } + + var fileNameRegex = new Regex(@"(.*/)?[-_a-zA-Z0-9]+\.(c|cc|cpp|cxx)"); + return (parts[3] == "0") && (parts[4] == "FILE") && (parts[5] == "LOCAL") && (parts[6] == "DEFAULT") && + (parts[7] == "ABS") && (fileNameRegex.IsMatch(parts[8])); + } +} diff --git a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/README.md b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/README.md index 5cd7d8e867f1..9e9586075efc 100644 --- a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/README.md +++ b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/README.md @@ -3,6 +3,15 @@ * Run these tests via `build.sh --run-smoke-test` * Various configuration settings are stored in `Config.cs` +## Dependencies + +Some tests need additional dependencies. These must be installed (manually and separately) on the system for the tests to pass. + +The following programs are used by some tests: + +- eu-readelf +- file + ## Prereq Packages Some prerelease scenarios, usually security updates, require non-source-built packages which are not publicly available.