| 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 | |