Skip to content

Commit

Permalink
feat: basic support for fork() + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ndrewh committed Nov 14, 2024
1 parent 84faf08 commit 9643911
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 3 deletions.
4 changes: 2 additions & 2 deletions pyda_core/pyda_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ void pyda_hook_cleancall(pyda_hook *cb) {

// If this also happens to be the run_until target for this thread,
// we deal with that here (instead of inserting two hooks)
if (cb->addr == pyda_get_run_until(t)) {
if (!t->python_exited && cb->addr == pyda_get_run_until(t)) {
// It is UB to modify PC in a hook that is also the run_until target
if (t->rip_updated_in_python) {
dr_fprintf(STDERR, "\n[Pyda] ERROR: Hook updated RIP, but run_until target is hit. This is UB. Continuing.");
Expand Down Expand Up @@ -642,7 +642,7 @@ int pyda_hook_syscall(int syscall_num, int is_pre) {

void pyda_hook_rununtil_reached(void *pc) {
pyda_thread *t = pyda_thread_getspecific(g_pyda_tls_idx);
if (t->errored) return;
if (t->errored || t->python_exited) return;
if (t->skip_next_hook) {
t->skip_next_hook = 0;
return;
Expand Down
29 changes: 29 additions & 0 deletions pyda_core/tool.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ static bool filter_syscall_event(void *drcontext, int sysnum);
static bool pre_syscall_event(void *drcontext, int sysnum);
static void post_syscall_event(void *drcontext, int sysnum);
static dr_signal_action_t signal_event(void *drcontext, dr_siginfo_t *siginfo);
static void fork_event(void *drcontext);
static void event_attach_post(void);


Expand Down Expand Up @@ -85,6 +86,7 @@ dr_client_main(client_id_t id, int argc, const char *argv[])
dr_register_post_attach_event(event_attach_post);

drmgr_register_signal_event(signal_event);
dr_register_fork_init_event(fork_event);
dr_request_synchronized_exit();

pthread_cond_init(&python_thread_init1, 0);
Expand Down Expand Up @@ -342,6 +344,33 @@ static dr_signal_action_t signal_event(void *drcontext, dr_siginfo_t *siginfo) {

return DR_SIGNAL_DELIVER;
}
static void fork_event(void *drcontext) {
// This is called on the NEW fork, which doesn't have any parallel Python threads anymore.
// TODO: How do we make sure that important locks aren't held at fork time when we have multiple threads?
pyda_thread *t = drmgr_get_tls_field(drcontext, g_pyda_tls_idx);
DEBUG_PRINTF("[Pyda] fork_init\n");

pyda_process *p = t->proc;

// Flush deleted hooks
drvector_lock(&p->threads);
for (int i=0; i<p->threads.entries; i++) {
dr_flush_file(STDERR);
if (p->threads.array[i] != t) pyda_thread_destroy(p->threads.array[i]);
}
p->threads.entries = 1;
p->threads.array[0] = t;
drvector_unlock(&p->threads);

// For now, we just mark the current thread as exited, which just means we won't
// try to yield to it in signal handlers or if we reach the last run_until hook.
//
// In the future, we could setup a new parallel Python thread that enters some
// "fork handler" as the entrypoint or whatever

t->python_exited = 1;
p->main_thread = t;
}

static void event_attach_post() {
DEBUG_PRINTF("event_attach_post on tid %d\n", dr_get_thread_id(dr_get_current_drcontext()));
Expand Down
10 changes: 9 additions & 1 deletion tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ def no_warnings_or_errors(stdout: bytes, stderr: bytes) -> bool:
lambda o, e: o.count(b"pass\n") == 1,
]
)),

("test_fork", "test_fork.c", "../examples/ltrace_multithreaded.py", RunOpts(), ExpectedResult(
retcode=0,
checkers=[
output_checker,
no_warnings_or_errors,
]
)),
]

def main():
Expand Down Expand Up @@ -336,4 +344,4 @@ def run_test(c_file, python_file, run_opts, expected_result, test_name, debug, n


if __name__ == '__main__':
main()
main()
30 changes: 30 additions & 0 deletions tests/test_fork.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void child_main() {
printf("child\n");
}

void parent_main() {
printf("parent\n");
}

int main(int argc, char** argv) {
printf("start\n");
int f = fork();
if (f == 0) {
// child
child_main();
} else if (f > 0) {
// parent
parent_main();

int status;
waitpid(f, &status, 0);
printf("child status %d\n", status);
} else {
printf("error\n");
}
printf("end\n");
}

0 comments on commit 9643911

Please sign in to comment.