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