Skip to content

Commit 383fa10

Browse files
committed
CoreCommands,PackageModel: introduce -debug-format option
This adds a new flag `-debug-format` that allows some structured control over the debug information format. This is particularly important for Windows where there are two different debugging information formats: - Code View (PDB) - DWARF While theoretically LLDB supports PDBs, the overall support for that is limited. Furthermore, the CodeView emitted by the swift frontend is limiting for debugging as expression evaluation as not possible due to the debug information encoding employed by Swift: skeletal information is emitted into the binary and the type information is queried dynamically by the AST deserialiser in the compiler. DWARF on the other hand does not support PE/COFF particularly well (e.g. exceeding the section name limits, representations potentially not being serialisable into the object file, etc), but allows a better experience with LLDB. More importantly, it breaks debugging with the system libraries (e.g. kernel32 or NTDLL). As a result, while it is often used with Swift code, PDBs can also be beneficial. Furthermore, DWARF forces the use of lld as the linker as link does not properly emit linked DWARF information. Provide a top-level control to emit the debug information in a particular format as both of these options require altering the C/C++, Swift, and linker flags. We default to DWARF on all platforms, but on Windows, if CodeView is selected, we will alter the flags appropriately to ease switching of debug information format.
1 parent a2ae2c9 commit 383fa10

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

Sources/CoreCommands/Options.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ public struct BuildOptions: ParsableArguments {
447447
@Option(name: .customLong("build-system"))
448448
var _buildSystem: BuildSystemProvider.Kind = .native
449449

450+
/// The Debug Information Format to use.
451+
@Option(name: .customLong("debug-info-format", withSingleDash: true))
452+
public var debugInfoFormat: DebugInfoFormat = .dwarf
453+
450454
public var buildSystem: BuildSystemProvider.Kind {
451455
#if os(macOS)
452456
// Force the Xcode build system if we want to build more than one arch.
@@ -499,6 +503,11 @@ public struct BuildOptions: ParsableArguments {
499503
/// See `BuildParameters.LinkTimeOptimizationMode.thin` for details.
500504
case thin
501505
}
506+
507+
public enum DebugInfoFormat: String, Codable, ExpressibleByArgument {
508+
case dwarf
509+
case codeview
510+
}
502511
}
503512

504513
public struct LinkerOptions: ParsableArguments {

Sources/CoreCommands/SwiftTool.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,12 +678,33 @@ public final class SwiftTool {
678678
component: destinationTriple.platformBuildPathComponent(buildSystem: options.build.buildSystem)
679679
)
680680

681+
var flags: BuildFlags = options.build.buildFlags
682+
if destinationTriple.isWindows() {
683+
switch options.build.debugInfoFormat {
684+
case .dwarf:
685+
// DWARF requires lld as link.exe expects CodeView debug info.
686+
flags = flags.merging(BuildFlags(
687+
cCompilerFlags: ["-gdwarf"],
688+
cxxCompilerFlags: ["-gdwarf"],
689+
swiftCompilerFlags: ["-g", "-use-ld=lld"],
690+
linkerFlags: ["-debug:dwarf"]
691+
))
692+
case .codeview:
693+
flags = flags.merging(BuildFlags(
694+
swiftCompilerFlags: ["-g", "-debug-info-format=codeview"],
695+
linkerFlags: ["-debug"]
696+
))
697+
}
698+
} else if options.build.debugInfoFormat == .codeview {
699+
observabilityScope.emit(error: "CodeView debug information is currently not supported for this platform")
700+
}
701+
681702
return try BuildParameters(
682703
dataPath: dataPath,
683704
configuration: options.build.configuration,
684705
toolchain: destinationToolchain,
685706
destinationTriple: destinationTriple,
686-
flags: options.build.buildFlags,
707+
flags: flags,
687708
pkgConfigDirectories: options.locations.pkgConfigDirectories,
688709
architectures: options.build.architectures,
689710
workers: options.build.jobs ?? UInt32(ProcessInfo.processInfo.activeProcessorCount),

Sources/PackageModel/BuildFlags.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,15 @@ public struct BuildFlags: Equatable, Encodable {
4040
self.linkerFlags = linkerFlags
4141
self.xcbuildFlags = xcbuildFlags
4242
}
43+
44+
public mutating func merging(_ flags: BuildFlags) -> Self {
45+
self.cCompilerFlags.insert(contentsOf: flags.cCompilerFlags, at: 0)
46+
self.cxxCompilerFlags.insert(contentsOf: flags.cxxCompilerFlags, at: 0)
47+
self.swiftCompilerFlags.insert(contentsOf: flags.swiftCompilerFlags, at: 0)
48+
self.linkerFlags.insert(contentsOf: flags.linkerFlags, at: 0)
49+
if self.xcbuildFlags != nil || flags.xcbuildFlags != nil {
50+
self.xcbuildFlags = (self.xcbuildFlags ?? []) + (flags.xcbuildFlags ?? [])
51+
}
52+
return self
53+
}
4354
}

Tests/CommandsTests/SwiftToolTests.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
@testable import Basics
14+
@testable import Build
1415
@testable import CoreCommands
1516
@testable import Commands
17+
@testable import PackageModel
1618
import SPMTestSupport
1719
import XCTest
1820

1921
import class TSCBasic.BufferedOutputByteStream
22+
import class TSCBasic.InMemoryFileSystem
2023
import protocol TSCBasic.OutputByteStream
2124
import var TSCBasic.stderrStream
2225

@@ -232,6 +235,66 @@ final class SwiftToolTests: CommandsTestCase {
232235
// Tests should not modify user's home dir .netrc so leaving that out intentionally
233236
}
234237
}
238+
239+
func testDebugFormatFlags() throws {
240+
let fs = InMemoryFileSystem(emptyFiles: [
241+
"/Pkg/Sources/exe/main.swift",
242+
])
243+
244+
let explicitDwarfOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc", "-debug-info-format", "dwarf"])
245+
let explicitDwarf = try SwiftTool.createSwiftToolForTest(options: explicitDwarfOptions)
246+
247+
let observer = ObservabilitySystem.makeForTesting()
248+
let graph = try loadPackageGraph(fileSystem: fs, manifests: [
249+
Manifest.createRootManifest(displayName: "Pkg",
250+
path: "/Pkg",
251+
targets: [TargetDescription(name: "exe")])
252+
], observabilityScope: observer.topScope)
253+
254+
var plan: BuildPlan
255+
256+
plan = try BuildPlan(
257+
buildParameters: explicitDwarf.buildParameters(),
258+
graph: graph,
259+
fileSystem: fs,
260+
observabilityScope: observer.topScope
261+
)
262+
try XCTAssertMatch(plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.first?.linkArguments() ?? [],
263+
[.anySequence, "-g", "-use-ld=lld", "-Xlinker", "-debug:dwarf"])
264+
265+
let explicitCodeViewOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc", "-debug-info-format", "codeview"])
266+
let explicitCodeView = try SwiftTool.createSwiftToolForTest(options: explicitCodeViewOptions)
267+
268+
plan = try BuildPlan(
269+
buildParameters: explicitCodeView.buildParameters(),
270+
graph: graph,
271+
fileSystem: fs,
272+
observabilityScope: observer.topScope
273+
)
274+
try XCTAssertMatch(plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.first?.linkArguments() ?? [],
275+
[.anySequence, "-g", "-debug-info-format=codeview", "-Xlinker", "-debug"])
276+
277+
let implicitDwarfOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc"])
278+
let implicitDwarf = try SwiftTool.createSwiftToolForTest(options: implicitDwarfOptions)
279+
plan = try BuildPlan(
280+
buildParameters: implicitDwarf.buildParameters(),
281+
graph: graph,
282+
fileSystem: fs,
283+
observabilityScope: observer.topScope
284+
)
285+
try XCTAssertMatch(plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.first?.linkArguments() ?? [],
286+
[.anySequence, "-g", "-use-ld=lld", "-Xlinker", "-debug:dwarf"])
287+
288+
// Explicitly pass Linux as when the SwiftTool tests are enabled on
289+
// Windows, this would fail otherwise as CodeView is supported on the
290+
// native host.
291+
let unsupportedCodeViewOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-linux-gnu", "-debug-info-format", "codeview"])
292+
let stream = BufferedOutputByteStream()
293+
let unsupportedCodeView = try SwiftTool.createSwiftToolForTest(outputStream: stream, options: unsupportedCodeViewOptions)
294+
295+
XCTAssertNotNil(try? unsupportedCodeView.buildParameters())
296+
XCTAssertMatch(stream.bytes.validDescription, .contains("error: CodeView debug information is currently not supported for this platform"))
297+
}
235298
}
236299

237300
extension SwiftTool {

0 commit comments

Comments
 (0)