1 | #include <atomic> |
2 | #include <cassert> |
3 | #include <chrono> |
4 | #include <cstdlib> |
5 | #include <cstring> |
6 | #include <errno.h> |
7 | #include <future> |
8 | #include <inttypes.h> |
9 | #include <memory> |
10 | #include <mutex> |
11 | #if !defined(_WIN32) |
12 | #include <pthread.h> |
13 | #include <signal.h> |
14 | #include <unistd.h> |
15 | #endif |
16 | #include "thread.h" |
17 | #include <setjmp.h> |
18 | #include <stdint.h> |
19 | #include <stdio.h> |
20 | #include <string.h> |
21 | #include <string> |
22 | #include <thread> |
23 | #include <time.h> |
24 | #include <vector> |
25 | #if defined(__APPLE__) |
26 | #include <TargetConditionals.h> |
27 | #endif |
28 | |
29 | static const char *const PRINT_PID_COMMAND = "print-pid" ; |
30 | |
31 | static bool g_print_thread_ids = false; |
32 | static std::mutex g_print_mutex; |
33 | static bool g_threads_do_segfault = false; |
34 | |
35 | static std::mutex g_jump_buffer_mutex; |
36 | static jmp_buf g_jump_buffer; |
37 | static bool g_is_segfaulting = false; |
38 | |
39 | static char g_message[256]; |
40 | |
41 | static volatile char g_c1 = '0'; |
42 | static volatile char g_c2 = '1'; |
43 | |
44 | static void print_pid() { |
45 | #if defined(_WIN32) |
46 | fprintf(stderr, "PID: %d\n" , ::GetCurrentProcessId()); |
47 | #else |
48 | fprintf(stderr, format: "PID: %d\n" , getpid()); |
49 | #endif |
50 | } |
51 | |
52 | static void signal_handler(int signo) { |
53 | #if defined(_WIN32) |
54 | // No signal support on Windows. |
55 | #else |
56 | const char *signal_name = nullptr; |
57 | switch (signo) { |
58 | case SIGUSR1: |
59 | signal_name = "SIGUSR1" ; |
60 | break; |
61 | case SIGSEGV: |
62 | signal_name = "SIGSEGV" ; |
63 | break; |
64 | default: |
65 | signal_name = nullptr; |
66 | } |
67 | |
68 | // Print notice that we received the signal on a given thread. |
69 | char buf[100]; |
70 | if (signal_name) |
71 | snprintf(buf, sizeof(buf), "received %s on thread id: %" PRIx64 "\n" , signal_name, get_thread_id()); |
72 | else |
73 | snprintf(buf, sizeof(buf), "received signo %d (%s) on thread id: %" PRIx64 "\n" , signo, strsignal(sig: signo), get_thread_id()); |
74 | write(STDOUT_FILENO, buf: buf, n: strlen(s: buf)); |
75 | |
76 | // Reset the signal handler if we're one of the expected signal handlers. |
77 | switch (signo) { |
78 | case SIGSEGV: |
79 | if (g_is_segfaulting) { |
80 | // Fix up the pointer we're writing to. This needs to happen if nothing |
81 | // intercepts the SIGSEGV (i.e. if somebody runs this from the command |
82 | // line). |
83 | longjmp(env: g_jump_buffer, val: 1); |
84 | } |
85 | break; |
86 | case SIGUSR1: |
87 | if (g_is_segfaulting) { |
88 | // Fix up the pointer we're writing to. This is used to test gdb remote |
89 | // signal delivery. A SIGSEGV will be raised when the thread is created, |
90 | // switched out for a SIGUSR1, and then this code still needs to fix the |
91 | // seg fault. (i.e. if somebody runs this from the command line). |
92 | longjmp(env: g_jump_buffer, val: 1); |
93 | } |
94 | break; |
95 | } |
96 | |
97 | // Reset the signal handler. |
98 | sig_t sig_result = signal(sig: signo, handler: signal_handler); |
99 | if (sig_result == SIG_ERR) { |
100 | fprintf(stderr, format: "failed to set signal handler: errno=%d\n" , errno); |
101 | exit(status: 1); |
102 | } |
103 | #endif |
104 | } |
105 | |
106 | static void swap_chars() { |
107 | #if defined(__x86_64__) || defined(__i386__) |
108 | asm volatile("movb %1, (%2)\n\t" |
109 | "movb %0, (%3)\n\t" |
110 | "movb %0, (%2)\n\t" |
111 | "movb %1, (%3)\n\t" |
112 | : |
113 | : "i" ('0'), "i" ('1'), "r" (&g_c1), "r" (&g_c2) |
114 | : "memory" ); |
115 | #elif defined(__aarch64__) |
116 | asm volatile("strb %w1, [%2]\n\t" |
117 | "strb %w0, [%3]\n\t" |
118 | "strb %w0, [%2]\n\t" |
119 | "strb %w1, [%3]\n\t" |
120 | : |
121 | : "r" ('0'), "r" ('1'), "r" (&g_c1), "r" (&g_c2) |
122 | : "memory" ); |
123 | #elif defined(__arm__) |
124 | asm volatile("strb %1, [%2]\n\t" |
125 | "strb %0, [%3]\n\t" |
126 | "strb %0, [%2]\n\t" |
127 | "strb %1, [%3]\n\t" |
128 | : |
129 | : "r" ('0'), "r" ('1'), "r" (&g_c1), "r" (&g_c2) |
130 | : "memory" ); |
131 | #else |
132 | #warning This may generate unpredictible assembly and cause the single-stepping test to fail. |
133 | #warning Please add appropriate assembly for your target. |
134 | g_c1 = '1'; |
135 | g_c2 = '0'; |
136 | |
137 | g_c1 = '0'; |
138 | g_c2 = '1'; |
139 | #endif |
140 | } |
141 | |
142 | static void trap() { |
143 | #if defined(__x86_64__) || defined(__i386__) |
144 | asm volatile("int3" ); |
145 | #elif defined(__aarch64__) |
146 | asm volatile("brk #0xf000" ); |
147 | #elif defined(__arm__) |
148 | asm volatile("udf #254" ); |
149 | #elif defined(__powerpc__) |
150 | asm volatile("trap" ); |
151 | #elif __has_builtin(__builtin_debugtrap()) |
152 | __builtin_debugtrap(); |
153 | #else |
154 | #warning Don't know how to generate a trap. Some tests may fail. |
155 | #endif |
156 | } |
157 | |
158 | static void hello() { |
159 | std::lock_guard<std::mutex> lock(g_print_mutex); |
160 | printf(format: "hello, world\n" ); |
161 | } |
162 | |
163 | static void *thread_func(std::promise<void> ready) { |
164 | ready.set_value(); |
165 | static std::atomic<int> s_thread_index(1); |
166 | const int this_thread_index = s_thread_index++; |
167 | if (g_print_thread_ids) { |
168 | std::lock_guard<std::mutex> lock(g_print_mutex); |
169 | printf("thread %d id: %" PRIx64 "\n" , this_thread_index, get_thread_id()); |
170 | } |
171 | |
172 | if (g_threads_do_segfault) { |
173 | // Sleep for a number of seconds based on the thread index. |
174 | // TODO add ability to send commands to test exe so we can |
175 | // handle timing more precisely. This is clunky. All we're |
176 | // trying to do is add predictability as to the timing of |
177 | // signal generation by created threads. |
178 | int sleep_seconds = 2 * (this_thread_index - 1); |
179 | std::this_thread::sleep_for(rtime: std::chrono::seconds(sleep_seconds)); |
180 | |
181 | // Test creating a SEGV. |
182 | { |
183 | std::lock_guard<std::mutex> lock(g_jump_buffer_mutex); |
184 | g_is_segfaulting = true; |
185 | int *bad_p = nullptr; |
186 | if (setjmp(g_jump_buffer) == 0) { |
187 | // Force a seg fault signal on this thread. |
188 | *bad_p = 0; |
189 | } else { |
190 | // Tell the system we're no longer seg faulting. |
191 | // Used by the SIGUSR1 signal handler that we inject |
192 | // in place of the SIGSEGV so it only tries to |
193 | // recover from the SIGSEGV if this seg fault code |
194 | // was in play. |
195 | g_is_segfaulting = false; |
196 | } |
197 | } |
198 | |
199 | { |
200 | std::lock_guard<std::mutex> lock(g_print_mutex); |
201 | printf("thread %" PRIx64 ": past SIGSEGV\n" , get_thread_id()); |
202 | } |
203 | } |
204 | |
205 | int sleep_seconds_remaining = 60; |
206 | std::this_thread::sleep_for(rtime: std::chrono::seconds(sleep_seconds_remaining)); |
207 | |
208 | return nullptr; |
209 | } |
210 | |
211 | static bool consume_front(std::string &str, const std::string &front) { |
212 | if (str.find(str: front) != 0) |
213 | return false; |
214 | |
215 | str = str.substr(pos: front.size()); |
216 | return true; |
217 | } |
218 | |
219 | int main(int argc, char **argv) { |
220 | lldb_enable_attach(); |
221 | |
222 | std::vector<std::thread> threads; |
223 | std::unique_ptr<uint8_t[]> heap_array_up; |
224 | int return_value = 0; |
225 | |
226 | #if !defined(_WIN32) |
227 | bool is_child = false; |
228 | |
229 | // Set the signal handler. |
230 | sig_t sig_result = signal(SIGALRM, handler: signal_handler); |
231 | if (sig_result == SIG_ERR) { |
232 | fprintf(stderr, format: "failed to set SIGALRM signal handler: errno=%d\n" , errno); |
233 | exit(status: 1); |
234 | } |
235 | |
236 | sig_result = signal(SIGUSR1, handler: signal_handler); |
237 | if (sig_result == SIG_ERR) { |
238 | fprintf(stderr, format: "failed to set SIGUSR1 handler: errno=%d\n" , errno); |
239 | exit(status: 1); |
240 | } |
241 | |
242 | sig_result = signal(SIGSEGV, handler: signal_handler); |
243 | if (sig_result == SIG_ERR) { |
244 | fprintf(stderr, format: "failed to set SIGSEGV handler: errno=%d\n" , errno); |
245 | exit(status: 1); |
246 | } |
247 | |
248 | sig_result = signal(SIGCHLD, SIG_IGN); |
249 | if (sig_result == SIG_ERR) { |
250 | fprintf(stderr, format: "failed to set SIGCHLD handler: errno=%d\n" , errno); |
251 | exit(status: 1); |
252 | } |
253 | #endif |
254 | |
255 | // Process command line args. |
256 | for (int i = 1; i < argc; ++i) { |
257 | std::string arg = argv[i]; |
258 | if (consume_front(str&: arg, front: "stderr:" )) { |
259 | // Treat remainder as text to go to stderr. |
260 | fprintf(stderr, format: "%s\n" , arg.c_str()); |
261 | } else if (consume_front(str&: arg, front: "retval:" )) { |
262 | // Treat as the return value for the program. |
263 | return_value = std::atoi(nptr: arg.c_str()); |
264 | } else if (consume_front(str&: arg, front: "sleep:" )) { |
265 | // Treat as the amount of time to have this process sleep (in seconds). |
266 | int sleep_seconds_remaining = std::atoi(nptr: arg.c_str()); |
267 | |
268 | // Loop around, sleeping until all sleep time is used up. Note that |
269 | // signals will cause sleep to end early with the number of seconds |
270 | // remaining. |
271 | std::this_thread::sleep_for( |
272 | rtime: std::chrono::seconds(sleep_seconds_remaining)); |
273 | |
274 | } else if (consume_front(str&: arg, front: "set-message:" )) { |
275 | // Copy the contents after "set-message:" to the g_message buffer. |
276 | // Used for reading inferior memory and verifying contents match |
277 | // expectations. |
278 | strncpy(dest: g_message, src: arg.c_str(), n: sizeof(g_message)); |
279 | |
280 | // Ensure we're null terminated. |
281 | g_message[sizeof(g_message) - 1] = '\0'; |
282 | |
283 | } else if (consume_front(str&: arg, front: "print-message:" )) { |
284 | std::lock_guard<std::mutex> lock(g_print_mutex); |
285 | printf(format: "message: %s\n" , g_message); |
286 | } else if (consume_front(str&: arg, front: "get-data-address-hex:" )) { |
287 | volatile void *data_p = nullptr; |
288 | |
289 | if (arg == "g_message" ) |
290 | data_p = &g_message[0]; |
291 | else if (arg == "g_c1" ) |
292 | data_p = &g_c1; |
293 | else if (arg == "g_c2" ) |
294 | data_p = &g_c2; |
295 | |
296 | std::lock_guard<std::mutex> lock(g_print_mutex); |
297 | printf(format: "data address: %p\n" , data_p); |
298 | } else if (consume_front(str&: arg, front: "get-heap-address-hex:" )) { |
299 | // Create a byte array if not already present. |
300 | if (!heap_array_up) |
301 | heap_array_up.reset(p: new uint8_t[32]); |
302 | |
303 | std::lock_guard<std::mutex> lock(g_print_mutex); |
304 | printf(format: "heap address: %p\n" , heap_array_up.get()); |
305 | |
306 | } else if (consume_front(str&: arg, front: "get-stack-address-hex:" )) { |
307 | std::lock_guard<std::mutex> lock(g_print_mutex); |
308 | printf(format: "stack address: %p\n" , &return_value); |
309 | } else if (consume_front(str&: arg, front: "get-code-address-hex:" )) { |
310 | void (*func_p)() = nullptr; |
311 | |
312 | if (arg == "hello" ) |
313 | func_p = hello; |
314 | else if (arg == "swap_chars" ) |
315 | func_p = swap_chars; |
316 | |
317 | std::lock_guard<std::mutex> lock(g_print_mutex); |
318 | printf(format: "code address: %p\n" , func_p); |
319 | } else if (consume_front(str&: arg, front: "call-function:" )) { |
320 | void (*func_p)() = nullptr; |
321 | |
322 | if (arg == "hello" ) |
323 | func_p = hello; |
324 | else if (arg == "swap_chars" ) |
325 | func_p = swap_chars; |
326 | func_p(); |
327 | #if !defined(_WIN32) && !defined(TARGET_OS_WATCH) && !defined(TARGET_OS_TV) |
328 | } else if (arg == "fork" ) { |
329 | pid_t fork_pid = fork(); |
330 | assert(fork_pid != -1); |
331 | is_child = fork_pid == 0; |
332 | } else if (arg == "vfork" ) { |
333 | if (vfork() == 0) |
334 | _exit(status: 0); |
335 | } else if (consume_front(str&: arg, front: "process:sync:" )) { |
336 | // this is only valid after fork |
337 | const char *filenames[] = {"parent" , "child" }; |
338 | std::string my_file = arg + "." + filenames[is_child]; |
339 | std::string other_file = arg + "." + filenames[!is_child]; |
340 | |
341 | // indicate that we're ready |
342 | FILE *f = fopen(filename: my_file.c_str(), modes: "w" ); |
343 | assert(f); |
344 | fclose(stream: f); |
345 | |
346 | // wait for the other process to be ready |
347 | for (int i = 0; i < 5; ++i) { |
348 | f = fopen(filename: other_file.c_str(), modes: "r" ); |
349 | if (f) |
350 | break; |
351 | std::this_thread::sleep_for(rtime: std::chrono::milliseconds(125 * (1<<i))); |
352 | } |
353 | assert(f); |
354 | fclose(stream: f); |
355 | #endif |
356 | } else if (consume_front(str&: arg, front: "thread:new" )) { |
357 | std::promise<void> promise; |
358 | std::future<void> ready = promise.get_future(); |
359 | threads.push_back(x: std::thread(thread_func, std::move(promise))); |
360 | ready.wait(); |
361 | } else if (consume_front(str&: arg, front: "thread:print-ids" )) { |
362 | // Turn on thread id announcing. |
363 | g_print_thread_ids = true; |
364 | |
365 | // And announce us. |
366 | { |
367 | std::lock_guard<std::mutex> lock(g_print_mutex); |
368 | printf("thread 0 id: %" PRIx64 "\n" , get_thread_id()); |
369 | } |
370 | } else if (consume_front(str&: arg, front: "thread:segfault" )) { |
371 | g_threads_do_segfault = true; |
372 | } else if (consume_front(str&: arg, front: "print-pid" )) { |
373 | print_pid(); |
374 | } else if (consume_front(str&: arg, front: "print-env:" )) { |
375 | // Print the value of specified envvar to stdout. |
376 | const char *value = getenv(name: arg.c_str()); |
377 | printf(format: "%s\n" , value ? value : "__unset__" ); |
378 | } else if (consume_front(str&: arg, front: "trap" )) { |
379 | trap(); |
380 | #if !defined(_WIN32) |
381 | } else if (arg == "stop" ) { |
382 | raise(SIGINT); |
383 | #endif |
384 | } else { |
385 | // Treat the argument as text for stdout. |
386 | printf(format: "%s\n" , argv[i]); |
387 | } |
388 | } |
389 | |
390 | // If we launched any threads, join them |
391 | for (std::vector<std::thread>::iterator it = threads.begin(); |
392 | it != threads.end(); ++it) |
393 | it->join(); |
394 | |
395 | return return_value; |
396 | } |
397 | |