Skip to content

Commit cf0acf8

Browse files
committed
first commit
0 parents  commit cf0acf8

File tree

6 files changed

+297
-0
lines changed

6 files changed

+297
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

Package.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "SimpleDebugger",
8+
products: [
9+
// Products define the executables and libraries a package produces, making them visible to other packages.
10+
.library(
11+
name: "SimpleDebugger",
12+
targets: ["SimpleDebugger"]),
13+
],
14+
targets: [
15+
// Targets are the basic building blocks of a package, defining a module or a test suite.
16+
// Targets can depend on other targets in this package and products from dependencies.
17+
.target(
18+
name: "SimpleDebugger"),
19+
20+
],
21+
cxxLanguageStandard: .cxx20
22+
)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# SimpleDebugger
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//
2+
// SimpleDebugger.h
3+
// SimpleDebugger
4+
//
5+
// Created by Noah Martin on 10/9/24.
6+
//
7+
8+
#if defined(__arm64__) || defined(__aarch64__)
9+
10+
#include "SimpleDebugger.h"
11+
12+
#import <pthread.h>
13+
#import <mutex>
14+
#import <mach/mach.h>
15+
#import <libgen.h>
16+
#import <os/log.h>
17+
18+
#include "mach_messages.h"
19+
20+
#include <mach/exception.h>
21+
#include <mach/arm/thread_state.h>
22+
23+
SimpleDebugger::SimpleDebugger() : exceptionPort(MACH_PORT_NULL) {}
24+
25+
bool SimpleDebugger::startDebugging() {
26+
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exceptionPort) != KERN_SUCCESS) {
27+
return false;
28+
}
29+
30+
if (mach_port_insert_right(mach_task_self(), exceptionPort, exceptionPort, MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) {
31+
return false;
32+
}
33+
34+
if (task_set_exception_ports(mach_task_self(),
35+
EXC_MASK_BREAKPOINT,
36+
exceptionPort,
37+
EXCEPTION_DEFAULT,
38+
ARM_THREAD_STATE64) != KERN_SUCCESS) {
39+
return false;
40+
}
41+
42+
m.lock();
43+
pthread_create(&serverThread, nullptr, &SimpleDebugger::exceptionServerWrapper, this);
44+
// Prevent returning until the server thread has started
45+
m.lock();
46+
return true;
47+
}
48+
49+
void SimpleDebugger::setExceptionCallback(ExceptionCallback callback) {
50+
exceptionCallback = std::move(callback);
51+
}
52+
53+
#define ARM64_BREAK_INSTRUCTION 0xD4200000
54+
55+
void protectPage(vm_address_t address, vm_size_t size, vm_prot_t newProtection) {
56+
kern_return_t result = vm_protect(mach_task_self(), address, size, 0, newProtection);
57+
58+
if (result != 0) {
59+
perror("error on vmprotect");
60+
}
61+
}
62+
63+
uint32_t setInstruction(vm_address_t address, uint32_t newInst) {
64+
uint32_t instruction = *((uint32_t *)address);
65+
thread_act_array_t threads;
66+
mach_msg_type_number_t thread_count;
67+
if (task_threads(mach_task_self(), &threads, &thread_count) != KERN_SUCCESS) {
68+
thread_count = 0;
69+
}
70+
71+
thread_t myThread = mach_thread_self();
72+
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
73+
if (threads[i] != myThread) {
74+
thread_suspend(threads[i]);
75+
}
76+
}
77+
protectPage(address, 1, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
78+
*(uint32_t *)address = newInst;
79+
protectPage(address, 1, VM_PROT_READ | VM_PROT_EXECUTE);
80+
81+
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
82+
if (threads[i] != myThread) {
83+
thread_resume(threads[i]);
84+
}
85+
}
86+
87+
vm_size_t size = thread_count * sizeof(thread_t);
88+
vm_deallocate(mach_task_self(), (vm_address_t) threads, size);
89+
return instruction;
90+
}
91+
92+
void SimpleDebugger::setBreakpoint(vm_address_t address) {
93+
uint32_t instruction = setInstruction(address, ARM64_BREAK_INSTRUCTION);
94+
originalInstruction.insert({address, instruction});
95+
}
96+
97+
SimpleDebugger::~SimpleDebugger() {
98+
// TODO: Handle stopping the exception server
99+
}
100+
101+
void* SimpleDebugger::exceptionServerWrapper(void* arg) {
102+
return static_cast<SimpleDebugger*>(arg)->exceptionServer();
103+
}
104+
105+
void* SimpleDebugger::exceptionServer() {
106+
MachExceptionMessage exceptionMessage = {{0}};
107+
108+
os_log(OS_LOG_DEFAULT, "Exception server started");
109+
110+
m.unlock();
111+
while (true) {
112+
kern_return_t kr = mach_msg(&exceptionMessage.header,
113+
MACH_RCV_MSG,
114+
0,
115+
sizeof(exceptionMessage),
116+
exceptionPort,
117+
MACH_MSG_TIMEOUT_NONE,
118+
MACH_PORT_NULL);
119+
if (kr != KERN_SUCCESS) {
120+
os_log(OS_LOG_DEFAULT, "Error receiving message");
121+
continue;
122+
}
123+
124+
if (exceptionMessage.exception == EXC_BREAKPOINT) {
125+
mach_port_t thread = exceptionMessage.thread.name;
126+
arm_thread_state64_t state;
127+
mach_msg_type_number_t state_count = ARM_THREAD_STATE64_COUNT;
128+
129+
kern_return_t kr = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, &state_count);
130+
if (kr != KERN_SUCCESS) {
131+
printf("Error getting thread state: %s\n", mach_error_string(kr));
132+
}
133+
if (exceptionCallback) {
134+
exceptionCallback(state, [this, exceptionMessage, state, state_count]() {
135+
continueFromBreak(exceptionMessage, state, state_count);
136+
});
137+
} else {
138+
continueFromBreak(exceptionMessage, state, state_count);
139+
}
140+
} else {
141+
os_log(OS_LOG_DEFAULT, "Not breakpoint message");
142+
}
143+
}
144+
145+
return nullptr;
146+
}
147+
148+
void SimpleDebugger::continueFromBreak(MachExceptionMessage exceptionMessage, arm_thread_state64_t state, mach_msg_type_number_t state_count) {
149+
150+
if (originalInstruction.contains(state.__pc)) {
151+
uint32_t orig = originalInstruction.at(state.__pc);
152+
setInstruction(state.__pc, orig);
153+
} else {
154+
// Address was not tracked, increment the pc and continue
155+
state.__pc += 4;
156+
157+
kern_return_t kr = thread_set_state(exceptionMessage.thread.name, ARM_THREAD_STATE64, (thread_state_t)&state, state_count);
158+
if (kr != KERN_SUCCESS) {
159+
printf("Error setting thread state: %s\n", mach_error_string(kr));
160+
}
161+
}
162+
163+
MachReplyMessage replyMessage = {{0}};
164+
165+
replyMessage.header = exceptionMessage.header;
166+
replyMessage.header.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(exceptionMessage.header.msgh_bits), 0);
167+
replyMessage.header.msgh_local_port = MACH_PORT_NULL;
168+
replyMessage.header.msgh_size = sizeof(replyMessage);
169+
replyMessage.NDR = exceptionMessage.NDR;
170+
replyMessage.returnCode = KERN_SUCCESS;
171+
replyMessage.header.msgh_id = exceptionMessage.header.msgh_id + 100;
172+
173+
kern_return_t kr = mach_msg(&replyMessage.header,
174+
MACH_SEND_MSG,
175+
sizeof(replyMessage),
176+
0,
177+
MACH_PORT_NULL,
178+
MACH_MSG_TIMEOUT_NONE,
179+
MACH_PORT_NULL);
180+
181+
if (kr != KERN_SUCCESS) {
182+
os_log(OS_LOG_DEFAULT, "Error sending reply: %s", mach_error_string(kr));
183+
}
184+
}
185+
186+
#endif
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// SimpleDebugger.h
3+
// SimpleDebugger
4+
//
5+
// Created by Noah Martin on 10/9/24.
6+
//
7+
8+
#if defined(__arm64__) || defined(__aarch64__)
9+
10+
#ifdef __cplusplus
11+
extern "C++" {
12+
13+
#import <functional>
14+
#import <mach/mach.h>
15+
#import <pthread.h>
16+
#import <mutex>
17+
#import <unordered_map>
18+
19+
20+
struct MachExceptionMessage;
21+
22+
class SimpleDebugger {
23+
public:
24+
using ExceptionCallback = std::function<void(arm_thread_state64_t state, std::function<void()>)>;
25+
26+
SimpleDebugger();
27+
28+
bool startDebugging();
29+
void setExceptionCallback(ExceptionCallback callback);
30+
void setBreakpoint(vm_address_t address);
31+
32+
~SimpleDebugger();
33+
34+
private:
35+
mach_port_t exceptionPort;
36+
pthread_t serverThread;
37+
std::mutex m;
38+
ExceptionCallback exceptionCallback;
39+
std::unordered_map<vm_address_t, uint32_t> originalInstruction;
40+
41+
static void* exceptionServerWrapper(void* arg);
42+
void* exceptionServer();
43+
void continueFromBreak(MachExceptionMessage exceptionMessage, arm_thread_state64_t state, mach_msg_type_number_t state_count);
44+
};
45+
}
46+
#endif
47+
48+
#endif
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// mach_messages.h
3+
// SimpleDebugger
4+
//
5+
// Created by Noah Martin on 10/9/24.
6+
//
7+
8+
#import <mach/mach.h>
9+
10+
#pragma pack(4)
11+
struct MachExceptionMessage
12+
{
13+
mach_msg_header_t header;
14+
mach_msg_body_t body;
15+
mach_msg_port_descriptor_t thread;
16+
mach_msg_port_descriptor_t task;
17+
NDR_record_t NDR;
18+
exception_type_t exception;
19+
mach_msg_type_number_t codeCount;
20+
mach_exception_data_type_t code[0];
21+
char padding[512];
22+
};
23+
#pragma pack()
24+
25+
#pragma pack(4)
26+
struct MachReplyMessage
27+
{
28+
mach_msg_header_t header;
29+
NDR_record_t NDR;
30+
kern_return_t returnCode;
31+
};
32+
#pragma pack()

0 commit comments

Comments
 (0)