Skip to content

Commit 45339b6

Browse files
authored
Merge pull request #66410 from al45tair/eng/PR-110262673-5.9
[Backtracing][Linux] Add Linux support to swift-backtrace.
2 parents 827b4fd + f61e72c commit 45339b6

File tree

5 files changed

+276
-25
lines changed

5 files changed

+276
-25
lines changed

stdlib/public/Backtracing/BacktraceFormatter.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ public struct BacktraceFormatter {
523523
///
524524
/// @result An array of strings, one per column.
525525
public func formatColumns(frame: Backtrace.Frame,
526-
addressWidth: Int = 64,
526+
addressWidth: Int,
527527
index: Int? = nil) -> [String] {
528528
let pc: String
529529
var attrs: [String] = []
@@ -563,7 +563,7 @@ public struct BacktraceFormatter {
563563
///
564564
/// @result An array of table rows.
565565
public func formatRows(frame: Backtrace.Frame,
566-
addressWidth: Int = 64,
566+
addressWidth: Int,
567567
index: Int? = nil) -> [TableRow] {
568568
return [.columns(formatColumns(frame: frame,
569569
addressWidth: addressWidth,
@@ -578,7 +578,7 @@ public struct BacktraceFormatter {
578578
///
579579
/// @result A `String` containing the formatted data.
580580
public func format(frame: Backtrace.Frame,
581-
addressWidth: Int = 64,
581+
addressWidth: Int,
582582
index: Int? = nil) -> String {
583583
let rows = formatRows(frame: frame,
584584
addressWidth: addressWidth,
@@ -593,7 +593,7 @@ public struct BacktraceFormatter {
593593
///
594594
/// @result A `String` containing the formatted data.
595595
public func format(frames: some Sequence<Backtrace.Frame>,
596-
addressWidth: Int = 64) -> String {
596+
addressWidth: Int) -> String {
597597
var rows: [TableRow] = []
598598

599599
var n = 0
@@ -734,7 +734,7 @@ public struct BacktraceFormatter {
734734
///
735735
/// @result An array of strings, one per column.
736736
public func formatColumns(frame: SymbolicatedBacktrace.Frame,
737-
addressWidth: Int = 64,
737+
addressWidth: Int,
738738
index: Int? = nil) -> [String] {
739739
let pc: String
740740
var attrs: [String] = []
@@ -851,7 +851,7 @@ public struct BacktraceFormatter {
851851
///
852852
/// @result An array of table rows.
853853
public func formatRows(frame: SymbolicatedBacktrace.Frame,
854-
addressWidth: Int = 64,
854+
addressWidth: Int,
855855
index: Int? = nil,
856856
showSource: Bool = true) -> [TableRow] {
857857
let columns = formatColumns(frame: frame,
@@ -880,7 +880,7 @@ public struct BacktraceFormatter {
880880
///
881881
/// @result A `String` containing the formatted data.
882882
public func format(frame: SymbolicatedBacktrace.Frame,
883-
addressWidth: Int = 64,
883+
addressWidth: Int,
884884
index: Int? = nil,
885885
showSource: Bool = true) -> String {
886886
let rows = formatRows(frame: frame, addressWidth: addressWidth,
@@ -902,7 +902,7 @@ public struct BacktraceFormatter {
902902
///
903903
/// @result A `String` containing the formatted data.
904904
public func format(frames: some Sequence<SymbolicatedBacktrace.Frame>,
905-
addressWidth: Int = 64) -> String {
905+
addressWidth: Int) -> String {
906906
var rows: [TableRow] = []
907907
var sourceLocationsShown = Set<SymbolicatedBacktrace.SourceLocation>()
908908

@@ -982,7 +982,7 @@ public struct BacktraceFormatter {
982982
///
983983
/// @result An array of strings, one per column.
984984
public func formatColumns(image: Backtrace.Image,
985-
addressWidth: Int = 64) -> [String] {
985+
addressWidth: Int) -> [String] {
986986
let addressRange = "\(hex(image.baseAddress, width: addressWidth))\(hex(image.endOfText, width: addressWidth))"
987987
let buildID: String
988988
if let bytes = image.buildID {
@@ -1011,7 +1011,7 @@ public struct BacktraceFormatter {
10111011
///
10121012
/// @result A string containing the formatted data.
10131013
public func format(images: some Sequence<Backtrace.Image>,
1014-
addressWidth: Int = 64) -> String {
1014+
addressWidth: Int) -> String {
10151015
let rows = images.map{
10161016
TableRow.columns(
10171017
formatColumns(image: $0,

stdlib/public/libexec/swift-backtrace/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ add_swift_target_executable(swift-backtrace BUILD_WITH_LIBEXEC
3030
main.swift
3131
AnsiColor.swift
3232
TargetMacOS.swift
33+
TargetLinux.swift
3334
Themes.swift
3435
Utils.swift
3536

@@ -44,5 +45,5 @@ add_swift_target_executable(swift-backtrace BUILD_WITH_LIBEXEC
4445
${BACKTRACING_COMPILE_FLAGS}
4546
-parse-as-library
4647

47-
TARGET_SDKS OSX)
48+
TARGET_SDKS OSX LINUX)
4849

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
//===--- TargetLinux.swift - Represents a process we are inspecting -------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Defines `Target`, which represents the process we are inspecting.
14+
// This is the Linux version.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#if os(Linux)
19+
20+
import Glibc
21+
22+
import _Backtracing
23+
@_spi(Internal) import _Backtracing
24+
@_spi(Contexts) import _Backtracing
25+
@_spi(MemoryReaders) import _Backtracing
26+
@_spi(Utils) import _Backtracing
27+
28+
@_implementationOnly import Runtime
29+
30+
struct TargetThread {
31+
typealias ThreadID = pid_t
32+
33+
var id: ThreadID
34+
var context: HostContext?
35+
var name: String
36+
var backtrace: SymbolicatedBacktrace
37+
}
38+
39+
class Target {
40+
typealias Address = UInt64
41+
42+
var pid: pid_t
43+
var name: String
44+
var signal: UInt64
45+
var faultAddress: Address
46+
var crashingThread: TargetThread.ThreadID
47+
48+
var images: [Backtrace.Image] = []
49+
50+
var threads: [TargetThread] = []
51+
var crashingThreadNdx: Int = -1
52+
53+
var signalName: String {
54+
switch signal {
55+
case UInt64(SIGQUIT): return "SIGQUIT"
56+
case UInt64(SIGABRT): return "SIGABRT"
57+
case UInt64(SIGBUS): return "SIGBUS"
58+
case UInt64(SIGFPE): return "SIGFPE"
59+
case UInt64(SIGILL): return "SIGILL"
60+
case UInt64(SIGSEGV): return "SIGSEGV"
61+
case UInt64(SIGTRAP): return "SIGTRAP"
62+
default: return "\(signal)"
63+
}
64+
}
65+
66+
var signalDescription: String {
67+
switch signal {
68+
case UInt64(SIGQUIT): return "Terminated"
69+
case UInt64(SIGABRT): return "Aborted"
70+
case UInt64(SIGBUS): return "Bus error"
71+
case UInt64(SIGFPE): return "Floating point exception"
72+
case UInt64(SIGILL): return "Illegal instruction"
73+
case UInt64(SIGSEGV): return "Bad pointer dereference"
74+
case UInt64(SIGTRAP): return "System trap"
75+
default:
76+
return "Signal \(signal)"
77+
}
78+
}
79+
80+
var reader: MemserverMemoryReader
81+
82+
// Get the name of a process
83+
private static func getProcessName(pid: pid_t) -> String {
84+
let path = "/proc/\(pid)/comm"
85+
guard let name = readString(from: path) else {
86+
return ""
87+
}
88+
return String(stripWhitespace(name))
89+
}
90+
91+
/// Get the name of a thread
92+
private func getThreadName(tid: Int64) -> String {
93+
let path = "/proc/\(pid)/task/\(tid)/comm"
94+
guard let name = readString(from: path) else {
95+
return ""
96+
}
97+
let trimmed = String(stripWhitespace(name))
98+
99+
// Allow the main thread to use the process' name, but other
100+
// threads will have an empty name unless they've set the name
101+
// explicitly
102+
if trimmed == self.name && pid != tid {
103+
return ""
104+
}
105+
return trimmed
106+
}
107+
108+
init(crashInfoAddr: UInt64, limit: Int?, top: Int, cache: Bool) {
109+
// fd #4 is reserved for the memory server
110+
let memserverFd: CInt = 4
111+
112+
pid = getppid()
113+
reader = MemserverMemoryReader(fd: memserverFd)
114+
name = Self.getProcessName(pid: pid)
115+
116+
let crashInfo: CrashInfo
117+
do {
118+
crashInfo = try reader.fetch(from: crashInfoAddr, as: CrashInfo.self)
119+
} catch {
120+
print("swift-backtrace: unable to fetch crash info.")
121+
exit(1)
122+
}
123+
124+
crashingThread = TargetThread.ThreadID(crashInfo.crashing_thread)
125+
signal = crashInfo.signal
126+
faultAddress = crashInfo.fault_address
127+
128+
images = Backtrace.captureImages(using: reader,
129+
forProcess: Int(pid))
130+
131+
do {
132+
try fetchThreads(threadListHead: Address(crashInfo.thread_list),
133+
limit: limit, top: top, cache: cache)
134+
} catch {
135+
print("swift-backtrace: failed to fetch thread information: \(error)")
136+
exit(1)
137+
}
138+
}
139+
140+
/// Fetch information about all of the process's threads; the crash_info
141+
/// structure contains a linked list of thread ucontexts, which may not
142+
/// include every thread. In particular, if a thread was stuck in an
143+
/// uninterruptible wait, we won't have a ucontext for it.
144+
func fetchThreads(
145+
threadListHead: Address,
146+
limit: Int?, top: Int, cache: Bool
147+
) throws {
148+
var next = threadListHead
149+
150+
while next != 0 {
151+
let t = try reader.fetch(from: next, as: thread.self)
152+
153+
next = t.next
154+
155+
guard let ucontext
156+
= try? reader.fetch(from: t.uctx, as: ucontext_t.self) else {
157+
// This can happen if a thread is in an uninterruptible wait
158+
continue
159+
}
160+
161+
let context = HostContext.fromHostMContext(ucontext.uc_mcontext)
162+
let backtrace = try Backtrace.capture(from: context,
163+
using: reader,
164+
images: images,
165+
limit: limit,
166+
top: top)
167+
guard let symbolicated
168+
= backtrace.symbolicated(with: images,
169+
sharedCacheInfo: nil,
170+
useSymbolCache: cache) else {
171+
print("unable to symbolicate backtrace for thread \(t.tid)")
172+
exit(1)
173+
}
174+
175+
threads.append(TargetThread(id: TargetThread.ThreadID(t.tid),
176+
context: context,
177+
name: getThreadName(tid: t.tid),
178+
backtrace: symbolicated))
179+
}
180+
181+
// Sort the threads by thread ID; the main thread always sorts
182+
// lower than any other.
183+
threads.sort {
184+
return $0.id == pid || ($1.id != pid && $0.id < $1.id)
185+
}
186+
187+
// Find the crashing thread index
188+
if let ndx = threads.firstIndex(where: { $0.id == crashingThread }) {
189+
crashingThreadNdx = ndx
190+
} else {
191+
print("unable to find the crashing thread")
192+
exit(1)
193+
}
194+
}
195+
196+
func redoBacktraces(limit: Int?, top: Int, cache: Bool) {
197+
for (ndx, thread) in threads.enumerated() {
198+
guard let context = thread.context else {
199+
continue
200+
}
201+
202+
guard let backtrace = try? Backtrace.capture(from: context,
203+
using: reader,
204+
images: images,
205+
limit: limit,
206+
top: top) else {
207+
print("unable to capture backtrace from context for thread \(ndx)")
208+
continue
209+
}
210+
211+
guard let symbolicated = backtrace.symbolicated(with: images,
212+
sharedCacheInfo: nil,
213+
useSymbolCache: cache) else {
214+
print("unable to symbolicate backtrace from context for thread \(ndx)")
215+
continue
216+
}
217+
218+
threads[ndx].backtrace = symbolicated
219+
}
220+
}
221+
222+
func withDebugger(_ body: () -> ()) throws {
223+
print("""
224+
From another shell, please run
225+
226+
lldb --attach-pid \(pid) -o c
227+
""")
228+
body()
229+
}
230+
}
231+
232+
#endif // os(Linux)

stdlib/public/libexec/swift-backtrace/Utils.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ internal func parseUInt64<S: StringProtocol>(_ s: S) -> UInt64? {
5151
}
5252
}
5353

54-
#if os(macOS)
54+
#if os(macOS) || os(Linux)
5555

5656
struct PosixError: Error {
5757
var errno: Int32
@@ -139,6 +139,8 @@ internal func spawn(_ path: String, args: [String]) throws {
139139
}
140140
}
141141

142+
#endif // os(macOS)
143+
142144
struct CFileStream: TextOutputStream {
143145
var fp: UnsafeMutablePointer<FILE>
144146

@@ -153,5 +155,3 @@ struct CFileStream: TextOutputStream {
153155

154156
var standardOutput = CFileStream(fp: stdout)
155157
var standardError = CFileStream(fp: stderr)
156-
157-
#endif // os(macOS)

0 commit comments

Comments
 (0)