AsmGrader 0.0.0
Loading...
Searching...
No Matches
tracer.hpp
Go to the documentation of this file.
1#pragma once
2
14
15#include <fmt/format.h>
16
17#include <array>
18#include <chrono>
19#include <concepts>
20#include <cstddef>
21#include <cstdint>
22#include <cstring>
23#include <ctime>
24#include <functional>
25#include <memory>
26#include <optional>
27#include <tuple>
28#include <type_traits>
29#include <variant>
30#include <vector>
31
32#include <linux/ptrace.h>
33#include <sys/ptrace.h>
34#include <sys/types.h> // pid_t
35#include <sys/user.h> // user_regs_struct, user_fpregs_struct (user_fpsimd_struct)
36
37namespace asmgrader {
38
39// HACK: Temporary fix for aarch64
40#ifdef __aarch64__
41using user_fpregs_struct = user_fpsimd_struct;
42#endif
43
48class Tracer
49{
50public:
51 constexpr static std::size_t MMAP_LENGTH = 4096;
52
53 Tracer() = default;
54
56 Result<void> begin(pid_t pid);
57
60
62 Result<SyscallRecord> execute_syscall(u64 sys_nr, std::array<std::uint64_t, 6> args);
63
68
71 Result<void> set_registers(user_regs_struct regs) const;
72 Result<void> set_fp_registers(user_fpregs_struct regs) const;
73
75 const std::vector<SyscallRecord>& get_records() const { return syscall_records_; }
76
78 std::optional<int> get_exit_code() const { return exit_code_; }
79
84 static Result<void> init_child();
85
87 Result<void> jump_to(std::uintptr_t address);
88
89 static constexpr auto DEFAULT_TIMEOUT = std::chrono::milliseconds{10};
90
91 template <typename... Args>
92 Result<void> setup_function_call(Args&&... args);
93
98 template <typename Ret>
100
102
103 std::uintptr_t get_mmapped_addr() const { return mmaped_address_; }
104
105private:
112 void assert_invariants() const;
113
114 Result<void> setup_function_return();
115
117 template <typename Arg>
118 Result<u64> setup_function_param(const Arg& arg);
119 template <std::floating_point Arg>
120 auto setup_function_param(const Arg& arg);
121
131 Result<TracedWaitid> resume_until(const std::function<bool(TracedWaitid)>& pred,
132 std::chrono::microseconds timeout = DEFAULT_TIMEOUT,
133 int ptrace_request = PTRACE_CONT) const;
134
135 Result<SyscallRecord> run_next_syscall(std::chrono::microseconds timeout = DEFAULT_TIMEOUT) const;
136
138 SyscallRecord get_syscall_entry_info(struct ptrace_syscall_info* entry) const;
139 void get_syscall_exit_info(SyscallRecord& rec, struct ptrace_syscall_info* exit) const;
140
141 // template <typename T>
142 // T from_raw_value(u64 value) const;
143
144 SyscallRecord::SyscallArg from_syscall_value(u64 value, SyscallEntry::Type type) const;
145
146 pid_t pid_ = -1;
147
148 std::unique_ptr<MemoryIOBase> memory_io_;
149
150 std::vector<SyscallRecord> syscall_records_;
151
152 std::optional<int> exit_code_;
153
154 std::size_t mmaped_address_{};
155
156 std::size_t mmaped_used_amt_{};
157};
158
159template <typename... Args>
161 constexpr std::size_t NUM_FP_ARGS = count_if_v<std::is_floating_point, Args...>;
162 constexpr std::size_t NUM_INT_ARGS = sizeof...(Args) - NUM_FP_ARGS;
163
164#ifdef __aarch64__
165 static_assert(NUM_FP_ARGS <= 8 && NUM_INT_ARGS <= 6, "Stack parameters are not yet supported for aarch64");
166#else // x86_64 assumed
167 static_assert(NUM_FP_ARGS <= 8 && NUM_INT_ARGS <= 8, "Stack parameters are not yet supported for x86_64");
168#endif
169
170 assert_invariants();
171
172 // reset the amount of memory we've "used" in the mmaped section,
173 // as we're preparing to enter a new function call
174 mmaped_used_amt_ = 0;
175
176 // prepare return location
177 TRY(setup_function_return());
178
179 // prepare arguments
180 user_regs_struct int_regs = TRY(get_registers());
181 user_fpregs_struct fp_regs = TRY(get_fp_registers());
182
183 // unused in the case that there are no function arguments
184 [[maybe_unused]] auto setup_raw_arg = [&, num_fp = 0, num_int = 0]<typename T>(T arg) mutable {
185 if constexpr (std::floating_point<T>) {
186#ifdef __aarch64__
187 static_assert(!std::floating_point<T>, "Floating point parameters not yet supported for aarch64");
188#else // x86_64 assumed
189 std::memcpy(&fp_regs.xmm_space[static_cast<std::ptrdiff_t>(num_fp * 4)], &arg, sizeof(T));
190#endif
191 num_fp++;
192 } else {
193#ifdef __aarch64__
194 int_regs.regs[num_int] = arg;
195#else // x86_64 assumed
196 switch (num_int) {
197 case 0:
198 int_regs.rdi = arg;
199 break;
200 case 1:
201 int_regs.rsi = arg;
202 break;
203 case 2:
204 int_regs.rdx = arg;
205 break;
206 case 3:
207 int_regs.rcx = arg;
208 break;
209 case 4:
210 int_regs.r8 = arg;
211 break;
212 case 5:
213 int_regs.r9 = arg;
214 break;
215 default:
216 unreachable();
217 }
218#endif
219 num_int++;
220 }
221 };
222
223 if constexpr (sizeof...(Args) > 0) {
224 std::tuple reg_param_vals = std::make_tuple(setup_function_param(std::forward<Args>(args))...);
225
226 std::optional first_error = tuple_find_first([](const auto& val) { return val.has_error(); }, reg_param_vals);
227
228 if (first_error.has_value()) {
229 return std::visit([](const auto& err_val) { return err_val.error(); }, *first_error);
230 }
231
232 std::apply([&](const auto&... vals) { (setup_raw_arg(*vals), ...); }, reg_param_vals);
233
234 TRY(set_registers(int_regs));
235 TRY(set_fp_registers(fp_regs));
236 }
237
238 return {};
239}
240
241template <typename Arg>
242Result<u64> Tracer::setup_function_param(const Arg& arg) {
243 if constexpr (std::integral<Arg>) {
244 return static_cast<u64>(arg);
245 } else if constexpr (std::is_pointer_v<Arg>) {
246 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
247 return reinterpret_cast<u64>(arg);
248 } else {
249 std::uintptr_t loc = mmaped_address_ + mmaped_used_amt_;
250 std::size_t num_bytes = TRY(memory_io_->write(loc, arg));
251
252 mmaped_used_amt_ += num_bytes;
253
254 return loc;
255 }
256}
257
258template <std::floating_point Arg>
259auto setup_function_param(const Arg& arg) {
260 return arg;
261}
262
263template <typename Ret>
265 static_assert(std::is_fundamental_v<Ret> || std::is_pointer_v<Ret>,
266 "Non-fundamental and non-pointer types are not yet supported as function return types");
267
268 if constexpr (std::floating_point<Ret>) {
269 user_fpregs_struct fp_regs = TRY(get_fp_registers());
270#ifdef __aarch64__
271 UNIMPLEMENTED("TODO: implement floating-point return types for aarch64");
272#else // x86_64 assumed
273
274 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
275 return *reinterpret_cast<Ret*>(&fp_regs.xmm_space[0]);
276#endif
277 } else {
278 user_regs_struct int_regs = TRY(get_registers());
279#ifdef __aarch64__
280 u64 ret = int_regs.regs[0];
281#else // x86_64 assumed
282 u64 ret = int_regs.rax;
283#endif
284
285 if constexpr (std::integral<Ret>) {
286 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
287 return *reinterpret_cast<Ret*>(&ret);
288 } else if constexpr (std::is_pointer_v<Ret>) {
289 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
290 return reinterpret_cast<Ret>(ret);
291 }
292 }
293}
294
295} // namespace asmgrader
std::variant wrapper for a partial implementation of C++23's expected type
Definition expected.hpp:34
Base class for interacting with a tracee's memory in a variety of ways at a (relatively) high-level F...
Definition memory_io_base.hpp:23
A lightweight wrapper of ptrace(2)
Definition tracer.hpp:49
Result< void > jump_to(std::uintptr_t address)
Set the child process's instruction pointer to address
Definition tracer.cpp:187
std::optional< int > get_exit_code() const
Obtain the process exit code, or nullopt if the process has not yet exited.
Definition tracer.hpp:78
Result< Ret > process_function_ret()
AFTER a function has been called, inspects register values (and memory if necessary) to construct the...
Definition tracer.hpp:264
Result< SyscallRecord > execute_syscall(u64 sys_nr, std::array< std::uint64_t, 6 > args)
Executes a syscall with the given arguments as the stopped tracee.
Definition tracer.cpp:235
std::uintptr_t get_mmapped_addr() const
Definition tracer.hpp:103
static Result< void > init_child()
Set up child process for tracing Call this within the newly-forked process.
Definition tracer.cpp:93
Result< user_fpregs_struct > get_fp_registers() const
Definition tracer.cpp:217
static constexpr auto DEFAULT_TIMEOUT
Definition tracer.hpp:89
Result< void > begin(pid_t pid)
Sets up tracing in parent process, then stops child immediately after exec call.
Definition tracer.cpp:51
Result< RunResult > run()
Run the child process. Collect syscall info each time one is executed.
Definition tracer.cpp:297
Result< user_regs_struct > get_registers() const
Get the general purpose registers of the stopped tracee IMPORTANT: this is (obviously) architecture-d...
Definition tracer.cpp:199
Result< void > set_fp_registers(user_fpregs_struct regs) const
Definition tracer.cpp:227
Result< void > set_registers(user_regs_struct regs) const
Set the general purpose registers of the stopped tracee IMPORTANT: this is (obviously) architecture-d...
Definition tracer.cpp:209
static constexpr std::size_t MMAP_LENGTH
Definition tracer.hpp:51
Result< void > setup_function_call(Args &&... args)
Definition tracer.hpp:160
MemoryIOBase & get_memory_io()
Definition tracer.cpp:535
const std::vector< SyscallRecord > & get_records() const
Obtain records of syscalls run so far in the child process.
Definition tracer.hpp:75
#define TRY(val)
If the supplied argument is an error (unexpected) type, then propegate it up the call stack....
Definition error_types.hpp:46
#define UNIMPLEMENTED(msg,...)
For features that have not yet / are not planned to be implemented, so that I don't bang my head agai...
Definition logging.hpp:52
Definition asm_buffer.hpp:19
void unreachable()
Definition unreachable.hpp:11
constexpr auto tuple_find_first(Func &&pred, const Tuple &val)
Definition tuple_matcher.hpp:36
constexpr std::size_t count_if_v
Definition count_if.hpp:17
auto setup_function_param(const Arg &arg)
Definition tracer.hpp:259
Type
Definition syscall.hpp:25
Record of a syscall for use with Tracer to keep track of which syscalls a child process invokes.
Definition syscall_record.hpp:22
std::variant< i32, i64, u32, u64, Result< std::string >, Result< std::vector< Result< std::string > > >, void *, Result< std::timespec > > SyscallArg
An arbitrary syscall argument void* is a catch-all for any pointer type.
Definition syscall_record.hpp:25
Definition tracer_types.hpp:43
Types primarily to wrap Linux result info for use with refTracer