-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdbg.cpp
More file actions
161 lines (147 loc) · 4.72 KB
/
dbg.cpp
File metadata and controls
161 lines (147 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include "dbg.hpp"
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include "util.hpp"
static constexpr int NOCHILD = -1;
using word = unsigned long;
static pid_t child_pid = NOCHILD;
static std::unordered_set<const void*> breakpoints;
static std::unordered_map<const void*, uint8_t> breakpoint_bytes;
namespace dbg {
int wait_process_exit() {
if (child_pid == NOCHILD) {
return 0;
}
while (1) {
int status;
util::throw_errno(waitpid(child_pid, &status, 0));
if (WIFEXITED(status)) {
child_pid = NOCHILD;
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
child_pid = NOCHILD;
return WTERMSIG(status);
}
}
}
void kill_process(int sgn) {
if (child_pid == NOCHILD) {
return;
}
if (kill(child_pid, sgn) < 0 && errno != ESRCH) {
util::throw_errno();
}
}
void step_into() {
if (child_pid == NOCHILD) {
std::cerr << "Cannot single step in stopped process\n";
return;
}
util::throw_errno(ptrace(PTRACE_SINGLESTEP, child_pid, nullptr, nullptr));
}
void read_memory(const void* addr, void* out, size_t sz) {
if (child_pid == NOCHILD) {
std::cerr << "Cannot read memory in stopped process\n";
return;
}
const word* in_addr = static_cast<const word*>(addr);
word* out_addr = static_cast<word*>(out);
for (size_t i = 0; i * sizeof(word) < sz; i++) {
errno = 0;
if (sz - i * sizeof(word) >= sizeof(word)) {
*out_addr = util::throw_errno(ptrace(PTRACE_PEEKDATA, child_pid, in_addr, nullptr));
} else {
word out = util::throw_errno(ptrace(PTRACE_PEEKDATA, child_pid, in_addr, nullptr));
memcpy(out_addr, static_cast<void*>(&out), sz - i * sizeof(word));
}
out_addr++;
in_addr++;
}
}
void write_memory(const void* addr, const void* data, size_t sz) {
if (child_pid == NOCHILD) {
std::cerr << "Cannot write memory in stopped process\n";
return;
}
const word* in_addr = static_cast<const word*>(data);
const word* out_addr = static_cast<const word*>(addr);
for (size_t i = 0; i * sizeof(word) < sz; i++) {
if (sz - i * sizeof(word) >= sizeof(word)) {
util::throw_errno(ptrace(PTRACE_POKEDATA, child_pid, out_addr, *in_addr));
} else {
word val;
read_memory(out_addr, &val, sizeof(val));
memcpy(&val, in_addr, sz - i * sizeof(word));
util::throw_errno(ptrace(PTRACE_POKEDATA, child_pid, out_addr, val));
}
out_addr++;
in_addr++;
}
}
// Injects a breakpoint into a running child process.
static void inject_breakpoint(const void* addr) {
// don't inject breakpoint if it's already injected
if (breakpoint_bytes.count(addr) > 0) {
return;
}
// TODO: inject the breakpoint into address addr, and store the original memory value in breakpoint_bytes
}
void spawn_process(const char* pathname, char* const argv[], char* const envp[]) {
if (child_pid != NOCHILD) {
kill_process();
wait_process_exit();
}
pid_t pid = util::throw_errno(fork());
if (pid == 0) {
// child
// TODO: start tracing and then execve the new process
} else {
// parent
child_pid = pid;
// TODO: wait for the child's SIGTRAP to signify the execve having run
for (const void* addr : breakpoints) {
inject_breakpoint(addr);
}
}
}
int continue_process() {
if (child_pid == NOCHILD) {
std::cerr << "Cannot continue stopped process\n";
return 0;
}
int status;
// TODO: continue the child process, and wait for it to change state, storing the status in status
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
struct user_regs_struct regs;
util::throw_errno(ptrace(PTRACE_GETREGS, child_pid, nullptr, ®s));
regs.rip--;
void* pc = reinterpret_cast<void*>(regs.rip);
if (breakpoints.count(pc) > 0) {
// we hit a breakpoint
// TODO: restore the original memory at address pc
breakpoint_bytes.erase(pc);
util::throw_errno(ptrace(PTRACE_SETREGS, child_pid, nullptr, ®s));
}
}
if (WIFEXITED(status) || WIFSIGNALED(status)) {
child_pid = NOCHILD;
}
return status;
}
void insert_breakpoint(const void* addr) {
breakpoints.insert(addr);
if (child_pid != NOCHILD) {
inject_breakpoint(addr);
}
}
} // namespace dbg