The main posixpp library and associated tests.
| Revision | 5f78080a2b8856e6279379708ec5929d07571563 (tree) |
|---|---|
| Time | 2022-12-08 23:28:42 |
| Author | Eric Hopper <hopper@omni...> |
| Commiter | Eric Hopper |
Add some comments inspired by a ChatGPT session.
| @@ -6,9 +6,15 @@ | ||
| 6 | 6 | |
| 7 | 7 | namespace syscalls::linux::x86_64 { |
| 8 | 8 | |
| 9 | +// An emum class that represents system call numbers. | |
| 9 | 10 | enum class call_id : ::std::uint16_t; |
| 11 | + | |
| 12 | +// The fundamental type of a system call argument. | |
| 10 | 13 | using val_t = ::std::int64_t; |
| 11 | 14 | |
| 15 | +// A wrapper that allows treating pointers and integers interchangeably. | |
| 16 | +// There are several comments to prompt the compiler to turn off warnings about | |
| 17 | +// bad conversions and use of `reinterpret_cast`. | |
| 12 | 18 | struct syscall_param { |
| 13 | 19 | syscall_param(val_t v) noexcept : value(v) { } // NOLINT |
| 14 | 20 | // NOLINTNEXTLINE |
| @@ -22,6 +28,10 @@ | ||
| 22 | 28 | val_t value; |
| 23 | 29 | }; |
| 24 | 30 | |
| 31 | + | |
| 32 | +// The full 6 argument system call comments the inline assembly more thoroughly. | |
| 33 | + | |
| 34 | +// No argument system call (like `getpid`). | |
| 25 | 35 | inline val_t do_syscall(call_id callnum) noexcept |
| 26 | 36 | { |
| 27 | 37 | val_t retval; |
| @@ -34,6 +44,7 @@ | ||
| 34 | 44 | return retval; |
| 35 | 45 | } |
| 36 | 46 | |
| 47 | +// Single argument system call. | |
| 37 | 48 | inline val_t do_syscall(call_id callnum, |
| 38 | 49 | syscall_param const &p1) noexcept |
| 39 | 50 | { |
| @@ -47,6 +58,7 @@ | ||
| 47 | 58 | return retval; |
| 48 | 59 | } |
| 49 | 60 | |
| 61 | +// Two argument system call. | |
| 50 | 62 | inline val_t do_syscall(call_id callnum, |
| 51 | 63 | syscall_param const &p1, |
| 52 | 64 | syscall_param const &p2) noexcept |
| @@ -61,6 +73,7 @@ | ||
| 61 | 73 | return retval; |
| 62 | 74 | } |
| 63 | 75 | |
| 76 | +// Three argument system call. | |
| 64 | 77 | inline val_t do_syscall(call_id callnum, |
| 65 | 78 | syscall_param const &p1, |
| 66 | 79 | syscall_param const &p2, |
| @@ -76,6 +89,7 @@ | ||
| 76 | 89 | return retval; |
| 77 | 90 | } |
| 78 | 91 | |
| 92 | +// Four argument system call. | |
| 79 | 93 | inline val_t do_syscall(call_id callnum, |
| 80 | 94 | syscall_param const &p1, |
| 81 | 95 | syscall_param const &p2, |
| @@ -93,6 +107,7 @@ | ||
| 93 | 107 | return retval; |
| 94 | 108 | } |
| 95 | 109 | |
| 110 | +// Five argument system call. | |
| 96 | 111 | inline val_t do_syscall(call_id callnum, |
| 97 | 112 | syscall_param const &p1, |
| 98 | 113 | syscall_param const &p2, |
| @@ -112,6 +127,7 @@ | ||
| 112 | 127 | return retval; |
| 113 | 128 | } |
| 114 | 129 | |
| 130 | +// Six argument system call. | |
| 115 | 131 | inline val_t do_syscall(call_id callnum, |
| 116 | 132 | syscall_param const &p1, |
| 117 | 133 | syscall_param const &p2, |
| @@ -120,22 +136,55 @@ | ||
| 120 | 136 | syscall_param const &p5, |
| 121 | 137 | syscall_param const &p6) noexcept |
| 122 | 138 | { |
| 139 | + // Declare this, though when the assembly is inlined, it should just | |
| 140 | + // magically be assigned to the `%rax` register. | |
| 123 | 141 | val_t retval; |
| 142 | + | |
| 143 | + // Declare alternate names for various registers and assign them the last few | |
| 144 | + // arguments for the system call. | |
| 124 | 145 | register volatile val_t rp4 asm ("r10") = p4.value; |
| 125 | 146 | register volatile val_t rp5 asm ("r8") = p5.value; |
| 126 | 147 | register volatile val_t rp6 asm ("r9") = p6.value; |
| 148 | + | |
| 149 | + // This inline assembly is just a single instruction with lots of hints to | |
| 150 | + // the compiler about how things should be set up before the instruction | |
| 151 | + // executes. The goal is for the compiler to simply arrange for the | |
| 152 | + // computations leading up to the instruction being emitted simply arrange | |
| 153 | + // for the right values to be in place before it's executed. | |
| 127 | 154 | asm volatile ( |
| 128 | - "syscall\n\t" | |
| 129 | - :"=a"(retval) | |
| 130 | - :"a"(static_cast<::std::uint64_t>(callnum)), "D"(p1.value), "S"(p2.value), "d"(p3.value), "r"(rp4), "r"(rp5), "r"(rp6) | |
| 155 | + "syscall\n\t" // The single instruction | |
| 156 | + | |
| 157 | + :"=a"(retval) // The `%rax` register should be put into `retval` after the | |
| 158 | + // instruction is executed (i.e. retval will simply end up | |
| 159 | + // being another name for `%rax`. | |
| 160 | + | |
| 161 | + // Declaring various input registers and where they come from. | |
| 162 | + :"a"(static_cast<::std::uint64_t>(callnum)), // %rax contains callnum | |
| 163 | + "D"(p1.value), // %rdi contains p1.value | |
| 164 | + "S"(p2.value), // %rsi contains p2.value | |
| 165 | + "d"(p3.value), // %rdx contains p3.value | |
| 166 | + "r"(rp4), // What rp4 means has already been declared above. | |
| 167 | + "r"(rp5), // What rp5 means has already been declared above. | |
| 168 | + "r"(rp6) // What rp6 means has already been declared above | |
| 169 | + | |
| 170 | + // Declaring what regsiters will no longer have usable values after the | |
| 171 | + // instruction finishes. Also declaring that the instruction may result | |
| 172 | + // in random places in memory having been changed. | |
| 131 | 173 | :"%rcx", "%r11", "memory" |
| 132 | 174 | ); |
| 175 | + | |
| 176 | + // This should not result in any instruction being emitted because %rax is | |
| 177 | + // where integer return values are stored in the x86_64 abi anyway. | |
| 133 | 178 | return retval; |
| 134 | 179 | } |
| 135 | 180 | |
| 136 | 181 | |
| 182 | +// This is where errno will go now, or the system call result. | |
| 137 | 183 | using expected_t = ::posixpp::expected<val_t>; |
| 138 | 184 | |
| 185 | +// This effectively creates 6 new functions that will each call the appropriate | |
| 186 | +// `do_syscall` overload. They then check for an error return and set up | |
| 187 | +// `::posixpp::expected` in the correct way for an error vs. normal return. | |
| 139 | 188 | template <typename... T> |
| 140 | 189 | expected_t |
| 141 | 190 | syscall_expected(call_id callnum, T &&... args) noexcept |
| @@ -347,6 +396,9 @@ | ||
| 347 | 396 | }; |
| 348 | 397 | |
| 349 | 398 | namespace priv_ { |
| 399 | +// Just a bunch of tests to make sure the enum is being set up as expected. | |
| 400 | +// Designed to catch people inserting values into the enum without paying | |
| 401 | +// attention to how the compiler is assigning integers to the various members. | |
| 350 | 402 | inline void compiletime_tests() |
| 351 | 403 | { |
| 352 | 404 | static_assert(static_cast<::std::uint16_t>(call_id::sendfile) == 40); |