Skip to content

Commit 855b129

Browse files
authored
Codesign w/ debugging entitlement for backtraces on macOS (#7010)
### Motivation: The new Swift backtracer will only function on macOS if: 1. `SWIFT_BACKTRACE` environment variable has its `enable` option set to `yes` or `tty`, and 2. The program you are running was signed with the `com.apple.security.get-task-allow` entitlement. Xcode automatically signs all local builds with that entitlement by default; without it the Swift backtracer won't work. SwiftPM should sign Debug binaries with the entitlement by default. It should also have an _option_ to sign Release binaries with the entitlement, noting that they should not be distributed in that state but that crashes may only manifest in release builds (where the optimizer is fully enabled). ### Modifications: Added new `--enable-get-task-allow-entitlement` and `--disable-get-task-allow-entitlement` CLI flags. Made it enabled by default for debug builds on macOS. When enabled, applying codesigning with `com.apple.security.get-task-allow` in `LLBuildManifestBuilder+Product.swift`. ### Result: Backtraces work on macOS when built with SwiftPM. Resolves rdar://112065568.
1 parent 6c8bb2e commit 855b129

File tree

12 files changed

+592
-117
lines changed

12 files changed

+592
-117
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Swift Next
77

88
In packages that specify resources using a future tools version, the generated resource bundle accessor will import `Foundation.Bundle` for its own implementation only. _Clients_ of such packages therefore no longer silently import `Foundation`, preventing inadvertent use of Foundation extensions to standard library APIs, which helps to avoid unexpected code size increases.
99

10+
* [#7010]
11+
12+
On macOS, `swift build` and `swift run` now produce binaries that allow backtraces in debug builds. Pass `SWIFT_BACKTRACE=enable=yes` environment variable to enable backtraces on such binaries when running them.
1013

1114
Swift 5.9
1215
-----------

Sources/Build/BuildDescription/ProductBuildDescription.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription
367367

368368
return flags
369369
}
370+
371+
func codeSigningArguments(plistPath: AbsolutePath, binaryPath: AbsolutePath) -> [String] {
372+
["codesign", "--force", "--sign", "-", "--entitlements", plistPath.pathString, binaryPath.pathString]
373+
}
370374
}
371375

372376
extension SortedArray where Element == AbsolutePath {

Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,19 @@ extension LLBuildManifestBuilder {
3333
testInputs = []
3434
}
3535

36+
// Create a phony node to represent the entire target.
37+
let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig)
38+
let output: Node = .virtual(targetName)
39+
40+
let finalProductNode: Node
3641
switch buildProduct.product.type {
3742
case .library(.static):
43+
finalProductNode = try .file(buildProduct.binaryPath)
3844
try self.manifest.addShellCmd(
3945
name: cmdName,
4046
description: "Archiving \(buildProduct.binaryPath.prettyPath())",
4147
inputs: (buildProduct.objects + [buildProduct.linkFileListPath]).map(Node.file),
42-
outputs: [.file(buildProduct.binaryPath)],
48+
outputs: [finalProductNode],
4349
arguments: try buildProduct.archiveArguments()
4450
)
4551

@@ -49,23 +55,55 @@ extension LLBuildManifestBuilder {
4955
+ [buildProduct.linkFileListPath]
5056
+ testInputs
5157

58+
let shouldCodeSign: Bool
59+
let linkedBinaryNode: Node
60+
let linkedBinaryPath = try buildProduct.binaryPath
61+
if case .executable = buildProduct.product.type,
62+
buildParameters.targetTriple.isMacOSX,
63+
buildParameters.debuggingParameters.shouldEnableDebuggingEntitlement {
64+
shouldCodeSign = true
65+
linkedBinaryNode = try .file(buildProduct.binaryPath, isMutated: true)
66+
} else {
67+
shouldCodeSign = false
68+
linkedBinaryNode = try .file(buildProduct.binaryPath)
69+
}
70+
5271
try self.manifest.addShellCmd(
5372
name: cmdName,
5473
description: "Linking \(buildProduct.binaryPath.prettyPath())",
5574
inputs: inputs.map(Node.file),
56-
outputs: [.file(buildProduct.binaryPath)],
75+
outputs: [linkedBinaryNode],
5776
arguments: try buildProduct.linkArguments()
5877
)
59-
}
6078

61-
// Create a phony node to represent the entire target.
62-
let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig)
63-
let output: Node = .virtual(targetName)
79+
if shouldCodeSign {
80+
let basename = try buildProduct.binaryPath.basename
81+
let plistPath = try buildProduct.binaryPath.parentDirectory
82+
.appending(component: "\(basename)-entitlement.plist")
83+
self.manifest.addEntitlementPlistCommand(
84+
entitlement: "com.apple.security.get-task-allow",
85+
outputPath: plistPath
86+
)
87+
88+
let cmdName = try buildProduct.product.getCommandName(config: self.buildConfig)
89+
let codeSigningOutput = Node.virtual(targetName + "-CodeSigning")
90+
try self.manifest.addShellCmd(
91+
name: "\(cmdName)-entitlements",
92+
description: "Applying debug entitlements to \(buildProduct.binaryPath.prettyPath())",
93+
inputs: [linkedBinaryNode, .file(plistPath)],
94+
outputs: [codeSigningOutput],
95+
arguments: buildProduct.codeSigningArguments(plistPath: plistPath, binaryPath: linkedBinaryPath)
96+
)
97+
finalProductNode = codeSigningOutput
98+
} else {
99+
finalProductNode = linkedBinaryNode
100+
}
101+
}
64102

65103
self.manifest.addNode(output, toTarget: targetName)
66-
try self.manifest.addPhonyCmd(
104+
self.manifest.addPhonyCmd(
67105
name: output.name,
68-
inputs: [.file(buildProduct.binaryPath)],
106+
inputs: [finalProductNode],
69107
outputs: [output]
70108
)
71109

Sources/Build/BuildManifest/LLBuildManifestBuilder.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import struct TSCBasic.ByteString
2222
import enum TSCBasic.ProcessEnv
2323
import func TSCBasic.topologicalSort
2424

25+
/// High-level interface to ``LLBuildManifest`` and ``LLBuildManifestWriter``.
2526
public class LLBuildManifestBuilder {
2627
public enum TargetKind {
2728
case main
@@ -42,7 +43,7 @@ public class LLBuildManifestBuilder {
4243
public let disableSandboxForPluginCommands: Bool
4344

4445
/// File system reference.
45-
let fileSystem: FileSystem
46+
let fileSystem: any FileSystem
4647

4748
/// ObservabilityScope with which to emit diagnostics
4849
public let observabilityScope: ObservabilityScope
@@ -60,7 +61,7 @@ public class LLBuildManifestBuilder {
6061
public init(
6162
_ plan: BuildPlan,
6263
disableSandboxForPluginCommands: Bool = false,
63-
fileSystem: FileSystem,
64+
fileSystem: any FileSystem,
6465
observabilityScope: ObservabilityScope
6566
) {
6667
self.plan = plan

Sources/CoreCommands/Options.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,12 @@ public struct BuildOptions: ParsableArguments {
477477
)
478478
public var linkTimeOptimizationMode: LinkTimeOptimizationMode?
479479

480+
@Flag(help: .hidden)
481+
public var enableGetTaskAllowEntitlement: Bool = false
482+
483+
@Flag(help: .hidden)
484+
public var disableGetTaskAllowEntitlement: Bool = false
485+
480486
// @Flag works best when there is a default value present
481487
// if true, false aren't enough and a third state is needed
482488
// nil should not be the goto. Instead create an enum

Sources/CoreCommands/SwiftTool.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,11 @@ public final class SwiftTool {
664664
return buildSystem
665665
}
666666

667+
static let entitlementsMacOSWarning = """
668+
`--disable-get-task-allow-entitlement` and `--disable-get-task-allow-entitlement` only have an effect \
669+
when building on macOS.
670+
"""
671+
667672
private func _buildParams(toolchain: UserToolchain) throws -> BuildParameters {
668673
let hostTriple = try self.getHostToolchain().targetTriple
669674
let targetTriple = toolchain.targetTriple
@@ -672,6 +677,12 @@ public final class SwiftTool {
672677
component: targetTriple.platformBuildPathComponent(buildSystem: options.build.buildSystem)
673678
)
674679

680+
if !targetTriple.isMacOSX && (
681+
options.build.disableGetTaskAllowEntitlement || options.build.enableGetTaskAllowEntitlement
682+
) {
683+
observabilityScope.emit(warning: Self.entitlementsMacOSWarning)
684+
}
685+
675686
return try BuildParameters(
676687
dataPath: dataPath,
677688
configuration: options.build.configuration,
@@ -685,7 +696,13 @@ public final class SwiftTool {
685696
sanitizers: options.build.enabledSanitizers,
686697
indexStoreMode: options.build.indexStoreMode.buildParameter,
687698
isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode,
688-
debugInfoFormat: options.build.debugInfoFormat.buildParameter,
699+
debuggingParameters: .init(
700+
debugInfoFormat: options.build.debugInfoFormat.buildParameter,
701+
targetTriple: targetTriple,
702+
shouldEnableDebuggingEntitlement:
703+
(options.build.configuration == .debug && !options.build.disableGetTaskAllowEntitlement) ||
704+
(options.build.enableGetTaskAllowEntitlement && !options.build.disableGetTaskAllowEntitlement)
705+
),
689706
driverParameters: .init(
690707
canRenameEntrypointFunctionName: driverSupport.checkSupportedFrontendFlags(
691708
flags: ["entry-point-function-name"],
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2020-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import struct Basics.Triple
14+
import enum PackageModel.BuildConfiguration
15+
16+
extension BuildParameters {
17+
public struct Debugging: Encodable {
18+
public init(
19+
debugInfoFormat: DebugInfoFormat = .dwarf,
20+
targetTriple: Triple,
21+
shouldEnableDebuggingEntitlement: Bool
22+
) {
23+
self.debugInfoFormat = debugInfoFormat
24+
25+
// Per rdar://112065568 for backtraces to work on macOS a special entitlement needs to be granted on the final
26+
// executable.
27+
self.shouldEnableDebuggingEntitlement = targetTriple.isMacOSX && shouldEnableDebuggingEntitlement
28+
}
29+
30+
public var debugInfoFormat: DebugInfoFormat
31+
32+
/// Whether the produced executable should be codesigned with the debugging entitlement, enabling enhanced
33+
/// backtraces on macOS.
34+
public var shouldEnableDebuggingEntitlement: Bool
35+
}
36+
37+
/// Represents the debugging strategy.
38+
///
39+
/// Swift binaries requires the swiftmodule files in order for lldb to work.
40+
/// On Darwin, linker can directly take the swiftmodule file path using the
41+
/// -add_ast_path flag. On other platforms, we convert the swiftmodule into
42+
/// an object file using Swift's modulewrap tool.
43+
public enum DebuggingStrategy {
44+
case swiftAST
45+
case modulewrap
46+
}
47+
48+
/// The debugging strategy according to the current build parameters.
49+
public var debuggingStrategy: DebuggingStrategy? {
50+
guard configuration == .debug else {
51+
return nil
52+
}
53+
54+
if targetTriple.isApple() {
55+
return .swiftAST
56+
}
57+
return .modulewrap
58+
}
59+
60+
/// Represents the debug information format.
61+
///
62+
/// The debug information format controls the format of the debug information
63+
/// that the compiler generates. Some platforms support debug information
64+
// formats other than DWARF.
65+
public enum DebugInfoFormat: String, Encodable {
66+
/// DWARF debug information format, the default format used by Swift.
67+
case dwarf
68+
/// CodeView debug information format, used on Windows.
69+
case codeview
70+
/// No debug information to be emitted.
71+
case none
72+
}
73+
74+
}

Sources/SPMBuildCore/BuildParameters/BuildParameters.swift

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,6 @@ public struct BuildParameters: Encodable {
2626
case auto
2727
}
2828

29-
/// Represents the debug information format.
30-
///
31-
/// The debug information format controls the format of the debug information
32-
/// that the compiler generates. Some platforms support debug information
33-
// formats other than DWARF.
34-
public enum DebugInfoFormat: String, Encodable {
35-
/// DWARF debug information format, the default format used by Swift.
36-
case dwarf
37-
/// CodeView debug information format, used on Windows.
38-
case codeview
39-
/// No debug information to be emitted.
40-
case none
41-
}
42-
43-
/// Represents the debugging strategy.
44-
///
45-
/// Swift binaries requires the swiftmodule files in order for lldb to work.
46-
/// On Darwin, linker can directly take the swiftmodule file path using the
47-
/// -add_ast_path flag. On other platforms, we convert the swiftmodule into
48-
/// an object file using Swift's modulewrap tool.
49-
public enum DebuggingStrategy {
50-
case swiftAST
51-
case modulewrap
52-
}
53-
5429
/// The path to the data directory.
5530
public var dataPath: AbsolutePath
5631

@@ -125,10 +100,11 @@ public struct BuildParameters: Encodable {
125100
/// Whether the Xcode build system is used.
126101
public var isXcodeBuildSystemEnabled: Bool
127102

128-
public var debugInfoFormat: DebugInfoFormat
129-
130103
public var shouldSkipBuilding: Bool
131104

105+
/// Build parameters related to debugging.
106+
public var debuggingParameters: Debugging
107+
132108
/// Build parameters related to Swift Driver.
133109
public var driverParameters: Driver
134110

@@ -155,21 +131,25 @@ public struct BuildParameters: Encodable {
155131
sanitizers: EnabledSanitizers = EnabledSanitizers(),
156132
indexStoreMode: IndexStoreMode = .auto,
157133
isXcodeBuildSystemEnabled: Bool = false,
158-
debugInfoFormat: DebugInfoFormat = .dwarf,
159134
shouldSkipBuilding: Bool = false,
135+
debuggingParameters: Debugging? = nil,
160136
driverParameters: Driver = .init(),
161137
linkingParameters: Linking = .init(),
162138
outputParameters: Output = .init(),
163139
testingParameters: Testing? = nil
164140
) throws {
165141
let targetTriple = try targetTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompilerPath)
142+
self.debuggingParameters = debuggingParameters ?? .init(
143+
targetTriple: targetTriple,
144+
shouldEnableDebuggingEntitlement: configuration == .debug
145+
)
166146

167147
self.dataPath = dataPath
168148
self.configuration = configuration
169149
self._toolchain = _Toolchain(toolchain: toolchain)
170150
self.hostTriple = try hostTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompilerPath)
171151
self.targetTriple = targetTriple
172-
switch debugInfoFormat {
152+
switch self.debuggingParameters.debugInfoFormat {
173153
case .dwarf:
174154
var flags = flags
175155
// DWARF requires lld as link.exe expects CodeView debug info.
@@ -205,13 +185,11 @@ public struct BuildParameters: Encodable {
205185
self.sanitizers = sanitizers
206186
self.indexStoreMode = indexStoreMode
207187
self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled
208-
self.debugInfoFormat = debugInfoFormat
209188
self.shouldSkipBuilding = shouldSkipBuilding
210189
self.driverParameters = driverParameters
211190
self.linkingParameters = linkingParameters
212191
self.outputParameters = outputParameters
213192
self.testingParameters = testingParameters ?? .init(configuration: configuration, targetTriple: targetTriple)
214-
215193
}
216194

217195
public func forTriple(_ targetTriple: Triple) throws -> BuildParameters {
@@ -280,19 +258,6 @@ public struct BuildParameters: Encodable {
280258
public var testOutputPath: AbsolutePath {
281259
return buildPath.appending(component: "testOutput.txt")
282260
}
283-
284-
/// The debugging strategy according to the current build parameters.
285-
public var debuggingStrategy: DebuggingStrategy? {
286-
guard configuration == .debug else {
287-
return nil
288-
}
289-
290-
if targetTriple.isApple() {
291-
return .swiftAST
292-
}
293-
return .modulewrap
294-
}
295-
296261
/// Returns the path to the binary of a product for the current build parameters.
297262
public func binaryPath(for product: ResolvedProduct) throws -> AbsolutePath {
298263
return try buildPath.appending(binaryRelativePath(for: product))

Sources/SPMBuildCore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
add_library(SPMBuildCore
1010
BinaryTarget+Extensions.swift
1111
BuildParameters/BuildParameters.swift
12+
BuildParameters/BuildParameters+Debugging.swift
1213
BuildParameters/BuildParameters+Driver.swift
1314
BuildParameters/BuildParameters+Linking.swift
1415
BuildParameters/BuildParameters+Output.swift

0 commit comments

Comments
 (0)