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
61
72 Result<RunResult> run_until(const std::function<bool(SyscallRecord)>& pred);
73
75 Result<SyscallRecord> execute_syscall(u64 sys_nr, std::array<std::uint64_t, 6> args);
76
81
84 Result<void> set_registers(user_regs_struct regs) const;
85 Result<void> set_fp_registers(user_fpregs_struct regs) const;
86
88 const std::vector<SyscallRecord>& get_records() const { return syscall_records_; }
89
91 std::optional<int> get_exit_code() const { return exit_code_; }
92
97 static Result<void> init_child();
98
100 Result<void> jump_to(std::uintptr_t address);
101
102 static constexpr auto DEFAULT_TIMEOUT = std::chrono::milliseconds{10};
103
104 template <typename... Args>
105 Result<void> setup_function_call(Args&&... args);
106
111 template <typename Ret>
113
115
116 std::uintptr_t get_mmapped_addr() const { return mmaped_address_; }
117
118private:
125 void assert_invariants() const;
126
127 Result<void> setup_function_return();
128
130 template <typename Arg>
131 Result<u64> setup_function_param(const Arg& arg);
132 template <std::floating_point Arg>
133 auto setup_function_param(const Arg& arg);
134
144 Result<TracedWaitid> resume_until(const std::function<bool(TracedWaitid)>& pred,
145 std::chrono::microseconds timeout = DEFAULT_TIMEOUT,
146 int ptrace_request = PTRACE_CONT) const;
147
148 Result<SyscallRecord> run_next_syscall(std::chrono::microseconds timeout = DEFAULT_TIMEOUT) const;
149
151 SyscallRecord get_syscall_entry_info(struct ptrace_syscall_info* entry) const;
152 void get_syscall_exit_info(SyscallRecord& rec, struct ptrace_syscall_info* exit) const;
153
154 // template <typename T>
155 // T from_raw_value(u64 value) const;
156
157 SyscallRecord::SyscallArg from_syscall_value(u64 value, SyscallEntry::Type type) const;
158
159 pid_t pid_ = -1;
160
161 std::unique_ptr<MemoryIOBase> memory_io_;
162
163 std::vector<SyscallRecord> syscall_records_;
164
165 std::optional<int> exit_code_;
166
167 std::size_t mmaped_address_{};
168
169 std::size_t mmaped_used_amt_{};
170};
171
172template <typename... Args>
174 constexpr std::size_t NUM_FP_ARGS = count_if_v<std::is_floating_point, Args...>;
175 constexpr std::size_t NUM_INT_ARGS = sizeof...(Args) - NUM_FP_ARGS;
176
177#ifdef __aarch64__
178 static_assert(NUM_FP_ARGS <= 8 && NUM_INT_ARGS <= 6, "Stack parameters are not yet supported for aarch64");
179#else // x86_64 assumed
180 static_assert(NUM_FP_ARGS <= 8 && NUM_INT_ARGS <= 8, "Stack parameters are not yet supported for x86_64");
181#endif
182
183 assert_invariants();
184
185 // reset the amount of memory we've "used" in the mmaped section,
186 // as we're preparing to enter a new function call
187 mmaped_used_amt_ = 0;
188
189 // prepare return location
190 TRY(setup_function_return());
191
192 // prepare arguments
193 user_regs_struct int_regs = TRY(get_registers());
194 user_fpregs_struct fp_regs = TRY(get_fp_registers());
195
196 // unused in the case that there are no function arguments
197 [[maybe_unused]] auto setup_raw_arg = [&, num_fp = 0, num_int = 0]<typename T>(T arg) mutable {
198 if constexpr (std::floating_point<T>) {
199#ifdef __aarch64__
200 static_assert(!std::floating_point<T>, "Floating point parameters not yet supported for aarch64");
201#else // x86_64 assumed
202 std::memcpy(&fp_regs.xmm_space[static_cast<std::ptrdiff_t>(num_fp * 4)], &arg, sizeof(T));
203#endif
204 num_fp++;
205 } else {
206#ifdef __aarch64__
207 int_regs.regs[num_int] = arg;
208#else // x86_64 assumed
209 switch (num_int) {
210 case 0:
211 int_regs.rdi = arg;
212 break;
213 case 1:
214 int_regs.rsi = arg;
215 break;
216 case 2:
217 int_regs.rdx = arg;
218 break;
219 case 3:
220 int_regs.rcx = arg;
221 break;
222 case 4:
223 int_regs.r8 = arg;
224 break;
225 case 5:
226 int_regs.r9 = arg;
227 break;
228 default:
229 unreachable();
230 }
231#endif
232 num_int++;
233 }
234 };
235
236 if constexpr (sizeof...(Args) > 0) {
237 std::tuple reg_param_vals = std::make_tuple(setup_function_param(std::forward<Args>(args))...);
238
239 std::optional first_error = tuple_find_first([](const auto& val) { return val.has_error(); }, reg_param_vals);
240
241 if (first_error.has_value()) {
242 return std::visit([](const auto& err_val) { return err_val.error(); }, *first_error);
243 }
244
245 std::apply([&](const auto&... vals) { (setup_raw_arg(*vals), ...); }, reg_param_vals);
246
247 TRY(set_registers(int_regs));
248 TRY(set_fp_registers(fp_regs));
249 }
250
251 return {};
252}
253
254template <typename Arg>
255Result<u64> Tracer::setup_function_param(const Arg& arg) {
256 if constexpr (std::integral<Arg>) {
257 return static_cast<u64>(arg);
258 } else if constexpr (std::is_pointer_v<Arg>) {
259 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
260 return reinterpret_cast<u64>(arg);
261 } else {
262 std::uintptr_t loc = mmaped_address_ + mmaped_used_amt_;
263 std::size_t num_bytes = TRY(memory_io_->write(loc, arg));
264
265 mmaped_used_amt_ += num_bytes;
266
267 return loc;
268 }
269}
270
271template <std::floating_point Arg>
272auto setup_function_param(const Arg& arg) {
273 return arg;
274}
275
276template <typename Ret>
278 static_assert(std::is_fundamental_v<Ret> || std::is_pointer_v<Ret>,
279 "Non-fundamental and non-pointer types are not yet supported as function return types");
280
281 if constexpr (std::floating_point<Ret>) {
282 user_fpregs_struct fp_regs = TRY(get_fp_registers());
283#ifdef __aarch64__
284 UNIMPLEMENTED("TODO: implement floating-point return types for aarch64");
285#else // x86_64 assumed
286
287 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
288 return *reinterpret_cast<Ret*>(&fp_regs.xmm_space[0]);
289#endif
290 } else {
291 user_regs_struct int_regs = TRY(get_registers());
292#ifdef __aarch64__
293 u64 ret = int_regs.regs[0];
294#else // x86_64 assumed
295 u64 ret = int_regs.rax;
296#endif
297
298 if constexpr (std::integral<Ret>) {
299 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
300 return *reinterpret_cast<Ret*>(&ret);
301 } else if constexpr (std::is_pointer_v<Ret>) {
302 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
303 return reinterpret_cast<Ret>(ret);
304 }
305 }
306}
307
308} // 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:25
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:223
std::optional< int > get_exit_code() const
Obtain the process exit code, or nullopt if the process has not yet exited.
Definition tracer.hpp:91
Result< Ret > process_function_ret()
AFTER a function has been called, inspects register values (and memory if necessary) to construct the...
Definition tracer.hpp:277
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:271
std::uintptr_t get_mmapped_addr() const
Definition tracer.hpp:116
static Result< void > init_child()
Set up child process for tracing Call this within the newly-forked process.
Definition tracer.cpp:100
Result< RunResult > run_until(const std::function< bool(SyscallRecord)> &pred)
Run the child process until pred returns true.
Definition tracer.cpp:336
Result< user_fpregs_struct > get_fp_registers() const
Definition tracer.cpp:253
static constexpr auto DEFAULT_TIMEOUT
Definition tracer.hpp:102
Result< void > begin(pid_t pid)
Sets up tracing in parent process, then stops child immediately after exec call.
Definition tracer.cpp:58
Result< RunResult > run()
Run the child process. Records each syscall execution. Equivalent to run_until({})
Definition tracer.cpp:332
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:235
Result< void > set_fp_registers(user_fpregs_struct regs) const
Definition tracer.cpp:263
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:245
static constexpr std::size_t MMAP_LENGTH
Definition tracer.hpp:51
Result< void > setup_function_call(Args &&... args)
Definition tracer.hpp:173
MemoryIOBase & get_memory_io()
Definition tracer.cpp:578
const std::vector< SyscallRecord > & get_records() const
Obtain records of syscalls run so far in the child process.
Definition tracer.hpp:88
#define TRY(val)
If the supplied argument is an error (unexpected) type, then propegate it up the call stack....
Definition error_types.hpp:52
#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:49
Definition asm_buffer.hpp:20
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:272
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