Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 1 | #ifndef AOS_IPC_LIB_LOCKLESS_QUEUE_STEPPING_H_ |
| 2 | #define AOS_IPC_LIB_LOCKLESS_QUEUE_STEPPING_H_ |
| 3 | |
Stephan Pleines | 682928d | 2024-05-31 20:43:48 -0700 | [diff] [blame^] | 4 | #include <stdlib.h> |
| 5 | #include <unistd.h> |
| 6 | |
Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 7 | #include <cinttypes> |
| 8 | #include <functional> |
Stephan Pleines | 682928d | 2024-05-31 20:43:48 -0700 | [diff] [blame^] | 9 | #include <tuple> |
Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 10 | |
| 11 | #include "aos/ipc_lib/lockless_queue.h" |
Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 12 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame] | 13 | namespace aos::ipc_lib::testing { |
Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 14 | |
| 15 | #if defined(__ARM_EABI__) |
| 16 | // There are various reasons why we might not actually be able to do this |
| 17 | // testing, but we still want to run the functions to test anything they test |
| 18 | // other than shm robustness. |
| 19 | // |
| 20 | // ARM doesn't have a nice way to do single-stepping, and I don't feel like |
| 21 | // dealing with editing the next instruction to be a breakpoint and then |
| 22 | // changing it back. |
| 23 | #else |
| 24 | |
| 25 | #define SUPPORTS_SHM_ROBUSTNESS_TEST |
| 26 | |
| 27 | // This code currently supports amd64 only, but it |
| 28 | // shouldn't be hard to port to i386 (it should just be using the name for |
| 29 | // only the smaller part of the flags register), but that's not tested, and |
| 30 | // porting it to any other architecture is more work. |
| 31 | // Currently, we skip doing anything exciting on arm (just run the code without |
| 32 | // any robustness testing) and refuse to compile anywhere else. |
| 33 | |
| 34 | #define SIMPLE_ASSERT(condition, message) \ |
| 35 | do { \ |
| 36 | if (!(condition)) { \ |
| 37 | static const char kMessage[] = message "\n"; \ |
| 38 | if (write(STDERR_FILENO, kMessage, sizeof(kMessage) - 1) != \ |
| 39 | (sizeof(kMessage) - 1)) { \ |
| 40 | static const char kFailureMessage[] = "writing failed\n"; \ |
| 41 | __attribute__((unused)) int ignore = write( \ |
| 42 | STDERR_FILENO, kFailureMessage, sizeof(kFailureMessage) - 1); \ |
| 43 | } \ |
| 44 | abort(); \ |
| 45 | } \ |
| 46 | } while (false) |
| 47 | |
| 48 | // Array to track writes to memory, and make sure they happen in the right |
| 49 | // order. |
| 50 | class WritesArray { |
| 51 | public: |
| 52 | uintptr_t At(size_t location) const { |
| 53 | SIMPLE_ASSERT(location < size_, "too far into writes array"); |
| 54 | return writes_[location]; |
| 55 | } |
| 56 | void Add(uintptr_t pointer) { |
| 57 | SIMPLE_ASSERT(size_ < kSize, "too many writes"); |
| 58 | writes_[size_++] = pointer; |
| 59 | } |
| 60 | |
| 61 | size_t size() const { return size_; } |
| 62 | |
| 63 | private: |
| 64 | static const size_t kSize = 20000; |
| 65 | |
| 66 | uintptr_t writes_[kSize]; |
| 67 | size_t size_ = 0; |
| 68 | }; |
| 69 | |
| 70 | enum class DieAtState { |
| 71 | // No SEGVs should be happening. |
| 72 | kDisabled, |
| 73 | // SEGVs are fine. Normal operation. |
| 74 | kRunning, |
| 75 | // We are manipulating a mutex. No SEGVs should be happening. |
| 76 | kWriting, |
| 77 | }; |
| 78 | |
| 79 | // What we exit with when we're exiting in the middle. |
| 80 | const int kExitEarlyValue = 123; |
| 81 | |
| 82 | // We have to keep track of everything in a global variable because there's no |
| 83 | // other way for the signal handlers to find it. |
| 84 | struct GlobalState { |
| 85 | // Constructs the singleton GlobalState. |
| 86 | static std::tuple<GlobalState *, WritesArray *> MakeGlobalState(); |
| 87 | |
| 88 | // Returns the global state. Atomic and safe from signal handlers. |
| 89 | static GlobalState *Get(); |
| 90 | |
| 91 | // Pointer to the queue memory, and its size. |
| 92 | void *lockless_queue_memory; |
| 93 | size_t lockless_queue_memory_size; |
| 94 | |
| 95 | // Pointer to a second block of memory the same size. This (on purpose) has |
| 96 | // the same size as lockless_queue_memory so we can point the robust mutexes |
| 97 | // here. |
| 98 | void *lockless_queue_memory_lock_backup; |
| 99 | |
| 100 | // Expected writes. |
| 101 | const WritesArray *writes_in; |
| 102 | // Actual writes. |
| 103 | WritesArray *writes_out; |
| 104 | // Location to die at, and how far we have gotten. |
| 105 | size_t die_at, current_location; |
| 106 | // State. |
| 107 | DieAtState state; |
| 108 | |
| 109 | // Returns true if the address is in the queue memory chunk. |
| 110 | bool IsInLocklessQueueMemory(void *address); |
| 111 | |
| 112 | // Calls mprotect(2) for the entire shared memory region with the given prot. |
| 113 | void ShmProtectOrDie(int prot); |
| 114 | |
| 115 | // Checks a write into the queue and conditionally dies. Tracks the write. |
| 116 | void HandleWrite(void *address); |
| 117 | |
| 118 | // Registers the handlers required to trap writes. |
| 119 | void RegisterSegvAndTrapHandlers(); |
| 120 | }; |
| 121 | |
| 122 | void TestShmRobustness(const LocklessQueueConfiguration &config, |
| 123 | ::std::function<void(void *)> prepare, |
| 124 | ::std::function<void(void *)> function, |
| 125 | ::std::function<void(void *)> check); |
| 126 | |
| 127 | // Capture the tid in the child so we can tell if it died. Uses mmap so it |
| 128 | // works across the process boundary. |
| 129 | class SharedTid { |
| 130 | public: |
| 131 | SharedTid(); |
| 132 | ~SharedTid(); |
| 133 | |
| 134 | // Captures the tid. |
| 135 | void Set(); |
| 136 | |
| 137 | // Returns the captured tid. |
| 138 | pid_t Get(); |
| 139 | |
| 140 | private: |
| 141 | pid_t *tid_; |
| 142 | }; |
| 143 | |
Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 144 | #endif |
| 145 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame] | 146 | } // namespace aos::ipc_lib::testing |
Austin Schuh | faec51a | 2023-09-08 17:43:32 -0700 | [diff] [blame] | 147 | |
| 148 | #endif // AOS_IPC_LIB_LOCKLESS_QUEUE_STEPPING_H_ |