Skip to content

Commit 3e78ff8

Browse files
[πŸ’] Detect system GNU ld script pretending to be system libraries (#9296) (#9359)
- **Explanation**: The `experimental-audit-binary-artifact` command fails in certain distributions since it doesn't account for `ld` scripts. - **Scope**: The cherry picked change is entirely scoped to the `experimental-audit-binary-artifact` command and has no changes in other parts of the Swift PM code. This command is also Linux only so it doesn't affect the other platforms. - **Issues**: This came up when trying to use the `experimental-audit-binary-artifact` in `swift-temporal-sdk`. - **Original PRs**: #9296 - **Risk**: Low since it only touches the experimental command - **Testing**: I just tested this on the latest nightly main toolchain and it works now. - **Reviewers**: @dschaefer2 @FranzBusch Co-authored-by: Daniel Grumberg <dany.grumberg@gmail.com>
1 parent fafd153 commit 3e78ff8

File tree

2 files changed

+64
-10
lines changed

2 files changed

+64
-10
lines changed

β€ŽSources/BinarySymbols/ClangHostDefaultObjectsDetector.swiftβ€Ž

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import Foundation
1414
import protocol TSCBasic.WritableByteStream
1515

1616
package func detectDefaultObjects(
17-
clang: AbsolutePath, fileSystem: any FileSystem, hostTriple: Triple
17+
clang: AbsolutePath, fileSystem: any FileSystem, hostTriple: Triple,
18+
observabilityScope: ObservabilityScope
1819
) async throws -> [AbsolutePath] {
1920
let clangProcess = AsyncProcess(args: clang.pathString, "-###", "-x", "c", "-")
2021
let stdinStream = try clangProcess.launch()
@@ -56,25 +57,71 @@ package func detectDefaultObjects(
5657
linkerArguments.append(contentsOf: ["-lm", "-lpthread", "-ldl"])
5758
}
5859

59-
for argument in linkerArguments {
60+
func handleArgument(_ argument: String) throws {
6061
if argument.hasPrefix("-L") {
6162
searchPaths.append(try AbsolutePath(validating: String(argument.dropFirst(2))))
6263
} else if argument.hasPrefix("-l") && !argument.hasSuffix("lto_library") {
6364
let libraryName = argument.dropFirst(2)
6465
let potentialLibraries = searchPaths.flatMap { path in
65-
if libraryName == "gcc_s" && hostTriple.isLinux() {
66-
// Try and pick this up first as libgcc_s tends to be either this or a GNU ld script that pulls this in.
67-
return [path.appending("libgcc_s.so.1")]
68-
} else {
69-
return libraryExtensions.map { ext in path.appending("\(hostTriple.dynamicLibraryPrefix)\(libraryName)\(ext)") }
66+
return libraryExtensions.map { ext in
67+
path.appending("\(hostTriple.dynamicLibraryPrefix)\(libraryName)\(ext)")
7068
}
7169
}
7270

7371
guard let library = potentialLibraries.first(where: { fileSystem.isFile($0) }) else {
74-
throw StringError("Couldn't find library: \(libraryName)")
72+
observabilityScope.emit(warning: "Could not find library: \(libraryName)")
73+
return
74+
}
75+
76+
// Try and detect if this a GNU ld linker script.
77+
if let fileContents = try fileSystem.readFileContents(library).validDescription {
78+
let lines = fileContents.split(whereSeparator: \.isNewline)
79+
guard lines.contains(where: { $0.contains("GNU ld script") }) else {
80+
objects.insert(library)
81+
return
82+
}
83+
84+
// If it is try and parse GROUP/INPUT commands as documented in https://sourceware.org/binutils/docs/ld/File-Commands.html
85+
// Empirically it seems like GROUP is the only used such directive for libraries of interest.
86+
// Empirically it looks like packaging linker scripts use spaces around parenthesis which greatly simplifies parsing.
87+
let inputs = lines.filter { $0.hasPrefix("GROUP") || $0.hasPrefix("INPUT") }
88+
let words = inputs.flatMap { $0.split(whereSeparator: \.isWhitespace) }
89+
let newArguments = words.filter {
90+
!["GROUP", "AS_NEEDED", "INPUT"].contains($0) && $0 != "(" && $0 != ")"
91+
}.map(String.init)
92+
93+
for arg in newArguments {
94+
if arg.hasPrefix("-l") {
95+
try handleArgument(arg)
96+
} else {
97+
// First try and locate the file relative to the linker script.
98+
let siblingPath = try AbsolutePath(
99+
validating: arg,
100+
relativeTo: try AbsolutePath(validating: library.dirname))
101+
if fileSystem.isFile(siblingPath) {
102+
try handleArgument(siblingPath.pathString)
103+
} else {
104+
// If this fails the file needs to be resolved relative to the search paths.
105+
guard
106+
let library = searchPaths.map({ $0.appending(arg) }).first(where: {
107+
fileSystem.isFile($0)
108+
})
109+
else {
110+
observabilityScope.emit(
111+
warning:
112+
"Malformed linker script at \(library): found no library named \(arg)"
113+
)
114+
continue
115+
}
116+
try handleArgument(library.pathString)
117+
}
118+
}
119+
}
120+
121+
} else {
122+
objects.insert(library)
75123
}
76124

77-
objects.insert(library)
78125
} else if try argument.hasSuffix(".o")
79126
&& fileSystem.isFile(AbsolutePath(validating: argument))
80127
{
@@ -86,5 +133,9 @@ package func detectDefaultObjects(
86133
}
87134
}
88135

136+
for argument in linkerArguments {
137+
try handleArgument(argument)
138+
}
139+
89140
return objects.compactMap { $0 }
90141
}

β€ŽSources/Commands/PackageCommands/AuditBinaryArtifact.swiftβ€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ struct AuditBinaryArtifact: AsyncSwiftCommand {
5050
var hostDefaultSymbols = ReferencedSymbols()
5151
let symbolProvider = LLVMObjdumpSymbolProvider(objdumpPath: objdump)
5252
for binary in try await detectDefaultObjects(
53-
clang: clang, fileSystem: fileSystem, hostTriple: hostTriple)
53+
clang: clang, fileSystem: fileSystem, hostTriple: hostTriple,
54+
observabilityScope:
55+
swiftCommandState.observabilityScope.makeChildScope(
56+
description: "DefaultObjectsDetector"))
5457
{
5558
try await symbolProvider.symbols(
5659
for: binary, symbols: &hostDefaultSymbols, recordUndefined: false)

0 commit comments

Comments
Β (0)