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