diff --git a/Sources/MachOKit/LoadCommand/Model/ThreadState.swift b/Sources/MachOKit/LoadCommand/Model/ThreadState.swift new file mode 100644 index 00000000..4cf69842 --- /dev/null +++ b/Sources/MachOKit/LoadCommand/Model/ThreadState.swift @@ -0,0 +1,41 @@ +// +// ThreadState.swift +// MachOKit +// +// Created by p-x9 on 2025/01/13 +// +// + +import Foundation +import MachOKitC + +public enum ThreadState { + case arm(ARMThreadState) + case arm64(ARM64ThreadState) + case i386(i386ThreadState) + case x86_64(x86_64ThreadState) +} + +public struct x86_64ThreadState: LayoutWrapper { + public typealias Layout = x86_thread_state64 + + public var layout: Layout +} + +public struct i386ThreadState: LayoutWrapper { + public typealias Layout = i386_thread_state + + public var layout: Layout +} + +public struct ARMThreadState: LayoutWrapper { + public typealias Layout = arm_thread_state + + public var layout: Layout +} + +public struct ARM64ThreadState: LayoutWrapper { + public typealias Layout = arm_thread_state64 + + public var layout: Layout +} diff --git a/Sources/MachOKit/LoadCommand/Model/ThreadStateFlavor.swift b/Sources/MachOKit/LoadCommand/Model/ThreadStateFlavor.swift index bb92f05e..bebfd56b 100644 --- a/Sources/MachOKit/LoadCommand/Model/ThreadStateFlavor.swift +++ b/Sources/MachOKit/LoadCommand/Model/ThreadStateFlavor.swift @@ -8,6 +8,20 @@ import Foundation +public enum ThreadStateFlavor: CustomStringConvertible { + case arm(ARMThreadStateFlavor) + case i386(i386ThreadStateFlavor) + case x86_64(x86ThreadStateFlavor) + + public var description: String { + switch self { + case let .arm(flavor): flavor.description + case let .i386(flavor): flavor.description + case let .x86_64(flavor): flavor.description + } + } +} + // MARK: - x86 public enum x86ThreadStateFlavor: UInt32, CaseIterable { case thread_state32 = 1 diff --git a/Sources/MachOKit/LoadCommand/ThreadCommand.swift b/Sources/MachOKit/LoadCommand/ThreadCommand.swift index c2c58eae..ce76efd8 100644 --- a/Sources/MachOKit/LoadCommand/ThreadCommand.swift +++ b/Sources/MachOKit/LoadCommand/ThreadCommand.swift @@ -45,13 +45,17 @@ extension ThreadCommand { return count } - public func state(cmdsStart: UnsafeRawPointer) -> Data? { + public func stateData(cmdsStart: UnsafeRawPointer) -> Data? { guard let count = count(cmdsStart: cmdsStart) else { return nil } + let stateSizeExpected = Int(count) * MemoryLayout.size let stateSize = Int(layout.cmdsize) - layoutSize - 2 * MemoryLayout.size - guard stateSizeExpected == stateSize else { return nil } + + // consider alignment + guard stateSizeExpected <= stateSize else { return nil } + let ptr = cmdsStart .advanced(by: offset) .advanced(by: layoutSize) @@ -89,13 +93,17 @@ extension ThreadCommand { return count } - public func state(in machO: MachOFile) -> Data? { + public func stateData(in machO: MachOFile) -> Data? { guard let count = count(in: machO) else { return nil } + let stateSizeExpected = Int(count) * MemoryLayout.size let stateSize = Int(layout.cmdsize) - layoutSize - 2 * MemoryLayout.size - guard stateSizeExpected == stateSize else { return nil } + + // consider alignment + guard stateSizeExpected <= stateSize else { return nil } + let offset = machO.cmdsStartOffset + offset + layoutSize + 2 * MemoryLayout.size return machO.fileHandle.readData( @@ -109,27 +117,27 @@ extension ThreadCommand { public func flavor( cmdsStart: UnsafeRawPointer, cpuType: CPUType - ) -> Flavor? { + ) -> ThreadStateFlavor? { guard let rawValue = _flavor(cmdsStart: cmdsStart) else { return nil } - return flavor(rawValue: rawValue, cpuType: cpuType) + return _flavor(rawValue: rawValue, cpuType: cpuType) } public func flavor( in machO: MachOFile, cpuType: CPUType - ) -> Flavor? { + ) -> ThreadStateFlavor? { guard let rawValue = _flavor(in: machO) else { return nil } - return flavor(rawValue: rawValue, cpuType: cpuType) + return _flavor(rawValue: rawValue, cpuType: cpuType) } - private func flavor( + private func _flavor( rawValue: UInt32, cpuType: CPUType - ) -> Flavor? { + ) -> ThreadStateFlavor? { switch cpuType { case .arm, .arm64, .arm64_32: let flavor = ARMThreadStateFlavor(rawValue: rawValue) @@ -153,17 +161,66 @@ extension ThreadCommand { } extension ThreadCommand { - public enum Flavor: CustomStringConvertible { - case arm(ARMThreadStateFlavor) - case i386(i386ThreadStateFlavor) - case x86_64(x86ThreadStateFlavor) - - public var description: String { - switch self { - case let .arm(flavor): flavor.description - case let .i386(flavor): flavor.description - case let .x86_64(flavor): flavor.description + public func state( + cmdsStart: UnsafeRawPointer, + cpuType: CPUType + ) -> ThreadState? { + guard let data = stateData(cmdsStart: cmdsStart) else { + return nil + } + return _state(data: data, cpuType: cpuType) + } + + public func state( + in machO: MachOFile, + cpuType: CPUType + ) -> ThreadState? { + guard let data = stateData(in: machO) else { + return nil + } + return _state(data: data, cpuType: cpuType) + } + + private func _state( + data: Data, + cpuType: CPUType + ) -> ThreadState? { + switch cpuType { + case .arm, .arm64_32: + guard data.count == ARMThreadState.layoutSize else { + return nil + } + let state = data.withUnsafeBytes { + $0.load(as: ARMThreadState.self) + } + return .arm(state) + + case .arm64: + guard data.count == ARM64ThreadState.layoutSize else { + return nil + } + let state = data.withUnsafeBytes { + $0.load(as: ARM64ThreadState.self) } + return .arm64(state) + case .i386, .x86: + guard data.count == i386ThreadState.layoutSize else { + return nil + } + let state = data.withUnsafeBytes { + $0.load(as: i386ThreadState.self) + } + return .i386(state) + case .x86_64: + guard data.count == x86_64ThreadState.layoutSize else { + return nil + } + let state = data.withUnsafeBytes { + $0.load(as: x86_64ThreadState.self) + } + return .x86_64(state) + default: break } + return nil } } diff --git a/Sources/MachOKitC/include/thread_state.h b/Sources/MachOKitC/include/thread_state.h new file mode 100644 index 00000000..f189cd1d --- /dev/null +++ b/Sources/MachOKitC/include/thread_state.h @@ -0,0 +1,79 @@ +// +// thread_state.h +// MachOKit +// +// Created by p-x9 on 2025/01/13 +// +// + +#ifndef thread_state_h +#define thread_state_h + +#include + +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/arm/_structs.h#L84 +struct arm_thread_state { + uint32_t r[13]; /* General purpose register r0-r12 */ + uint32_t sp; /* Stack pointer r13 */ + uint32_t lr; /* Link register r14 */ + uint32_t pc; /* Program counter r15 */ + uint32_t cpsr; /* Current program status register */ +}; + +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/arm/_structs.h#L101 +struct arm_thread_state64 { + uint64_t x[29]; /* General purpose registers x0-x28 */ + uint64_t fp; /* Frame pointer x29 */ + uint64_t lr; /* Link register x30 */ + uint64_t sp; /* Stack pointer x31 */ + uint64_t pc; /* Program counter */ + uint32_t cpsr; /* Current program status register */ + uint32_t flags; /* Flags describing structure format */ +}; + +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/i386/_structs.h#L66 +struct i386_thread_state { + unsigned int eax; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int edi; + unsigned int esi; + unsigned int ebp; + unsigned int esp; + unsigned int ss; + unsigned int eflags; + unsigned int eip; + unsigned int cs; + unsigned int ds; + unsigned int es; + unsigned int fs; + unsigned int gs; +}; + +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/i386/_structs.h#L738 +struct x86_thread_state64 { + uint64_t rax; + uint64_t rbx; + uint64_t rcx; + uint64_t rdx; + uint64_t rdi; + uint64_t rsi; + uint64_t rbp; + uint64_t rsp; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + uint64_t rip; + uint64_t rflags; + uint64_t cs; + uint64_t fs; + uint64_t gs; +}; + +#endif /* thread_state_h */ diff --git a/Tests/MachOKitTests/MachOFilePrintTests.swift b/Tests/MachOKitTests/MachOFilePrintTests.swift index b7bbfe30..5ba4dddb 100644 --- a/Tests/MachOKitTests/MachOFilePrintTests.swift +++ b/Tests/MachOKitTests/MachOFilePrintTests.swift @@ -458,29 +458,26 @@ extension MachOFilePrintTests { } func testThreadCommand() { - let path = "/usr/local/bin/texindex" // has thread command + let path = "/cores/core.2627" // has thread command let url = URL(fileURLWithPath: path) guard let machO = try? MachOFile(url: url) else { return } let commands = Array(machO.loadCommands.infos(of: LoadCommand.thread)) + Array(machO.loadCommands.infos(of: LoadCommand.unixthread)) + let cpuType = machO.header.cpuType! + for command in commands { + let flavor = command.flavor( + in: machO, + cpuType: cpuType + ) print("Flavor:", - command.flavor( - in: machO, - cpuType: machO.header.cpuType! - )?.description ?? "unknown" + flavor?.description ?? "unknown" ) print("Count:", command.count(in: machO) ?? 0) - if let state = command.state(in: machO) { - print( - "State:", - state.withUnsafeBytes { - [UInt64]($0.bindMemory(to: UInt64.self)) - }.map { "0x" + String($0, radix: 16) } - .joined(separator: ", ") - ) + if let state = command.state(in: machO, cpuType: cpuType) { + print("State:", state) } } } diff --git a/Tests/MachOKitTests/MachOPrintTests.swift b/Tests/MachOKitTests/MachOPrintTests.swift index 8b5951e5..e12e9a73 100644 --- a/Tests/MachOKitTests/MachOPrintTests.swift +++ b/Tests/MachOKitTests/MachOPrintTests.swift @@ -392,22 +392,22 @@ extension MachOPrintTests { let commands = Array(machO.loadCommands.infos(of: LoadCommand.thread)) + Array(machO.loadCommands.infos(of: LoadCommand.unixthread)) + let cpuType = machO.header.cpuType! + for command in commands { + let flavor = command.flavor( + cmdsStart: machO.cmdsStartPtr, + cpuType: cpuType + ) print("Flavor:", - command.flavor( - cmdsStart: machO.cmdsStartPtr, - cpuType: machO.header.cpuType! - )?.description ?? "unknown" + flavor?.description ?? "unknown" ) print("Count:", command.count(cmdsStart: machO.cmdsStartPtr) ?? 0) - if let state = command.state(cmdsStart: machO.cmdsStartPtr) { - print( - "State:", - state.withUnsafeBytes { - [UInt64]($0.bindMemory(to: UInt64.self)) - }.map { "0x" + String($0, radix: 16) } - .joined(separator: ", ") - ) + if let state = command.state( + cmdsStart: machO.cmdsStartPtr, + cpuType: cpuType + ) { + print("State:", state) } } }