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