-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add function to print all threads (#88)
- Loading branch information
Showing
5 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// | ||
// EMGTracer+PrintThreads.m | ||
// | ||
// | ||
// Created by Itay Brenner on 15/8/24. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
#import <Tracer.h> | ||
@import TracerSwift; | ||
|
||
@implementation EMGTracer (PrintThread) | ||
|
||
+ (void)printThreads { | ||
[ThreadHelper printThreads]; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// | ||
// ThreadHelper.swift | ||
// Tracer | ||
// | ||
// Created by Itay Brenner on 23/7/24. | ||
// | ||
|
||
import Foundation | ||
import Darwin | ||
import MachO | ||
|
||
public struct StackFrame { | ||
let symbol: String | ||
let file: String | ||
let address: UInt | ||
|
||
var demangledSymbol: String { | ||
return _stdlib_demangleName(symbol) | ||
} | ||
} | ||
|
||
public struct ThreadInfo: Hashable { | ||
let name: String | ||
let number: Int | ||
} | ||
|
||
@objc | ||
public class ThreadHelper: NSObject { | ||
public static let main_thread_t = mach_thread_self() | ||
static var symbolsLoaded = false | ||
static var symbolAddressTuples = [(UInt, String)]() | ||
|
||
@objc | ||
public static func printThreads() { | ||
NSLog("Stack trace:") | ||
let backtrace = callStackForAllThreads() | ||
|
||
for (thread, stackframe) in backtrace { | ||
NSLog("Thread \(thread.number): \(thread.name)") | ||
|
||
for (index, frame) in stackframe.enumerated() { | ||
NSLog(" \(index) - \(frame.demangledSymbol) [0x\(String(frame.address, radix: 16))] (\(frame.file)") | ||
} | ||
} | ||
} | ||
|
||
public static func callStackForAllThreads() -> [ThreadInfo: [StackFrame]] { | ||
var result: [ThreadInfo: [StackFrame]] = [:] | ||
|
||
var count: mach_msg_type_number_t = 0 | ||
var threads: thread_act_array_t! | ||
|
||
guard task_threads(mach_task_self_, &(threads), &count) == KERN_SUCCESS else { | ||
return result | ||
} | ||
|
||
for i in 0..<count { | ||
let index = Int(i) | ||
if let p_thread = pthread_from_mach_thread_np((threads[index])) { | ||
let thread: thread_t = threads[index] | ||
let pthread = pthread_from_mach_thread_np(thread)! | ||
if pthread == pthread_self() { | ||
// Skip our thread | ||
continue | ||
} | ||
|
||
let threadName = getThreadName(p_thread) ?? "" | ||
|
||
thread_suspend(thread) | ||
let stacks = getCallStack(thread) ?? [] | ||
thread_resume(thread) | ||
|
||
let threadInfo: ThreadInfo = ThreadInfo(name: threadName,number: index) | ||
|
||
result[threadInfo] = stacks | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
static func getCallStack(_ threadId: thread_t) -> [StackFrame]? { | ||
var symbols = [StackFrame]() | ||
let array = getStacktrace(forThread: threadId) | ||
for address in array { | ||
var info = Dl_info() | ||
if dladdr(UnsafeRawPointer(bitPattern: UInt(address)), &info) != 0 { | ||
let functionName = info.dli_sname.map { String(cString: $0) } ?? alternativeSymbolName(UInt(address)) | ||
let fileName = info.dli_fname.map { String(cString: $0) } ?? "<unknown>" | ||
|
||
symbols.append(StackFrame(symbol: functionName, file: fileName, address: UInt(address))) | ||
} | ||
} | ||
return symbols | ||
} | ||
|
||
static func alternativeSymbolName(_ address: UInt) -> String { | ||
NSLog("Using alternate name") | ||
if (!symbolsLoaded) { | ||
parseImages() | ||
} | ||
|
||
var previous: (UInt, String)? = nil | ||
for (addr, str) in symbolAddressTuples { | ||
if addr > address { | ||
return previous?.1 ?? "Invalid" | ||
} | ||
previous = (addr, str) | ||
} | ||
return "<unknown>" | ||
} | ||
|
||
private static func getThreadName(_ thread: pthread_t) -> String? { | ||
var name = [Int8](repeating: 0, count: 256) | ||
|
||
let result = pthread_getname_np(thread, &name, name.count) | ||
if result != 0 { | ||
print("Failed to get thread name: \(result)") | ||
return nil | ||
} | ||
|
||
return String(cString: name) | ||
} | ||
|
||
private static func parseImages() { | ||
for i in 0..<_dyld_image_count() { | ||
guard let header = _dyld_get_image_header(i) else { continue } | ||
let slide = _dyld_get_image_vmaddr_slide(i) | ||
|
||
let bytes: UnsafeRawPointer = UnsafeRawPointer(OpaquePointer(header)) | ||
var symtabCommand: symtab_command? | ||
var linkeditCmd: segment_command_64? | ||
bytes.processLoadComands { command, commandPointer in | ||
switch command.cmd { | ||
case UInt32(LC_SYMTAB): | ||
let commandType = commandPointer.load(as: symtab_command.self) | ||
symtabCommand = commandType | ||
case UInt32(LC_SEGMENT_64): | ||
let cmd = commandPointer.load(as: segment_command_64.self) | ||
var segname = cmd.segname | ||
if strcmp(&segname, SEG_LINKEDIT) == 0 { | ||
linkeditCmd = commandPointer.load(as: segment_command_64.self) | ||
} | ||
default: | ||
break | ||
} | ||
return true | ||
} | ||
|
||
guard let command = symtabCommand, let linkeditCmd = linkeditCmd else { continue } | ||
|
||
let linkeditBase = slide + Int(linkeditCmd.vmaddr) - Int(linkeditCmd.fileoff) | ||
parseTable(command: command, linkeditBase, slide) | ||
} | ||
|
||
symbolAddressTuples.sort { addr1, addr2 in | ||
return addr1.0 < addr2.0 | ||
} | ||
symbolsLoaded = true | ||
} | ||
|
||
private static func parseTable(command: symtab_command, _ linkeditBase: Int, _ slide: Int) { | ||
let imageBase = UnsafeRawPointer(bitPattern: linkeditBase)! | ||
let nsyms = command.nsyms | ||
let symStart = imageBase.advanced(by: Int(command.symoff)) | ||
let strStart = imageBase.advanced(by: Int(command.stroff)) | ||
for i in 0..<nsyms { | ||
let symbolStart = symStart.advanced(by: Int(i) * MemoryLayout<nlist_64>.size) | ||
let nlist = symbolStart.load(as: nlist_64.self) | ||
guard (nlist.n_type & UInt8(N_STAB) == 0) && nlist.n_value != 0 else { continue } | ||
|
||
let stringStart = strStart.advanced(by: Int(nlist.n_un.n_strx)) | ||
let string = String(cString: stringStart.assumingMemoryBound(to: UInt8.self)) | ||
|
||
// Add slide since frame addresses will have it | ||
symbolAddressTuples.append((UInt(nlist.n_value) + UInt(slide), string)) | ||
} | ||
} | ||
|
||
static func getStacktrace(forThread thread: thread_t) -> [UInt64] { | ||
var frameCount: UInt64 = 0 | ||
let kMaxFramesPerStack = 512 | ||
var frames = [UInt64](repeating: 0, count: kMaxFramesPerStack) | ||
|
||
FIRCLSWriteThreadStack(thread, &frames, UInt64(kMaxFramesPerStack), &frameCount) | ||
|
||
return Array(frames.prefix(Int(frameCount))) | ||
} | ||
} | ||
|
||
@_silgen_name("swift_demangle") | ||
public | ||
func _stdlib_demangleImpl( | ||
mangledName: UnsafePointer<CChar>?, | ||
mangledNameLength: UInt, | ||
outputBuffer: UnsafeMutablePointer<CChar>?, | ||
outputBufferSize: UnsafeMutablePointer<UInt>?, | ||
flags: UInt32 | ||
) -> UnsafeMutablePointer<CChar>? | ||
|
||
public func _stdlib_demangleName(_ mangledName: String) -> String { | ||
return mangledName.utf8CString.withUnsafeBufferPointer { (mangledNameUTF8CStr) in | ||
let demangledNamePtr = _stdlib_demangleImpl( | ||
mangledName: mangledNameUTF8CStr.baseAddress, | ||
mangledNameLength: UInt(mangledNameUTF8CStr.count - 1), | ||
outputBuffer: nil, | ||
outputBufferSize: nil, | ||
flags: 0) | ||
|
||
if let demangledNamePtr = demangledNamePtr { | ||
let demangledName = String(cString: demangledNamePtr) | ||
free(demangledNamePtr) | ||
return demangledName | ||
} | ||
return mangledName | ||
} | ||
} | ||
|
||
@_silgen_name("FIRCLSWriteThreadStack") | ||
func FIRCLSWriteThreadStack(_ thread: thread_t, _ frames: UnsafeMutablePointer<UInt64>, _ framesCapacity: UInt64, _ framesWritten: UnsafeMutablePointer<UInt64>) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// | ||
// UnsafeRawPointer+Commands.swift | ||
// Tracer | ||
// | ||
// Created by Itay Brenner on 15/8/24. | ||
// | ||
|
||
import Foundation | ||
import MachO | ||
|
||
extension UnsafeRawPointer { | ||
func numberOfCommands() -> (Int, UnsafeRawPointer)? { | ||
let headerPointer = load(as: mach_header_64.self) | ||
let headerSize: Int | ||
if headerPointer.magic == MH_MAGIC_64 { | ||
headerSize = MemoryLayout<mach_header_64>.size | ||
} else { | ||
return nil | ||
} | ||
|
||
return (Int(headerPointer.ncmds), advanced(by: headerSize)) | ||
} | ||
|
||
func processLoadComands(_ callback: (load_command, UnsafeRawPointer) -> Bool) { | ||
var pointer: UnsafeRawPointer | ||
guard let (numberOfCommands, headers) = numberOfCommands() else { return } | ||
|
||
if numberOfCommands > 1000 { | ||
print("Too many load commands") | ||
return | ||
} | ||
|
||
pointer = headers | ||
for _ in 0..<numberOfCommands { | ||
let command = pointer.load(as: load_command.self) | ||
if !callback(command, pointer) { | ||
break | ||
} | ||
pointer = pointer.advanced(by: Int(command.cmdsize)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters