mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
We want to be able to efficiently serialise lists of images, and to do so it makes most sense to create a separate `ImageMap` type. This also provides a useful place to put methods to e.g. find an image by address or by build ID. rdar://124913332
298 lines
8.8 KiB
Swift
298 lines
8.8 KiB
Swift
//===--- TargetLinux.swift - Represents a process we are inspecting -------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2022 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Defines `Target`, which represents the process we are inspecting.
|
|
// This is the Linux version.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if os(Linux)
|
|
|
|
#if canImport(Glibc)
|
|
import Glibc
|
|
#elseif canImport(Musl)
|
|
import Musl
|
|
#endif
|
|
|
|
import Runtime
|
|
@_spi(Internal) import Runtime
|
|
@_spi(Contexts) import Runtime
|
|
@_spi(MemoryReaders) import Runtime
|
|
@_spi(Utils) import Runtime
|
|
|
|
enum SomeBacktrace {
|
|
case raw(Backtrace)
|
|
case symbolicated(SymbolicatedBacktrace)
|
|
}
|
|
|
|
struct TargetThread {
|
|
typealias ThreadID = pid_t
|
|
|
|
var id: ThreadID
|
|
var context: HostContext?
|
|
var name: String
|
|
var backtrace: SomeBacktrace
|
|
}
|
|
|
|
class Target {
|
|
typealias Address = UInt64
|
|
|
|
var pid: pid_t
|
|
var name: String
|
|
var signal: UInt64
|
|
var faultAddress: Address
|
|
var crashingThread: TargetThread.ThreadID
|
|
|
|
var images: ImageMap
|
|
|
|
var threads: [TargetThread] = []
|
|
var crashingThreadNdx: Int = -1
|
|
|
|
var signalName: String {
|
|
switch signal {
|
|
case UInt64(SIGQUIT): return "SIGQUIT"
|
|
case UInt64(SIGABRT): return "SIGABRT"
|
|
case UInt64(SIGBUS): return "SIGBUS"
|
|
case UInt64(SIGFPE): return "SIGFPE"
|
|
case UInt64(SIGILL): return "SIGILL"
|
|
case UInt64(SIGSEGV): return "SIGSEGV"
|
|
case UInt64(SIGTRAP): return "SIGTRAP"
|
|
default: return "\(signal)"
|
|
}
|
|
}
|
|
|
|
var signalDescription: String {
|
|
switch signal {
|
|
case UInt64(SIGQUIT): return "Terminated"
|
|
case UInt64(SIGABRT): return "Aborted"
|
|
case UInt64(SIGBUS): return "Bus error"
|
|
case UInt64(SIGFPE): return "Floating point exception"
|
|
case UInt64(SIGILL): return "Illegal instruction"
|
|
case UInt64(SIGSEGV): return "Bad pointer dereference"
|
|
case UInt64(SIGTRAP): return "System trap"
|
|
default:
|
|
return "Signal \(signal)"
|
|
}
|
|
}
|
|
|
|
var reader: MemserverMemoryReader
|
|
|
|
// Get the name of a process
|
|
private static func getProcessName(pid: pid_t) -> String {
|
|
let path = "/proc/\(pid)/comm"
|
|
guard let name = readString(from: path) else {
|
|
return ""
|
|
}
|
|
return String(stripWhitespace(name))
|
|
}
|
|
|
|
/// Get the name of a thread
|
|
private func getThreadName(tid: Int64) -> String {
|
|
let path = "/proc/\(pid)/task/\(tid)/comm"
|
|
guard let name = readString(from: path) else {
|
|
return ""
|
|
}
|
|
let trimmed = String(stripWhitespace(name))
|
|
|
|
// Allow the main thread to use the process' name, but other
|
|
// threads will have an empty name unless they've set the name
|
|
// explicitly
|
|
if trimmed == self.name && pid != tid {
|
|
return ""
|
|
}
|
|
return trimmed
|
|
}
|
|
|
|
init(crashInfoAddr: UInt64, limit: Int?, top: Int, cache: Bool,
|
|
symbolicate: SwiftBacktrace.Symbolication) {
|
|
// fd #4 is reserved for the memory server
|
|
let memserverFd: CInt = 4
|
|
|
|
pid = getppid()
|
|
reader = MemserverMemoryReader(fd: memserverFd)
|
|
name = Self.getProcessName(pid: pid)
|
|
|
|
let crashInfo: CrashInfo
|
|
do {
|
|
crashInfo = try reader.fetch(from: crashInfoAddr, as: CrashInfo.self)
|
|
} catch {
|
|
print("swift-backtrace: unable to fetch crash info.")
|
|
exit(1)
|
|
}
|
|
|
|
crashingThread = TargetThread.ThreadID(crashInfo.crashing_thread)
|
|
signal = crashInfo.signal
|
|
faultAddress = crashInfo.fault_address
|
|
|
|
images = ImageMap.capture(using: reader, forProcess: Int(pid))
|
|
|
|
do {
|
|
try fetchThreads(threadListHead: Address(crashInfo.thread_list),
|
|
limit: limit, top: top, cache: cache,
|
|
symbolicate: symbolicate)
|
|
} catch {
|
|
print("swift-backtrace: failed to fetch thread information: \(error)")
|
|
exit(1)
|
|
}
|
|
}
|
|
|
|
/// Fetch information about all of the process's threads; the crash_info
|
|
/// structure contains a linked list of thread ucontexts, which may not
|
|
/// include every thread. In particular, if a thread was stuck in an
|
|
/// uninterruptible wait, we won't have a ucontext for it.
|
|
func fetchThreads(
|
|
threadListHead: Address,
|
|
limit: Int?, top: Int, cache: Bool,
|
|
symbolicate: SwiftBacktrace.Symbolication
|
|
) throws {
|
|
var next = threadListHead
|
|
|
|
while next != 0 {
|
|
let t = try reader.fetch(from: next, as: thread.self)
|
|
|
|
next = t.next
|
|
|
|
guard let ucontext
|
|
= try? reader.fetch(from: t.uctx, as: ucontext_t.self) else {
|
|
// This can happen if a thread is in an uninterruptible wait
|
|
continue
|
|
}
|
|
|
|
let context = HostContext.fromHostMContext(ucontext.uc_mcontext)
|
|
let backtrace = try Backtrace.capture(from: context,
|
|
using: reader,
|
|
images: images,
|
|
algorithm: .auto,
|
|
limit: limit,
|
|
offset: 0,
|
|
top: top)
|
|
|
|
let shouldSymbolicate: Bool
|
|
var options: Backtrace.SymbolicationOptions
|
|
switch symbolicate {
|
|
case .off:
|
|
shouldSymbolicate = false
|
|
options = []
|
|
case .fast:
|
|
shouldSymbolicate = true
|
|
options = [ .showSourceLocations ]
|
|
case .full:
|
|
shouldSymbolicate = true
|
|
options = [ .showInlineFrames, .showSourceLocations ]
|
|
}
|
|
|
|
if cache {
|
|
options.insert(.useSymbolCache)
|
|
}
|
|
|
|
if shouldSymbolicate {
|
|
guard let symbolicated = backtrace.symbolicated(
|
|
with: images,
|
|
options: options
|
|
) else {
|
|
print("unable to symbolicate backtrace for thread \(t.tid)")
|
|
exit(1)
|
|
}
|
|
|
|
threads.append(TargetThread(id: TargetThread.ThreadID(t.tid),
|
|
context: context,
|
|
name: getThreadName(tid: t.tid),
|
|
backtrace: .symbolicated(symbolicated)))
|
|
} else {
|
|
threads.append(TargetThread(id: TargetThread.ThreadID(t.tid),
|
|
context: context,
|
|
name: getThreadName(tid: t.tid),
|
|
backtrace: .raw(backtrace)))
|
|
}
|
|
}
|
|
|
|
// Sort the threads by thread ID; the main thread always sorts
|
|
// lower than any other.
|
|
threads.sort {
|
|
return $0.id == pid || ($1.id != pid && $0.id < $1.id)
|
|
}
|
|
|
|
// Find the crashing thread index
|
|
if let ndx = threads.firstIndex(where: { $0.id == crashingThread }) {
|
|
crashingThreadNdx = ndx
|
|
} else {
|
|
print("unable to find the crashing thread")
|
|
exit(1)
|
|
}
|
|
}
|
|
|
|
func redoBacktraces(
|
|
limit: Int?, top: Int, cache: Bool,
|
|
symbolicate: SwiftBacktrace.Symbolication
|
|
) {
|
|
for (ndx, thread) in threads.enumerated() {
|
|
guard let context = thread.context else {
|
|
continue
|
|
}
|
|
|
|
guard let backtrace = try? Backtrace.capture(from: context,
|
|
using: reader,
|
|
images: images,
|
|
algorithm: .auto,
|
|
limit: limit,
|
|
offset: 0,
|
|
top: top) else {
|
|
print("unable to capture backtrace from context for thread \(ndx)")
|
|
continue
|
|
}
|
|
|
|
let shouldSymbolicate: Bool
|
|
var options: Backtrace.SymbolicationOptions
|
|
switch symbolicate {
|
|
case .off:
|
|
shouldSymbolicate = false
|
|
options = []
|
|
case .fast:
|
|
shouldSymbolicate = true
|
|
options = [ .showSourceLocations ]
|
|
case .full:
|
|
shouldSymbolicate = true
|
|
options = [ .showInlineFrames, .showSourceLocations ]
|
|
}
|
|
|
|
if cache {
|
|
options.insert(.useSymbolCache)
|
|
}
|
|
|
|
if shouldSymbolicate {
|
|
guard let symbolicated = backtrace.symbolicated(
|
|
with: images,
|
|
options: options
|
|
) else {
|
|
print("unable to symbolicate backtrace from context for thread \(ndx)")
|
|
continue
|
|
}
|
|
|
|
threads[ndx].backtrace = .symbolicated(symbolicated)
|
|
} else {
|
|
threads[ndx].backtrace = .raw(backtrace)
|
|
}
|
|
}
|
|
}
|
|
|
|
func withDebugger(_ body: () -> ()) throws {
|
|
print("""
|
|
From another shell, please run
|
|
|
|
lldb --attach-pid \(pid) -o c
|
|
""")
|
|
body()
|
|
}
|
|
}
|
|
|
|
#endif // os(Linux)
|