Skip to content

Commit 76e4076

Browse files
committed
[Backtracing][Linux] Add Linux support to swift-backtrace.
We need a Linux specific `Target` implementation, and a couple of minor tweaks to make things build everywhere. rdar://110262673
1 parent c8377ae commit 76e4076

File tree

4 files changed

+266
-15
lines changed

4 files changed

+266
-15
lines changed

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+
public 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+
public 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)

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

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#if os(macOS)
13+
#if (os(macOS) || os(Linux)) && (arch(x86_64) || arch(arm64))
1414

1515
#if canImport(Darwin)
1616
import Darwin.C
@@ -524,7 +524,13 @@ Generate a backtrace for the parent process.
524524
tcgetattr(0, &oldAttrs)
525525

526526
var newAttrs = oldAttrs
527-
newAttrs.c_lflag &= ~(UInt(ICANON) | UInt(ECHO))
527+
528+
#if os(Linux)
529+
newAttrs.c_lflag &= ~UInt32(ICANON | ECHO)
530+
#else
531+
newAttrs.c_lflag &= ~UInt(ICANON | ECHO)
532+
#endif
533+
528534
tcsetattr(0, TCSANOW, &newAttrs)
529535

530536
return oldAttrs
@@ -581,7 +587,7 @@ Generate a backtrace for the parent process.
581587
static func backtraceFormatter() -> BacktraceFormatter {
582588
var terminalSize = winsize(ws_row: 24, ws_col: 80,
583589
ws_xpixel: 1024, ws_ypixel: 768)
584-
_ = ioctl(0, TIOCGWINSZ, &terminalSize)
590+
_ = ioctl(0, CUnsignedLong(TIOCGWINSZ), &terminalSize)
585591

586592
return BacktraceFormatter(formattingOptions
587593
.theme(theme)
@@ -607,15 +613,14 @@ Generate a backtrace for the parent process.
607613

608614
writeln("")
609615
writeln(theme.crashReason(description))
610-
writeln("")
611616

612617
var mentionedImages = Set<Int>()
613618
let formatter = backtraceFormatter()
614619

615620
func dump(ndx: Int, thread: TargetThread) {
616621
let crashed = thread.id == target.crashingThread ? " crashed" : ""
617622
let name = !thread.name.isEmpty ? " \"\(thread.name)\"" : ""
618-
writeln("Thread \(ndx)\(name)\(crashed):\n")
623+
writeln("\nThread \(ndx)\(name)\(crashed):\n")
619624

620625
if args.registers! == .all {
621626
if let context = thread.context {
@@ -660,6 +665,7 @@ Generate a backtrace for the parent process.
660665
}
661666
}
662667

668+
let addressWidthInChars = (crashingThread.backtrace.addressWidth + 3) / 4
663669
switch args.showImages! {
664670
case .none:
665671
break
@@ -671,10 +677,12 @@ Generate a backtrace for the parent process.
671677
} else {
672678
writeln("\n\nImages:\n")
673679
}
674-
writeln(formatter.format(images: images))
680+
writeln(formatter.format(images: images,
681+
addressWidth: addressWidthInChars))
675682
case .all:
676683
writeln("\n\nImages:\n")
677-
writeln(formatter.format(images: target.images))
684+
writeln(formatter.format(images: target.images,
685+
addressWidth: addressWidthInChars))
678686
}
679687
}
680688

@@ -750,11 +758,14 @@ Generate a backtrace for the parent process.
750758
let name = thread.name.isEmpty ? "" : " \(thread.name)"
751759
writeln("Thread \(currentThread) id=\(thread.id)\(name)\(crashed)\n")
752760

761+
let addressWidthInChars = (backtrace.addressWidth + 3) / 4
762+
753763
if let frame = backtrace.frames.drop(while: {
754764
$0.isSwiftRuntimeFailure
755765
}).first {
756766
let formatter = backtraceFormatter()
757-
let formatted = formatter.format(frame: frame)
767+
let formatted = formatter.format(frame: frame,
768+
addressWidth: addressWidthInChars)
758769
writeln("\(formatted)")
759770
}
760771
break
@@ -809,6 +820,7 @@ Generate a backtrace for the parent process.
809820
var rows: [BacktraceFormatter.TableRow] = []
810821
for (n, thread) in target.threads.enumerated() {
811822
let backtrace = thread.backtrace
823+
let addressWidthInChars = (backtrace.addressWidth + 3) / 4
812824

813825
let crashed: String
814826
if n == target.crashingThreadNdx {
@@ -827,7 +839,10 @@ Generate a backtrace for the parent process.
827839
$0.isSwiftRuntimeFailure
828840
}).first {
829841

830-
rows += formatter.formatRows(frame: frame).map{ row in
842+
rows += formatter.formatRows(
843+
frame: frame,
844+
addressWidth: addressWidthInChars).map{ row in
845+
831846
switch row {
832847
case let .columns(columns):
833848
return .columns([ "", "" ] + columns)
@@ -846,8 +861,11 @@ Generate a backtrace for the parent process.
846861
writeln(output)
847862
case "images":
848863
let formatter = backtraceFormatter()
849-
let images = target.threads[currentThread].backtrace.images
850-
let output = formatter.format(images: images)
864+
let backtrace = target.threads[currentThread].backtrace
865+
let images = backtrace.images
866+
let addressWidthInChars = (backtrace.addressWidth + 3) / 4
867+
let output = formatter.format(images: images,
868+
addressWidth: addressWidthInChars)
851869

852870
writeln(output)
853871
case "set":

0 commit comments

Comments
 (0)