Skip to content

Commit

Permalink
Add a snapshot test for the native symbols in the universal binary
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewlock committed Sep 18, 2024
1 parent 62fa7be commit 5ff7c72
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .azure-pipelines/ultimate-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ stages:
target: builder
baseImage: "universal"
useNativeSdkVersion: true
command: "Clean BuildNativeLoader BuildNativeWrapper ExtractDebugInfoLinux"
command: "Clean BuildNativeLoader BuildNativeWrapper TestNativeWrapper ExtractDebugInfoLinux"
retryCountForRunCommand: 1

- publish: $(monitoringHome)/linux-musl-x64
Expand Down
2 changes: 2 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@
"SignDlls",
"SignMsi",
"SummaryOfSnapshotChanges",
"TestNativeWrapper",
"UpdateChangeLog",
"UpdateSnapshots",
"UpdateSnapshotsFromBuild",
Expand Down Expand Up @@ -698,6 +699,7 @@
"SignDlls",
"SignMsi",
"SummaryOfSnapshotChanges",
"TestNativeWrapper",
"UpdateChangeLog",
"UpdateSnapshots",
"UpdateSnapshotsFromBuild",
Expand Down
79 changes: 79 additions & 0 deletions tracer/build/_build/Build.Profiler.Steps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Nuke.Common.Utilities;
using System.Collections;
using System.Threading.Tasks;
using DiffMatchPatch;
using Logger = Serilog.Log;

partial class Build
Expand Down Expand Up @@ -137,6 +138,84 @@ partial class Build
arguments: $"--build {NativeBuildDirectory} --parallel {Environment.ProcessorCount} --target wrapper");
});

Target TestNativeWrapper => _ => _
.Unlisted()
.Description("Test that the Native wrapper symbols haven't changed")
.After(CompileNativeWrapper)
.OnlyWhenStatic(() => IsLinux)
.Executes(() =>
{
var (arch, _) = GetUnixArchitectureAndExtension();
var libraryPath = ProfilerDeployDirectory / arch / LinuxApiWrapperLibrary;
var output = Nm.Value($"-D{libraryPath}").Select(x => x.Text).ToList();
// Gives output similar to this:
// 0000000000006bc8 D DdDotnetFolder
// 0000000000006bd0 D DdDotnetMuslFolder
// w _ITM_deregisterTMCloneTable
// w _ITM_registerTMCloneTable
// w __cxa_finalize
// w __deregister_frame_info
// U __errno_location
// U __tls_get_addr
// 0000000000002d1b T _fini
// 0000000000002d18 T _init
// 0000000000003d70 T accept
// 0000000000003e30 T accept4
// U access
//
// The types of symbols are:
// D: Data section symbol. These symbols are initialized global variables.
// w: Weak symbol. These symbols are weakly referenced and can be overridden by other symbols.
// U: Undefined symbol. These symbols are referenced in the file but defined elsewhere.
// T: Text section symbol. These symbols are functions or executable code.
// B: BSS (Block Started by Symbol) section symbol. These symbols are uninitialized global variables.
//
// We only care about the Undefined symbols - we don't want to accidentally add more of them
Logger.Debug("NM output: {Output}", string.Join(Environment.NewLine, output));
var symbols = output
.Select(x => x.Trim())
.Where(x => x.StartsWith("U "))
.Select(x => x.TrimStart("U "))
.OrderBy(x => x)
.ToList();
var received = string.Join(Environment.NewLine, symbols);
var verifiedPath = TestsDirectory / "snapshots" / "native-wrapper-symbols.verified.txt";
var verified = File.Exists(verifiedPath)
? File.ReadAllText(verifiedPath)
: string.Empty;
Logger.Information("Comparing snapshot of Undefined symbols in the Native Wrapper library using {Path}...", verifiedPath);
var dmp = new diff_match_patch();
var diff = dmp.diff_main(verified, received);
dmp.diff_cleanupSemantic(diff);
var changedSymbols = diff
.Where(x => x.operation != Operation.EQUAL)
.Select(x => x.text.Trim())
.ToList();
if (changedSymbols.Count == 0)
{
Logger.Information("No changes found in Undefined symbols in the Native Wrapper library");
return;
}
PrintDiff(diff);
throw new Exception($"Found differences in undefined symbols ({string.Join(",", changedSymbols)}) in the Native Wrapper library. " +
"Verify that these changes are expected, and will not cause problems. " +
"Removing symbols is generally a safe operation, but adding them could cause crashes. " +
$"If the new symbols are safe to add, update the snapshot file at {verifiedPath} with the " +
"new values");
});

Target CompileNativeWrapperNativeTests => _ => _
.Unlisted()
.Description("Compile Native wrapper unit tests")
Expand Down
1 change: 1 addition & 0 deletions tracer/build/_build/Build.Steps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ partial class Build
[LazyPathExecutable(name: "cppcheck")] readonly Lazy<Tool> CppCheck;
[LazyPathExecutable(name: "run-clang-tidy")] readonly Lazy<Tool> RunClangTidy;
[LazyPathExecutable(name: "patchelf")] readonly Lazy<Tool> PatchElf;
[LazyPathExecutable(name: "nm")] readonly Lazy<Tool> Nm;

//OSX Tools
readonly string[] OsxArchs = { "arm64", "x86_64" };
Expand Down
72 changes: 39 additions & 33 deletions tracer/build/_build/Build.Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,39 +337,7 @@ partial class Build
var diff = dmp.diff_main(File.ReadAllText(source.ToString().Replace("received", "verified")), File.ReadAllText(source));
dmp.diff_cleanupSemantic(diff);
foreach (var t in diff)
{
if (t.operation != Operation.EQUAL)
{
var str = DiffToString(t);
if (str.Contains(value: '\n'))
{
// if the diff is multiline, start with a newline so that all changes are aligned
// otherwise it's easy to miss the first line of the diff
str = "\n" + str;
}
Logger.Information(str);
}
}
}
string DiffToString(Diff diff)
{
if (diff.operation == Operation.EQUAL)
{
return string.Empty;
}
var symbol = diff.operation switch
{
Operation.DELETE => '-',
Operation.INSERT => '+',
_ => throw new Exception("Unknown value of the Option enum.")
};
// put the symbol at the beginning of each line to make diff clearer when whole blocks of text are missing
var lines = diff.text.TrimEnd(trimChar: '\n').Split(Environment.NewLine);
return string.Join(Environment.NewLine, lines.Select(l => symbol + l));
PrintDiff(diff);
}
});

Expand Down Expand Up @@ -511,4 +479,42 @@ private static string GetDefaultRuntimeIdentifier(bool isAlpine)

private static MSBuildTargetPlatform ARM64TargetPlatform = (MSBuildTargetPlatform)"ARM64";
private static MSBuildTargetPlatform ARM64ECTargetPlatform = (MSBuildTargetPlatform)"ARM64EC";

private static void PrintDiff(List<Diff> diff)
{
foreach (var t in diff)
{
if (t.operation != Operation.EQUAL)
{
var str = DiffToString(t);
if (str.Contains(value: '\n'))
{
// if the diff is multiline, start with a newline so that all changes are aligned
// otherwise it's easy to miss the first line of the diff
str = "\n" + str;
}

Logger.Information(str);
}
}

string DiffToString(Diff diff)
{
if (diff.operation == Operation.EQUAL)
{
return string.Empty;
}

var symbol = diff.operation switch
{
Operation.DELETE => '-',
Operation.INSERT => '+',
_ => throw new Exception("Unknown value of the Option enum.")
};
// put the symbol at the beginning of each line to make diff clearer when whole blocks of text are missing
var lines = diff.text.TrimEnd(trimChar: '\n').Split(Environment.NewLine);
return string.Join(Environment.NewLine, lines.Select(l => symbol + l));
}

}
}
14 changes: 14 additions & 0 deletions tracer/test/snapshots/native-wrapper-symbols.verified.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
__errno_location
__tls_get_addr
access
asprintf
free
getenv
malloc
strcasecmp
strcmp
strcpy
strlen
strncmp
strncpy
strrchr

0 comments on commit 5ff7c72

Please sign in to comment.