blob: 8be7b44fe418c72905764363327a988eead47330 [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
Stephan Pleines682928d2024-05-31 20:43:48 -07004#include <stdlib.h>
5#include <unistd.h>
6
Austin Schuhfaec51a2023-09-08 17:43:32 -07007#include <cinttypes>
8#include <functional>
Stephan Pleines682928d2024-05-31 20:43:48 -07009#include <tuple>
Austin Schuhfaec51a2023-09-08 17:43:32 -070010
11#include "aos/ipc_lib/lockless_queue.h"
Austin Schuhfaec51a2023-09-08 17:43:32 -070012
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -080013namespace aos::ipc_lib::testing {
Austin Schuhfaec51a2023-09-08 17:43:32 -070014
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.
50class 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
70enum 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.
80const 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.
84struct 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
122void 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.
129class 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 Schuhfaec51a2023-09-08 17:43:32 -0700144#endif
145
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800146} // namespace aos::ipc_lib::testing
Austin Schuhfaec51a2023-09-08 17:43:32 -0700147
148#endif // AOS_IPC_LIB_LOCKLESS_QUEUE_STEPPING_H_