Skip to content

Commit 9421a3b

Browse files
Fix dlsym issue (DataDog#6048)
## Summary of changes This PR addresses the issue DataDog#6045 ## Reason for change When using the `dlsym` function, the compiler adds in the import symbols table that we need the `dlsym` symbol. Before being a universal binary (same binary used for glibc-based linux and musl-libc-based linux) and the compiler added in a `DT_NEEDED` section the library `libdl.so` (the library containing `dlsym`). When the wrapper is loaded, it will look through all the `DT_NEEDED` sections to find a library that contains the `dlsym` symbol. Since being a universal binary, the `DT_NEEDED` sections are removed (part of being universal) and we have to resolve by hand needed symbols (`dlsym`, `pthread_once` ..). If we use `dlsym` (or other symbol), we will hit this issue. ## Implementation details - use `__dd_dlsym` instead ## Test coverage Added a snapshot test using `nm` that verifies that the undefined symbols in the universal binary haven't changed. It's equivalent to running ```bash nm -D Datadog.Linux.ApiWrapper.x64.so | grep ' U ' | awk '{print $2}' | sed 's/@.*//' | sort ``` but done using Nuke instead. It would probably make sense for this to be a "normal" test in the native tests, but given it has a dependency on `nm`, which is _definitely_ available in the universal build dockerfile it was quicker and easier to get this up and running directly. When it fails, it prints the diff and throws an exception, e.g. ```bash System.Exception: Found differences in undefined symbols (dlsym) 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 C:\repos\dd-trace-dotnet\tracer\test\snapshots\native-wrapper-symbols-x64.verified.txt with the new values ``` ## Other details This will be hotfixed onto 3.3.1 and 2.59.1 --------- Co-authored-by: Andrew Lock <[email protected]>
1 parent 4e08a41 commit 9421a3b

File tree

8 files changed

+152
-35
lines changed

8 files changed

+152
-35
lines changed

.nuke/build.schema.json

+2
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@
477477
"SignDlls",
478478
"SignMsi",
479479
"SummaryOfSnapshotChanges",
480+
"TestNativeWrapper",
480481
"UpdateChangeLog",
481482
"UpdateSnapshots",
482483
"UpdateSnapshotsFromBuild",
@@ -698,6 +699,7 @@
698699
"SignDlls",
699700
"SignMsi",
700701
"SummaryOfSnapshotChanges",
702+
"TestNativeWrapper",
701703
"UpdateChangeLog",
702704
"UpdateSnapshots",
703705
"UpdateSnapshotsFromBuild",

profiler/src/ProfilerEngine/Datadog.Linux.ApiWrapper/functions_to_wrap.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ void initLibrary(void)
209209

210210
// Bash provides its own version of the getenv/setenv functions
211211
// Fetch the original ones and use those instead
212-
char *(*real_getenv)(const char *) = (char *(*)(const char *))dlsym(RTLD_NEXT, "getenv");
213-
int (*real_setenv)(const char *, const char *, int) = (int (*)(const char *, const char *, int))dlsym(RTLD_NEXT, "setenv");
212+
char *(*real_getenv)(const char *) = __dd_dlsym(RTLD_NEXT, "getenv");
213+
int (*real_setenv)(const char *, const char *, int) = __dd_dlsym(RTLD_NEXT, "setenv");
214214

215215
if (real_getenv == NULL || real_setenv == NULL)
216216
{

tracer/build/_build/Build.Profiler.Steps.cs

+79
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Nuke.Common.Utilities;
1818
using System.Collections;
1919
using System.Threading.Tasks;
20+
using DiffMatchPatch;
2021
using Logger = Serilog.Log;
2122

2223
partial class Build
@@ -137,6 +138,84 @@ partial class Build
137138
arguments: $"--build {NativeBuildDirectory} --parallel {Environment.ProcessorCount} --target wrapper");
138139
});
139140

141+
Target TestNativeWrapper => _ => _
142+
.Unlisted()
143+
.Description("Test that the Native wrapper symbols haven't changed")
144+
.After(CompileNativeWrapper)
145+
.OnlyWhenStatic(() => IsLinux)
146+
.Executes(() =>
147+
{
148+
var (arch, _) = GetUnixArchitectureAndExtension();
149+
var libraryPath = ProfilerDeployDirectory / arch / LinuxApiWrapperLibrary;
150+
151+
var output = Nm.Value($"-D {libraryPath}").Select(x => x.Text).ToList();
152+
153+
// Gives output similar to this:
154+
// 0000000000006bc8 D DdDotnetFolder
155+
// 0000000000006bd0 D DdDotnetMuslFolder
156+
// w _ITM_deregisterTMCloneTable
157+
// w _ITM_registerTMCloneTable
158+
// w __cxa_finalize
159+
// w __deregister_frame_info
160+
// U __errno_location
161+
// U __tls_get_addr
162+
// 0000000000002d1b T _fini
163+
// 0000000000002d18 T _init
164+
// 0000000000003d70 T accept
165+
// 0000000000003e30 T accept4
166+
// U access
167+
//
168+
// The types of symbols are:
169+
// D: Data section symbol. These symbols are initialized global variables.
170+
// w: Weak symbol. These symbols are weakly referenced and can be overridden by other symbols.
171+
// U: Undefined symbol. These symbols are referenced in the file but defined elsewhere.
172+
// T: Text section symbol. These symbols are functions or executable code.
173+
// B: BSS (Block Started by Symbol) section symbol. These symbols are uninitialized global variables.
174+
//
175+
// We only care about the Undefined symbols - we don't want to accidentally add more of them
176+
177+
Logger.Debug("NM output: {Output}", string.Join(Environment.NewLine, output));
178+
179+
var symbols = output
180+
.Select(x => x.Trim())
181+
.Where(x => x.StartsWith("U "))
182+
.Select(x => x.TrimStart("U "))
183+
.OrderBy(x => x)
184+
.ToList();
185+
186+
187+
var received = string.Join(Environment.NewLine, symbols);
188+
var verifiedPath = TestsDirectory / "snapshots" / $"native-wrapper-symbols-{UnixArchitectureIdentifier}.verified.txt";
189+
var verified = File.Exists(verifiedPath)
190+
? File.ReadAllText(verifiedPath)
191+
: string.Empty;
192+
193+
Logger.Information("Comparing snapshot of Undefined symbols in the Native Wrapper library using {Path}...", verifiedPath);
194+
195+
var dmp = new diff_match_patch();
196+
var diff = dmp.diff_main(verified, received);
197+
dmp.diff_cleanupSemantic(diff);
198+
199+
var changedSymbols = diff
200+
.Where(x => x.operation != Operation.EQUAL)
201+
.Select(x => x.text.Trim())
202+
.ToList();
203+
204+
if (changedSymbols.Count == 0)
205+
{
206+
Logger.Information("No changes found in Undefined symbols in the Native Wrapper library");
207+
return;
208+
}
209+
210+
PrintDiff(diff);
211+
212+
throw new Exception($"Found differences in undefined symbols ({string.Join(",", changedSymbols)}) in the Native Wrapper library. " +
213+
"Verify that these changes are expected, and will not cause problems. " +
214+
"Removing symbols is generally a safe operation, but adding them could cause crashes. " +
215+
$"If the new symbols are safe to add, update the snapshot file at {verifiedPath} with the " +
216+
"new values");
217+
});
218+
140219
Target CompileNativeWrapperNativeTests => _ => _
141220
.Unlisted()
142221
.Description("Compile Native wrapper unit tests")

tracer/build/_build/Build.Steps.cs

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ partial class Build
9999
[LazyPathExecutable(name: "cppcheck")] readonly Lazy<Tool> CppCheck;
100100
[LazyPathExecutable(name: "run-clang-tidy")] readonly Lazy<Tool> RunClangTidy;
101101
[LazyPathExecutable(name: "patchelf")] readonly Lazy<Tool> PatchElf;
102+
[LazyPathExecutable(name: "nm")] readonly Lazy<Tool> Nm;
102103

103104
//OSX Tools
104105
readonly string[] OsxArchs = { "arm64", "x86_64" };

tracer/build/_build/Build.Utilities.cs

+39-33
Original file line numberDiff line numberDiff line change
@@ -337,39 +337,7 @@ partial class Build
337337
var diff = dmp.diff_main(File.ReadAllText(source.ToString().Replace("received", "verified")), File.ReadAllText(source));
338338
dmp.diff_cleanupSemantic(diff);
339339

340-
foreach (var t in diff)
341-
{
342-
if (t.operation != Operation.EQUAL)
343-
{
344-
var str = DiffToString(t);
345-
if (str.Contains(value: '\n'))
346-
{
347-
// if the diff is multiline, start with a newline so that all changes are aligned
348-
// otherwise it's easy to miss the first line of the diff
349-
str = "\n" + str;
350-
}
351-
352-
Logger.Information(str);
353-
}
354-
}
355-
}
356-
357-
string DiffToString(Diff diff)
358-
{
359-
if (diff.operation == Operation.EQUAL)
360-
{
361-
return string.Empty;
362-
}
363-
364-
var symbol = diff.operation switch
365-
{
366-
Operation.DELETE => '-',
367-
Operation.INSERT => '+',
368-
_ => throw new Exception("Unknown value of the Option enum.")
369-
};
370-
// put the symbol at the beginning of each line to make diff clearer when whole blocks of text are missing
371-
var lines = diff.text.TrimEnd(trimChar: '\n').Split(Environment.NewLine);
372-
return string.Join(Environment.NewLine, lines.Select(l => symbol + l));
340+
PrintDiff(diff);
373341
}
374342
});
375343

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

512480
private static MSBuildTargetPlatform ARM64TargetPlatform = (MSBuildTargetPlatform)"ARM64";
513481
private static MSBuildTargetPlatform ARM64ECTargetPlatform = (MSBuildTargetPlatform)"ARM64EC";
482+
483+
private static void PrintDiff(List<Diff> diff, bool printEqual = false)
484+
{
485+
foreach (var t in diff)
486+
{
487+
if (printEqual || t.operation != Operation.EQUAL)
488+
{
489+
var str = DiffToString(t);
490+
if (str.Contains(value: '\n'))
491+
{
492+
// if the diff is multiline, start with a newline so that all changes are aligned
493+
// otherwise it's easy to miss the first line of the diff
494+
str = "\n" + str;
495+
}
496+
497+
Logger.Information(str);
498+
}
499+
}
500+
501+
string DiffToString(Diff diff)
502+
{
503+
if (diff.operation == Operation.EQUAL)
504+
{
505+
return string.Empty;
506+
}
507+
508+
var symbol = diff.operation switch
509+
{
510+
Operation.DELETE => '-',
511+
Operation.INSERT => '+',
512+
_ => throw new Exception("Unknown value of the Option enum.")
513+
};
514+
// put the symbol at the beginning of each line to make diff clearer when whole blocks of text are missing
515+
var lines = diff.text.TrimEnd(trimChar: '\n').Split(Environment.NewLine);
516+
return string.Join(Environment.NewLine, lines.Select(l => symbol + l));
517+
}
518+
519+
}
514520
}

tracer/build/_build/Build.cs

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ void DeleteReparsePoints(string path)
230230
.Description("")
231231
.After(Clean)
232232
.DependsOn(CompileNativeWrapper)
233+
.DependsOn(TestNativeWrapper)
233234
.DependsOn(PublishNativeWrapper);
234235

235236
Target PackageTracerHome => _ => _
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
__errno_location
2+
access
3+
asprintf
4+
free
5+
getauxval
6+
getenv
7+
malloc
8+
strcasecmp
9+
strcmp
10+
strcpy
11+
strlen
12+
strncmp
13+
strncpy
14+
strrchr
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
__errno_location
2+
__tls_get_addr
3+
access
4+
asprintf
5+
free
6+
getenv
7+
malloc
8+
strcasecmp
9+
strcmp
10+
strcpy
11+
strlen
12+
strncmp
13+
strncpy
14+
strrchr

0 commit comments

Comments
 (0)