| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <assert.h> |
| #if defined(TEST_CLONE) |
| #include <sched.h> |
| #endif |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| int g_val = 0; |
| |
| void parent_func() { |
| g_val = 1; |
| printf("function run in parent\n"); |
| } |
| |
| int parent_done[2]; |
| char parent_done_str[16]; |
| |
| void wait_for_parent() { |
| char buf[2]; |
| // wait for the parent to finish its part |
| int ret = read(parent_done[0], buf, sizeof(buf)); |
| assert(ret == 2); |
| ret = close(parent_done[0]); |
| assert(ret == 0); |
| } |
| |
| // This is the function we set breakpoint on. |
| int child_func(const char *argv0) { |
| // we need to avoid memory modifications for vfork(), yet we want |
| // to be able to test watchpoints, so do the next best thing |
| // and restore the original value |
| g_val = 2; |
| g_val = 0; |
| execl(argv0, argv0, parent_done_str, NULL); |
| assert(0 && "this should not be reached"); |
| return 1; |
| } |
| |
| int child_top_func(void *argv0_ptr) { |
| const char *argv0 = static_cast<char*>(argv0_ptr); |
| |
| int ret = close(parent_done[1]); |
| assert(ret == 0); |
| |
| // NB: when using vfork(), the parent may be suspended while running |
| // this function, so do not rely on any synchronization until we exec |
| #if defined(TEST_FORK) |
| if (TEST_FORK != vfork) |
| #endif |
| wait_for_parent(); |
| |
| return child_func(argv0); |
| } |
| |
| int main(int argc, char* argv[]) { |
| alignas(uintmax_t) char stack[4096]; |
| int ret; |
| |
| if (argv[1]) { |
| parent_done[0] = atoi(argv[1]); |
| assert(parent_done[0] != 0); |
| |
| #if defined(TEST_FORK) |
| // for vfork(), we need to synchronize after exec |
| if (TEST_FORK == vfork) |
| wait_for_parent(); |
| #endif |
| |
| fprintf(stderr, "function run in exec'd child\n"); |
| return 0; |
| } |
| |
| ret = pipe(parent_done); |
| assert(ret == 0); |
| |
| ret = snprintf(parent_done_str, sizeof(parent_done_str), |
| "%d", parent_done[0]); |
| assert(ret != -1); |
| |
| #if defined(TEST_CLONE) |
| pid_t pid = clone(child_top_func, &stack[sizeof(stack)], 0, argv[0]); |
| #elif defined(TEST_FORK) |
| pid_t pid = TEST_FORK(); |
| // NB: this must be equivalent to the clone() call above |
| if (pid == 0) |
| _exit(child_top_func(argv[0])); |
| #endif |
| assert(pid != -1); |
| |
| ret = close(parent_done[0]); |
| assert(ret == 0); |
| |
| parent_func(); |
| |
| // resume the child |
| ret = write(parent_done[1], "go", 2); |
| assert(ret == 2); |
| ret = close(parent_done[1]); |
| assert(ret == 0); |
| |
| int status, wait_flags = 0; |
| #if defined(TEST_CLONE) |
| wait_flags = __WALL; |
| #endif |
| pid_t waited = waitpid(pid, &status, wait_flags); |
| assert(waited == pid); |
| assert(WIFEXITED(status)); |
| assert(WEXITSTATUS(status) == 0); |
| |
| return 0; |
| } |