diff --git a/breakpoints.c b/breakpoints.c
index 1eff8b0..387b2a5 100644
--- a/breakpoints.c
+++ b/breakpoints.c
@@ -203,14 +203,7 @@ breakpoints_init(Process *proc, int enable)
proc->breakpoints = dict_init(dict_key2hash_int,
dict_key_cmp_int);
- if (proc->list_of_symbols != NULL) {
- struct library_symbol * sym = proc->list_of_symbols;
- while (sym != NULL) {
- struct library_symbol * next = sym->next;
- free(sym);
- sym = next;
- }
- }
+ destroy_library_symbol_chain(proc->list_of_symbols);
proc->list_of_symbols = NULL;
if (options.libcalls && proc->filename) {
diff --git a/common.h b/common.h
index 49861cf..c0b24e5 100644
--- a/common.h
+++ b/common.h
@@ -252,6 +252,7 @@ enum process_status {
ps_invalid, /* Failure. */
ps_stop, /* Job-control stop. */
ps_tracing_stop,
+ ps_sleeping,
ps_zombie,
ps_other, /* Necessary other states can be added as needed. */
};
@@ -265,6 +266,7 @@ enum pcb_status {
extern Process * pid2proc(pid_t pid);
extern void add_process(Process * proc);
extern void remove_process(Process * proc);
+extern void change_process_leader(Process * proc, Process * leader);
extern Process *each_process(Process * start,
enum pcb_status (* cb)(Process * proc, void * data),
void * data);
@@ -313,6 +315,10 @@ extern void add_library_symbol(GElf_Addr addr, const char *name,
struct library_symbol **library_symbolspp,
enum toplt type_of_plt, int is_weak);
+extern struct library_symbol * clone_library_symbol(struct library_symbol * s);
+extern void destroy_library_symbol(struct library_symbol * s);
+extern void destroy_library_symbol_chain(struct library_symbol * chain);
+
/* Arch-dependent stuff: */
extern char * pid2name(pid_t pid);
extern pid_t process_leader(pid_t pid);
@@ -335,6 +341,7 @@ extern int syscall_p(Process * proc, int status, int * sysnum);
extern void continue_process(pid_t pid);
extern void continue_after_signal(pid_t pid, int signum);
extern void continue_after_breakpoint(Process * proc, Breakpoint * sbp);
+extern void continue_after_vfork(Process * proc);
extern void ltrace_exiting(void);
extern long gimme_arg(enum tof type, Process * proc, int arg_num, arg_type_info * info);
extern void save_register_args(enum tof type, Process * proc);
diff --git a/dict.c b/dict.c
index 486a461..ba318cd 100644
--- a/dict.c
+++ b/dict.c
@@ -180,7 +180,9 @@ dict_key_cmp_int(void *key1, void *key2) {
}
Dict *
-dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)) {
+dict_clone2(Dict * old, void * (*key_clone)(void *, void *),
+ void * (*value_clone)(void *, void *), void * data)
+{
Dict *d;
int i;
@@ -199,17 +201,64 @@ dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*))
de_old = old->buckets[i];
de_new = &d->buckets[i];
while (de_old) {
+ void * nkey, * nval;
*de_new = malloc(sizeof(struct dict_entry));
if (!*de_new) {
perror("malloc()");
exit(1);
}
memcpy(*de_new, de_old, sizeof(struct dict_entry));
- (*de_new)->key = key_clone(de_old->key);
- (*de_new)->value = value_clone(de_old->value);
+
+ /* The error detection is rather weak :-/ */
+ nkey = key_clone(de_old->key, data);
+ if (nkey == NULL && de_old->key != NULL) {
+ perror("key_clone");
+ err:
+ /* XXX Will this actually work? We
+ * simply memcpy the old dictionary
+ * over up there. */
+ dict_clear(d);
+ free(de_new);
+ return NULL;
+ }
+
+ nval = value_clone(de_old->value, data);
+ if (nval == NULL && de_old->value != NULL) {
+ perror("value_clone");
+ goto err;
+ }
+
+ (*de_new)->key = nkey;
+ (*de_new)->value = nval;
de_new = &(*de_new)->next;
de_old = de_old->next;
}
}
return d;
}
+
+struct wrap_clone_cb
+{
+ void * (*key_clone)(void *);
+ void * (*value_clone)(void *);
+};
+
+static void *
+value_clone_1(void * arg, void * data)
+{
+ return ((struct wrap_clone_cb *)data)->value_clone(arg);
+}
+
+static void *
+key_clone_1(void * arg, void * data)
+{
+ return ((struct wrap_clone_cb *)data)->key_clone(arg);
+}
+
+Dict *
+dict_clone(Dict * old, void * (*key_clone)(void *),
+ void * (*value_clone)(void *))
+{
+ struct wrap_clone_cb cb = { key_clone, value_clone };
+ return dict_clone2(old, &key_clone_1, &value_clone_1, &cb);
+}
diff --git a/dict.h b/dict.h
index a70c3d5..27dc7bf 100644
--- a/dict.h
+++ b/dict.h
@@ -18,3 +18,7 @@ extern int dict_key_cmp_string(void *key1, void *key2);
extern unsigned int dict_key2hash_int(void *key);
extern int dict_key_cmp_int(void *key1, void *key2);
extern Dict * dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*));
+extern Dict * dict_clone2(Dict * old,
+ void * (* key_clone)(void * key, void * data),
+ void * (* value_clone)(void * value, void * data),
+ void * data);
diff --git a/handle_event.c b/handle_event.c
index 0aa40f7..f56c537 100644
--- a/handle_event.c
+++ b/handle_event.c
@@ -7,6 +7,7 @@
#include <signal.h>
#include <assert.h>
#include <sys/time.h>
+#include <errno.h>
#include "common.h"
@@ -35,6 +36,18 @@ static char * shortsignal(Process *proc, int signum);
static char * sysname(Process *proc, int sysnum);
static char * arch_sysname(Process *proc, int sysnum);
+static Event *
+call_handler(Process * proc, Event * event)
+{
+ assert(proc != NULL);
+
+ Event_Handler * handler = proc->event_handler;
+ if (handler == NULL)
+ return event;
+
+ return (*handler->on_event) (handler, event);
+}
+
void
handle_event(Event *event) {
if (exiting == 1) {
@@ -44,15 +57,22 @@ handle_event(Event *event) {
}
debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)",
event->proc ? event->proc->pid : -1, event->type);
- /* If the thread group defines an overriding event handler,
- give it a chance to kick in. */
- if (event->proc != NULL
- && event->proc->leader != NULL) {
- Event_Handler * handler = event->proc->leader->event_handler;
- if (handler != NULL) {
- event = (*handler->on_event) (handler, event);
+
+ /* If the thread group or an individual task define an
+ overriding event handler, give them a chance to kick in.
+ We will end up calling both handlers, if the first one
+ doesn't sink the event. */
+ if (event->proc != NULL) {
+ event = call_handler(event->proc, event);
+ if (event == NULL)
+ /* It was handled. */
+ return;
+
+ /* Note: the previous handler has a chance to alter
+ * the event. */
+ if (event->proc->leader != NULL) {
+ event = call_handler(event->proc->leader, event);
if (event == NULL)
- /* It was handled. */
return;
}
}
@@ -102,6 +122,7 @@ handle_event(Event *event) {
handle_arch_sysret(event);
return;
case EVENT_CLONE:
+ case EVENT_VFORK:
debug(1, "event: clone (%u)", event->e_un.newpid);
handle_clone(event);
return;
@@ -125,14 +146,17 @@ handle_event(Event *event) {
/* TODO */
static void *
-address_clone(void * addr) {
+address_clone(void * addr, void * data)
+{
debug(DEBUG_FUNCTION, "address_clone(%p)", addr);
return addr;
}
static void *
-breakpoint_clone(void * bp) {
+breakpoint_clone(void * bp, void * data)
+{
Breakpoint * b;
+ Dict * map = data;
debug(DEBUG_FUNCTION, "breakpoint_clone(%p)", bp);
b = malloc(sizeof(Breakpoint));
if (!b) {
@@ -140,6 +164,15 @@ breakpoint_clone(void * bp) {
exit(1);
}
memcpy(b, bp, sizeof(Breakpoint));
+ if (b->libsym != NULL) {
+ struct library_symbol * sym = dict_find_entry(map, b->libsym);
+ if (b->libsym == NULL) {
+ fprintf(stderr, "Can't find cloned symbol %s.\n",
+ b->libsym->name);
+ return NULL;
+ }
+ b->libsym = sym;
+ }
return b;
}
@@ -204,6 +237,40 @@ pending_new_remove(pid_t pid) {
}
}
+static int
+clone_breakpoints(Process * proc, Process * orig_proc)
+{
+ /* When copying breakpoints, we also have to copy the
+ * referenced symbols, and link them properly. */
+ Dict * map = dict_init(&dict_key2hash_int, &dict_key_cmp_int);
+ struct library_symbol * it = proc->list_of_symbols;
+ proc->list_of_symbols = NULL;
+ for (; it != NULL; it = it->next) {
+ struct library_symbol * libsym = clone_library_symbol(it);
+ if (libsym == NULL) {
+ int save_errno;
+ err:
+ save_errno = errno;
+ destroy_library_symbol_chain(proc->list_of_symbols);
+ dict_clear(map);
+ errno = save_errno;
+ return -1;
+ }
+ libsym->next = proc->list_of_symbols;
+ proc->list_of_symbols = libsym;
+ if (dict_enter(map, it, libsym) != 0)
+ goto err;
+ }
+
+ proc->breakpoints = dict_clone2(orig_proc->breakpoints,
+ address_clone, breakpoint_clone, map);
+ if (proc->breakpoints == NULL)
+ goto err;
+
+ dict_clear(map);
+ return 0;
+}
+
static void
handle_clone(Event * event) {
Process *p;
@@ -216,7 +283,6 @@ handle_clone(Event * event) {
exit(1);
}
memcpy(p, event->proc, sizeof(Process));
- p->breakpoints = dict_clone(event->proc->breakpoints, address_clone, breakpoint_clone);
p->pid = event->e_un.newpid;
p->parent = event->proc;
@@ -239,7 +305,17 @@ handle_clone(Event * event) {
p->state = STATE_BEING_CREATED;
add_process(p);
}
- continue_process(event->proc->pid);
+
+ if (p->leader == p)
+ clone_breakpoints(p, event->proc->leader);
+ else
+ /* Thread groups share breakpoints. */
+ p->breakpoints = NULL;
+
+ if (event->type == EVENT_VFORK)
+ continue_after_vfork(p);
+ else
+ continue_process(event->proc->pid);
}
static void
@@ -253,8 +329,6 @@ handle_new(Event * event) {
pending_new_insert(event->e_un.newpid);
} else {
assert(proc->state == STATE_BEING_CREATED);
- if (proc->event_handler != NULL)
- destroy_event_handler(proc);
if (options.follow) {
proc->state = STATE_ATTACHED;
} else {
diff --git a/libltrace.c b/libltrace.c
index e731fe1..19bfafd 100644
--- a/libltrace.c
+++ b/libltrace.c
@@ -107,6 +107,11 @@ ltrace_init(int argc, char **argv) {
}
}
if (command) {
+ /* Check that the binary ABI is supported before
+ * calling execute_program. */
+ struct ltelf lte = {};
+ open_elf(<e, command);
+
open_program(command, execute_program(command, argv), 0);
}
opt_p_tmp = opt_p;
diff --git a/ltrace-elf.c b/ltrace-elf.c
index d88d5a6..9aea4a9 100644
--- a/ltrace-elf.c
+++ b/ltrace-elf.c
@@ -136,18 +136,14 @@ static GElf_Addr get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot,
}
int
-do_init_elf(struct ltelf *lte, const char *filename) {
- int i;
- GElf_Addr relplt_addr = 0;
- size_t relplt_size = 0;
-
- debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename);
- debug(1, "Reading ELF from %s...", filename);
-
+open_elf(struct ltelf *lte, const char *filename)
+{
lte->fd = open(filename, O_RDONLY);
if (lte->fd == -1)
return 1;
+ elf_version(EV_CURRENT);
+
#ifdef HAVE_ELF_C_READ_MMAP
lte->elf = elf_begin(lte->fd, ELF_C_READ_MMAP, NULL);
#else
@@ -180,6 +176,21 @@ do_init_elf(struct ltelf *lte, const char *filename) {
error(EXIT_FAILURE, 0,
"\"%s\" is ELF from incompatible architecture", filename);
+ return 0;
+}
+
+int
+do_init_elf(struct ltelf *lte, const char *filename) {
+ int i;
+ GElf_Addr relplt_addr = 0;
+ size_t relplt_size = 0;
+
+ debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename);
+ debug(1, "Reading ELF from %s...", filename);
+
+ if (open_elf(lte, filename) < 0)
+ return -1;
+
Elf_Data *plt_data = NULL;
GElf_Addr ppcgot = 0;
@@ -465,30 +476,76 @@ do_close_elf(struct ltelf *lte) {
close(lte->fd);
}
+static struct library_symbol *
+create_library_symbol(const char * name, GElf_Addr addr)
+{
+ size_t namel = strlen(name) + 1;
+ struct library_symbol * sym = calloc(sizeof(*sym) + namel, 1);
+ if (sym == NULL) {
+ perror("create_library_symbol");
+ return NULL;
+ }
+ sym->name = (char *)(sym + 1);
+ memcpy(sym->name, name, namel);
+ sym->enter_addr = (void *)(uintptr_t) addr;
+ return sym;
+}
+
void
add_library_symbol(GElf_Addr addr, const char *name,
struct library_symbol **library_symbolspp,
- enum toplt type_of_plt, int is_weak) {
+ enum toplt type_of_plt, int is_weak)
+{
struct library_symbol *s;
debug(DEBUG_FUNCTION, "add_library_symbol()");
- s = malloc(sizeof(struct library_symbol) + strlen(name) + 1);
+ s = create_library_symbol(name, addr);
if (s == NULL)
error(EXIT_FAILURE, errno, "add_library_symbol failed");
s->needs_init = 1;
s->is_weak = is_weak;
s->plt_type = type_of_plt;
+
s->next = *library_symbolspp;
- s->enter_addr = (void *)(uintptr_t) addr;
- s->name = (char *)(s + 1);
- strcpy(s->name, name);
*library_symbolspp = s;
debug(2, "addr: %p, symbol: \"%s\"", (void *)(uintptr_t) addr, name);
}
+struct library_symbol *
+clone_library_symbol(struct library_symbol * sym)
+{
+ struct library_symbol * copy
+ = create_library_symbol(sym->name,
+ (GElf_Addr)(uintptr_t)sym->enter_addr);
+ if (copy == NULL)
+ return NULL;
+
+ copy->needs_init = sym->needs_init;
+ copy->is_weak = sym->is_weak;
+ copy->plt_type = sym->plt_type;
+
+ return copy;
+}
+
+void
+destroy_library_symbol(struct library_symbol * sym)
+{
+ free(sym);
+}
+
+void
+destroy_library_symbol_chain(struct library_symbol * sym)
+{
+ while (sym != NULL) {
+ struct library_symbol * next = sym->next;
+ destroy_library_symbol(sym);
+ sym = next;
+ }
+}
+
/* stolen from elfutils-0.123 */
static unsigned long
private_elf_gnu_hash(const char *name) {
@@ -620,8 +677,6 @@ read_elf(Process *proc) {
library_num = 0;
proc->libdl_hooked = 0;
- elf_version(EV_CURRENT);
-
if (do_init_elf(lte, proc->filename))
return NULL;
@@ -637,7 +692,7 @@ read_elf(Process *proc) {
for (i = 0; i < library_num; ++i) {
if (do_init_elf(<e[i + 1], library[i]))
error(EXIT_FAILURE, errno, "Can't open \"%s\"",
- proc->filename);
+ library[i]);
}
if (!options.no_plt) {
diff --git a/ltrace-elf.h b/ltrace-elf.h
index a29fe2c..3b675c5 100644
--- a/ltrace-elf.h
+++ b/ltrace-elf.h
@@ -44,6 +44,7 @@ struct ltelf {
extern size_t library_num;
extern char *library[MAX_LIBRARIES];
+extern int open_elf(struct ltelf *lte, const char *filename);
extern struct library_symbol *read_elf(Process *);
extern GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *);
diff --git a/ltrace.h b/ltrace.h
index 0ff4572..194704d 100644
--- a/ltrace.h
+++ b/ltrace.h
@@ -9,6 +9,7 @@ enum Event_type {
EVENT_ARCH_SYSCALL,
EVENT_ARCH_SYSRET,
EVENT_CLONE,
+ EVENT_VFORK,
EVENT_EXEC,
EVENT_BREAKPOINT,
EVENT_LIBCALL,
diff --git a/proc.c b/proc.c
index 0425e09..f4d3396 100644
--- a/proc.c
+++ b/proc.c
@@ -154,9 +154,30 @@ pid2proc(pid_t pid) {
return each_process(NULL, &find_proc, (void *)(uintptr_t)pid);
}
-
static Process * list_of_processes = NULL;
+static void
+unlist_process(Process * proc)
+{
+ Process *tmp;
+
+ if (list_of_processes == proc) {
+ list_of_processes = list_of_processes->next;
+ return;
+ }
+
+ for (tmp = list_of_processes; ; tmp = tmp->next) {
+ /* If the following assert fails, the process wasn't
+ * in the list. */
+ assert(tmp->next != NULL);
+
+ if (tmp->next == proc) {
+ tmp->next = tmp->next->next;
+ return;
+ }
+ }
+}
+
Process *
each_process(Process * proc,
enum pcb_status (* cb)(Process * proc, void * data),
@@ -213,6 +234,23 @@ add_process(Process * proc)
*leaderp = proc;
}
+void
+change_process_leader(Process * proc, Process * leader)
+{
+ Process ** leaderp = &list_of_processes;
+ if (proc->leader == leader)
+ return;
+
+ assert(leader != NULL);
+ unlist_process(proc);
+ if (proc != leader)
+ leaderp = &leader->next;
+
+ proc->leader = leader;
+ proc->next = *leaderp;
+ *leaderp = proc;
+}
+
static enum pcb_status
clear_leader(Process * proc, void * data)
{
@@ -242,31 +280,14 @@ delete_events_for(Process * proc)
void
remove_process(Process *proc)
{
- Process *tmp, *tmp2;
-
debug(DEBUG_FUNCTION, "remove_proc(pid=%d)", proc->pid);
if (proc->leader == proc)
each_task(proc, &clear_leader, NULL);
- if (list_of_processes == proc) {
- tmp = list_of_processes;
- list_of_processes = list_of_processes->next;
- delete_events_for(tmp);
- free(tmp);
- return;
- }
- tmp = list_of_processes;
- while (tmp->next) {
- if (tmp->next == proc) {
- tmp2 = tmp->next;
- tmp->next = tmp->next->next;
- delete_events_for(tmp2);
- free(tmp2);
- return;
- }
- tmp = tmp->next;
- }
+ unlist_process(proc);
+ delete_events_for(proc);
+ free(proc);
}
void
@@ -283,7 +304,8 @@ destroy_event_handler(Process * proc)
Event_Handler * handler = proc->event_handler;
debug(DEBUG_FUNCTION, "destroy_event_handler(pid=%d, %p)", proc->pid, handler);
assert(handler != NULL);
- handler->destroy(handler);
+ if (handler->destroy != NULL)
+ handler->destroy(handler);
free(handler);
proc->event_handler = NULL;
}
diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
index 8a79583..0685342 100644
--- a/sysdeps/linux-gnu/events.c
+++ b/sysdeps/linux-gnu/events.c
@@ -240,13 +240,20 @@ next_event(void)
if (errno != 0)
perror("syscall_p");
}
- if (WIFSTOPPED(status) && ((status>>16 == PTRACE_EVENT_FORK) || (status>>16 == PTRACE_EVENT_VFORK) || (status>>16 == PTRACE_EVENT_CLONE))) {
- unsigned long data;
- ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
- event.type = EVENT_CLONE;
- event.e_un.newpid = data;
- debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d", pid, (int)data);
- return &event;
+ if (WIFSTOPPED(status)) {
+ int what = status >> 16;
+ if (what == PTRACE_EVENT_VFORK
+ || what == PTRACE_EVENT_FORK
+ || what == PTRACE_EVENT_CLONE) {
+ unsigned long data;
+ event.type = what == PTRACE_EVENT_VFORK
+ ? EVENT_VFORK : EVENT_CLONE;
+ ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
+ event.e_un.newpid = data;
+ debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d",
+ pid, (int)data);
+ return &event;
+ }
}
if (WIFSTOPPED(status) && (status>>16 == PTRACE_EVENT_EXEC)) {
event.type = EVENT_EXEC;
diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c
index e3b71e5..a99593c 100644
--- a/sysdeps/linux-gnu/proc.c
+++ b/sysdeps/linux-gnu/proc.c
@@ -148,7 +148,7 @@ process_status_cb(const char * line, const char * prefix, void * data)
switch (c) {
case 'Z': RETURN(ps_zombie);
case 't': RETURN(ps_tracing_stop);
- case 'T': {
+ case 'T':
/* This can be either "T (stopped)" or, for older
* kernels, "T (tracing stop)". */
if (!strcmp(status, "T (stopped)\n"))
@@ -161,7 +161,8 @@ process_status_cb(const char * line, const char * prefix, void * data)
RETURN(ps_stop); /* Some sort of stop
* anyway. */
}
- }
+ case 'D':
+ case 'S': RETURN(ps_sleeping);
}
RETURN(ps_other);
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index f8a1779..ba3806d 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -164,9 +164,10 @@ continue_process(pid_t pid)
struct pid_task {
pid_t pid; /* This may be 0 for tasks that exited
* mid-handling. */
- int sigstopped;
- int got_event;
- int delivered;
+ int sigstopped : 1;
+ int got_event : 1;
+ int delivered : 1;
+ int vforked : 1;
} * pids;
struct pid_set {
@@ -213,23 +214,6 @@ struct process_stopping_handler
struct pid_set pids;
};
-static enum pcb_status
-task_stopped(Process * task, void * data)
-{
- /* If the task is already stopped, don't worry about it.
- * Likewise if it managed to become a zombie or terminate in
- * the meantime. This can happen when the whole thread group
- * is terminating. */
- switch (process_status(task->pid)) {
- case ps_invalid:
- case ps_tracing_stop:
- case ps_zombie:
- return pcb_cont;
- default:
- return pcb_stop;
- }
-}
-
static struct pid_task *
get_task_info(struct pid_set * pids, pid_t pid)
{
@@ -261,6 +245,57 @@ add_task_info(struct pid_set * pids, pid_t pid)
}
static enum pcb_status
+task_stopped(Process * task, void * data)
+{
+ enum process_status st = process_status(task->pid);
+ if (data != NULL)
+ *(enum process_status *)data = st;
+
+ /* If the task is already stopped, don't worry about it.
+ * Likewise if it managed to become a zombie or terminate in
+ * the meantime. This can happen when the whole thread group
+ * is terminating. */
+ switch (st) {
+ case ps_invalid:
+ case ps_tracing_stop:
+ case ps_zombie:
+ return pcb_cont;
+ default:
+ return pcb_stop;
+ }
+}
+
+/* Task is blocked if it's stopped, or if it's a vfork parent. */
+static enum pcb_status
+task_blocked(Process * task, void * data)
+{
+ struct pid_set * pids = data;
+ struct pid_task * task_info = get_task_info(pids, task->pid);
+ if (task_info != NULL
+ && task_info->vforked)
+ return pcb_cont;
+
+ return task_stopped(task, NULL);
+}
+
+static Event * process_vfork_on_event(Event_Handler * super, Event * event);
+
+static enum pcb_status
+task_vforked(Process * task, void * data)
+{
+ if (task->event_handler != NULL
+ && task->event_handler->on_event == &process_vfork_on_event)
+ return pcb_stop;
+ return pcb_cont;
+}
+
+static int
+is_vfork_parent(Process * task)
+{
+ return each_task(task->leader, &task_vforked, NULL) != NULL;
+}
+
+static enum pcb_status
send_sigstop(Process * task, void * data)
{
Process * leader = task->leader;
@@ -283,9 +318,11 @@ send_sigstop(Process * task, void * data)
return pcb_cont;
/* Don't bother sending SIGSTOP if we are already stopped, or
- * if we sent the SIGSTOP already, which happens when we
- * inherit the handler from breakpoint re-enablement. */
- if (task_stopped(task, NULL) == pcb_cont)
+ * if we sent the SIGSTOP already, which happens when we are
+ * handling "onexit" and inherited the handler from breakpoint
+ * re-enablement. */
+ enum process_status st;
+ if (task_stopped(task, &st) == pcb_cont)
return pcb_cont;
if (task_info->sigstopped) {
if (!task_info->delivered)
@@ -293,6 +330,16 @@ send_sigstop(Process * task, void * data)
task_info->delivered = 0;
}
+ /* Also don't attempt to stop the process if it's a parent of
+ * vforked process. We set up event handler specially to hint
+ * us. In that case parent is in D state, which we use to
+ * weed out unnecessary looping. */
+ if (st == ps_sleeping
+ && is_vfork_parent (task)) {
+ task_info->vforked = 1;
+ return pcb_cont;
+ }
+
if (task_kill(task->pid, SIGSTOP) >= 0) {
debug(DEBUG_PROCESS, "send SIGSTOP to %d", task->pid);
task_info->sigstopped = 1;
@@ -536,7 +583,7 @@ process_stopping_on_event(Event_Handler * super, Event * event)
switch (state) {
case psh_stopping:
/* If everyone is stopped, singlestep. */
- if (each_task(leader, &task_stopped, NULL) == NULL) {
+ if (each_task(leader, &task_blocked, &self->pids) == NULL) {
debug(DEBUG_PROCESS, "all stopped, now SINGLESTEP %d",
teb->pid);
if (sbp->enabled)
@@ -742,6 +789,110 @@ ltrace_exiting_install_handler(Process * proc)
return 0;
}
+/*
+ * When the traced process vforks, it's suspended until the child
+ * process calls _exit or exec*. In the meantime, the two share the
+ * address space.
+ *
+ * The child process should only ever call _exit or exec*, but we
+ * can't count on that (it's not the role of ltrace to policy, but to
+ * observe). In any case, we will _at least_ have to deal with
+ * removal of vfork return breakpoint (which we have to smuggle back
+ * in, so that the parent can see it, too), and introduction of exec*
+ * return breakpoint. Since we already have both breakpoint actions
+ * to deal with, we might as well support it all.
+ *
+ * The gist is that we pretend that the child is in a thread group
+ * with its parent, and handle it as a multi-threaded case, with the
+ * exception that we know that the parent is blocked, and don't
+ * attempt to stop it. When the child execs, we undo the setup.
+ *
+ * XXX The parent process could be un-suspended before ltrace gets
+ * child exec/exit event. Make sure this is taken care of.
+ */
+
+struct process_vfork_handler
+{
+ Event_Handler super;
+ void * bp_addr;
+};
+
+static Event *
+process_vfork_on_event(Event_Handler * super, Event * event)
+{
+ struct process_vfork_handler * self = (void *)super;
+ Breakpoint * sbp;
+ assert(self != NULL);
+
+ switch (event->type) {
+ case EVENT_BREAKPOINT:
+ /* Remember the vfork return breakpoint. */
+ if (self->bp_addr == NULL)
+ self->bp_addr = event->e_un.brk_addr;
+ break;
+
+ case EVENT_EXIT:
+ case EVENT_EXIT_SIGNAL:
+ case EVENT_EXEC:
+ /* Smuggle back in the vfork return breakpoint, so
+ * that our parent can trip over it once again. */
+ if (self->bp_addr != NULL) {
+ sbp = dict_find_entry(event->proc->leader->breakpoints,
+ self->bp_addr);
+ if (sbp != NULL)
+ insert_breakpoint(event->proc->leader,
+ self->bp_addr, sbp->libsym,
+ 1);
+ }
+
+ continue_process(event->proc->parent->pid);
+
+ /* Remove the leader that we artificially set up
+ * earlier. */
+ change_process_leader(event->proc, event->proc);
+ destroy_event_handler(event->proc);
+
+ /* XXXXX this could happen in the middle of handling
+ * multi-threaded breakpoint. We must be careful to
+ * undo the effects that we introduced above (vforked
+ * = 1 et.al.). */
+
+ default:
+ ;
+ }
+
+ return event;
+}
+
+void
+continue_after_vfork(Process * proc)
+{
+ debug(DEBUG_PROCESS, "continue_after_vfork: pid=%d", proc->pid);
+ struct process_vfork_handler * handler = calloc(sizeof(*handler), 1);
+ if (handler == NULL) {
+ perror("malloc vfork handler");
+ /* Carry on not bothering to treat the process as
+ * necessary. */
+ continue_process(proc->parent->pid);
+ return;
+ }
+
+ /* We must set up custom event handler, so that we see
+ * exec/exit events for the task itself. */
+ handler->super.on_event = process_vfork_on_event;
+ install_event_handler(proc, &handler->super);
+
+ /* Make sure that the child is sole thread. */
+ assert(proc->leader == proc);
+ assert(proc->next == NULL || proc->next->leader != proc);
+
+ /* Make sure that the child's parent is properly set up. */
+ assert(proc->parent != NULL);
+ assert(proc->parent->leader != NULL);
+
+ change_process_leader(proc, proc->parent->leader);
+}
+
/* If ltrace gets SIGINT, the processes directly or indirectly run by
* ltrace get it too. We just have to wait long enough for the signal
* to be delivered and the process terminated, which we notice and
diff --git a/testsuite/ltrace.main/main-threaded.exp b/testsuite/ltrace.main/main-threaded.exp
index 0157797..5539805 100644
--- a/testsuite/ltrace.main/main-threaded.exp
+++ b/testsuite/ltrace.main/main-threaded.exp
@@ -19,7 +19,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != ""
}
# set options for ltrace.
-ltrace_options "-l" "$objdir/$subdir/libmain.so" "-f"
+ltrace_options "-l" "$lib_sl" "-f"
# Run PUT for ltarce.
set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
@@ -35,5 +35,5 @@ if [regexp {ELF from incompatible architecture} $exec_output] {
}
# Verify the output by checking numbers of print in main-threaded.ltrace.
-set pattern "print("
+set pattern "print"
ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 30
diff --git a/testsuite/ltrace.main/main-vfork.c b/testsuite/ltrace.main/main-vfork.c
new file mode 100644
index 0000000..a5f6c40
--- /dev/null
+++ b/testsuite/ltrace.main/main-vfork.c
@@ -0,0 +1,28 @@
+#include <unistd.h>
+
+extern void print (char *);
+
+#define PRINT_LOOP 10
+
+void
+th_main (char * arg)
+{
+ int i;
+ for (i=0; i<PRINT_LOOP; i++)
+ print (arg);
+}
+
+int main (int argc, char ** argv)
+{
+ if (argc != 1)
+ {
+ th_main ("aaa");
+ return 0;
+ }
+
+ if (!vfork ())
+ execlp (argv[0], argv[0], "", NULL);
+ th_main ("bbb");
+
+ return 0;
+}
diff --git a/testsuite/ltrace.main/main-vfork.exp b/testsuite/ltrace.main/main-vfork.exp
new file mode 100644
index 0000000..299c5e0
--- /dev/null
+++ b/testsuite/ltrace.main/main-vfork.exp
@@ -0,0 +1,39 @@
+# This file was written by Yao Qi <qiyao@cn.ibm.com>.
+
+set testfile "main-vfork"
+set srcfile ${testfile}.c
+set binfile ${testfile}
+set libfile "main-lib"
+set libsrc $srcdir/$subdir/$libfile.c
+set lib_sl $objdir/$subdir/lib$testfile.so
+
+
+if [get_compiler_info $binfile] {
+ return -1
+}
+
+verbose "compiling source file now....."
+if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != ""
+ || [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executable [list debug shlib=$lib_sl] ] != ""} {
+ send_user "Testcase compile failed, so all tests in this file will automatically fail.\n"
+}
+
+# set options for ltrace.
+ltrace_options "-l" "$lib_sl" "-f"
+
+# Run PUT for ltarce.
+set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
+
+# Check the output of this program.
+verbose "ltrace runtest output: $exec_output\n"
+if [regexp {ELF from incompatible architecture} $exec_output] {
+ fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!"
+ return
+} elseif [ regexp {Couldn't get .hash data} $exec_output ] {
+ fail "Couldn't get .hash data!"
+ return
+}
+
+# Verify the output by checking numbers of print in main-vfork.ltrace.
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "print" 20
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2
diff --git a/testsuite/ltrace.torture/vfork-thread.c b/testsuite/ltrace.torture/vfork-thread.c
new file mode 100644
index 0000000..f909bd3
--- /dev/null
+++ b/testsuite/ltrace.torture/vfork-thread.c
@@ -0,0 +1,50 @@
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+
+void *
+routine (void *data)
+{
+ int i;
+ for (i = 0; i < 6; ++i)
+ {
+ puts ("bleble");
+ sleep (1);
+ }
+}
+
+
+void *
+routine2 (void *data)
+{
+ pid_t child = vfork ();
+ if (child == 0)
+ {
+ int i, j;
+ puts ("vforked");
+ for (i = 0; i < 100000; ++i)
+ for (j = 0; j < 10000; ++j)
+ ;
+ puts ("vforked child exiting");
+ _exit (0);
+ }
+ puts ("parent continuing");
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t thread;
+ pthread_create (&thread, NULL, &routine, NULL);
+
+ sleep (1);
+
+ pthread_t thread2;
+ pthread_create (&thread2, NULL, &routine2, NULL);
+ pthread_join (thread2, NULL);
+ pthread_join (thread, NULL);
+ return 0;
+}
diff --git a/testsuite/ltrace.torture/vfork-thread.exp b/testsuite/ltrace.torture/vfork-thread.exp
new file mode 100644
index 0000000..bd01319
--- /dev/null
+++ b/testsuite/ltrace.torture/vfork-thread.exp
@@ -0,0 +1,32 @@
+# This file was written by Yao Qi <qiyao@cn.ibm.com>.
+
+set testfile "vfork-thread"
+set srcfile ${testfile}.c
+set binfile ${testfile}
+
+
+verbose "compiling source file now....."
+# Build the shared libraries this test case needs.
+if { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/${binfile}" executable [list debug ldflags=-pthread] ] != "" } {
+ send_user "Testcase compile failed, so all tests in this file will automatically fail\n."
+}
+
+ltrace_options "-f"
+
+# Run PUT for ltarce.
+set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile]
+
+# Check the output of this program.
+verbose "ltrace runtest output: $exec_output\n"
+if [regexp {ELF from incompatible architecture} $exec_output] {
+ fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!"
+ return
+} elseif [ regexp {Couldn't get .hash data} $exec_output ] {
+ fail "Couldn't get .hash data!"
+ return
+}
+
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "puts" 9
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "sleep" 7
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "_exit" 1