Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Sources/MachOKit/LoadCommand/Model/ThreadState.swift
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions Sources/MachOKit/LoadCommand/Model/ThreadStateFlavor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
97 changes: 77 additions & 20 deletions Sources/MachOKit/LoadCommand/ThreadCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<UInt32>.size
let stateSize = Int(layout.cmdsize) - layoutSize - 2 * MemoryLayout<UInt32>.size
guard stateSizeExpected == stateSize else { return nil }

// consider alignment
guard stateSizeExpected <= stateSize else { return nil }

let ptr = cmdsStart
.advanced(by: offset)
.advanced(by: layoutSize)
Expand Down Expand Up @@ -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<UInt32>.size
let stateSize = Int(layout.cmdsize) - layoutSize - 2 * MemoryLayout<UInt32>.size
guard stateSizeExpected == stateSize else { return nil }

// consider alignment
guard stateSizeExpected <= stateSize else { return nil }

let offset = machO.cmdsStartOffset + offset + layoutSize + 2 * MemoryLayout<UInt32>.size

return machO.fileHandle.readData(
Expand All @@ -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)
Expand All @@ -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
}
}
79 changes: 79 additions & 0 deletions Sources/MachOKitC/include/thread_state.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

// 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 */
23 changes: 10 additions & 13 deletions Tests/MachOKitTests/MachOFilePrintTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
24 changes: 12 additions & 12 deletions Tests/MachOKitTests/MachOPrintTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down