10241 lines
288 KiB
Diff
10241 lines
288 KiB
Diff
diff --git a/Makefile.am b/Makefile.am
|
|
index 6c299d8..a00c8bf 100644
|
|
--- a/Makefile.am
|
|
+++ b/Makefile.am
|
|
@@ -24,7 +24,10 @@ libltrace_la_SOURCES = \
|
|
output.c \
|
|
proc.c \
|
|
read_config_file.c \
|
|
- summary.c
|
|
+ summary.c \
|
|
+ library.c \
|
|
+ filter.c \
|
|
+ glob.c
|
|
|
|
libltrace_la_LIBADD = \
|
|
$(libelf_LIBS) \
|
|
@@ -56,7 +59,10 @@ noinst_HEADERS = \
|
|
ltrace.h \
|
|
options.h \
|
|
output.h \
|
|
- read_config_file.h
|
|
+ read_config_file.h \
|
|
+ library.h \
|
|
+ filter.h \
|
|
+ glob.h
|
|
|
|
dist_man1_MANS = \
|
|
ltrace.1
|
|
diff --git a/breakpoint.h b/breakpoint.h
|
|
index ce6f501..0398072 100644
|
|
--- a/breakpoint.h
|
|
+++ b/breakpoint.h
|
|
@@ -33,74 +33,106 @@
|
|
* enable. Realized tracepoints enable breakpoints, which are a
|
|
* low-level realization of high-level tracepoint.
|
|
*
|
|
- * Tracepoints are provided by the main binary as well as by any
|
|
- * opened libraries: every time an ELF file is mapped into the address
|
|
- * space, a new set of tracepoints is extracted, and filtered
|
|
- * according to user settings. Those tracepoints that are left are
|
|
- * then realized, and the tracing starts.
|
|
- *
|
|
- * A scheme like this would take care of gradually introducing
|
|
- * breakpoints when the library is mapped, and therefore ready, and
|
|
- * would avoid certain hacks. For example on PPC64, we don't actually
|
|
- * add breakpoints to PLT. Instead, we read the PLT (which contains
|
|
- * addresses, not code), to figure out where to put the breakpoints.
|
|
- * In prelinked code, that address is non-zero, and points to an
|
|
- * address that's not yet mapped. ptrace then fails when we try to
|
|
- * add the breakpoint.
|
|
- *
|
|
- * Ideally, return breakpoints would be just a special kind of
|
|
- * tracepoint that has attached some magic. Or a feature of a
|
|
- * tracepoint. Service breakpoints like the handling of dlopen would
|
|
- * be a low-level breakpoint, likely without tracepoint attached.
|
|
+ * Service breakpoints like the handling of dlopen would be a
|
|
+ * low-level breakpoint, likely without tracepoint attached.
|
|
*
|
|
* So that's for sometimes.
|
|
*/
|
|
|
|
-#include "arch.h"
|
|
+#include "sysdep.h"
|
|
+#include "library.h"
|
|
|
|
struct Process;
|
|
struct breakpoint;
|
|
|
|
struct bp_callbacks {
|
|
- void (*on_hit) (struct breakpoint *bp, struct Process *proc);
|
|
- void (*on_destroy) (struct breakpoint *bp);
|
|
+ void (*on_hit)(struct breakpoint *bp, struct Process *proc);
|
|
+ void (*on_continue)(struct breakpoint *bp, struct Process *proc);
|
|
+ void (*on_retract)(struct breakpoint *bp, struct Process *proc);
|
|
};
|
|
|
|
struct breakpoint {
|
|
struct bp_callbacks *cbs;
|
|
+ struct library_symbol *libsym;
|
|
void *addr;
|
|
unsigned char orig_value[BREAKPOINT_LENGTH];
|
|
int enabled;
|
|
- struct library_symbol *libsym;
|
|
-#ifdef __arm__
|
|
- int thumb_mode;
|
|
-#endif
|
|
+ struct arch_breakpoint_data arch;
|
|
};
|
|
|
|
/* Call on-hit handler of BP, if any is set. */
|
|
void breakpoint_on_hit(struct breakpoint *bp, struct Process *proc);
|
|
|
|
-/* Call on-destroy handler of BP, if any is set. */
|
|
-void breakpoint_on_destroy(struct breakpoint *bp);
|
|
+/* Call on-continue handler of BP. If none is set, call
|
|
+ * continue_after_breakpoint. */
|
|
+void breakpoint_on_continue(struct breakpoint *bp, struct Process *proc);
|
|
+
|
|
+/* Call on-retract handler of BP, if any is set. This should be
|
|
+ * called before the breakpoints are destroyed. The reason for a
|
|
+ * separate interface is that breakpoint_destroy has to be callable
|
|
+ * without PROC. ON_DISABLE might be useful as well, but that would
|
|
+ * be called every time we disable the breakpoint, which is too often
|
|
+ * (a breakpoint has to be disabled every time that we need to execute
|
|
+ * the instruction underneath it). */
|
|
+void breakpoint_on_retract(struct breakpoint *bp, struct Process *proc);
|
|
+
|
|
+/* Initialize a breakpoint structure. That doesn't actually realize
|
|
+ * the breakpoint. The breakpoint is initially assumed to be
|
|
+ * disabled. orig_value has to be set separately. CBS may be
|
|
+ * NULL. */
|
|
+int breakpoint_init(struct breakpoint *bp, struct Process *proc,
|
|
+ target_address_t addr, struct library_symbol *libsym);
|
|
+
|
|
+/* Make a clone of breakpoint BP into the area of memory pointed to by
|
|
+ * RETP. The original breakpoint was assigned to process OLD_PROC,
|
|
+ * the cloned breakpoint will be attached to process NEW_PROC.
|
|
+ * Returns 0 on success or a negative value on failure. */
|
|
+int breakpoint_clone(struct breakpoint *retp, struct Process *new_proc,
|
|
+ struct breakpoint *bp, struct Process *old_proc);
|
|
+
|
|
+/* Set callbacks. If CBS is non-NULL, then BP->cbs shall be NULL. */
|
|
+void breakpoint_set_callbacks(struct breakpoint *bp, struct bp_callbacks *cbs);
|
|
|
|
-/* This is actually three functions rolled in one:
|
|
- * - breakpoint_init
|
|
- * - proc_insert_breakpoint
|
|
- * - breakpoint_enable
|
|
- * XXX I think it should be broken up somehow. */
|
|
+/* Destroy a breakpoint structure. */
|
|
+void breakpoint_destroy(struct breakpoint *bp);
|
|
+
|
|
+/* Call enable_breakpoint the first time it's called. Returns 0 on
|
|
+ * success and a negative value on failure. */
|
|
+int breakpoint_turn_on(struct breakpoint *bp, struct Process *proc);
|
|
+
|
|
+/* Call disable_breakpoint when turned off the same number of times
|
|
+ * that it was turned on. Returns 0 on success and a negative value
|
|
+ * on failure. */
|
|
+int breakpoint_turn_off(struct breakpoint *bp, struct Process *proc);
|
|
+
|
|
+/* Utility function that does what typically needs to be done when a
|
|
+ * breakpoint is to be inserted. It checks whether there is another
|
|
+ * breakpoint in PROC->LEADER for given ADDR. If not, it allocates
|
|
+ * memory for a new breakpoint on the heap, initializes it, and calls
|
|
+ * PROC_ADD_BREAKPOINT to add the newly-created breakpoint. For newly
|
|
+ * added as well as preexisting breakpoints, it then calls
|
|
+ * BREAKPOINT_TURN_ON. If anything fails, it cleans up and returns
|
|
+ * NULL. Otherwise it returns the breakpoint for ADDR. */
|
|
struct breakpoint *insert_breakpoint(struct Process *proc, void *addr,
|
|
- struct library_symbol *libsym, int enable);
|
|
+ struct library_symbol *libsym);
|
|
+
|
|
+/* Name of a symbol associated with BP. May be NULL. */
|
|
+const char *breakpoint_name(const struct breakpoint *bp);
|
|
|
|
-/* */
|
|
+/* A library that this breakpoint comes from. May be NULL. */
|
|
+struct library *breakpoint_library(const struct breakpoint *bp);
|
|
+
|
|
+/* Again, this seems to be several interfaces rolled into one:
|
|
+ * - breakpoint_disable
|
|
+ * - proc_remove_breakpoint
|
|
+ * - breakpoint_destroy
|
|
+ * XXX */
|
|
void delete_breakpoint(struct Process *proc, void *addr);
|
|
|
|
/* XXX some of the following belongs to proc.h/proc.c. */
|
|
struct breakpoint *address2bpstruct(struct Process *proc, void *addr);
|
|
void enable_all_breakpoints(struct Process *proc);
|
|
void disable_all_breakpoints(struct Process *proc);
|
|
-int breakpoints_init(struct Process *proc, int enable);
|
|
-
|
|
-void reinitialize_breakpoints(struct Process *proc);
|
|
-
|
|
+int breakpoints_init(struct Process *proc);
|
|
|
|
#endif /* BREAKPOINT_H */
|
|
diff --git a/breakpoints.c b/breakpoints.c
|
|
index 5713fe4..9536266 100644
|
|
--- a/breakpoints.c
|
|
+++ b/breakpoints.c
|
|
@@ -3,6 +3,7 @@
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
+#include <errno.h>
|
|
|
|
#ifdef __powerpc__
|
|
#include <sys/ptrace.h>
|
|
@@ -10,21 +11,52 @@
|
|
|
|
#include "breakpoint.h"
|
|
#include "common.h"
|
|
+#include "proc.h"
|
|
+#include "library.h"
|
|
+
|
|
+#ifndef ARCH_HAVE_TRANSLATE_ADDRESS
|
|
+int
|
|
+arch_translate_address_dyn(struct Process *proc,
|
|
+ target_address_t addr, target_address_t *ret)
|
|
+{
|
|
+ *ret = addr;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct ltelf;
|
|
+int
|
|
+arch_translate_address(struct ltelf *lte,
|
|
+ target_address_t addr, target_address_t *ret)
|
|
+{
|
|
+ *ret = addr;
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
|
|
void
|
|
breakpoint_on_hit(struct breakpoint *bp, struct Process *proc)
|
|
{
|
|
assert(bp != NULL);
|
|
if (bp->cbs != NULL && bp->cbs->on_hit != NULL)
|
|
- (bp->cbs->on_hit) (bp, proc);
|
|
+ (bp->cbs->on_hit)(bp, proc);
|
|
+}
|
|
+
|
|
+void
|
|
+breakpoint_on_continue(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ assert(bp != NULL);
|
|
+ if (bp->cbs != NULL && bp->cbs->on_continue != NULL)
|
|
+ (bp->cbs->on_continue)(bp, proc);
|
|
+ else
|
|
+ continue_after_breakpoint(proc, bp);
|
|
}
|
|
|
|
void
|
|
-breakpoint_on_destroy(struct breakpoint *bp)
|
|
+breakpoint_on_retract(struct breakpoint *bp, struct Process *proc)
|
|
{
|
|
assert(bp != NULL);
|
|
- if (bp->cbs != NULL && bp->cbs->on_destroy != NULL)
|
|
- (bp->cbs->on_destroy) (bp);
|
|
+ if (bp->cbs != NULL && bp->cbs->on_retract != NULL)
|
|
+ (bp->cbs->on_retract)(bp, proc);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
@@ -39,52 +71,171 @@ address2bpstruct(Process *proc, void *addr)
|
|
return dict_find_entry(proc->breakpoints, addr);
|
|
}
|
|
|
|
-struct breakpoint *
|
|
-insert_breakpoint(Process *proc, void *addr,
|
|
- struct library_symbol *libsym, int enable)
|
|
+#ifndef ARCH_HAVE_BREAKPOINT_DATA
|
|
+int
|
|
+arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp)
|
|
{
|
|
- struct breakpoint *sbp;
|
|
+ return 0;
|
|
+}
|
|
|
|
- Process * leader = proc->leader;
|
|
+void
|
|
+arch_breakpoint_destroy(struct breakpoint *sbp)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void
|
|
+breakpoint_init_base(struct breakpoint *bp, struct Process *proc,
|
|
+ target_address_t addr, struct library_symbol *libsym)
|
|
+{
|
|
+ bp->cbs = NULL;
|
|
+ bp->addr = addr;
|
|
+ memset(bp->orig_value, 0, sizeof(bp->orig_value));
|
|
+ bp->enabled = 0;
|
|
+ bp->libsym = libsym;
|
|
+}
|
|
+
|
|
+/* On second thought, I don't think we need PROC. All the translation
|
|
+ * (arch_translate_address in particular) should be doable using
|
|
+ * static lookups of various sections in the ELF file. We shouldn't
|
|
+ * need process for anything. */
|
|
+int
|
|
+breakpoint_init(struct breakpoint *bp, struct Process *proc,
|
|
+ target_address_t addr, struct library_symbol *libsym)
|
|
+{
|
|
+ breakpoint_init_base(bp, proc, addr, libsym);
|
|
+ return arch_breakpoint_init(proc, bp);
|
|
+}
|
|
+
|
|
+void
|
|
+breakpoint_set_callbacks(struct breakpoint *bp, struct bp_callbacks *cbs)
|
|
+{
|
|
+ if (bp->cbs != NULL)
|
|
+ assert(bp->cbs == NULL);
|
|
+ bp->cbs = cbs;
|
|
+}
|
|
+
|
|
+void
|
|
+breakpoint_destroy(struct breakpoint *bp)
|
|
+{
|
|
+ if (bp == NULL)
|
|
+ return;
|
|
+ arch_breakpoint_destroy(bp);
|
|
+}
|
|
+
|
|
+struct find_symbol_data {
|
|
+ struct library_symbol *old_libsym;
|
|
+ struct library_symbol *found_libsym;
|
|
+};
|
|
+
|
|
+static enum callback_status
|
|
+find_sym_in_lib(struct Process *proc, struct library *lib, void *u)
|
|
+{
|
|
+ struct find_symbol_data *fs = u;
|
|
+ fs->found_libsym
|
|
+ = library_each_symbol(lib, NULL, library_symbol_equal_cb,
|
|
+ fs->old_libsym);
|
|
+ return fs->found_libsym != NULL ? CBS_STOP : CBS_CONT;
|
|
+}
|
|
+
|
|
+int
|
|
+breakpoint_clone(struct breakpoint *retp, struct Process *new_proc,
|
|
+ struct breakpoint *bp, struct Process *old_proc)
|
|
+{
|
|
+ /* Find library and symbol that this breakpoint was linked to. */
|
|
+ struct library_symbol *libsym = bp->libsym;
|
|
+ struct library *lib = NULL;
|
|
+ if (libsym != NULL) {
|
|
+ struct find_symbol_data f_data = {
|
|
+ .old_libsym = libsym,
|
|
+ };
|
|
+ lib = proc_each_library(old_proc, NULL,
|
|
+ find_sym_in_lib, &f_data);
|
|
+ assert(lib != NULL);
|
|
+ libsym = f_data.found_libsym;
|
|
+ }
|
|
+
|
|
+ /* LIB and LIBSYM now hold the new library and symbol that
|
|
+ * correspond to the original breakpoint. Now we can do the
|
|
+ * clone itself. */
|
|
+ breakpoint_init_base(retp, new_proc, bp->addr, libsym);
|
|
+ memcpy(retp->orig_value, bp->orig_value, sizeof(bp->orig_value));
|
|
+ retp->enabled = bp->enabled;
|
|
+ if (arch_breakpoint_clone(retp, bp) < 0)
|
|
+ return -1;
|
|
+ breakpoint_set_callbacks(retp, bp->cbs);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+breakpoint_turn_on(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ bp->enabled++;
|
|
+ if (bp->enabled == 1) {
|
|
+ assert(proc->pid != 0);
|
|
+ enable_breakpoint(proc, bp);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+breakpoint_turn_off(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ bp->enabled--;
|
|
+ if (bp->enabled == 0)
|
|
+ disable_breakpoint(proc, bp);
|
|
+ assert(bp->enabled >= 0);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct breakpoint *
|
|
+insert_breakpoint(struct Process *proc, void *addr,
|
|
+ struct library_symbol *libsym)
|
|
+{
|
|
+ Process *leader = proc->leader;
|
|
|
|
/* Only the group leader should be getting the breakpoints and
|
|
* thus have ->breakpoint initialized. */
|
|
assert(leader != NULL);
|
|
assert(leader->breakpoints != NULL);
|
|
|
|
-#ifdef __arm__
|
|
- int thumb_mode = (int)addr & 1;
|
|
- if (thumb_mode)
|
|
- addr = (void *)((int)addr & ~1);
|
|
-#endif
|
|
-
|
|
- debug(DEBUG_FUNCTION, "insert_breakpoint(pid=%d, addr=%p, symbol=%s)", proc->pid, addr, libsym ? libsym->name : "NULL");
|
|
- debug(1, "symbol=%s, addr=%p", libsym?libsym->name:"(nil)", addr);
|
|
-
|
|
- if (!addr)
|
|
- return NULL;
|
|
+ debug(DEBUG_FUNCTION, "insert_breakpoint(pid=%d, addr=%p, symbol=%s)",
|
|
+ proc->pid, addr, libsym ? libsym->name : "NULL");
|
|
|
|
- if (libsym)
|
|
- libsym->needs_init = 0;
|
|
+ assert(addr != 0);
|
|
|
|
- sbp = dict_find_entry(leader->breakpoints, addr);
|
|
+ /* XXX what we need to do instead is have a list of
|
|
+ * breakpoints that are enabled at this address. The
|
|
+ * following works if every breakpoint is the same and there's
|
|
+ * no extra data, but that doesn't hold anymore. For now it
|
|
+ * will suffice, about the only realistic case where we need
|
|
+ * to have more than one breakpoint per address is return from
|
|
+ * a recursive library call. */
|
|
+ struct breakpoint *sbp = dict_find_entry(leader->breakpoints, addr);
|
|
if (sbp == NULL) {
|
|
- sbp = calloc(1, sizeof(*sbp));
|
|
- if (sbp == NULL) {
|
|
- return NULL; /* TODO FIXME XXX: error_mem */
|
|
+ sbp = malloc(sizeof(*sbp));
|
|
+ if (sbp == NULL
|
|
+ || breakpoint_init(sbp, proc, addr, libsym) < 0) {
|
|
+ free(sbp);
|
|
+ return NULL;
|
|
+ }
|
|
+ if (proc_add_breakpoint(leader, sbp) < 0) {
|
|
+ fail:
|
|
+ breakpoint_destroy(sbp);
|
|
+ free(sbp);
|
|
+ return NULL;
|
|
}
|
|
- dict_enter(leader->breakpoints, addr, sbp);
|
|
- sbp->addr = addr;
|
|
- sbp->libsym = libsym;
|
|
}
|
|
-#ifdef __arm__
|
|
- sbp->thumb_mode = thumb_mode | proc->thumb_mode;
|
|
- proc->thumb_mode = 0;
|
|
-#endif
|
|
- sbp->enabled++;
|
|
- if (sbp->enabled == 1 && enable) {
|
|
- assert(proc->pid != 0);
|
|
- enable_breakpoint(proc, sbp);
|
|
+
|
|
+ if (breakpoint_turn_on(sbp, proc) < 0) {
|
|
+ proc_remove_breakpoint(leader, sbp);
|
|
+ goto fail;
|
|
}
|
|
|
|
return sbp;
|
|
@@ -93,23 +244,41 @@ insert_breakpoint(Process *proc, void *addr,
|
|
void
|
|
delete_breakpoint(Process *proc, void *addr)
|
|
{
|
|
- struct breakpoint *sbp;
|
|
-
|
|
debug(DEBUG_FUNCTION, "delete_breakpoint(pid=%d, addr=%p)", proc->pid, addr);
|
|
|
|
Process * leader = proc->leader;
|
|
assert(leader != NULL);
|
|
|
|
- sbp = dict_find_entry(leader->breakpoints, addr);
|
|
- assert(sbp); /* FIXME: remove after debugging has been done. */
|
|
+ struct breakpoint *sbp = dict_find_entry(leader->breakpoints, addr);
|
|
+ assert(sbp != NULL);
|
|
/* This should only happen on out-of-memory conditions. */
|
|
if (sbp == NULL)
|
|
return;
|
|
|
|
- sbp->enabled--;
|
|
- if (sbp->enabled == 0)
|
|
- disable_breakpoint(proc, sbp);
|
|
- assert(sbp->enabled >= 0);
|
|
+ if (breakpoint_turn_off(sbp, proc) < 0) {
|
|
+ fprintf(stderr, "Couldn't turn off the breakpoint %s@%p\n",
|
|
+ breakpoint_name(sbp), sbp->addr);
|
|
+ return;
|
|
+ }
|
|
+ if (sbp->enabled == 0) {
|
|
+ proc_remove_breakpoint(leader, sbp);
|
|
+ breakpoint_destroy(sbp);
|
|
+ free(sbp);
|
|
+ }
|
|
+}
|
|
+
|
|
+const char *
|
|
+breakpoint_name(const struct breakpoint *bp)
|
|
+{
|
|
+ assert(bp != NULL);
|
|
+ return bp->libsym != NULL ? bp->libsym->name : NULL;
|
|
+}
|
|
+
|
|
+struct library *
|
|
+breakpoint_library(const struct breakpoint *bp)
|
|
+{
|
|
+ assert(bp != NULL);
|
|
+ return bp->libsym != NULL ? bp->libsym->lib : NULL;
|
|
}
|
|
|
|
static void
|
|
@@ -176,108 +345,97 @@ disable_all_breakpoints(Process *proc) {
|
|
dict_apply_to_all(proc->breakpoints, disable_bp_cb, proc);
|
|
}
|
|
|
|
-static void
|
|
-free_bp_cb(void *addr, void *sbp, void *data) {
|
|
- debug(DEBUG_FUNCTION, "free_bp_cb(sbp=%p)", sbp);
|
|
- assert(sbp);
|
|
- free(sbp);
|
|
-}
|
|
+/* XXX This is not currently properly supported. On clone, this is
|
|
+ * just sliced. Hopefully at the point that clone is done, this
|
|
+ * breakpoint is not necessary anymore. If this use case ends up
|
|
+ * being important, we need to add a clone and destroy callbacks to
|
|
+ * breakpoints, and we should also probably drop arch_breakpoint_data
|
|
+ * so that we don't end up with two different customization mechanisms
|
|
+ * for one structure. */
|
|
+struct entry_breakpoint {
|
|
+ struct breakpoint super;
|
|
+ target_address_t dyn_addr;
|
|
+};
|
|
|
|
static void
|
|
-entry_callback_hit(struct breakpoint *bp, struct Process *proc)
|
|
+entry_breakpoint_on_hit(struct breakpoint *a, struct Process *proc)
|
|
{
|
|
+ struct entry_breakpoint *bp = (void *)a;
|
|
if (proc == NULL || proc->leader == NULL)
|
|
return;
|
|
- delete_breakpoint(proc, bp->addr); // xxx
|
|
- reinitialize_breakpoints(proc->leader);
|
|
+ target_address_t dyn_addr = bp->dyn_addr;
|
|
+ delete_breakpoint(proc, bp->super.addr);
|
|
+ linkmap_init(proc, dyn_addr);
|
|
+ arch_dynlink_done(proc);
|
|
}
|
|
|
|
int
|
|
-breakpoints_init(Process *proc, int enable)
|
|
+entry_breakpoint_init(struct Process *proc,
|
|
+ struct entry_breakpoint *bp, target_address_t addr,
|
|
+ struct library *lib)
|
|
{
|
|
- debug(DEBUG_FUNCTION, "breakpoints_init(pid=%d)", proc->pid);
|
|
- if (proc->breakpoints) { /* let's remove that struct */
|
|
- dict_apply_to_all(proc->breakpoints, free_bp_cb, NULL);
|
|
- dict_clear(proc->breakpoints);
|
|
- proc->breakpoints = NULL;
|
|
- }
|
|
+ int err;
|
|
+ if ((err = breakpoint_init(&bp->super, proc, addr, NULL)) < 0)
|
|
+ return err;
|
|
|
|
- /* Only the thread group leader should hold the breakpoints.
|
|
- * (N.B. PID may be set to 0 temporarily when called by
|
|
- * handle_exec). */
|
|
- assert(proc->leader == proc);
|
|
+ static struct bp_callbacks entry_callbacks = {
|
|
+ .on_hit = entry_breakpoint_on_hit,
|
|
+ };
|
|
+ bp->super.cbs = &entry_callbacks;
|
|
+ bp->dyn_addr = lib->dyn_addr;
|
|
+ return 0;
|
|
+}
|
|
|
|
- proc->breakpoints = dict_init(dict_key2hash_int,
|
|
- dict_key_cmp_int);
|
|
+int
|
|
+breakpoints_init(Process *proc)
|
|
+{
|
|
+ debug(DEBUG_FUNCTION, "breakpoints_init(pid=%d)", proc->pid);
|
|
|
|
- destroy_library_symbol_chain(proc->list_of_symbols);
|
|
- proc->list_of_symbols = NULL;
|
|
+ /* XXX breakpoint dictionary should be initialized
|
|
+ * outside. Here we just put in breakpoints. */
|
|
+ assert(proc->breakpoints != NULL);
|
|
|
|
- GElf_Addr entry;
|
|
- if (options.libcalls && proc->filename) {
|
|
- proc->list_of_symbols = read_elf(proc, &entry);
|
|
- if (proc->list_of_symbols == NULL) {
|
|
- fail:
|
|
- /* XXX leak breakpoints */
|
|
- return -1;
|
|
- }
|
|
+ /* Only the thread group leader should hold the breakpoints. */
|
|
+ assert(proc->leader == proc);
|
|
|
|
- if (opt_e) {
|
|
- struct library_symbol **tmp1 = &proc->list_of_symbols;
|
|
- while (*tmp1) {
|
|
- struct opt_e_t *tmp2 = opt_e;
|
|
- int keep = !opt_e_enable;
|
|
-
|
|
- while (tmp2) {
|
|
- if (!strcmp((*tmp1)->name,
|
|
- tmp2->name)) {
|
|
- keep = opt_e_enable;
|
|
- }
|
|
- tmp2 = tmp2->next;
|
|
- }
|
|
- if (!keep) {
|
|
- *tmp1 = (*tmp1)->next;
|
|
- } else {
|
|
- tmp1 = &((*tmp1)->next);
|
|
- }
|
|
- }
|
|
+ /* N.B. the following used to be conditional on this, and
|
|
+ * maybe it still needs to be. */
|
|
+ assert(proc->filename != NULL);
|
|
+
|
|
+ struct library *lib = ltelf_read_main_binary(proc, proc->filename);
|
|
+ struct entry_breakpoint *entry_bp = NULL;
|
|
+ int bp_state = 0;
|
|
+ int result = -1;
|
|
+ switch (lib != NULL) {
|
|
+ fail:
|
|
+ switch (bp_state) {
|
|
+ case 2:
|
|
+ proc_remove_library(proc, lib);
|
|
+ proc_remove_breakpoint(proc, &entry_bp->super);
|
|
+ case 1:
|
|
+ breakpoint_destroy(&entry_bp->super);
|
|
}
|
|
+ library_destroy(lib);
|
|
+ free(entry_bp);
|
|
+ case 0:
|
|
+ return result;
|
|
}
|
|
|
|
- struct breakpoint *entry_bp
|
|
- = insert_breakpoint(proc, (void *)(uintptr_t)entry, NULL, 1);
|
|
- if (entry_bp == NULL) {
|
|
- fprintf(stderr, "fail!\n");
|
|
+ entry_bp = malloc(sizeof(*entry_bp));
|
|
+ if (entry_bp == NULL
|
|
+ || (result = entry_breakpoint_init(proc, entry_bp,
|
|
+ lib->entry, lib)) < 0)
|
|
goto fail;
|
|
- }
|
|
+ ++bp_state;
|
|
|
|
- static struct bp_callbacks entry_callbacks = {
|
|
- .on_hit = entry_callback_hit,
|
|
- };
|
|
- entry_bp->cbs = &entry_callbacks;
|
|
+ if ((result = proc_add_breakpoint(proc, &entry_bp->super)) < 0)
|
|
+ goto fail;
|
|
+ ++bp_state;
|
|
+
|
|
+ if ((result = breakpoint_turn_on(&entry_bp->super, proc)) < 0)
|
|
+ goto fail;
|
|
+ proc_add_library(proc, lib);
|
|
|
|
proc->callstack_depth = 0;
|
|
return 0;
|
|
}
|
|
-
|
|
-void
|
|
-reinitialize_breakpoints(Process *proc) {
|
|
- struct library_symbol *sym;
|
|
-
|
|
- debug(DEBUG_FUNCTION, "reinitialize_breakpoints(pid=%d)", proc->pid);
|
|
-
|
|
- sym = proc->list_of_symbols;
|
|
-
|
|
- while (sym) {
|
|
- if (sym->needs_init) {
|
|
- insert_breakpoint(proc, sym2addr(proc, sym), sym, 1);
|
|
- if (sym->needs_init && !sym->is_weak) {
|
|
- fprintf(stderr,
|
|
- "could not re-initialize breakpoint for \"%s\" in file \"%s\"\n",
|
|
- sym->name, proc->filename);
|
|
- exit(1);
|
|
- }
|
|
- }
|
|
- sym = sym->next;
|
|
- }
|
|
-}
|
|
diff --git a/common.h b/common.h
|
|
index ed618b5..04e095c 100644
|
|
--- a/common.h
|
|
+++ b/common.h
|
|
@@ -2,7 +2,4 @@
|
|
#include <config.h>
|
|
-#if defined(HAVE_LIBUNWIND)
|
|
-#include <libunwind.h>
|
|
-#endif /* defined(HAVE_LIBUNWIND) */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
@@ -17,6 +14,7 @@
|
|
#include "debug.h"
|
|
#include "ltrace-elf.h"
|
|
#include "read_config_file.h"
|
|
+#include "proc.h"
|
|
|
|
#if defined HAVE_LIBIBERTY || defined HAVE_LIBSUPC__
|
|
# define USE_DEMANGLE
|
|
@@ -116,116 +114,9 @@ struct Function {
|
|
Function * next;
|
|
};
|
|
|
|
-enum toplt {
|
|
- LS_TOPLT_NONE = 0, /* PLT not used for this symbol. */
|
|
- LS_TOPLT_EXEC, /* PLT for this symbol is executable. */
|
|
- LS_TOPLT_POINT /* PLT for this symbol is a non-executable. */
|
|
-};
|
|
-
|
|
extern Function * list_of_functions;
|
|
extern char *PLTs_initialized_by_here;
|
|
|
|
-struct library_symbol {
|
|
- char * name;
|
|
- void * enter_addr;
|
|
- char needs_init;
|
|
- enum toplt plt_type;
|
|
- char is_weak;
|
|
- struct library_symbol * next;
|
|
-};
|
|
-
|
|
-struct callstack_element {
|
|
- union {
|
|
- int syscall;
|
|
- struct library_symbol * libfunc;
|
|
- } c_un;
|
|
- int is_syscall;
|
|
- void * return_addr;
|
|
- struct timeval time_spent;
|
|
- void * arch_ptr;
|
|
-};
|
|
-
|
|
-#define MAX_CALLDEPTH 64
|
|
-
|
|
-typedef enum Process_State Process_State;
|
|
-enum Process_State {
|
|
- STATE_ATTACHED = 0,
|
|
- STATE_BEING_CREATED,
|
|
- STATE_IGNORED /* ignore this process (it's a fork and no -f was used) */
|
|
-};
|
|
-
|
|
-typedef struct Event_Handler Event_Handler;
|
|
-struct Event_Handler {
|
|
- /* Event handler that overrides the default one. Should
|
|
- * return NULL if the event was handled, otherwise the
|
|
- * returned event is passed to the default handler. */
|
|
- Event * (* on_event)(Event_Handler * self, Event * event);
|
|
-
|
|
- /* Called when the event handler removal is requested. */
|
|
- void (* destroy)(Event_Handler * self);
|
|
-};
|
|
-
|
|
-/* XXX We would rather have this all organized a little differently,
|
|
- * have Process for the whole group and Task for what's there for
|
|
- * per-thread stuff. But for now this is the less invasive way of
|
|
- * structuring it. */
|
|
-struct Process {
|
|
- Process_State state;
|
|
- Process * parent; /* needed by STATE_BEING_CREATED */
|
|
- char * filename;
|
|
- pid_t pid;
|
|
-
|
|
- /* Dictionary of breakpoints (which is a mapping
|
|
- * address->breakpoint). This is NULL for non-leader
|
|
- * processes. */
|
|
- Dict * breakpoints;
|
|
-
|
|
- int mask_32bit; /* 1 if 64-bit ltrace is tracing 32-bit process */
|
|
- unsigned int personality;
|
|
- int tracesysgood; /* signal indicating a PTRACE_SYSCALL trap */
|
|
-
|
|
- int callstack_depth;
|
|
- struct callstack_element callstack[MAX_CALLDEPTH];
|
|
- struct library_symbol * list_of_symbols;
|
|
-
|
|
- int libdl_hooked;
|
|
- /* Arch-dependent: */
|
|
- void * debug; /* arch-dep process debug struct */
|
|
- long debug_state; /* arch-dep debug state */
|
|
- void * instruction_pointer;
|
|
- void * stack_pointer; /* To get return addr, args... */
|
|
- void * return_addr;
|
|
- void * arch_ptr;
|
|
- short e_machine;
|
|
- short need_to_reinitialize_breakpoints;
|
|
-#ifdef __arm__
|
|
- int thumb_mode; /* ARM execution mode: 0: ARM, 1: Thumb */
|
|
-#endif
|
|
-
|
|
-#if defined(HAVE_LIBUNWIND)
|
|
- /* libunwind address space */
|
|
- unw_addr_space_t unwind_as;
|
|
- void *unwind_priv;
|
|
-#endif /* defined(HAVE_LIBUNWIND) */
|
|
-
|
|
- /* Set in leader. */
|
|
- Event_Handler * event_handler;
|
|
-
|
|
-
|
|
- /**
|
|
- * Process chaining.
|
|
- **/
|
|
- Process * next;
|
|
-
|
|
- /* LEADER points to the leader thread of the POSIX.1 process.
|
|
- If X->LEADER == X, then X is the leader thread and the
|
|
- Process structures chained by NEXT represent other threads,
|
|
- up until, but not including, the next leader thread.
|
|
- LEADER may be NULL after the leader has already exited. In
|
|
- that case this process is waiting to be collected. */
|
|
- Process * leader;
|
|
-};
|
|
-
|
|
struct opt_c_struct {
|
|
int count;
|
|
struct timeval tv;
|
|
@@ -248,23 +139,6 @@ enum process_status {
|
|
ps_other, /* Necessary other states can be added as needed. */
|
|
};
|
|
|
|
-enum pcb_status {
|
|
- pcb_stop, /* The iteration should stop. */
|
|
- pcb_cont, /* The iteration should continue. */
|
|
-};
|
|
-
|
|
-/* Process list */
|
|
-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);
|
|
-extern Process *each_task(Process * start,
|
|
- enum pcb_status (* cb)(Process * proc, void * data),
|
|
- void * data);
|
|
-
|
|
/* Events */
|
|
enum ecb_status {
|
|
ecb_cont, /* The iteration should continue. */
|
|
@@ -279,31 +153,15 @@ extern Event * each_qd_event(enum ecb_status (* cb)(Event * event, void * data),
|
|
extern void enque_event(Event * event);
|
|
extern void handle_event(Event * event);
|
|
|
|
-extern void install_event_handler(Process * proc, Event_Handler * handler);
|
|
-extern void destroy_event_handler(Process * proc);
|
|
-
|
|
extern pid_t execute_program(const char * command, char ** argv);
|
|
extern int display_arg(enum tof type, Process * proc, int arg_num, arg_type_info * info);
|
|
extern void disable_all_breakpoints(Process * proc);
|
|
|
|
-extern Process * open_program(char * filename, pid_t pid, int init_breakpoints);
|
|
-extern void open_pid(pid_t pid);
|
|
extern void show_summary(void);
|
|
extern arg_type_info * lookup_prototype(enum arg_type at);
|
|
|
|
-extern int do_init_elf(struct ltelf *lte, const char *filename);
|
|
-extern void do_close_elf(struct ltelf *lte);
|
|
-extern int in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym);
|
|
-extern struct library_symbol *library_symbols;
|
|
-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);
|
|
-
|
|
struct breakpoint;
|
|
+struct library_symbol;
|
|
|
|
/* Arch-dependent stuff: */
|
|
extern char * pid2name(pid_t pid);
|
|
@@ -311,8 +169,8 @@ extern pid_t process_leader(pid_t pid);
|
|
extern int process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n);
|
|
extern int process_stopped(pid_t pid);
|
|
extern enum process_status process_status(pid_t pid);
|
|
-extern void trace_set_options(Process * proc, pid_t pid);
|
|
-extern void wait_for_proc(pid_t pid);
|
|
+extern void trace_set_options(struct Process *proc);
|
|
+extern int wait_for_proc(pid_t pid);
|
|
extern void trace_me(void);
|
|
extern int trace_pid(pid_t pid);
|
|
extern void untrace_pid(pid_t pid);
|
|
@@ -322,13 +180,13 @@ extern void set_instruction_pointer(Process * proc, void * addr);
|
|
extern void * get_stack_pointer(Process * proc);
|
|
extern void * get_return_addr(Process * proc, void * stack_pointer);
|
|
extern void set_return_addr(Process * proc, void * addr);
|
|
-extern void enable_breakpoint(Process * proc, struct breakpoint *sbp);
|
|
-extern void disable_breakpoint(Process * proc, struct breakpoint *sbp);
|
|
+extern void enable_breakpoint(struct Process *proc, struct breakpoint *sbp);
|
|
+extern void disable_breakpoint(struct Process *proc, struct breakpoint *sbp);
|
|
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_syscall(Process *proc, int sysnum, int ret_p);
|
|
-extern void continue_after_breakpoint(Process * proc, struct breakpoint *sbp);
|
|
+extern void continue_after_breakpoint(struct Process *proc, struct breakpoint *sbp);
|
|
extern void continue_after_vfork(Process * proc);
|
|
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);
|
|
@@ -337,7 +195,7 @@ extern int umovelong (Process * proc, void * addr, long * result, arg_type_info
|
|
extern size_t umovebytes (Process *proc, void * addr, void * laddr, size_t count);
|
|
extern int ffcheck(void * maddr);
|
|
extern void * sym2addr(Process *, struct library_symbol *);
|
|
-extern int linkmap_init(Process *, struct ltelf *);
|
|
+extern int linkmap_init(struct Process *proc, void *dyn_addr);
|
|
extern void arch_check_dbg(Process *proc);
|
|
extern int task_kill (pid_t pid, int sig);
|
|
|
|
@@ -357,9 +215,51 @@ void trace_fail_warning(pid_t pid);
|
|
* If the call to OS_LTRACE_EXITING_SIGHANDLER didn't handle the
|
|
* request, OS_LTRACE_EXITING is called when the next event is
|
|
* generated. Therefore it's called in "safe" context, without
|
|
- * re-entrancy concerns, but it's only called after an even is
|
|
+ * re-entrancy concerns, but it's only called after an event is
|
|
* generated. */
|
|
int os_ltrace_exiting_sighandler(void);
|
|
void os_ltrace_exiting(void);
|
|
|
|
-extern struct ltelf main_lte;
|
|
+int arch_elf_init(struct ltelf *lte, struct library *lib);
|
|
+void arch_elf_destroy(struct ltelf *lte);
|
|
+
|
|
+enum plt_status {
|
|
+ plt_fail,
|
|
+ plt_ok,
|
|
+ plt_default,
|
|
+};
|
|
+
|
|
+enum plt_status arch_elf_add_plt_entry(struct Process *p, struct ltelf *l,
|
|
+ const char *n, GElf_Rela *r, size_t i,
|
|
+ struct library_symbol **ret);
|
|
+
|
|
+int arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp);
|
|
+void arch_breakpoint_destroy(struct breakpoint *sbp);
|
|
+int arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp);
|
|
+
|
|
+void arch_library_init(struct library *lib);
|
|
+void arch_library_destroy(struct library *lib);
|
|
+void arch_library_clone(struct library *retp, struct library *lib);
|
|
+
|
|
+int arch_library_symbol_init(struct library_symbol *libsym);
|
|
+void arch_library_symbol_destroy(struct library_symbol *libsym);
|
|
+int arch_library_symbol_clone(struct library_symbol *retp,
|
|
+ struct library_symbol *libsym);
|
|
+
|
|
+int arch_process_init(struct Process *proc);
|
|
+void arch_process_destroy(struct Process *proc);
|
|
+int arch_process_clone(struct Process *retp, struct Process *proc);
|
|
+int arch_process_exec(struct Process *proc);
|
|
+
|
|
+typedef void *target_address_t;
|
|
+/* This should extract entry point address and interpreter (dynamic
|
|
+ * linker) bias if possible. Returns 0 if there were no errors, -1
|
|
+ * otherwise. Sets *ENTRYP and *INTERP_BIASP to non-zero values if
|
|
+ * the corresponding value is known. Unknown values are set to 0. */
|
|
+int process_get_entry(struct Process *proc,
|
|
+ target_address_t *entryp,
|
|
+ target_address_t *interp_biasp);
|
|
+
|
|
+/* This is called after the dynamic linker is done with the
|
|
+ * process startup. */
|
|
+void arch_dynlink_done(struct Process *proc);
|
|
diff --git a/debug.c b/debug.c
|
|
index 1be873b..5fb9feb 100644
|
|
--- a/debug.c
|
|
+++ b/debug.c
|
|
@@ -16,6 +16,7 @@ debug_(int level, const char *file, int line, const char *fmt, ...) {
|
|
va_end(args);
|
|
|
|
output_line(NULL, "DEBUG: %s:%d: %s", file, line, buf);
|
|
+ fflush(options.output);
|
|
}
|
|
|
|
/*
|
|
diff --git a/defs.h b/defs.h
|
|
index dea000b..1eadb47 100644
|
|
--- a/defs.h
|
|
+++ b/defs.h
|
|
@@ -14,5 +14,3 @@
|
|
#ifndef DEFAULT_ARRAYLEN
|
|
#define DEFAULT_ARRAYLEN 4 /* default maximum # array elements */
|
|
#endif /* (-A switch) */
|
|
-
|
|
-#define MAX_LIBRARIES 200
|
|
diff --git a/dict.c b/dict.c
|
|
index ba318cd..b32ef8e 100644
|
|
--- a/dict.c
|
|
+++ b/dict.c
|
|
@@ -24,13 +24,14 @@ struct dict_entry {
|
|
|
|
struct dict {
|
|
struct dict_entry *buckets[DICTTABLESIZE];
|
|
- unsigned int (*key2hash) (void *);
|
|
- int (*key_cmp) (void *, void *);
|
|
+ unsigned int (*key2hash) (const void *);
|
|
+ int (*key_cmp) (const void *, const void *);
|
|
};
|
|
|
|
Dict *
|
|
-dict_init(unsigned int (*key2hash) (void *),
|
|
- int (*key_cmp) (void *, void *)) {
|
|
+dict_init(unsigned int (*key2hash) (const void *),
|
|
+ int (*key_cmp) (const void *, const void *))
|
|
+{
|
|
Dict *d;
|
|
int i;
|
|
|
|
@@ -103,7 +104,31 @@ dict_enter(Dict *d, void *key, void *value) {
|
|
}
|
|
|
|
void *
|
|
-dict_find_entry(Dict *d, void *key) {
|
|
+dict_remove(Dict *d, void *key)
|
|
+{
|
|
+ assert(d != NULL);
|
|
+ debug(DEBUG_FUNCTION, "dict_remove(%p)", key);
|
|
+
|
|
+ unsigned int hash = d->key2hash(key);
|
|
+ unsigned int bucketpos = hash % DICTTABLESIZE;
|
|
+
|
|
+ struct dict_entry **entryp;
|
|
+ for (entryp = &d->buckets[bucketpos]; (*entryp) != NULL;
|
|
+ entryp = &(*entryp)->next) {
|
|
+ struct dict_entry *entry = *entryp;
|
|
+ if (hash != entry->hash)
|
|
+ continue;
|
|
+ if (d->key_cmp(key, entry->key) == 0) {
|
|
+ *entryp = entry->next;
|
|
+ return entry->value;
|
|
+ }
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+void *
|
|
+dict_find_entry(Dict *d, const void *key)
|
|
+{
|
|
unsigned int hash;
|
|
unsigned int bucketpos;
|
|
struct dict_entry *entry;
|
|
@@ -147,7 +172,8 @@ dict_apply_to_all(Dict *d,
|
|
/*****************************************************************************/
|
|
|
|
unsigned int
|
|
-dict_key2hash_string(void *key) {
|
|
+dict_key2hash_string(const void *key)
|
|
+{
|
|
const char *s = (const char *)key;
|
|
unsigned int total = 0, shift = 0;
|
|
|
|
@@ -163,19 +189,22 @@ dict_key2hash_string(void *key) {
|
|
}
|
|
|
|
int
|
|
-dict_key_cmp_string(void *key1, void *key2) {
|
|
+dict_key_cmp_string(const void *key1, const void *key2)
|
|
+{
|
|
assert(key1);
|
|
assert(key2);
|
|
return strcmp((const char *)key1, (const char *)key2);
|
|
}
|
|
|
|
unsigned int
|
|
-dict_key2hash_int(void *key) {
|
|
+dict_key2hash_int(const void *key)
|
|
+{
|
|
return (unsigned long)key;
|
|
}
|
|
|
|
int
|
|
-dict_key_cmp_int(void *key1, void *key2) {
|
|
+dict_key_cmp_int(const void *key1, const void *key2)
|
|
+{
|
|
return key1 - key2;
|
|
}
|
|
|
|
diff --git a/dict.h b/dict.h
|
|
index 27dc7bf..f41011a 100644
|
|
--- a/dict.h
|
|
+++ b/dict.h
|
|
@@ -4,19 +4,22 @@
|
|
|
|
typedef struct dict Dict;
|
|
|
|
-extern Dict *dict_init(unsigned int (*key2hash) (void *),
|
|
- int (*key_cmp) (void *, void *));
|
|
+extern Dict *dict_init(unsigned int (*key2hash) (const void *),
|
|
+ int (*key_cmp) (const void *, const void *));
|
|
extern void dict_clear(Dict *d);
|
|
extern int dict_enter(Dict *d, void *key, void *value);
|
|
-extern void *dict_find_entry(Dict *d, void *key);
|
|
+extern void *dict_remove(Dict *d, void *key);
|
|
+extern void *dict_find_entry(Dict *d, const void *key);
|
|
extern void dict_apply_to_all(Dict *d,
|
|
void (*func) (void *key, void *value, void *data),
|
|
void *data);
|
|
|
|
-extern unsigned int dict_key2hash_string(void *key);
|
|
-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 unsigned int dict_key2hash_string(const void *key);
|
|
+extern int dict_key_cmp_string(const void *key1, const void *key2);
|
|
+
|
|
+extern unsigned int dict_key2hash_int(const void *key);
|
|
+extern int dict_key_cmp_int(const void *key1, const 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),
|
|
diff --git a/display_args.c b/display_args.c
|
|
index c639c88..5df34ca 100644
|
|
--- a/display_args.c
|
|
+++ b/display_args.c
|
|
@@ -5,6 +5,7 @@
|
|
#include <limits.h>
|
|
|
|
#include "common.h"
|
|
+#include "proc.h"
|
|
|
|
static int display_char(int what);
|
|
static int display_string(enum tof type, Process *proc,
|
|
diff --git a/execute_program.c b/execute_program.c
|
|
index 859f32c..55df205 100644
|
|
--- a/execute_program.c
|
|
+++ b/execute_program.c
|
|
@@ -78,6 +78,7 @@ execute_program(const char * command, char **argv)
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
+ fail:
|
|
perror("ltrace: fork");
|
|
exit(1);
|
|
} else if (!pid) { /* child */
|
|
@@ -89,9 +90,9 @@ execute_program(const char * command, char **argv)
|
|
_exit(1);
|
|
}
|
|
|
|
- wait_for_proc(pid);
|
|
+ if (wait_for_proc(pid) < 0)
|
|
+ goto fail;
|
|
|
|
debug(1, "PID=%d", pid);
|
|
-
|
|
return pid;
|
|
}
|
|
diff --git a/filter.c b/filter.c
|
|
new file mode 100644
|
|
index 0000000..003010d
|
|
--- /dev/null
|
|
+++ b/filter.c
|
|
@@ -0,0 +1,188 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2012 Petr Machata, Red Hat Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#include <stdlib.h>
|
|
+#include <assert.h>
|
|
+#include <stdio.h>
|
|
+#include <string.h>
|
|
+
|
|
+#include "filter.h"
|
|
+#include "library.h"
|
|
+
|
|
+void
|
|
+filter_init(struct filter *filt)
|
|
+{
|
|
+ filt->rules = NULL;
|
|
+ filt->next = NULL;
|
|
+}
|
|
+
|
|
+void
|
|
+filter_destroy(struct filter *filt)
|
|
+{
|
|
+ struct filter_rule *it;
|
|
+ for (it = filt->rules; it != NULL; ) {
|
|
+ struct filter_rule *next = it->next;
|
|
+ filter_rule_destroy(it);
|
|
+ it = next;
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+filter_rule_init(struct filter_rule *rule, enum filter_rule_type type,
|
|
+ struct filter_lib_matcher *matcher,
|
|
+ regex_t symbol_re)
|
|
+{
|
|
+ rule->type = type;
|
|
+ rule->lib_matcher = matcher;
|
|
+ rule->symbol_re = symbol_re;
|
|
+ rule->next = NULL;
|
|
+}
|
|
+
|
|
+void
|
|
+filter_rule_destroy(struct filter_rule *rule)
|
|
+{
|
|
+ filter_lib_matcher_destroy(rule->lib_matcher);
|
|
+ regfree(&rule->symbol_re);
|
|
+}
|
|
+
|
|
+void
|
|
+filter_add_rule(struct filter *filt, struct filter_rule *rule)
|
|
+{
|
|
+ struct filter_rule **rulep;
|
|
+ for (rulep = &filt->rules; *rulep != NULL; rulep = &(*rulep)->next)
|
|
+ ;
|
|
+ *rulep = rule;
|
|
+}
|
|
+
|
|
+void
|
|
+filter_lib_matcher_name_init(struct filter_lib_matcher *matcher,
|
|
+ enum filter_lib_matcher_type type,
|
|
+ regex_t libname_re)
|
|
+{
|
|
+ switch (type) {
|
|
+ case FLM_MAIN:
|
|
+ assert(type != type);
|
|
+ abort();
|
|
+
|
|
+ case FLM_SONAME:
|
|
+ case FLM_PATHNAME:
|
|
+ matcher->type = type;
|
|
+ matcher->libname_re = libname_re;
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+filter_lib_matcher_main_init(struct filter_lib_matcher *matcher)
|
|
+{
|
|
+ matcher->type = FLM_MAIN;
|
|
+}
|
|
+
|
|
+void
|
|
+filter_lib_matcher_destroy(struct filter_lib_matcher *matcher)
|
|
+{
|
|
+ switch (matcher->type) {
|
|
+ case FLM_SONAME:
|
|
+ case FLM_PATHNAME:
|
|
+ regfree(&matcher->libname_re);
|
|
+ break;
|
|
+ case FLM_MAIN:
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int
|
|
+re_match_or_error(regex_t *re, const char *name, const char *what)
|
|
+{
|
|
+ int status = regexec(re, name, 0, NULL, 0);
|
|
+ if (status == 0)
|
|
+ return 1;
|
|
+ if (status == REG_NOMATCH)
|
|
+ return 0;
|
|
+
|
|
+ char buf[200];
|
|
+ regerror(status, re, buf, sizeof buf);
|
|
+ fprintf(stderr, "Error when matching %s: %s\n", name, buf);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+matcher_matches_library(struct filter_lib_matcher *matcher, struct library *lib)
|
|
+{
|
|
+ switch (matcher->type) {
|
|
+ case FLM_SONAME:
|
|
+ return re_match_or_error(&matcher->libname_re, lib->soname,
|
|
+ "library soname");
|
|
+ case FLM_PATHNAME:
|
|
+ return re_match_or_error(&matcher->libname_re, lib->pathname,
|
|
+ "library pathname");
|
|
+ case FLM_MAIN:
|
|
+ return lib->type == LT_LIBTYPE_MAIN;
|
|
+ }
|
|
+ assert(matcher->type != matcher->type);
|
|
+ abort();
|
|
+}
|
|
+
|
|
+int
|
|
+filter_matches_library(struct filter *filt, struct library *lib)
|
|
+{
|
|
+ if (filt == NULL)
|
|
+ return 0;
|
|
+
|
|
+ struct filter_rule *it;
|
|
+ for (it = filt->rules; it != NULL; it = it->next)
|
|
+ switch (it->type) {
|
|
+ case FR_ADD:
|
|
+ if (matcher_matches_library(it->lib_matcher, lib))
|
|
+ return 1;
|
|
+ case FR_SUBTRACT:
|
|
+ continue;
|
|
+ };
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+filter_matches_symbol(struct filter *filt,
|
|
+ const char *sym_name, struct library *lib)
|
|
+{
|
|
+ for (; filt != NULL; filt = filt->next) {
|
|
+ int matches = 0;
|
|
+ struct filter_rule *it;
|
|
+ for (it = filt->rules; it != NULL; it = it->next) {
|
|
+ switch (it->type) {
|
|
+ case FR_ADD:
|
|
+ if (matches)
|
|
+ continue;
|
|
+ break;
|
|
+ case FR_SUBTRACT:
|
|
+ if (!matches)
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (matcher_matches_library(it->lib_matcher, lib)
|
|
+ && re_match_or_error(&it->symbol_re, sym_name,
|
|
+ "symbol name"))
|
|
+ matches = !matches;
|
|
+ }
|
|
+ if (matches)
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
diff --git a/filter.h b/filter.h
|
|
new file mode 100644
|
|
index 0000000..65c575a
|
|
--- /dev/null
|
|
+++ b/filter.h
|
|
@@ -0,0 +1,99 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2012 Petr Machata, Red Hat Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+/* This file contains declarations and types for working with symbol
|
|
+ * filters. */
|
|
+
|
|
+#ifndef FILTER_H
|
|
+#define FILTER_H
|
|
+
|
|
+#include <sys/types.h>
|
|
+#include <regex.h>
|
|
+
|
|
+struct library;
|
|
+struct library_symbol;
|
|
+
|
|
+enum filter_lib_matcher_type {
|
|
+ /* Match by soname. */
|
|
+ FLM_SONAME,
|
|
+ /* Match by path name. */
|
|
+ FLM_PATHNAME,
|
|
+ /* Match main binary. */
|
|
+ FLM_MAIN,
|
|
+};
|
|
+
|
|
+struct filter_lib_matcher {
|
|
+ enum filter_lib_matcher_type type;
|
|
+ regex_t libname_re;
|
|
+};
|
|
+
|
|
+enum filter_rule_type {
|
|
+ FR_ADD,
|
|
+ FR_SUBTRACT,
|
|
+};
|
|
+
|
|
+struct filter_rule {
|
|
+ struct filter_rule *next;
|
|
+ struct filter_lib_matcher *lib_matcher;
|
|
+ regex_t symbol_re; /* Regex for matching symbol name. */
|
|
+ enum filter_rule_type type;
|
|
+};
|
|
+
|
|
+struct filter {
|
|
+ struct filter *next;
|
|
+ struct filter_rule *rules;
|
|
+};
|
|
+
|
|
+void filter_init(struct filter *filt);
|
|
+void filter_destroy(struct filter *filt);
|
|
+
|
|
+/* Both SYMBOL_RE and MATCHER are owned and destroyed by RULE. */
|
|
+void filter_rule_init(struct filter_rule *rule, enum filter_rule_type type,
|
|
+ struct filter_lib_matcher *matcher,
|
|
+ regex_t symbol_re);
|
|
+
|
|
+void filter_rule_destroy(struct filter_rule *rule);
|
|
+
|
|
+/* RULE is added to FILT and owned and destroyed by it. */
|
|
+void filter_add_rule(struct filter *filt, struct filter_rule *rule);
|
|
+
|
|
+/* Create a matcher that matches library name. RE is owned and
|
|
+ * destroyed by MATCHER. TYPE shall be FLM_SONAME or
|
|
+ * FLM_PATHNAME. */
|
|
+void filter_lib_matcher_name_init(struct filter_lib_matcher *matcher,
|
|
+ enum filter_lib_matcher_type type,
|
|
+ regex_t re);
|
|
+
|
|
+/* Create a matcher that matches main binary. */
|
|
+void filter_lib_matcher_main_init(struct filter_lib_matcher *matcher);
|
|
+
|
|
+void filter_lib_matcher_destroy(struct filter_lib_matcher *matcher);
|
|
+
|
|
+/* Ask whether FILTER might match a symbol in LIB. 0 if no, non-0 if
|
|
+ * yes. Note that positive answer doesn't mean that anything will
|
|
+ * actually be matched, just that potentially it could. */
|
|
+int filter_matches_library(struct filter *filt, struct library *lib);
|
|
+
|
|
+/* Ask whether FILTER matches this symbol. Returns 0 if it doesn't,
|
|
+ * or non-0 value if it does. */
|
|
+int filter_matches_symbol(struct filter *filt, const char *sym_name,
|
|
+ struct library *lib);
|
|
+
|
|
+#endif /* FILTER_H */
|
|
diff --git a/glob.c b/glob.c
|
|
new file mode 100644
|
|
index 0000000..6c5c9ef
|
|
--- /dev/null
|
|
+++ b/glob.c
|
|
@@ -0,0 +1,275 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2007, 2008, 2012 Petr Machata, Red Hat Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#include <sys/types.h>
|
|
+#include <regex.h>
|
|
+#include <string.h>
|
|
+#include <stdlib.h>
|
|
+
|
|
+static ssize_t
|
|
+match_character_class(const char *glob, size_t length, size_t from)
|
|
+{
|
|
+ size_t i;
|
|
+ if (length > 0)
|
|
+ for (i = from + 2; i < length - 1 && glob[++i] != ':'; )
|
|
+ ;
|
|
+ if (i >= length || glob[++i] != ']')
|
|
+ return -1;
|
|
+ return i;
|
|
+}
|
|
+
|
|
+static ssize_t
|
|
+match_brack(const char *glob, size_t length, size_t from, int *exclmp)
|
|
+{
|
|
+ size_t i = from + 1;
|
|
+
|
|
+ if (i >= length)
|
|
+ return -1;
|
|
+
|
|
+ /* Complement operator. */
|
|
+ *exclmp = 0;
|
|
+ if (glob[i] == '^' || glob[i] == '!') {
|
|
+ *exclmp = glob[i++] == '!';
|
|
+ if (i >= length)
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* On first character, both [ and ] are legal. But when [ is
|
|
+ * followed with :, it's character class. */
|
|
+ if (glob[i] == '[' && glob[i + 1] == ':') {
|
|
+ ssize_t j = match_character_class(glob, length, i);
|
|
+ if (j < 0)
|
|
+ fail:
|
|
+ return -1;
|
|
+ i = j;
|
|
+ }
|
|
+ ++i; /* skip any character, including [ or ] */
|
|
+
|
|
+ int escape = 0;
|
|
+ for (; i < length; ++i) {
|
|
+ char c = glob[i];
|
|
+ if (escape) {
|
|
+ ++i;
|
|
+ escape = 0;
|
|
+
|
|
+ } else if (c == '[' && glob[i + 1] == ':') {
|
|
+ ssize_t j = match_character_class(glob, length, i);
|
|
+ if (j < 0)
|
|
+ goto fail;
|
|
+ i = j;
|
|
+
|
|
+ } else if (c == ']') {
|
|
+ return i;
|
|
+ }
|
|
+ }
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static int
|
|
+append(char **bufp, const char *str, size_t str_size,
|
|
+ size_t *sizep, size_t *allocp)
|
|
+{
|
|
+ if (str_size == 0)
|
|
+ str_size = strlen(str);
|
|
+ size_t nsize = *sizep + str_size;
|
|
+ if (nsize > *allocp) {
|
|
+ size_t nalloc = nsize * 2;
|
|
+ char *nbuf = realloc(*bufp, nalloc);
|
|
+ if (nbuf == NULL)
|
|
+ return -1;
|
|
+ *allocp = nalloc;
|
|
+ *bufp = nbuf;
|
|
+ }
|
|
+
|
|
+ memcpy(*bufp + *sizep, str, str_size);
|
|
+ *sizep = nsize;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+glob_to_regex(const char *glob, char **retp)
|
|
+{
|
|
+ size_t allocd = 0;
|
|
+ size_t size = 0;
|
|
+ char *buf = NULL;
|
|
+
|
|
+ size_t length = strlen(glob);
|
|
+ int escape = 0;
|
|
+ size_t i;
|
|
+ for(i = 0; i < length; ++i) {
|
|
+ char c = glob[i];
|
|
+ if (escape) {
|
|
+ if (c == '\\') {
|
|
+ if (append(&buf, "\\\\", 0,
|
|
+ &size, &allocd) < 0) {
|
|
+ fail:
|
|
+ free(buf);
|
|
+ return REG_ESPACE;
|
|
+ }
|
|
+
|
|
+ } else if (c == '*') {
|
|
+ if (append(&buf, "\\*", 0, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ } else if (c == '?') {
|
|
+ if (append(&buf, "?", 0, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ } else if (append(&buf, (char[]){ '\\', c }, 2,
|
|
+ &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ escape = 0;
|
|
+ } else {
|
|
+ if (c == '\\')
|
|
+ escape = 1;
|
|
+ else if (c == '[') {
|
|
+ int exclm;
|
|
+ ssize_t j = match_brack(glob, length, i, &exclm);
|
|
+ if (j < 0)
|
|
+ return REG_EBRACK;
|
|
+ if (exclm
|
|
+ && append(&buf, "[^", 2,
|
|
+ &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ if (append(&buf, glob + i + 2*exclm,
|
|
+ j - i + 1 - 2*exclm,
|
|
+ &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ i = j;
|
|
+
|
|
+ } else if (c == '*') {
|
|
+ if (append(&buf, ".*", 0, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ } else if (c == '?') {
|
|
+ if (append(&buf, ".", 0, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ } else if (c == '.') {
|
|
+ if (append(&buf, "\\.", 0, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ } else if (append(&buf, &c, 1, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (escape) {
|
|
+ free(buf);
|
|
+ return REG_EESCAPE;
|
|
+ }
|
|
+
|
|
+ {
|
|
+ char c = 0;
|
|
+ if (append(&buf, &c, 1, &size, &allocd) < 0)
|
|
+ goto fail;
|
|
+ }
|
|
+ *retp = buf;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+globcomp(regex_t *preg, const char *glob, int cflags)
|
|
+{
|
|
+ char *regex;
|
|
+ int status = glob_to_regex(glob, ®ex);
|
|
+ if (status != 0)
|
|
+ return status;
|
|
+ status = regcomp(preg, regex, cflags);
|
|
+ free(regex);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+#ifdef TEST
|
|
+#include <assert.h>
|
|
+#include <stdio.h>
|
|
+
|
|
+static void
|
|
+translate(const char *glob, int exp_status, const char *expect)
|
|
+{
|
|
+ char *pattern = NULL;
|
|
+ int status = glob_to_regex(glob, &pattern);
|
|
+ if (status != exp_status) {
|
|
+ fprintf(stderr, "translating %s, expected status %d, got %d\n",
|
|
+ glob, exp_status, status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (status == 0) {
|
|
+ assert(pattern != NULL);
|
|
+ if (strcmp(pattern, expect) != 0)
|
|
+ fprintf(stderr, "translating %s, expected %s, got %s\n",
|
|
+ glob, expect, pattern);
|
|
+ free(pattern);
|
|
+ } else {
|
|
+ assert(pattern == NULL);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+try_match(const char *glob, const char *str, int expect)
|
|
+{
|
|
+ regex_t preg;
|
|
+ int status = globcomp(&preg, glob, 0);
|
|
+ assert(status == 0);
|
|
+ status = regexec(&preg, str, 0, NULL, 0);
|
|
+ assert(status == expect);
|
|
+ regfree(&preg);
|
|
+}
|
|
+
|
|
+int
|
|
+main(void)
|
|
+{
|
|
+ translate("*", 0, ".*");
|
|
+ translate("?", 0, ".");
|
|
+ translate(".*", 0, "\\..*");
|
|
+ translate("*.*", 0, ".*\\..*");
|
|
+ translate("*a*", 0, ".*a.*");
|
|
+ translate("[abc]", 0, "[abc]");
|
|
+ translate("[^abc]", 0, "[^abc]");
|
|
+ translate("[!abc]", 0, "[^abc]");
|
|
+ translate("[]]", 0, "[]]");
|
|
+ translate("[[]", 0, "[[]");
|
|
+ translate("[^]]", 0, "[^]]");
|
|
+ translate("[^a-z]", 0, "[^a-z]");
|
|
+ translate("[abc\\]]", 0, "[abc\\]]");
|
|
+ translate("[abc\\]def]", 0, "[abc\\]def]");
|
|
+ translate("[[:space:]]", 0, "[[:space:]]");
|
|
+ translate("[^[:space:]]", 0, "[^[:space:]]");
|
|
+ translate("[![:space:]]", 0, "[^[:space:]]");
|
|
+ translate("[^a-z]*", 0, "[^a-z].*");
|
|
+ translate("[^a-z]bar*", 0, "[^a-z]bar.*");
|
|
+ translate("*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.", 0,
|
|
+ ".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\."
|
|
+ ".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.");
|
|
+
|
|
+ translate("\\", REG_EESCAPE, NULL);
|
|
+ translate("[^[:naotuh\\", REG_EBRACK, NULL);
|
|
+ translate("[^[:", REG_EBRACK, NULL);
|
|
+ translate("[^[", REG_EBRACK, NULL);
|
|
+ translate("[^", REG_EBRACK, NULL);
|
|
+ translate("[\\", REG_EBRACK, NULL);
|
|
+ translate("[", REG_EBRACK, NULL);
|
|
+
|
|
+ try_match("abc*def", "abc012def", 0);
|
|
+ try_match("abc*def", "ab012def", REG_NOMATCH);
|
|
+ try_match("[abc]*def", "a1def", 0);
|
|
+ try_match("[abc]*def", "b1def", 0);
|
|
+ try_match("[abc]*def", "d1def", REG_NOMATCH);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#endif
|
|
diff --git a/glob.h b/glob.h
|
|
new file mode 100644
|
|
index 0000000..d60c0a2
|
|
--- /dev/null
|
|
+++ b/glob.h
|
|
@@ -0,0 +1,32 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2007, 2008, 2012 Petr Machata, Red Hat Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#ifndef _GLOB_H_
|
|
+#define _GLOB_H_
|
|
+
|
|
+#include <sys/types.h>
|
|
+#include <regex.h>
|
|
+
|
|
+/* This is akin to regcomp(3), except it compiles a glob expression
|
|
+ * passed in GLOB. See glob(7) for more information about the syntax
|
|
+ * supported by globcomp. */
|
|
+int globcomp(regex_t *preg, const char *glob, int cflags);
|
|
+
|
|
+#endif /* _GLOB_H_ */
|
|
diff --git a/handle_event.c b/handle_event.c
|
|
index ec4c9f3..73c118a 100644
|
|
--- a/handle_event.c
|
|
+++ b/handle_event.c
|
|
@@ -1,20 +1,18 @@
|
|
#define _GNU_SOURCE
|
|
#include "config.h"
|
|
|
|
+#include <assert.h>
|
|
+#include <errno.h>
|
|
+#include <signal.h>
|
|
#include <stdio.h>
|
|
-#include <string.h>
|
|
#include <stdlib.h>
|
|
-#include <signal.h>
|
|
-#include <assert.h>
|
|
+#include <string.h>
|
|
#include <sys/time.h>
|
|
-#include <errno.h>
|
|
-
|
|
-#ifdef __powerpc__
|
|
-#include <sys/ptrace.h>
|
|
-#endif
|
|
|
|
-#include "common.h"
|
|
#include "breakpoint.h"
|
|
+#include "common.h"
|
|
+#include "library.h"
|
|
+#include "proc.h"
|
|
|
|
static void handle_signal(Event *event);
|
|
static void handle_exit(Event *event);
|
|
@@ -42,7 +40,7 @@ call_handler(Process * proc, Event * event)
|
|
{
|
|
assert(proc != NULL);
|
|
|
|
- Event_Handler * handler = proc->event_handler;
|
|
+ struct event_handler *handler = proc->event_handler;
|
|
if (handler == NULL)
|
|
return event;
|
|
|
|
@@ -86,60 +84,72 @@ handle_event(Event *event)
|
|
debug(1, "event: none");
|
|
return;
|
|
case EVENT_SIGNAL:
|
|
- debug(1, "event: signal (%s [%d])",
|
|
+ debug(1, "[%d] event: signal (%s [%d])",
|
|
+ event->proc->pid,
|
|
shortsignal(event->proc, event->e_un.signum),
|
|
event->e_un.signum);
|
|
handle_signal(event);
|
|
return;
|
|
case EVENT_EXIT:
|
|
- debug(1, "event: exit (%d)", event->e_un.ret_val);
|
|
+ debug(1, "[%d] event: exit (%d)",
|
|
+ event->proc->pid,
|
|
+ event->e_un.ret_val);
|
|
handle_exit(event);
|
|
return;
|
|
case EVENT_EXIT_SIGNAL:
|
|
- debug(1, "event: exit signal (%s [%d])",
|
|
+ debug(1, "[%d] event: exit signal (%s [%d])",
|
|
+ event->proc->pid,
|
|
shortsignal(event->proc, event->e_un.signum),
|
|
event->e_un.signum);
|
|
handle_exit_signal(event);
|
|
return;
|
|
case EVENT_SYSCALL:
|
|
- debug(1, "event: syscall (%s [%d])",
|
|
+ debug(1, "[%d] event: syscall (%s [%d])",
|
|
+ event->proc->pid,
|
|
sysname(event->proc, event->e_un.sysnum),
|
|
event->e_un.sysnum);
|
|
handle_syscall(event);
|
|
return;
|
|
case EVENT_SYSRET:
|
|
- debug(1, "event: sysret (%s [%d])",
|
|
+ debug(1, "[%d] event: sysret (%s [%d])",
|
|
+ event->proc->pid,
|
|
sysname(event->proc, event->e_un.sysnum),
|
|
event->e_un.sysnum);
|
|
handle_sysret(event);
|
|
return;
|
|
case EVENT_ARCH_SYSCALL:
|
|
- debug(1, "event: arch_syscall (%s [%d])",
|
|
- arch_sysname(event->proc, event->e_un.sysnum),
|
|
- event->e_un.sysnum);
|
|
+ debug(1, "[%d] event: arch_syscall (%s [%d])",
|
|
+ event->proc->pid,
|
|
+ arch_sysname(event->proc, event->e_un.sysnum),
|
|
+ event->e_un.sysnum);
|
|
handle_arch_syscall(event);
|
|
return;
|
|
case EVENT_ARCH_SYSRET:
|
|
- debug(1, "event: arch_sysret (%s [%d])",
|
|
- arch_sysname(event->proc, event->e_un.sysnum),
|
|
- event->e_un.sysnum);
|
|
+ debug(1, "[%d] event: arch_sysret (%s [%d])",
|
|
+ event->proc->pid,
|
|
+ arch_sysname(event->proc, event->e_un.sysnum),
|
|
+ event->e_un.sysnum);
|
|
handle_arch_sysret(event);
|
|
return;
|
|
case EVENT_CLONE:
|
|
case EVENT_VFORK:
|
|
- debug(1, "event: clone (%u)", event->e_un.newpid);
|
|
+ debug(1, "[%d] event: clone (%u)",
|
|
+ event->proc->pid, event->e_un.newpid);
|
|
handle_clone(event);
|
|
return;
|
|
case EVENT_EXEC:
|
|
- debug(1, "event: exec()");
|
|
+ debug(1, "[%d] event: exec()",
|
|
+ event->proc->pid);
|
|
handle_exec(event);
|
|
return;
|
|
case EVENT_BREAKPOINT:
|
|
- debug(1, "event: breakpoint");
|
|
+ debug(1, "[%d] event: breakpoint %p",
|
|
+ event->proc->pid, event->e_un.brk_addr);
|
|
handle_breakpoint(event);
|
|
return;
|
|
case EVENT_NEW:
|
|
- debug(1, "event: new process");
|
|
+ debug(1, "[%d] event: new process",
|
|
+ event->e_un.newpid);
|
|
handle_new(event);
|
|
return;
|
|
default:
|
|
@@ -148,37 +158,6 @@ handle_event(Event *event)
|
|
}
|
|
}
|
|
|
|
-/* TODO */
|
|
-static void *
|
|
-address_clone(void * addr, void * data)
|
|
-{
|
|
- debug(DEBUG_FUNCTION, "address_clone(%p)", addr);
|
|
- return addr;
|
|
-}
|
|
-
|
|
-static void *
|
|
-breakpoint_clone(void *bp, void *data)
|
|
-{
|
|
- Dict *map = data;
|
|
- debug(DEBUG_FUNCTION, "breakpoint_clone(%p)", bp);
|
|
- struct breakpoint *b = malloc(sizeof(*b));
|
|
- if (!b) {
|
|
- perror("malloc()");
|
|
- exit(1);
|
|
- }
|
|
- memcpy(b, bp, sizeof(*b));
|
|
- 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;
|
|
-}
|
|
-
|
|
typedef struct Pending_New Pending_New;
|
|
struct Pending_New {
|
|
pid_t pid;
|
|
@@ -240,83 +219,45 @@ 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;
|
|
-
|
|
+handle_clone(Event *event)
|
|
+{
|
|
debug(DEBUG_FUNCTION, "handle_clone(pid=%d)", event->proc->pid);
|
|
|
|
- p = malloc(sizeof(Process));
|
|
- if (!p) {
|
|
+ struct Process *proc = malloc(sizeof(*proc));
|
|
+ if (proc == NULL) {
|
|
+ fail:
|
|
+ free(proc);
|
|
+ /* XXX proper error handling here, please. */
|
|
perror("malloc()");
|
|
exit(1);
|
|
}
|
|
- memcpy(p, event->proc, sizeof(Process));
|
|
- p->pid = event->e_un.newpid;
|
|
- p->parent = event->proc;
|
|
+
|
|
+ if (process_clone(proc, event->proc, event->e_un.newpid) < 0)
|
|
+ goto fail;
|
|
+ proc->parent = event->proc;
|
|
|
|
/* We save register values to the arch pointer, and these need
|
|
to be per-thread. */
|
|
- p->arch_ptr = NULL;
|
|
-
|
|
- if (pending_new(p->pid)) {
|
|
- pending_new_remove(p->pid);
|
|
- if (p->event_handler != NULL)
|
|
- destroy_event_handler(p);
|
|
- if (event->proc->state == STATE_ATTACHED && options.follow) {
|
|
- p->state = STATE_ATTACHED;
|
|
- } else {
|
|
- p->state = STATE_IGNORED;
|
|
- }
|
|
- continue_process(p->pid);
|
|
- add_process(p);
|
|
+ proc->arch_ptr = NULL;
|
|
+
|
|
+ if (pending_new(proc->pid)) {
|
|
+ pending_new_remove(proc->pid);
|
|
+ /* XXX this used to be destroy_event_handler call, but
|
|
+ * I don't think we want to call that on a shared
|
|
+ * state. */
|
|
+ proc->event_handler = NULL;
|
|
+ if (event->proc->state == STATE_ATTACHED && options.follow)
|
|
+ proc->state = STATE_ATTACHED;
|
|
+ else
|
|
+ proc->state = STATE_IGNORED;
|
|
+ continue_process(proc->pid);
|
|
} else {
|
|
- p->state = STATE_BEING_CREATED;
|
|
- add_process(p);
|
|
+ proc->state = STATE_BEING_CREATED;
|
|
}
|
|
|
|
- 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);
|
|
+ continue_after_vfork(proc);
|
|
else
|
|
continue_process(event->proc->pid);
|
|
}
|
|
@@ -445,14 +386,38 @@ handle_exit_signal(Event *event) {
|
|
}
|
|
|
|
static void
|
|
+output_syscall(struct Process *proc, const char *name,
|
|
+ void (*output)(enum tof, struct Process *,
|
|
+ struct library_symbol *))
|
|
+{
|
|
+ struct library_symbol syscall;
|
|
+ if (library_symbol_init(&syscall, 0, name, 0, LS_TOPLT_NONE) >= 0) {
|
|
+ (*output)(LT_TOF_SYSCALL, proc, &syscall);
|
|
+ library_symbol_destroy(&syscall);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+output_syscall_left(struct Process *proc, const char *name)
|
|
+{
|
|
+ output_syscall(proc, name, &output_left);
|
|
+}
|
|
+
|
|
+static void
|
|
+output_syscall_right(struct Process *proc, const char *name)
|
|
+{
|
|
+ output_syscall(proc, name, &output_right);
|
|
+}
|
|
+
|
|
+static void
|
|
handle_syscall(Event *event) {
|
|
debug(DEBUG_FUNCTION, "handle_syscall(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum);
|
|
if (event->proc->state != STATE_IGNORED) {
|
|
callstack_push_syscall(event->proc, event->e_un.sysnum);
|
|
- if (options.syscalls) {
|
|
- output_left(LT_TOF_SYSCALL, event->proc,
|
|
- sysname(event->proc, event->e_un.sysnum));
|
|
- }
|
|
+ if (options.syscalls)
|
|
+ output_syscall_left(event->proc,
|
|
+ sysname(event->proc,
|
|
+ event->e_un.sysnum));
|
|
}
|
|
continue_after_syscall(event->proc, event->e_un.sysnum, 0);
|
|
}
|
|
@@ -461,20 +426,36 @@ static void
|
|
handle_exec(Event * event) {
|
|
Process * proc = event->proc;
|
|
|
|
+ /* Save the PID so that we can use it after unsuccessful
|
|
+ * process_exec. */
|
|
+ pid_t pid = proc->pid;
|
|
+
|
|
debug(DEBUG_FUNCTION, "handle_exec(pid=%d)", proc->pid);
|
|
if (proc->state == STATE_IGNORED) {
|
|
- untrace_pid(proc->pid);
|
|
+ untrace:
|
|
+ untrace_pid(pid);
|
|
remove_process(proc);
|
|
return;
|
|
}
|
|
output_line(proc, "--- Called exec() ---");
|
|
- proc->mask_32bit = 0;
|
|
- proc->personality = 0;
|
|
- proc->arch_ptr = NULL;
|
|
- free(proc->filename);
|
|
- proc->filename = pid2name(proc->pid);
|
|
- breakpoints_init(proc, 0);
|
|
- proc->callstack_depth = 0;
|
|
+
|
|
+ if (process_exec(proc) < 0) {
|
|
+ fprintf(stderr,
|
|
+ "couldn't reinitialize process %d after exec\n", pid);
|
|
+ goto untrace;
|
|
+ }
|
|
+
|
|
+ continue_process(proc->pid);
|
|
+
|
|
+ /* After the exec, we expect to hit the first executable
|
|
+ * instruction.
|
|
+ *
|
|
+ * XXX TODO It would be nice to have this removed, but then we
|
|
+ * need to do that also for initial call to wait_for_proc in
|
|
+ * execute_program. In that case we could generate a
|
|
+ * EVENT_FIRST event or something, or maybe this could somehow
|
|
+ * be rolled into EVENT_NEW. */
|
|
+ wait_for_proc(proc->pid);
|
|
continue_process(proc->pid);
|
|
}
|
|
|
|
@@ -484,8 +465,9 @@ handle_arch_syscall(Event *event) {
|
|
if (event->proc->state != STATE_IGNORED) {
|
|
callstack_push_syscall(event->proc, 0xf0000 + event->e_un.sysnum);
|
|
if (options.syscalls) {
|
|
- output_left(LT_TOF_SYSCALL, event->proc,
|
|
- arch_sysname(event->proc, event->e_un.sysnum));
|
|
+ output_syscall_left(event->proc,
|
|
+ arch_sysname(event->proc,
|
|
+ event->e_un.sysnum));
|
|
}
|
|
}
|
|
continue_process(event->proc->pid);
|
|
@@ -522,10 +504,11 @@ handle_sysret(Event *event) {
|
|
if (opt_T || options.summary) {
|
|
calc_time_spent(event->proc);
|
|
}
|
|
- if (options.syscalls) {
|
|
- output_right(LT_TOF_SYSCALLR, event->proc,
|
|
- sysname(event->proc, event->e_un.sysnum));
|
|
- }
|
|
+ if (options.syscalls)
|
|
+ output_syscall_right(event->proc,
|
|
+ sysname(event->proc,
|
|
+ event->e_un.sysnum));
|
|
+
|
|
assert(event->proc->callstack_depth > 0);
|
|
unsigned d = event->proc->callstack_depth - 1;
|
|
assert(event->proc->callstack[d].is_syscall);
|
|
@@ -541,10 +524,10 @@ handle_arch_sysret(Event *event) {
|
|
if (opt_T || options.summary) {
|
|
calc_time_spent(event->proc);
|
|
}
|
|
- if (options.syscalls) {
|
|
- output_right(LT_TOF_SYSCALLR, event->proc,
|
|
- arch_sysname(event->proc, event->e_un.sysnum));
|
|
- }
|
|
+ if (options.syscalls)
|
|
+ output_syscall_right(event->proc,
|
|
+ arch_sysname(event->proc,
|
|
+ event->e_un.sysnum));
|
|
callstack_pop(event->proc);
|
|
}
|
|
continue_process(event->proc->pid);
|
|
@@ -556,7 +539,7 @@ output_right_tos(struct Process *proc)
|
|
size_t d = proc->callstack_depth;
|
|
struct callstack_element *elem = &proc->callstack[d - 1];
|
|
if (proc->state != STATE_IGNORED)
|
|
- output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc->name);
|
|
+ output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc);
|
|
}
|
|
|
|
static void
|
|
@@ -564,43 +564,7 @@ handle_breakpoint(Event *event)
|
|
|
|
for (i = event->proc->callstack_depth - 1; i >= 0; i--) {
|
|
if (brk_addr == event->proc->callstack[i].return_addr) {
|
|
- struct library_symbol *libsym =
|
|
- event->proc->callstack[i].c_un.libfunc;
|
|
-#ifdef __powerpc__
|
|
- /*
|
|
- * PPC HACK! (XXX FIXME TODO)
|
|
- * The PLT gets modified during the first call,
|
|
- * so be sure to re-enable the breakpoint.
|
|
- */
|
|
- unsigned long a;
|
|
- void *addr = sym2addr(event->proc, libsym);
|
|
-
|
|
- if (libsym->plt_type != LS_TOPLT_POINT) {
|
|
- unsigned char break_insn[] = BREAKPOINT_VALUE;
|
|
-
|
|
- sbp = address2bpstruct(leader, addr);
|
|
- assert(sbp);
|
|
- a = ptrace(PTRACE_PEEKTEXT, event->proc->pid,
|
|
- addr);
|
|
-
|
|
- if (memcmp(&a, break_insn, BREAKPOINT_LENGTH)) {
|
|
- sbp->enabled--;
|
|
- insert_breakpoint(event->proc, addr,
|
|
- libsym, 1);
|
|
- }
|
|
- } else {
|
|
- sbp = dict_find_entry(leader->breakpoints, addr);
|
|
- /* On powerpc, the breakpoint address
|
|
- may end up being actual entry point
|
|
- of the library symbol, not the PLT
|
|
- address we computed. In that case,
|
|
- sbp is NULL. */
|
|
- if (sbp == NULL || addr != sbp->addr) {
|
|
- insert_breakpoint(event->proc, addr,
|
|
- libsym, 1);
|
|
- }
|
|
- }
|
|
-#elif defined(__mips__)
|
|
+#if defined(__mips__)
|
|
void *addr = NULL;
|
|
struct library_symbol *sym= event->proc->callstack[i].c_un.libfunc;
|
|
struct library_symbol *new_sym;
|
|
@@ -624,14 +571,14 @@ handle_breakpoint(Event *event)
|
|
sbp = dict_find_entry(leader->breakpoints, addr);
|
|
if (sbp) {
|
|
if (addr != sbp->addr) {
|
|
- insert_breakpoint(event->proc, addr, sym, 1);
|
|
+ insert_breakpoint(event->proc, addr, sym);
|
|
}
|
|
} else {
|
|
new_sym=malloc(sizeof(*new_sym) + strlen(sym->name) + 1);
|
|
memcpy(new_sym,sym,sizeof(*new_sym) + strlen(sym->name) + 1);
|
|
new_sym->next = leader->list_of_symbols;
|
|
leader->list_of_symbols = new_sym;
|
|
- insert_breakpoint(event->proc, addr, new_sym, 1);
|
|
+ insert_breakpoint(event->proc, addr, new_sym);
|
|
}
|
|
#endif
|
|
for (j = event->proc->callstack_depth - 1; j > i; j--) {
|
|
@@ -644,6 +591,9 @@ handle_breakpoint(Event *event)
|
|
}
|
|
event->proc->return_addr = brk_addr;
|
|
|
|
+ struct library_symbol *libsym =
|
|
+ event->proc->callstack[i].c_un.libfunc;
|
|
+
|
|
output_right_tos(event->proc);
|
|
callstack_pop(event->proc);
|
|
|
|
@@ -666,41 +616,44 @@ handle_breakpoint(Event *event)
|
|
callstack_pop(event->proc);
|
|
}
|
|
|
|
- sbp = address2bpstruct(leader, brk_addr);
|
|
- continue_after_breakpoint(event->proc, sbp);
|
|
+ /* Maybe the previous callstack_pop's got rid
|
|
+ * of the breakpoint, but if we are in a
|
|
+ * recursive call, it's still enabled. In
|
|
+ * that case we need to skip it properly. */
|
|
+ if ((sbp = address2bpstruct(leader, brk_addr)) != NULL) {
|
|
+ continue_after_breakpoint(event->proc, sbp);
|
|
+ } else {
|
|
+ set_instruction_pointer(event->proc, brk_addr);
|
|
+ continue_process(event->proc->pid);
|
|
+ }
|
|
return;
|
|
}
|
|
}
|
|
|
|
- if ((sbp = address2bpstruct(leader, brk_addr))) {
|
|
+ if ((sbp = address2bpstruct(leader, brk_addr)) != NULL)
|
|
breakpoint_on_hit(sbp, event->proc);
|
|
-
|
|
- if (sbp->libsym == NULL) {
|
|
- continue_after_breakpoint(event->proc, sbp);
|
|
- return;
|
|
- }
|
|
-
|
|
- if (strcmp(sbp->libsym->name, "") == 0) {
|
|
- debug(DEBUG_PROCESS, "Hit _dl_debug_state breakpoint!\n");
|
|
- arch_check_dbg(leader);
|
|
- }
|
|
-
|
|
- if (event->proc->state != STATE_IGNORED) {
|
|
+ else if (event->proc->state != STATE_IGNORED)
|
|
+ output_line(event->proc,
|
|
+ "unexpected breakpoint at %p", brk_addr);
|
|
+
|
|
+ /* breakpoint_on_hit may delete its own breakpoint, so we have
|
|
+ * to look it up again. */
|
|
+ if ((sbp = address2bpstruct(leader, brk_addr)) != NULL) {
|
|
+ if (event->proc->state != STATE_IGNORED
|
|
+ && sbp->libsym != NULL) {
|
|
event->proc->stack_pointer = get_stack_pointer(event->proc);
|
|
event->proc->return_addr =
|
|
get_return_addr(event->proc, event->proc->stack_pointer);
|
|
callstack_push_symfunc(event->proc, sbp->libsym);
|
|
- output_left(LT_TOF_FUNCTION, event->proc, sbp->libsym->name);
|
|
+ output_left(LT_TOF_FUNCTION, event->proc, sbp->libsym);
|
|
}
|
|
|
|
- continue_after_breakpoint(event->proc, sbp);
|
|
+ breakpoint_on_continue(sbp, event->proc);
|
|
return;
|
|
+ } else {
|
|
+ set_instruction_pointer(event->proc, brk_addr);
|
|
}
|
|
|
|
- if (event->proc->state != STATE_IGNORED && !options.no_plt) {
|
|
- output_line(event->proc, "unexpected breakpoint at %p",
|
|
- brk_addr);
|
|
- }
|
|
continue_process(event->proc->pid);
|
|
}
|
|
|
|
@@ -745,9 +698,8 @@ callstack_push_symfunc(Process *proc, struct library_symbol *sym) {
|
|
elem->c_un.libfunc = sym;
|
|
|
|
elem->return_addr = proc->return_addr;
|
|
- if (elem->return_addr) {
|
|
- insert_breakpoint(proc, elem->return_addr, NULL, 1);
|
|
- }
|
|
+ if (elem->return_addr)
|
|
+ insert_breakpoint(proc, elem->return_addr, NULL);
|
|
|
|
/* handle functions like atexit() on mips which have no return */
|
|
if (opt_T || options.summary) {
|
|
diff --git a/libltrace.c b/libltrace.c
|
|
index 777ad1b..92f2701 100644
|
|
--- a/libltrace.c
|
|
+++ b/libltrace.c
|
|
@@ -1,22 +1,23 @@
|
|
#include "config.h"
|
|
|
|
+#include <sys/param.h>
|
|
+#include <sys/wait.h>
|
|
+#include <errno.h>
|
|
+#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
-#include <unistd.h>
|
|
#include <string.h>
|
|
-#include <errno.h>
|
|
-#include <sys/param.h>
|
|
-#include <signal.h>
|
|
-#include <sys/wait.h>
|
|
+#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
+#include "proc.h"
|
|
|
|
char *command = NULL;
|
|
|
|
int exiting = 0; /* =1 if a SIGINT or SIGTERM has been received */
|
|
|
|
-static enum pcb_status
|
|
-stop_non_p_processes (Process * proc, void * data)
|
|
+static enum callback_status
|
|
+stop_non_p_processes(Process *proc, void *data)
|
|
{
|
|
int stop = 1;
|
|
|
|
@@ -38,7 +39,7 @@ stop_non_p_processes (Process * proc, void * data)
|
|
kill(proc->pid, SIGSTOP);
|
|
}
|
|
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
static void
|
|
@@ -104,20 +105,22 @@ ltrace_init(int argc, char **argv) {
|
|
}
|
|
opt_F = opt_F->next;
|
|
}
|
|
- if (opt_e) {
|
|
- struct opt_e_t *tmp = opt_e;
|
|
- while (tmp) {
|
|
- debug(1, "Option -e: %s\n", tmp->name);
|
|
- tmp = tmp->next;
|
|
- }
|
|
- }
|
|
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);
|
|
+ pid_t pid = execute_program(command, argv);
|
|
+ struct Process *proc = open_program(command, pid);
|
|
+ if (proc == NULL) {
|
|
+ fprintf(stderr, "couldn't open program '%s': %s\n",
|
|
+ command, strerror(errno));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
+
|
|
+ trace_set_options(proc);
|
|
+ continue_process(pid);
|
|
}
|
|
opt_p_tmp = opt_p;
|
|
while (opt_p_tmp) {
|
|
diff --git a/library.c b/library.c
|
|
new file mode 100644
|
|
index 0000000..92fccea
|
|
--- /dev/null
|
|
+++ b/library.c
|
|
@@ -0,0 +1,354 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc.
|
|
+ * Copyright (C) 2001,2009 Juan Cespedes
|
|
+ * Copyright (C) 2006 Ian Wienand
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <assert.h>
|
|
+
|
|
+#include "library.h"
|
|
+#include "proc.h" // for enum callback_status
|
|
+#include "debug.h"
|
|
+#include "common.h" // for arch_library_symbol_init, arch_library_init
|
|
+
|
|
+#ifndef ARCH_HAVE_LIBRARY_DATA
|
|
+void
|
|
+arch_library_init(struct library *lib)
|
|
+{
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_destroy(struct library *lib)
|
|
+{
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_clone(struct library *retp, struct library *lib)
|
|
+{
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifndef ARCH_HAVE_LIBRARY_SYMBOL_DATA
|
|
+int
|
|
+arch_library_symbol_init(struct library_symbol *libsym)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_symbol_destroy(struct library_symbol *libsym)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_library_symbol_clone(struct library_symbol *retp,
|
|
+ struct library_symbol *libsym)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+unsigned int
|
|
+target_address_hash(const void *key)
|
|
+{
|
|
+ /* XXX this assumes that key is passed by value. */
|
|
+ union {
|
|
+ target_address_t addr;
|
|
+ unsigned int ints[sizeof(target_address_t)
|
|
+ / sizeof(unsigned int)];
|
|
+ } u = { .addr = (target_address_t)key };
|
|
+
|
|
+ size_t i;
|
|
+ unsigned int h = 0;
|
|
+ for (i = 0; i < sizeof(u.ints) / sizeof(*u.ints); ++i)
|
|
+ h ^= dict_key2hash_int((void *)(uintptr_t)u.ints[i]);
|
|
+ return h;
|
|
+}
|
|
+
|
|
+int
|
|
+target_address_cmp(const void *key1, const void *key2)
|
|
+{
|
|
+ /* XXX this assumes that key is passed by value. */
|
|
+ target_address_t addr1 = (target_address_t)key1;
|
|
+ target_address_t addr2 = (target_address_t)key2;
|
|
+ return addr1 < addr2 ? 1
|
|
+ : addr1 > addr2 ? -1 : 0;
|
|
+}
|
|
+
|
|
+/* If the other symbol owns the name, we need to make the copy, so
|
|
+ * that the life-times of the two symbols are not dependent on each
|
|
+ * other. */
|
|
+static int
|
|
+strdup_if_owned(const char **retp, const char *str, int owned)
|
|
+{
|
|
+ if (!owned || str == NULL) {
|
|
+ *retp = str;
|
|
+ return 0;
|
|
+ } else {
|
|
+ *retp = strdup(str);
|
|
+ return *retp != NULL ? 0 : -1;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+private_library_symbol_init(struct library_symbol *libsym,
|
|
+ target_address_t addr,
|
|
+ const char *name, int own_name,
|
|
+ enum toplt type_of_plt)
|
|
+{
|
|
+ libsym->next = NULL;
|
|
+ libsym->lib = NULL;
|
|
+ libsym->plt_type = type_of_plt;
|
|
+ libsym->name = name;
|
|
+ libsym->own_name = own_name;
|
|
+ libsym->enter_addr = (void *)(uintptr_t)addr;
|
|
+}
|
|
+
|
|
+static void
|
|
+private_library_symbol_destroy(struct library_symbol *libsym)
|
|
+{
|
|
+ library_symbol_set_name(libsym, NULL, 0);
|
|
+}
|
|
+
|
|
+int
|
|
+library_symbol_init(struct library_symbol *libsym,
|
|
+ target_address_t addr, const char *name, int own_name,
|
|
+ enum toplt type_of_plt)
|
|
+{
|
|
+ private_library_symbol_init(libsym, addr, name, own_name, type_of_plt);
|
|
+
|
|
+ /* If arch init fails, we've already set libsym->name and
|
|
+ * own_name. But we return failure, and the client code isn't
|
|
+ * supposed to call library_symbol_destroy in such a case. */
|
|
+ return arch_library_symbol_init(libsym);
|
|
+}
|
|
+
|
|
+void
|
|
+library_symbol_destroy(struct library_symbol *libsym)
|
|
+{
|
|
+ if (libsym != NULL) {
|
|
+ private_library_symbol_destroy(libsym);
|
|
+ arch_library_symbol_destroy(libsym);
|
|
+ }
|
|
+}
|
|
+
|
|
+int
|
|
+library_symbol_clone(struct library_symbol *retp, struct library_symbol *libsym)
|
|
+{
|
|
+ const char *name;
|
|
+ if (strdup_if_owned(&name, libsym->name, libsym->own_name) < 0)
|
|
+ return -1;
|
|
+
|
|
+ private_library_symbol_init(retp, libsym->enter_addr,
|
|
+ name, libsym->own_name, libsym->plt_type);
|
|
+
|
|
+ if (arch_library_symbol_clone(retp, libsym) < 0) {
|
|
+ private_library_symbol_destroy(retp);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+library_symbol_cmp(struct library_symbol *a, struct library_symbol *b)
|
|
+{
|
|
+ if (a->enter_addr < b->enter_addr)
|
|
+ return -1;
|
|
+ if (a->enter_addr > b->enter_addr)
|
|
+ return 1;
|
|
+ if (a->name != NULL && b->name != NULL)
|
|
+ return strcmp(a->name, b->name);
|
|
+ if (a->name == NULL) {
|
|
+ if (b->name == NULL)
|
|
+ return 0;
|
|
+ return -1;
|
|
+ }
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+void
|
|
+library_symbol_set_name(struct library_symbol *libsym,
|
|
+ const char *name, int own_name)
|
|
+{
|
|
+ if (libsym->own_name)
|
|
+ free((char *)libsym->name);
|
|
+ libsym->name = name;
|
|
+ libsym->own_name = own_name;
|
|
+}
|
|
+
|
|
+enum callback_status
|
|
+library_symbol_equal_cb(struct library_symbol *libsym, void *u)
|
|
+{
|
|
+ struct library_symbol *standard = u;
|
|
+ return library_symbol_cmp(libsym, standard) == 0 ? CBS_STOP : CBS_CONT;
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+private_library_init(struct library *lib, enum library_type type)
|
|
+{
|
|
+ lib->next = NULL;
|
|
+ lib->soname = NULL;
|
|
+ lib->own_soname = 0;
|
|
+ lib->pathname = NULL;
|
|
+ lib->own_pathname = 0;
|
|
+ lib->symbols = NULL;
|
|
+ lib->type = type;
|
|
+}
|
|
+
|
|
+void
|
|
+library_init(struct library *lib, enum library_type type)
|
|
+{
|
|
+ private_library_init(lib, type);
|
|
+ arch_library_init(lib);
|
|
+}
|
|
+
|
|
+int
|
|
+library_clone(struct library *retp, struct library *lib)
|
|
+{
|
|
+ const char *soname = NULL;
|
|
+ const char *pathname;
|
|
+ if (strdup_if_owned(&soname, lib->soname, lib->own_soname) < 0
|
|
+ || strdup_if_owned(&pathname,
|
|
+ lib->pathname, lib->own_pathname) < 0) {
|
|
+ if (lib->own_soname)
|
|
+ free((char *)soname);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ private_library_init(retp, lib->type);
|
|
+ library_set_soname(retp, soname, lib->own_soname);
|
|
+ library_set_soname(retp, pathname, lib->own_pathname);
|
|
+ arch_library_clone(retp, lib);
|
|
+
|
|
+ struct library_symbol *it;
|
|
+ struct library_symbol **nsymp = &retp->symbols;
|
|
+ for (it = lib->symbols; it != NULL; it = it->next) {
|
|
+ *nsymp = malloc(sizeof(**nsymp));
|
|
+ if (*nsymp == NULL
|
|
+ || library_symbol_clone(*nsymp, it) < 0) {
|
|
+ /* Release what we managed to allocate. */
|
|
+ library_destroy(retp);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ (*nsymp)->lib = retp;
|
|
+ nsymp = &(*nsymp)->next;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+library_destroy(struct library *lib)
|
|
+{
|
|
+ if (lib == NULL)
|
|
+ return;
|
|
+
|
|
+ arch_library_destroy(lib);
|
|
+ library_set_soname(lib, NULL, 0);
|
|
+ library_set_pathname(lib, NULL, 0);
|
|
+
|
|
+ struct library_symbol *sym;
|
|
+ for (sym = lib->symbols; sym != NULL; ) {
|
|
+ struct library_symbol *next = sym->next;
|
|
+ library_symbol_destroy(sym);
|
|
+ free(sym);
|
|
+ sym = next;
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+library_set_soname(struct library *lib, const char *new_name, int own_name)
|
|
+{
|
|
+ if (lib->own_soname)
|
|
+ free((char *)lib->soname);
|
|
+ lib->soname = new_name;
|
|
+ lib->own_soname = own_name;
|
|
+}
|
|
+
|
|
+void
|
|
+library_set_pathname(struct library *lib, const char *new_name, int own_name)
|
|
+{
|
|
+ if (lib->own_pathname)
|
|
+ free((char *)lib->pathname);
|
|
+ lib->pathname = new_name;
|
|
+ lib->own_pathname = own_name;
|
|
+}
|
|
+
|
|
+struct library_symbol *
|
|
+library_each_symbol(struct library *lib, struct library_symbol *start_after,
|
|
+ enum callback_status (*cb)(struct library_symbol *, void *),
|
|
+ void *data)
|
|
+{
|
|
+ struct library_symbol *it = start_after == NULL ? lib->symbols
|
|
+ : start_after->next;
|
|
+
|
|
+ while (it != NULL) {
|
|
+ struct library_symbol *next = it->next;
|
|
+
|
|
+ switch ((*cb)(it, data)) {
|
|
+ case CBS_FAIL:
|
|
+ /* XXX handle me */
|
|
+ case CBS_STOP:
|
|
+ return it;
|
|
+ case CBS_CONT:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ it = next;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+void
|
|
+library_add_symbol(struct library *lib, struct library_symbol *first)
|
|
+{
|
|
+ struct library_symbol *last;
|
|
+ for (last = first; last != NULL; ) {
|
|
+ last->lib = lib;
|
|
+ if (last->next != NULL)
|
|
+ last = last->next;
|
|
+ else
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ assert(last->next == NULL);
|
|
+ last->next = lib->symbols;
|
|
+ lib->symbols = first;
|
|
+}
|
|
+
|
|
+enum callback_status
|
|
+library_named_cb(struct Process *proc, struct library *lib, void *name)
|
|
+{
|
|
+ if (name == lib->soname
|
|
+ || strcmp(lib->soname, (char *)name) == 0)
|
|
+ return CBS_STOP;
|
|
+ else
|
|
+ return CBS_CONT;
|
|
+}
|
|
+
|
|
+enum callback_status
|
|
+library_with_key_cb(struct Process *proc, struct library *lib, void *keyp)
|
|
+{
|
|
+ return lib->key == *(target_address_t *)keyp ? CBS_STOP : CBS_CONT;
|
|
+}
|
|
diff --git a/library.h b/library.h
|
|
new file mode 100644
|
|
index 0000000..c387b02
|
|
--- /dev/null
|
|
+++ b/library.h
|
|
@@ -0,0 +1,196 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2012 Petr Machata, Red Hat Inc.
|
|
+ * Copyright (C) 2006 Paul Gilliam
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#ifndef _LIBRARY_H_
|
|
+#define _LIBRARY_H_
|
|
+
|
|
+#include <stdint.h>
|
|
+#include "sysdep.h"
|
|
+
|
|
+struct Process;
|
|
+struct library;
|
|
+
|
|
+enum toplt {
|
|
+ LS_TOPLT_NONE = 0, /* PLT not used for this symbol. */
|
|
+ LS_TOPLT_EXEC, /* PLT for this symbol is executable. */
|
|
+};
|
|
+
|
|
+/* We should in general be able to trace 64-bit processes with 32-bit
|
|
+ * ltrace. (At least PPC has several PTRACE requests related to
|
|
+ * tracing 64-on-32, so presumably it should be possible.) But ltrace
|
|
+ * is currently hopelessly infested with using void* for host address.
|
|
+ * So keep with it, for now. */
|
|
+typedef void *target_address_t;
|
|
+
|
|
+/* Dict interface. */
|
|
+unsigned int target_address_hash(const void *key);
|
|
+int target_address_cmp(const void *key1, const void *key2);
|
|
+
|
|
+struct library_symbol {
|
|
+ struct library_symbol *next;
|
|
+ struct library *lib;
|
|
+ const char *name;
|
|
+ target_address_t enter_addr;
|
|
+ enum toplt plt_type;
|
|
+ char own_name;
|
|
+ struct arch_library_symbol_data arch;
|
|
+};
|
|
+
|
|
+/* Init LIBSYM. NAME will be freed when LIBSYM is destroyed if
|
|
+ * OWN_NAME. ARCH has to be initialized by a separate call. */
|
|
+int library_symbol_init(struct library_symbol *libsym,
|
|
+ target_address_t addr, const char *name, int own_name,
|
|
+ enum toplt type_of_plt);
|
|
+
|
|
+/* Copy library symbol SYM into the area pointed-to by RETP. Return 0
|
|
+ * on success or a negative value on failure. */
|
|
+int library_symbol_clone(struct library_symbol *retp,
|
|
+ struct library_symbol *sym);
|
|
+
|
|
+/* Destroy library symbol. This essentially just frees name if it's
|
|
+ * owned. It doesn't free the memory associated with SYM pointer
|
|
+ * itself. Returns 0 on success or a negative value in case of an
|
|
+ * error (which would be an out of memory condition). */
|
|
+void library_symbol_destroy(struct library_symbol *sym);
|
|
+
|
|
+/* Compare two library symbols. Returns a negative value, 0, or a
|
|
+ * positive value, much like strcmp. The function compares symbol
|
|
+ * addresses, and if those are equal, it compares symbol names. If
|
|
+ * those are equal, too, the symbols are considered equal. */
|
|
+int library_symbol_cmp(struct library_symbol *a, struct library_symbol *b);
|
|
+
|
|
+/* Set a name for library symbol. This frees the old name, if
|
|
+ * that is owned. */
|
|
+void library_symbol_set_name(struct library_symbol *libsym,
|
|
+ const char *name, int own_name);
|
|
+
|
|
+/* A function that can be used as library_each_symbol callback. Looks
|
|
+ * for a symbol SYM for which library_symbol_cmp(SYM, STANDARD)
|
|
+ * returns 0. */
|
|
+enum callback_status library_symbol_equal_cb(struct library_symbol *libsym,
|
|
+ void *standard);
|
|
+
|
|
+enum library_type {
|
|
+ LT_LIBTYPE_MAIN,
|
|
+ LT_LIBTYPE_DSO,
|
|
+};
|
|
+
|
|
+/* XXX we might consider sharing libraries across processes. Things
|
|
+ * like libc will be opened by every single process, no point cloning
|
|
+ * these everywhere. But for now, keep the ownership structure
|
|
+ * simple. */
|
|
+struct library {
|
|
+ struct library *next;
|
|
+
|
|
+ /* Unique key. Two library objects are considered equal, if
|
|
+ * they have the same key. */
|
|
+ target_address_t key;
|
|
+
|
|
+ /* Address where the library is mapped. Two library objects
|
|
+ * are considered equal, if they have the same base. */
|
|
+ target_address_t base;
|
|
+
|
|
+ /* Absolute address of the entry point. Useful for main
|
|
+ * binary, though I suppose the value might be useful for the
|
|
+ * dynamic linker, too (in case we ever want to do early
|
|
+ * process tracing). */
|
|
+ target_address_t entry;
|
|
+
|
|
+ /* Address of PT_DYNAMIC segment. */
|
|
+ target_address_t dyn_addr;
|
|
+
|
|
+ /* Symbols associated with the library. */
|
|
+ struct library_symbol *symbols;
|
|
+
|
|
+ const char *soname;
|
|
+ const char *pathname;
|
|
+
|
|
+ enum library_type type;
|
|
+
|
|
+ char own_soname : 1;
|
|
+ char own_pathname : 1;
|
|
+
|
|
+ struct arch_library_data arch;
|
|
+};
|
|
+
|
|
+/* Init LIB. */
|
|
+void library_init(struct library *lib, enum library_type type);
|
|
+
|
|
+/* Initialize RETP to a library identical to LIB. Symbols are not
|
|
+ * shared, but copied over. Returns 0 on success and a negative value
|
|
+ * in case of failure. */
|
|
+int library_clone(struct library *retp, struct library *lib);
|
|
+
|
|
+/* Destroy library. Doesn't free LIB itself. Symbols are destroyed
|
|
+ * and freed. */
|
|
+void library_destroy(struct library *lib);
|
|
+
|
|
+/* Set library soname. Frees the old name if necessary. */
|
|
+void library_set_soname(struct library *lib,
|
|
+ const char *new_name, int own_name);
|
|
+
|
|
+/* Set library pathname. Frees the old name if necessary. */
|
|
+void library_set_pathname(struct library *lib,
|
|
+ const char *new_name, int own_name);
|
|
+
|
|
+/* Iterate through list of symbols of library LIB. Restarts are
|
|
+ * supported via START_AFTER (see each_process for details of
|
|
+ * iteration interface). */
|
|
+struct library_symbol *library_each_symbol
|
|
+ (struct library *lib, struct library_symbol *start_after,
|
|
+ enum callback_status (*cb)(struct library_symbol *, void *),
|
|
+ void *data);
|
|
+
|
|
+/* Add a new symbol SYM to LIB. SYM is assumed owned, we need to
|
|
+ * overwrite SYM->next. */
|
|
+void library_add_symbol(struct library *lib, struct library_symbol *sym);
|
|
+
|
|
+/* A function that can be used as proc_each_library callback. Looks
|
|
+ * for a library with the name passed in DATA. PROC is ignored. */
|
|
+enum callback_status library_named_cb(struct Process *proc,
|
|
+ struct library *lib, void *name);
|
|
+
|
|
+/* A function that can be used as proc_each_library callback. Looks
|
|
+ * for a library with given base.
|
|
+ *
|
|
+ * NOTE: The key is passed as a POINTER to target_address_t (that
|
|
+ * because in general, target_address_t doesn't fit in void*). */
|
|
+enum callback_status library_with_key_cb(struct Process *proc,
|
|
+ struct library *lib, void *keyp);
|
|
+
|
|
+/* XXX this should really be in backend.h (as on pmachata/revamp
|
|
+ * branch), or, on this branch, in common.h. But we need
|
|
+ * target_address_t (which should also be in backend.h, I reckon), so
|
|
+ * stuff it here for the time being. */
|
|
+/* This function is implemented in the back end. It is called for all
|
|
+ * raw addresses as read from symbol tables etc. If necessary on
|
|
+ * given architecture, this function should translate the address
|
|
+ * according to .opd or other indirection mechanism. Returns 0 on
|
|
+ * success and a negative value on failure. */
|
|
+struct ltelf;
|
|
+int arch_translate_address(struct ltelf *lte,
|
|
+ target_address_t addr, target_address_t *ret);
|
|
+/* This is the same function as arch_translate_address, except it's
|
|
+ * used at the point that we don't have ELF available anymore. */
|
|
+int arch_translate_address_dyn(struct Process *proc,
|
|
+ target_address_t addr, target_address_t *ret);
|
|
+
|
|
+#endif /* _LIBRARY_H_ */
|
|
diff --git a/ltrace-elf.c b/ltrace-elf.c
|
|
index f7fc239..a311c5f 100644
|
|
--- a/ltrace-elf.c
|
|
+++ b/ltrace-elf.c
|
|
@@ -1,68 +1,96 @@
|
|
#include "config.h"
|
|
|
|
+#include <assert.h>
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
-#include <error.h>
|
|
#include <fcntl.h>
|
|
#include <gelf.h>
|
|
#include <inttypes.h>
|
|
+#include <search.h>
|
|
#include <stdint.h>
|
|
+#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
-#include <assert.h>
|
|
|
|
#include "common.h"
|
|
-
|
|
-void do_close_elf(struct ltelf *lte);
|
|
-void add_library_symbol(GElf_Addr addr, const char *name,
|
|
- struct library_symbol **library_symbolspp,
|
|
- enum toplt type_of_plt, int is_weak);
|
|
-int in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym);
|
|
-static GElf_Addr opd2addr(struct ltelf *ltc, GElf_Addr addr);
|
|
-
|
|
-struct library_symbol *library_symbols = NULL;
|
|
-struct ltelf main_lte;
|
|
+#include "proc.h"
|
|
+#include "library.h"
|
|
+#include "filter.h"
|
|
|
|
#ifdef PLT_REINITALISATION_BP
|
|
extern char *PLTs_initialized_by_here;
|
|
#endif
|
|
|
|
-#ifndef DT_PPC_GOT
|
|
-# define DT_PPC_GOT (DT_LOPROC + 0)
|
|
-#endif
|
|
-
|
|
-#define PPC_PLT_STUB_SIZE 16
|
|
-
|
|
-static Elf_Data *loaddata(Elf_Scn *scn, GElf_Shdr *shdr)
|
|
+#ifndef ARCH_HAVE_LTELF_DATA
|
|
+int
|
|
+arch_elf_init(struct ltelf *lte, struct library *lib)
|
|
{
|
|
- Elf_Data *data = elf_getdata(scn, NULL);
|
|
- if (data == NULL || elf_getdata(scn, data) != NULL
|
|
- || data->d_off || data->d_size != shdr->sh_size)
|
|
- return NULL;
|
|
- return data;
|
|
+ return 0;
|
|
}
|
|
|
|
-static int inside(GElf_Addr addr, GElf_Shdr *shdr)
|
|
+void
|
|
+arch_elf_destroy(struct ltelf *lte)
|
|
{
|
|
- return addr >= shdr->sh_addr
|
|
- && addr < shdr->sh_addr + shdr->sh_size;
|
|
}
|
|
+#endif
|
|
|
|
-static int maybe_pick_section(GElf_Addr addr,
|
|
- Elf_Scn *in_sec, GElf_Shdr *in_shdr,
|
|
- Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr)
|
|
+int
|
|
+default_elf_add_plt_entry(struct Process *proc, struct ltelf *lte,
|
|
+ const char *a_name, GElf_Rela *rela, size_t ndx,
|
|
+ struct library_symbol **ret)
|
|
{
|
|
- if (inside (addr, in_shdr)) {
|
|
- *tgt_sec = in_sec;
|
|
- *tgt_shdr = *in_shdr;
|
|
- return 1;
|
|
+ char *name = strdup(a_name);
|
|
+ if (name == NULL) {
|
|
+ fail:
|
|
+ free(name);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ GElf_Addr addr = arch_plt_sym_val(lte, ndx, rela);
|
|
+
|
|
+ struct library_symbol *libsym = malloc(sizeof(*libsym));
|
|
+ if (libsym == NULL)
|
|
+ goto fail;
|
|
+
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ target_address_t taddr = (target_address_t)
|
|
+ (uintptr_t)(addr + lte->bias);
|
|
+
|
|
+ if (library_symbol_init(libsym, taddr, name, 1, LS_TOPLT_EXEC) < 0) {
|
|
+ free(libsym);
|
|
+ goto fail;
|
|
}
|
|
+
|
|
+ *ret = libsym;
|
|
return 0;
|
|
}
|
|
|
|
-static int get_section_covering(struct ltelf *lte, GElf_Addr addr,
|
|
- Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr)
|
|
+#ifndef ARCH_HAVE_ADD_PLT_ENTRY
|
|
+enum plt_status
|
|
+arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte,
|
|
+ const char *a_name, GElf_Rela *rela, size_t ndx,
|
|
+ struct library_symbol **ret)
|
|
+{
|
|
+ return plt_default;
|
|
+}
|
|
+#endif
|
|
+
|
|
+Elf_Data *
|
|
+elf_loaddata(Elf_Scn *scn, GElf_Shdr *shdr)
|
|
+{
|
|
+ Elf_Data *data = elf_getdata(scn, NULL);
|
|
+ if (data == NULL || elf_getdata(scn, data) != NULL
|
|
+ || data->d_off || data->d_size != shdr->sh_size)
|
|
+ return NULL;
|
|
+ return data;
|
|
+}
|
|
+
|
|
+static int
|
|
+elf_get_section_if(struct ltelf *lte, Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr,
|
|
+ int (*predicate)(Elf_Scn *, GElf_Shdr *, void *data),
|
|
+ void *data)
|
|
{
|
|
int i;
|
|
for (i = 1; i < lte->ehdr.e_shnum; ++i) {
|
|
@@ -72,69 +100,115 @@ static int get_section_covering(struct ltelf *lte, GElf_Addr addr,
|
|
scn = elf_getscn(lte->elf, i);
|
|
if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {
|
|
debug(1, "Couldn't read section or header.");
|
|
+ return -1;
|
|
+ }
|
|
+ if (predicate(scn, &shdr, data)) {
|
|
+ *tgt_sec = scn;
|
|
+ *tgt_shdr = shdr;
|
|
return 0;
|
|
}
|
|
-
|
|
- if (maybe_pick_section(addr, scn, &shdr, tgt_sec, tgt_shdr))
|
|
- return 1;
|
|
}
|
|
+ return -1;
|
|
|
|
- return 0;
|
|
}
|
|
|
|
-static GElf_Addr read32be(Elf_Data *data, size_t offset)
|
|
+static int
|
|
+inside_p(Elf_Scn *scn, GElf_Shdr *shdr, void *data)
|
|
{
|
|
- if (data->d_size < offset + 4) {
|
|
- debug(1, "Not enough data to read 32bit value at offset %zd.",
|
|
- offset);
|
|
- return 0;
|
|
- }
|
|
+ GElf_Addr addr = *(GElf_Addr *)data;
|
|
+ return addr >= shdr->sh_addr
|
|
+ && addr < shdr->sh_addr + shdr->sh_size;
|
|
+}
|
|
+
|
|
+int
|
|
+elf_get_section_covering(struct ltelf *lte, GElf_Addr addr,
|
|
+ Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr)
|
|
+{
|
|
+ return elf_get_section_if(lte, tgt_sec, tgt_shdr,
|
|
+ &inside_p, &addr);
|
|
+}
|
|
|
|
- unsigned char const *buf = data->d_buf + offset;
|
|
- return ((Elf32_Word)buf[0] << 24)
|
|
- | ((Elf32_Word)buf[1] << 16)
|
|
- | ((Elf32_Word)buf[2] << 8)
|
|
- | ((Elf32_Word)buf[3]);
|
|
+static int
|
|
+type_p(Elf_Scn *scn, GElf_Shdr *shdr, void *data)
|
|
+{
|
|
+ GElf_Word type = *(GElf_Word *)data;
|
|
+ return shdr->sh_type == type;
|
|
}
|
|
|
|
-static GElf_Addr get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot,
|
|
- Elf_Data *plt_data)
|
|
+int
|
|
+elf_get_section_type(struct ltelf *lte, GElf_Word type,
|
|
+ Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr)
|
|
{
|
|
- Elf_Scn *ppcgot_sec = NULL;
|
|
- GElf_Shdr ppcgot_shdr;
|
|
- if (ppcgot != 0
|
|
- && !get_section_covering(lte, ppcgot, &ppcgot_sec, &ppcgot_shdr))
|
|
- // xxx should be the log out
|
|
- fprintf(stderr,
|
|
- "DT_PPC_GOT=%#" PRIx64 ", but no such section found.\n",
|
|
- ppcgot);
|
|
-
|
|
- if (ppcgot_sec != NULL) {
|
|
- Elf_Data *data = loaddata(ppcgot_sec, &ppcgot_shdr);
|
|
- if (data == NULL
|
|
- || data->d_size < 8 )
|
|
- debug(1, "Couldn't read GOT data.");
|
|
- else {
|
|
- // where PPCGOT begins in .got
|
|
- size_t offset = ppcgot - ppcgot_shdr.sh_addr;
|
|
- GElf_Addr glink_vma = read32be(data, offset + 4);
|
|
- if (glink_vma != 0) {
|
|
- debug(1, "PPC GOT glink_vma address: %#" PRIx64,
|
|
- glink_vma);
|
|
- return glink_vma;
|
|
- }
|
|
- }
|
|
- }
|
|
+ return elf_get_section_if(lte, tgt_sec, tgt_shdr,
|
|
+ &type_p, &type);
|
|
+}
|
|
|
|
- if (plt_data != NULL) {
|
|
- GElf_Addr glink_vma = read32be(plt_data, 0);
|
|
- debug(1, ".plt glink_vma address: %#" PRIx64, glink_vma);
|
|
- return glink_vma;
|
|
- }
|
|
+struct section_named_data {
|
|
+ struct ltelf *lte;
|
|
+ const char *name;
|
|
+};
|
|
+
|
|
+static int
|
|
+name_p(Elf_Scn *scn, GElf_Shdr *shdr, void *d)
|
|
+{
|
|
+ struct section_named_data *data = d;
|
|
+ const char *name = elf_strptr(data->lte->elf,
|
|
+ data->lte->ehdr.e_shstrndx,
|
|
+ shdr->sh_name);
|
|
+ return strcmp(name, data->name) == 0;
|
|
+}
|
|
|
|
+int
|
|
+elf_get_section_named(struct ltelf *lte, const char *name,
|
|
+ Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr)
|
|
+{
|
|
+ struct section_named_data data = {
|
|
+ .lte = lte,
|
|
+ .name = name,
|
|
+ };
|
|
+ return elf_get_section_if(lte, tgt_sec, tgt_shdr,
|
|
+ &name_p, &data);
|
|
+}
|
|
+
|
|
+static int
|
|
+need_data(Elf_Data *data, GElf_Xword offset, GElf_Xword size)
|
|
+{
|
|
+ assert(data != NULL);
|
|
+ if (data->d_size < size || offset > data->d_size - size) {
|
|
+ debug(1, "Not enough data to read %zd-byte value"
|
|
+ " at offset %zd.", size, offset);
|
|
+ return -1;
|
|
+ }
|
|
return 0;
|
|
}
|
|
|
|
+#define DEF_READER(NAME, SIZE) \
|
|
+ int \
|
|
+ NAME(Elf_Data *data, GElf_Xword offset, uint##SIZE##_t *retp) \
|
|
+ { \
|
|
+ if (!need_data(data, offset, SIZE / 8) < 0) \
|
|
+ return -1; \
|
|
+ \
|
|
+ if (data->d_buf == NULL) /* NODATA section */ { \
|
|
+ *retp = 0; \
|
|
+ return 0; \
|
|
+ } \
|
|
+ \
|
|
+ union { \
|
|
+ uint##SIZE##_t dst; \
|
|
+ char buf[0]; \
|
|
+ } u; \
|
|
+ memcpy(u.buf, data->d_buf + offset, sizeof(u.dst)); \
|
|
+ *retp = u.dst; \
|
|
+ return 0; \
|
|
+ }
|
|
+
|
|
+DEF_READER(elf_read_u16, 16)
|
|
+DEF_READER(elf_read_u32, 32)
|
|
+DEF_READER(elf_read_u64, 64)
|
|
+
|
|
+#undef DEF_READER
|
|
+
|
|
int
|
|
open_elf(struct ltelf *lte, const char *filename)
|
|
{
|
|
@@ -150,17 +224,22 @@ open_elf(struct ltelf *lte, const char *filename)
|
|
lte->elf = elf_begin(lte->fd, ELF_C_READ, NULL);
|
|
#endif
|
|
|
|
- if (lte->elf == NULL || elf_kind(lte->elf) != ELF_K_ELF)
|
|
- error(EXIT_FAILURE, 0, "Can't open ELF file \"%s\"", filename);
|
|
+ if (lte->elf == NULL || elf_kind(lte->elf) != ELF_K_ELF) {
|
|
+ fprintf(stderr, "\"%s\" is not an ELF file\n", filename);
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
- if (gelf_getehdr(lte->elf, <e->ehdr) == NULL)
|
|
- error(EXIT_FAILURE, 0, "Can't read ELF header of \"%s\"",
|
|
- filename);
|
|
+ if (gelf_getehdr(lte->elf, <e->ehdr) == NULL) {
|
|
+ fprintf(stderr, "can't read ELF header of \"%s\": %s\n",
|
|
+ filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
- if (lte->ehdr.e_type != ET_EXEC && lte->ehdr.e_type != ET_DYN)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "\"%s\" is not an ELF executable nor shared library",
|
|
- filename);
|
|
+ if (lte->ehdr.e_type != ET_EXEC && lte->ehdr.e_type != ET_DYN) {
|
|
+ fprintf(stderr, "\"%s\" is neither an ELF executable"
|
|
+ " nor a shared library\n", filename);
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
if ((lte->ehdr.e_ident[EI_CLASS] != LT_ELFCLASS
|
|
|| lte->ehdr.e_machine != LT_ELF_MACHINE)
|
|
@@ -172,18 +251,58 @@ open_elf(struct ltelf *lte, const char *filename)
|
|
&& (lte->ehdr.e_ident[EI_CLASS] != LT_ELFCLASS3
|
|
|| lte->ehdr.e_machine != LT_ELF_MACHINE3)
|
|
#endif
|
|
- )
|
|
- error(EXIT_FAILURE, 0,
|
|
- "\"%s\" is ELF from incompatible architecture", filename);
|
|
+ ) {
|
|
+ fprintf(stderr,
|
|
+ "\"%s\" is ELF from incompatible architecture\n",
|
|
+ filename);
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
return 0;
|
|
}
|
|
|
|
-int
|
|
-do_init_elf(struct ltelf *lte, const char *filename) {
|
|
+static void
|
|
+read_symbol_table(struct ltelf *lte, const char *filename,
|
|
+ Elf_Scn *scn, GElf_Shdr *shdr, const char *name,
|
|
+ Elf_Data **datap, size_t *countp, const char **strsp)
|
|
+{
|
|
+ *datap = elf_getdata(scn, NULL);
|
|
+ *countp = shdr->sh_size / shdr->sh_entsize;
|
|
+ if ((*datap == NULL || elf_getdata(scn, *datap) != NULL)
|
|
+ && options.static_filter != NULL) {
|
|
+ fprintf(stderr, "Couldn't get data of section"
|
|
+ " %s from \"%s\": %s\n",
|
|
+ name, filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
+
|
|
+ scn = elf_getscn(lte->elf, shdr->sh_link);
|
|
+ GElf_Shdr shdr2;
|
|
+ if (scn == NULL || gelf_getshdr(scn, &shdr2) == NULL) {
|
|
+ fprintf(stderr, "Couldn't get header of section"
|
|
+ " #%d from \"%s\": %s\n",
|
|
+ shdr2.sh_link, filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
+
|
|
+ Elf_Data *data = elf_getdata(scn, NULL);
|
|
+ if (data == NULL || elf_getdata(scn, data) != NULL
|
|
+ || shdr2.sh_size != data->d_size || data->d_off) {
|
|
+ fprintf(stderr, "Couldn't get data of section"
|
|
+ " #%d from \"%s\": %s\n",
|
|
+ shdr2.sh_link, filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
+
|
|
+ *strsp = data->d_buf;
|
|
+}
|
|
+
|
|
+static int
|
|
+do_init_elf(struct ltelf *lte, const char *filename, GElf_Addr bias)
|
|
+{
|
|
int i;
|
|
GElf_Addr relplt_addr = 0;
|
|
- size_t relplt_size = 0;
|
|
+ GElf_Addr soname_offset = 0;
|
|
|
|
debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename);
|
|
debug(1, "Reading ELF from %s...", filename);
|
|
@@ -191,8 +310,25 @@ do_init_elf(struct ltelf *lte, const char *filename) {
|
|
if (open_elf(lte, filename) < 0)
|
|
return -1;
|
|
|
|
- Elf_Data *plt_data = NULL;
|
|
- GElf_Addr ppcgot = 0;
|
|
+ /* Find out the base address. */
|
|
+ {
|
|
+ GElf_Phdr phdr;
|
|
+ for (i = 0; gelf_getphdr (lte->elf, i, &phdr) != NULL; ++i) {
|
|
+ if (phdr.p_type == PT_LOAD) {
|
|
+ lte->base_addr = phdr.p_vaddr + bias;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (lte->base_addr == 0) {
|
|
+ fprintf(stderr, "Couldn't determine base address of %s\n",
|
|
+ filename);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ lte->bias = bias;
|
|
+ lte->entry_addr = lte->ehdr.e_entry + lte->bias;
|
|
|
|
for (i = 1; i < lte->ehdr.e_shnum; ++i) {
|
|
Elf_Scn *scn;
|
|
@@ -200,68 +336,29 @@ do_init_elf(struct ltelf *lte, const char *filename) {
|
|
const char *name;
|
|
|
|
scn = elf_getscn(lte->elf, i);
|
|
- if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get section header from \"%s\"",
|
|
- filename);
|
|
+ if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {
|
|
+ fprintf(stderr, "Couldn't get section #%d from"
|
|
+ " \"%s\": %s\n", i, filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
name = elf_strptr(lte->elf, lte->ehdr.e_shstrndx, shdr.sh_name);
|
|
- if (name == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get section header from \"%s\"",
|
|
- filename);
|
|
+ if (name == NULL) {
|
|
+ fprintf(stderr, "Couldn't get name of section #%d from"
|
|
+ " \"%s\": %s\n", i, filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
if (shdr.sh_type == SHT_SYMTAB) {
|
|
- Elf_Data *data;
|
|
-
|
|
- lte->symtab = elf_getdata(scn, NULL);
|
|
- lte->symtab_count = shdr.sh_size / shdr.sh_entsize;
|
|
- if ((lte->symtab == NULL
|
|
- || elf_getdata(scn, lte->symtab) != NULL)
|
|
- && opt_x != NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .symtab data from \"%s\"",
|
|
- filename);
|
|
-
|
|
- scn = elf_getscn(lte->elf, shdr.sh_link);
|
|
- if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get section header from \"%s\"",
|
|
- filename);
|
|
+ read_symbol_table(lte, filename,
|
|
+ scn, &shdr, name, <e->symtab,
|
|
+ <e->symtab_count, <e->strtab);
|
|
|
|
- data = elf_getdata(scn, NULL);
|
|
- if (data == NULL || elf_getdata(scn, data) != NULL
|
|
- || shdr.sh_size != data->d_size || data->d_off)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .strtab data from \"%s\"",
|
|
- filename);
|
|
-
|
|
- lte->strtab = data->d_buf;
|
|
} else if (shdr.sh_type == SHT_DYNSYM) {
|
|
- Elf_Data *data;
|
|
-
|
|
- lte->dynsym = elf_getdata(scn, NULL);
|
|
- lte->dynsym_count = shdr.sh_size / shdr.sh_entsize;
|
|
- if (lte->dynsym == NULL
|
|
- || elf_getdata(scn, lte->dynsym) != NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .dynsym data from \"%s\"",
|
|
- filename);
|
|
+ read_symbol_table(lte, filename,
|
|
+ scn, &shdr, name, <e->dynsym,
|
|
+ <e->dynsym_count, <e->dynstr);
|
|
|
|
- scn = elf_getscn(lte->elf, shdr.sh_link);
|
|
- if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get section header from \"%s\"",
|
|
- filename);
|
|
-
|
|
- data = elf_getdata(scn, NULL);
|
|
- if (data == NULL || elf_getdata(scn, data) != NULL
|
|
- || shdr.sh_size != data->d_size || data->d_off)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .dynstr data from \"%s\"",
|
|
- filename);
|
|
-
|
|
- lte->dynstr = data->d_buf;
|
|
} else if (shdr.sh_type == SHT_DYNAMIC) {
|
|
Elf_Data *data;
|
|
size_t j;
|
|
@@ -270,135 +367,39 @@ do_init_elf(struct ltelf *lte, const char *filename) {
|
|
lte->dyn_sz = shdr.sh_size;
|
|
|
|
data = elf_getdata(scn, NULL);
|
|
- if (data == NULL || elf_getdata(scn, data) != NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .dynamic data from \"%s\"",
|
|
- filename);
|
|
+ if (data == NULL || elf_getdata(scn, data) != NULL) {
|
|
+ fprintf(stderr, "Couldn't get .dynamic data"
|
|
+ " from \"%s\": %s\n",
|
|
+ filename, strerror(errno));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) {
|
|
GElf_Dyn dyn;
|
|
|
|
- if (gelf_getdyn(data, j, &dyn) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .dynamic data from \"%s\"",
|
|
- filename);
|
|
-#ifdef __mips__
|
|
-/**
|
|
- MIPS ABI Supplement:
|
|
-
|
|
- DT_PLTGOT This member holds the address of the .got section.
|
|
-
|
|
- DT_MIPS_SYMTABNO This member holds the number of entries in the
|
|
- .dynsym section.
|
|
-
|
|
- DT_MIPS_LOCAL_GOTNO This member holds the number of local global
|
|
- offset table entries.
|
|
-
|
|
- DT_MIPS_GOTSYM This member holds the index of the first dyamic
|
|
- symbol table entry that corresponds to an entry in the gobal offset
|
|
- table.
|
|
-
|
|
- */
|
|
- if(dyn.d_tag==DT_PLTGOT){
|
|
- lte->pltgot_addr=dyn.d_un.d_ptr;
|
|
- }
|
|
- if(dyn.d_tag==DT_MIPS_LOCAL_GOTNO){
|
|
- lte->mips_local_gotno=dyn.d_un.d_val;
|
|
+ if (gelf_getdyn(data, j, &dyn) == NULL) {
|
|
+ fprintf(stderr, "Couldn't get .dynamic"
|
|
+ " data from \"%s\": %s\n",
|
|
+ filename, strerror(errno));
|
|
+ exit(EXIT_FAILURE);
|
|
}
|
|
- if(dyn.d_tag==DT_MIPS_GOTSYM){
|
|
- lte->mips_gotsym=dyn.d_un.d_val;
|
|
- }
|
|
-#endif // __mips__
|
|
if (dyn.d_tag == DT_JMPREL)
|
|
relplt_addr = dyn.d_un.d_ptr;
|
|
else if (dyn.d_tag == DT_PLTRELSZ)
|
|
- relplt_size = dyn.d_un.d_val;
|
|
- else if (dyn.d_tag == DT_PPC_GOT) {
|
|
- ppcgot = dyn.d_un.d_val;
|
|
- debug(1, "ppcgot %#" PRIx64, ppcgot);
|
|
- }
|
|
- }
|
|
- } else if (shdr.sh_type == SHT_HASH) {
|
|
- Elf_Data *data;
|
|
- size_t j;
|
|
-
|
|
- lte->hash_type = SHT_HASH;
|
|
-
|
|
- data = elf_getdata(scn, NULL);
|
|
- if (data == NULL || elf_getdata(scn, data) != NULL
|
|
- || data->d_off || data->d_size != shdr.sh_size)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .hash data from \"%s\"",
|
|
- filename);
|
|
-
|
|
- if (shdr.sh_entsize == 4) {
|
|
- /* Standard conforming ELF. */
|
|
- if (data->d_type != ELF_T_WORD)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .hash data from \"%s\"",
|
|
- filename);
|
|
- lte->hash = (Elf32_Word *) data->d_buf;
|
|
- } else if (shdr.sh_entsize == 8) {
|
|
- /* Alpha or s390x. */
|
|
- Elf32_Word *dst, *src;
|
|
- size_t hash_count = data->d_size / 8;
|
|
-
|
|
- lte->hash = (Elf32_Word *)
|
|
- malloc(hash_count * sizeof(Elf32_Word));
|
|
- if (lte->hash == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't convert .hash section from \"%s\"",
|
|
- filename);
|
|
- lte->lte_flags |= LTE_HASH_MALLOCED;
|
|
- dst = lte->hash;
|
|
- src = (Elf32_Word *) data->d_buf;
|
|
- if ((data->d_type == ELF_T_WORD
|
|
- && __BYTE_ORDER == __BIG_ENDIAN)
|
|
- || (data->d_type == ELF_T_XWORD
|
|
- && lte->ehdr.e_ident[EI_DATA] ==
|
|
- ELFDATA2MSB))
|
|
- ++src;
|
|
- for (j = 0; j < hash_count; ++j, src += 2)
|
|
- *dst++ = *src;
|
|
- } else
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Unknown .hash sh_entsize in \"%s\"",
|
|
- filename);
|
|
- } else if (shdr.sh_type == SHT_GNU_HASH
|
|
- && lte->hash == NULL) {
|
|
- Elf_Data *data;
|
|
-
|
|
- lte->hash_type = SHT_GNU_HASH;
|
|
-
|
|
- if (shdr.sh_entsize != 0
|
|
- && shdr.sh_entsize != 4) {
|
|
- error(EXIT_FAILURE, 0,
|
|
- ".gnu.hash sh_entsize in \"%s\" "
|
|
- "should be 4, but is %#" PRIx64,
|
|
- filename, shdr.sh_entsize);
|
|
+ lte->relplt_size = dyn.d_un.d_val;
|
|
+ else if (dyn.d_tag == DT_SONAME)
|
|
+ soname_offset = dyn.d_un.d_val;
|
|
}
|
|
-
|
|
- data = loaddata(scn, &shdr);
|
|
- if (data == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .gnu.hash data from \"%s\"",
|
|
- filename);
|
|
-
|
|
- lte->hash = (Elf32_Word *) data->d_buf;
|
|
} else if (shdr.sh_type == SHT_PROGBITS
|
|
|| shdr.sh_type == SHT_NOBITS) {
|
|
if (strcmp(name, ".plt") == 0) {
|
|
lte->plt_addr = shdr.sh_addr;
|
|
lte->plt_size = shdr.sh_size;
|
|
- if (shdr.sh_flags & SHF_EXECINSTR) {
|
|
- lte->lte_flags |= LTE_PLT_EXECUTABLE;
|
|
- }
|
|
- if (lte->ehdr.e_machine == EM_PPC) {
|
|
- plt_data = loaddata(scn, &shdr);
|
|
- if (plt_data == NULL)
|
|
- fprintf(stderr,
|
|
- "Can't load .plt data\n");
|
|
- }
|
|
+ lte->plt_data = elf_loaddata(scn, &shdr);
|
|
+ if (lte->plt_data == NULL)
|
|
+ fprintf(stderr,
|
|
+ "Can't load .plt data\n");
|
|
+ lte->plt_flags = shdr.sh_flags;
|
|
}
|
|
#ifdef ARCH_SUPPORTS_OPD
|
|
else if (strcmp(name, ".opd") == 0) {
|
|
@@ -410,449 +411,376 @@ do_init_elf(struct ltelf *lte, const char *filename) {
|
|
}
|
|
}
|
|
|
|
- if (lte->dynsym == NULL || lte->dynstr == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't find .dynsym or .dynstr in \"%s\"", filename);
|
|
+ if (lte->dynsym == NULL || lte->dynstr == NULL) {
|
|
+ fprintf(stderr, "Couldn't find .dynsym or .dynstr in \"%s\"\n",
|
|
+ filename);
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
if (!relplt_addr || !lte->plt_addr) {
|
|
debug(1, "%s has no PLT relocations", filename);
|
|
lte->relplt = NULL;
|
|
lte->relplt_count = 0;
|
|
- } else if (relplt_size == 0) {
|
|
+ } else if (lte->relplt_size == 0) {
|
|
debug(1, "%s has unknown PLT size", filename);
|
|
lte->relplt = NULL;
|
|
lte->relplt_count = 0;
|
|
} else {
|
|
- if (lte->ehdr.e_machine == EM_PPC) {
|
|
- GElf_Addr glink_vma
|
|
- = get_glink_vma(lte, ppcgot, plt_data);
|
|
-
|
|
- assert (relplt_size % 12 == 0);
|
|
- size_t count = relplt_size / 12; // size of RELA entry
|
|
- lte->plt_stub_vma = glink_vma
|
|
- - (GElf_Addr)count * PPC_PLT_STUB_SIZE;
|
|
- debug(1, "stub_vma is %#" PRIx64, lte->plt_stub_vma);
|
|
- }
|
|
|
|
for (i = 1; i < lte->ehdr.e_shnum; ++i) {
|
|
Elf_Scn *scn;
|
|
GElf_Shdr shdr;
|
|
|
|
scn = elf_getscn(lte->elf, i);
|
|
- if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get section header from \"%s\"",
|
|
- filename);
|
|
+ if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {
|
|
+ fprintf(stderr, "Couldn't get section header"
|
|
+ " from \"%s\": %s\n",
|
|
+ filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
if (shdr.sh_addr == relplt_addr
|
|
- && shdr.sh_size == relplt_size) {
|
|
+ && shdr.sh_size == lte->relplt_size) {
|
|
lte->relplt = elf_getdata(scn, NULL);
|
|
lte->relplt_count =
|
|
shdr.sh_size / shdr.sh_entsize;
|
|
if (lte->relplt == NULL
|
|
- || elf_getdata(scn, lte->relplt) != NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get .rel*.plt data from \"%s\"",
|
|
- filename);
|
|
+ || elf_getdata(scn, lte->relplt) != NULL) {
|
|
+ fprintf(stderr, "Couldn't get .rel*.plt"
|
|
+ " data from \"%s\": %s\n",
|
|
+ filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
break;
|
|
}
|
|
}
|
|
|
|
- if (i == lte->ehdr.e_shnum)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't find .rel*.plt section in \"%s\"",
|
|
- filename);
|
|
+ if (i == lte->ehdr.e_shnum) {
|
|
+ fprintf(stderr,
|
|
+ "Couldn't find .rel*.plt section in \"%s\"\n",
|
|
+ filename);
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
debug(1, "%s %zd PLT relocations", filename, lte->relplt_count);
|
|
}
|
|
+
|
|
+ if (soname_offset != 0)
|
|
+ lte->soname = lte->dynstr + soname_offset;
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
+/* XXX temporarily non-static */
|
|
void
|
|
do_close_elf(struct ltelf *lte) {
|
|
debug(DEBUG_FUNCTION, "do_close_elf()");
|
|
- if (lte->lte_flags & LTE_HASH_MALLOCED)
|
|
- free((char *)lte->hash);
|
|
+ arch_elf_destroy(lte);
|
|
elf_end(lte->elf);
|
|
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)
|
|
+static int
|
|
+populate_plt(struct Process *proc, const char *filename,
|
|
+ struct ltelf *lte, struct library *lib)
|
|
{
|
|
- struct library_symbol *s;
|
|
-
|
|
- debug(DEBUG_FUNCTION, "add_library_symbol()");
|
|
-
|
|
- s = create_library_symbol(name, addr);
|
|
- if (s == NULL)
|
|
- error(EXIT_FAILURE, errno, "add_library_symbol failed");
|
|
+ size_t i;
|
|
+ for (i = 0; i < lte->relplt_count; ++i) {
|
|
+ GElf_Rel rel;
|
|
+ GElf_Rela rela;
|
|
+ GElf_Sym sym;
|
|
+ void *ret;
|
|
|
|
- s->needs_init = 1;
|
|
- s->is_weak = is_weak;
|
|
- s->plt_type = type_of_plt;
|
|
+ if (lte->relplt->d_type == ELF_T_REL) {
|
|
+ ret = gelf_getrel(lte->relplt, i, &rel);
|
|
+ rela.r_offset = rel.r_offset;
|
|
+ rela.r_info = rel.r_info;
|
|
+ rela.r_addend = 0;
|
|
+ } else {
|
|
+ ret = gelf_getrela(lte->relplt, i, &rela);
|
|
+ }
|
|
|
|
- s->next = *library_symbolspp;
|
|
- *library_symbolspp = s;
|
|
+ if (ret == NULL
|
|
+ || ELF64_R_SYM(rela.r_info) >= lte->dynsym_count
|
|
+ || gelf_getsym(lte->dynsym, ELF64_R_SYM(rela.r_info),
|
|
+ &sym) == NULL) {
|
|
+ fprintf(stderr,
|
|
+ "Couldn't get relocation from \"%s\": %s\n",
|
|
+ filename, elf_errmsg(-1));
|
|
+ exit(EXIT_FAILURE);
|
|
+ }
|
|
|
|
- debug(2, "addr: %p, symbol: \"%s\"", (void *)(uintptr_t) addr, name);
|
|
-}
|
|
+ char const *name = lte->dynstr + sym.st_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;
|
|
+ if (!filter_matches_symbol(options.plt_filter, name, lib))
|
|
+ continue;
|
|
|
|
- return copy;
|
|
+ struct library_symbol *libsym = NULL;
|
|
+ switch (arch_elf_add_plt_entry(proc, lte, name,
|
|
+ &rela, i, &libsym)) {
|
|
+ case plt_default:
|
|
+ if (default_elf_add_plt_entry(proc, lte, name,
|
|
+ &rela, i, &libsym) < 0)
|
|
+ /* fall-through */
|
|
+ case plt_fail:
|
|
+ return -1;
|
|
+ /* fall-through */
|
|
+ case plt_ok:
|
|
+ if (libsym != NULL)
|
|
+ library_add_symbol(lib, libsym);
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
}
|
|
|
|
-void
|
|
-destroy_library_symbol(struct library_symbol * sym)
|
|
-{
|
|
- free(sym);
|
|
-}
|
|
+/* When -x rules result in request to trace several aliases, we only
|
|
+ * want to add such symbol once. The only way that those symbols
|
|
+ * differ in is their name, e.g. in glibc you have __GI___libc_free,
|
|
+ * __cfree, __free, __libc_free, cfree and free all defined on the
|
|
+ * same address. So instead we keep this unique symbol struct for
|
|
+ * each address, and replace name in libsym with a shorter variant if
|
|
+ * we find it. */
|
|
+struct unique_symbol {
|
|
+ target_address_t addr;
|
|
+ struct library_symbol *libsym;
|
|
+};
|
|
|
|
-void
|
|
-destroy_library_symbol_chain(struct library_symbol * sym)
|
|
+static int
|
|
+unique_symbol_cmp(const void *key, const void *val)
|
|
{
|
|
- 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) {
|
|
- unsigned long h = 5381;
|
|
- const unsigned char *string = (const unsigned char *)name;
|
|
- unsigned char c;
|
|
- for (c = *string; c; c = *++string)
|
|
- h = h * 33 + c;
|
|
- return h & 0xffffffff;
|
|
+ const struct unique_symbol *sym_key = key;
|
|
+ const struct unique_symbol *sym_val = val;
|
|
+ return sym_key->addr != sym_val->addr;
|
|
}
|
|
|
|
static int
|
|
-symbol_matches(struct ltelf *lte, size_t lte_i, GElf_Sym *sym,
|
|
- size_t symidx, const char *name)
|
|
+populate_this_symtab(struct Process *proc, const char *filename,
|
|
+ struct ltelf *lte, struct library *lib,
|
|
+ Elf_Data *symtab, const char *strtab, size_t size)
|
|
{
|
|
- GElf_Sym tmp_sym;
|
|
- GElf_Sym *tmp;
|
|
-
|
|
- tmp = (sym) ? (sym) : (&tmp_sym);
|
|
-
|
|
- if (gelf_getsym(lte[lte_i].dynsym, symidx, tmp) == NULL)
|
|
- error(EXIT_FAILURE, 0, "Couldn't get symbol from .dynsym");
|
|
- else {
|
|
- tmp->st_value += lte[lte_i].base_addr;
|
|
- debug(2, "symbol found: %s, %zd, %#" PRIx64,
|
|
- name, lte_i, tmp->st_value);
|
|
+ /* Using sorted array would be arguably better, but this
|
|
+ * should be well enough for the number of symbols that we
|
|
+ * typically deal with. */
|
|
+ size_t num_symbols = 0;
|
|
+ struct unique_symbol *symbols = malloc(sizeof(*symbols) * size);
|
|
+ if (symbols == NULL) {
|
|
+ fprintf(stderr, "couldn't insert symbols for -x: %s\n",
|
|
+ strerror(errno));
|
|
+ return -1;
|
|
}
|
|
- return tmp->st_value != 0
|
|
- && tmp->st_shndx != SHN_UNDEF
|
|
- && strcmp(name, lte[lte_i].dynstr + tmp->st_name) == 0;
|
|
-}
|
|
|
|
-int
|
|
-in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym) {
|
|
+ GElf_Word secflags[lte->ehdr.e_shnum];
|
|
size_t i;
|
|
- unsigned long hash;
|
|
- unsigned long gnu_hash;
|
|
-
|
|
- if (!count)
|
|
- return 1;
|
|
-
|
|
-#ifdef ELF_HASH_TAKES_SIGNED_CHAR
|
|
- hash = elf_hash(name);
|
|
-#else
|
|
- hash = elf_hash((const unsigned char *)name);
|
|
-#endif
|
|
- gnu_hash = private_elf_gnu_hash(name);
|
|
-
|
|
- for (i = 0; i < count; ++i) {
|
|
- if (lte[i].hash == NULL)
|
|
+ for (i = 1; i < lte->ehdr.e_shnum; ++i) {
|
|
+ Elf_Scn *scn = elf_getscn(lte->elf, i);
|
|
+ if (scn == NULL)
|
|
continue;
|
|
-
|
|
- if (lte[i].hash_type == SHT_GNU_HASH) {
|
|
- Elf32_Word * hashbase = lte[i].hash;
|
|
- Elf32_Word nbuckets = *hashbase++;
|
|
- Elf32_Word symbias = *hashbase++;
|
|
- Elf32_Word bitmask_nwords = *hashbase++;
|
|
- Elf32_Word * buckets;
|
|
- Elf32_Word * chain_zero;
|
|
- Elf32_Word bucket;
|
|
-
|
|
- // +1 for skipped `shift'
|
|
- hashbase += lte[i].ehdr.e_ident[EI_CLASS] * bitmask_nwords + 1;
|
|
- buckets = hashbase;
|
|
- hashbase += nbuckets;
|
|
- chain_zero = hashbase - symbias;
|
|
- bucket = buckets[gnu_hash % nbuckets];
|
|
-
|
|
- if (bucket != 0) {
|
|
- const Elf32_Word *hasharr = &chain_zero[bucket];
|
|
- do
|
|
- if ((*hasharr & ~1u) == (gnu_hash & ~1u)) {
|
|
- int symidx = hasharr - chain_zero;
|
|
- if (symbol_matches(lte, i,
|
|
- sym, symidx,
|
|
- name))
|
|
- return 1;
|
|
- }
|
|
- while ((*hasharr++ & 1u) == 0);
|
|
- }
|
|
- } else {
|
|
- Elf32_Word nbuckets, symndx;
|
|
- Elf32_Word *buckets, *chain;
|
|
- nbuckets = lte[i].hash[0];
|
|
- buckets = <e[i].hash[2];
|
|
- chain = <e[i].hash[2 + nbuckets];
|
|
-
|
|
- for (symndx = buckets[hash % nbuckets];
|
|
- symndx != STN_UNDEF; symndx = chain[symndx])
|
|
- if (symbol_matches(lte, i, sym, symndx, name))
|
|
- return 1;
|
|
- }
|
|
+ GElf_Shdr shdr;
|
|
+ if (gelf_getshdr(scn, &shdr) == NULL)
|
|
+ continue;
|
|
+ secflags[i] = shdr.sh_flags;
|
|
}
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static GElf_Addr
|
|
-opd2addr(struct ltelf *lte, GElf_Addr addr) {
|
|
-#ifdef ARCH_SUPPORTS_OPD
|
|
- unsigned long base, offset;
|
|
-
|
|
- if (!lte->opd)
|
|
- return addr;
|
|
-
|
|
- base = (unsigned long)lte->opd->d_buf;
|
|
- offset = (unsigned long)addr - (unsigned long)lte->opd_addr;
|
|
- if (offset > lte->opd_size)
|
|
- error(EXIT_FAILURE, 0, "static plt not in .opd");
|
|
-
|
|
- return *(GElf_Addr*)(base + offset);
|
|
-#else //!ARCH_SUPPORTS_OPD
|
|
- return addr;
|
|
-#endif
|
|
-}
|
|
|
|
-struct library_symbol *
|
|
-read_elf(Process *proc, GElf_Addr *entryp)
|
|
-{
|
|
- struct ltelf lte[MAX_LIBRARIES + 1];
|
|
- size_t i;
|
|
- struct opt_x_t *xptr;
|
|
- struct opt_x_t *opt_x_loc = opt_x;
|
|
- struct library_symbol **lib_tail = NULL;
|
|
- int exit_out = 0;
|
|
- int count = 0;
|
|
+ size_t lib_len = strlen(lib->soname);
|
|
+ for (i = 0; i < size; ++i) {
|
|
+ GElf_Sym sym;
|
|
+ if (gelf_getsym(symtab, i, &sym) == NULL) {
|
|
+ fail:
|
|
+ fprintf(stderr,
|
|
+ "couldn't get symbol #%zd from %s: %s\n",
|
|
+ i, filename, elf_errmsg(-1));
|
|
+ continue;
|
|
+ }
|
|
|
|
- debug(DEBUG_FUNCTION, "read_elf(file=%s)", proc->filename);
|
|
+ /* XXX support IFUNC as well. */
|
|
+ if (GELF_ST_TYPE(sym.st_info) != STT_FUNC
|
|
+ || sym.st_value == 0)
|
|
+ continue;
|
|
|
|
- memset(lte, 0, sizeof(*lte));
|
|
- library_symbols = NULL;
|
|
- library_num = 0;
|
|
- proc->libdl_hooked = 0;
|
|
+ const char *orig_name = strtab + sym.st_name;
|
|
+ const char *version = strchr(orig_name, '@');
|
|
+ size_t len = version != NULL ? (assert(version > orig_name),
|
|
+ (size_t)(version - orig_name))
|
|
+ : strlen(orig_name);
|
|
+ char name[len + 1];
|
|
+ memcpy(name, orig_name, len);
|
|
+ name[len] = 0;
|
|
|
|
- if (do_init_elf(lte, proc->filename))
|
|
- return NULL;
|
|
+ if (!filter_matches_symbol(options.static_filter, name, lib))
|
|
+ continue;
|
|
|
|
- memcpy(&main_lte, lte, sizeof(struct ltelf));
|
|
+ target_address_t addr = (target_address_t)
|
|
+ (uintptr_t)(sym.st_value + lte->bias);
|
|
+ target_address_t naddr;
|
|
+
|
|
+ /* On arches that support OPD, the value of typical
|
|
+ * function symbol will be a pointer to .opd, but some
|
|
+ * will point directly to .text. We don't want to
|
|
+ * translate those. */
|
|
+ if (secflags[sym.st_shndx] & SHF_EXECINSTR) {
|
|
+ naddr = addr;
|
|
+ } else if (arch_translate_address(lte, addr, &naddr) < 0) {
|
|
+ fprintf(stderr,
|
|
+ "couldn't translate address of %s@%s: %s\n",
|
|
+ name, lib->soname, strerror(errno));
|
|
+ continue;
|
|
+ }
|
|
|
|
- if (opt_p && opt_p->pid > 0) {
|
|
- linkmap_init(proc, lte);
|
|
- proc->libdl_hooked = 1;
|
|
- }
|
|
+ char *full_name;
|
|
+ if (lib->type != LT_LIBTYPE_MAIN) {
|
|
+ full_name = malloc(strlen(name) + 1 + lib_len + 1);
|
|
+ if (full_name == NULL)
|
|
+ goto fail;
|
|
+ sprintf(full_name, "%s@%s", name, lib->soname);
|
|
+ } else {
|
|
+ full_name = strdup(name);
|
|
+ if (full_name == NULL)
|
|
+ goto fail;
|
|
+ }
|
|
|
|
- proc->e_machine = lte->ehdr.e_machine;
|
|
+ /* Look whether we already have a symbol for this
|
|
+ * address. If not, add this one. */
|
|
+ struct unique_symbol key = { naddr, NULL };
|
|
+ struct unique_symbol *unique
|
|
+ = lsearch(&key, symbols, &num_symbols,
|
|
+ sizeof(*symbols), &unique_symbol_cmp);
|
|
+
|
|
+ if (unique->libsym == NULL) {
|
|
+ struct library_symbol *libsym = malloc(sizeof(*libsym));
|
|
+ if (libsym == NULL
|
|
+ || library_symbol_init(libsym, naddr, full_name,
|
|
+ 1, LS_TOPLT_NONE) < 0) {
|
|
+ --num_symbols;
|
|
+ goto fail;
|
|
+ }
|
|
+ unique->libsym = libsym;
|
|
+ unique->addr = naddr;
|
|
|
|
- for (i = 0; i < library_num; ++i) {
|
|
- if (do_init_elf(<e[i + 1], library[i]))
|
|
- error(EXIT_FAILURE, errno, "Can't open \"%s\"",
|
|
- library[i]);
|
|
- }
|
|
+ } else if (strlen(full_name) < strlen(unique->libsym->name)) {
|
|
+ library_symbol_set_name(unique->libsym, full_name, 1);
|
|
|
|
- if (!options.no_plt) {
|
|
-#ifdef __mips__
|
|
- // MIPS doesn't use the PLT and the GOT entries get changed
|
|
- // on startup.
|
|
- for(i=lte->mips_gotsym; i<lte->dynsym_count;i++){
|
|
- GElf_Sym sym;
|
|
- const char *name;
|
|
- GElf_Addr addr = arch_plt_sym_val(lte, i, 0);
|
|
- if (gelf_getsym(lte->dynsym, i, &sym) == NULL){
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get relocation from \"%s\"",
|
|
- proc->filename);
|
|
- }
|
|
- name=lte->dynstr+sym.st_name;
|
|
- if(ELF64_ST_TYPE(sym.st_info) != STT_FUNC){
|
|
- debug(2,"sym %s not a function",name);
|
|
- continue;
|
|
- }
|
|
- add_library_symbol(addr, name, &library_symbols, 0,
|
|
- ELF64_ST_BIND(sym.st_info) != 0);
|
|
- if (!lib_tail)
|
|
- lib_tail = &(library_symbols->next);
|
|
+ } else {
|
|
+ free(full_name);
|
|
}
|
|
-#else
|
|
- for (i = 0; i < lte->relplt_count; ++i) {
|
|
- GElf_Rel rel;
|
|
- GElf_Rela rela;
|
|
- GElf_Sym sym;
|
|
- GElf_Addr addr;
|
|
- void *ret;
|
|
- const char *name;
|
|
-
|
|
- if (lte->relplt->d_type == ELF_T_REL) {
|
|
- ret = gelf_getrel(lte->relplt, i, &rel);
|
|
- rela.r_offset = rel.r_offset;
|
|
- rela.r_info = rel.r_info;
|
|
- rela.r_addend = 0;
|
|
- } else
|
|
- ret = gelf_getrela(lte->relplt, i, &rela);
|
|
-
|
|
- if (ret == NULL
|
|
- || ELF64_R_SYM(rela.r_info) >= lte->dynsym_count
|
|
- || gelf_getsym(lte->dynsym, ELF64_R_SYM(rela.r_info),
|
|
- &sym) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get relocation from \"%s\"",
|
|
- proc->filename);
|
|
-
|
|
- name = lte->dynstr + sym.st_name;
|
|
- count = library_num ? library_num+1 : 0;
|
|
-
|
|
- if (in_load_libraries(name, lte, count, NULL)) {
|
|
- enum toplt pltt;
|
|
- if (sym.st_value == 0 && lte->plt_stub_vma != 0) {
|
|
- pltt = LS_TOPLT_EXEC;
|
|
- addr = lte->plt_stub_vma + PPC_PLT_STUB_SIZE * i;
|
|
- }
|
|
- else {
|
|
- pltt = PLTS_ARE_EXECUTABLE(lte)
|
|
- ? LS_TOPLT_EXEC : LS_TOPLT_POINT;
|
|
- addr = arch_plt_sym_val(lte, i, &rela);
|
|
- }
|
|
+ }
|
|
|
|
- add_library_symbol(addr, name, &library_symbols, pltt,
|
|
- ELF64_ST_BIND(sym.st_info) == STB_WEAK);
|
|
- if (!lib_tail)
|
|
- lib_tail = &(library_symbols->next);
|
|
- }
|
|
- }
|
|
-#endif // !__mips__
|
|
- } else {
|
|
- lib_tail = &library_symbols;
|
|
+ for (i = 0; i < num_symbols; ++i) {
|
|
+ assert(symbols[i].libsym != NULL);
|
|
+ library_add_symbol(lib, symbols[i].libsym);
|
|
}
|
|
|
|
- for (i = 0; i < lte->symtab_count; ++i) {
|
|
- GElf_Sym sym;
|
|
- GElf_Addr addr;
|
|
- const char *name;
|
|
+ free(symbols);
|
|
|
|
- if (gelf_getsym(lte->symtab, i, &sym) == NULL)
|
|
- error(EXIT_FAILURE, 0,
|
|
- "Couldn't get symbol from \"%s\"",
|
|
- proc->filename);
|
|
+ return 0;
|
|
+}
|
|
|
|
- name = lte->strtab + sym.st_name;
|
|
- addr = sym.st_value;
|
|
- if (!addr)
|
|
- continue;
|
|
+static int
|
|
+populate_symtab(struct Process *proc, const char *filename,
|
|
+ struct ltelf *lte, struct library *lib)
|
|
+{
|
|
+ if (lte->symtab != NULL && lte->strtab != NULL)
|
|
+ return populate_this_symtab(proc, filename, lte, lib,
|
|
+ lte->symtab, lte->strtab,
|
|
+ lte->symtab_count);
|
|
+ else
|
|
+ return populate_this_symtab(proc, filename, lte, lib,
|
|
+ lte->dynsym, lte->dynstr,
|
|
+ lte->dynsym_count);
|
|
+}
|
|
|
|
- for (xptr = opt_x_loc; xptr; xptr = xptr->next)
|
|
- if (xptr->name && strcmp(xptr->name, name) == 0) {
|
|
- /* FIXME: Should be able to use &library_symbols as above. But
|
|
- when you do, none of the real library symbols cause breaks. */
|
|
- add_library_symbol(opd2addr(lte, addr),
|
|
- name, lib_tail, LS_TOPLT_NONE, 0);
|
|
- xptr->found = 1;
|
|
- break;
|
|
- }
|
|
+int
|
|
+ltelf_read_library(struct library *lib, struct Process *proc,
|
|
+ const char *filename, GElf_Addr bias)
|
|
+{
|
|
+ struct ltelf lte = {};
|
|
+ if (do_init_elf(<e, filename, bias) < 0)
|
|
+ return -1;
|
|
+ if (arch_elf_init(<e, lib) < 0) {
|
|
+ fprintf(stderr, "Backend initialization failed.\n");
|
|
+ return -1;
|
|
}
|
|
|
|
- unsigned found_count = 0;
|
|
+ proc->e_machine = lte.ehdr.e_machine;
|
|
|
|
- for (xptr = opt_x_loc; xptr; xptr = xptr->next) {
|
|
- if (xptr->found)
|
|
- continue;
|
|
+ int status = 0;
|
|
+ if (lib == NULL)
|
|
+ goto fail;
|
|
|
|
- GElf_Sym sym;
|
|
- GElf_Addr addr;
|
|
- if (in_load_libraries(xptr->name, lte, library_num+1, &sym)) {
|
|
- debug(2, "found symbol %s @ %#" PRIx64 ", adding it.",
|
|
- xptr->name, sym.st_value);
|
|
- addr = sym.st_value;
|
|
- if (ELF32_ST_TYPE (sym.st_info) == STT_FUNC) {
|
|
- add_library_symbol(addr, xptr->name, lib_tail, LS_TOPLT_NONE, 0);
|
|
- xptr->found = 1;
|
|
- found_count++;
|
|
- }
|
|
- }
|
|
- if (found_count == opt_x_cnt){
|
|
- debug(2, "done, found everything: %d\n", found_count);
|
|
- break;
|
|
- }
|
|
+ /* Note that we set soname and pathname as soon as they are
|
|
+ * allocated, so in case of further errors, this get released
|
|
+ * when LIB is release, which should happen in the caller when
|
|
+ * we return error. */
|
|
+
|
|
+ if (lib->pathname == NULL) {
|
|
+ char *pathname = strdup(filename);
|
|
+ if (pathname == NULL)
|
|
+ goto fail;
|
|
+ library_set_pathname(lib, pathname, 1);
|
|
}
|
|
|
|
- if (lte->ehdr.e_entry != 0) {
|
|
- *entryp = opd2addr(lte, lte->ehdr.e_entry);
|
|
+ if (lte.soname != NULL) {
|
|
+ char *soname = strdup(lte.soname);
|
|
+ if (soname == NULL)
|
|
+ goto fail;
|
|
+ library_set_soname(lib, soname, 1);
|
|
} else {
|
|
+ const char *soname = rindex(lib->pathname, '/') + 1;
|
|
+ if (soname == NULL)
|
|
+ soname = lib->pathname;
|
|
+ library_set_soname(lib, soname, 0);
|
|
}
|
|
|
|
- for (xptr = opt_x_loc; xptr; xptr = xptr->next)
|
|
- if ( ! xptr->found) {
|
|
- char *badthing = "WARNING";
|
|
-#ifdef PLT_REINITALISATION_BP
|
|
- if (strcmp(xptr->name, PLTs_initialized_by_here) == 0) {
|
|
- if (lte->ehdr.e_entry) {
|
|
- fprintf (stderr, "WARNING: Using e_ent"
|
|
- "ry from elf header (%p) for "
|
|
- "address of \"%s\"\n", (void*)
|
|
- (long) lte->ehdr.e_entry,
|
|
- PLTs_initialized_by_here);
|
|
- continue;
|
|
- }
|
|
- badthing = "ERROR";
|
|
- exit_out = 1;
|
|
- }
|
|
-#endif
|
|
- fprintf (stderr,
|
|
- "%s: Couldn't find symbol \"%s\" in file \"%s\" assuming it will be loaded by libdl!"
|
|
- "\n", badthing, xptr->name, proc->filename);
|
|
- }
|
|
- if (exit_out) {
|
|
- exit (1);
|
|
- }
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ target_address_t entry = (target_address_t)(uintptr_t)lte.entry_addr;
|
|
+ if (arch_translate_address(<e, entry, &entry) < 0)
|
|
+ goto fail;
|
|
+
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ lib->base = (target_address_t)(uintptr_t)lte.base_addr;
|
|
+ lib->entry = entry;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ lib->dyn_addr = (target_address_t)(uintptr_t)lte.dyn_addr;
|
|
+
|
|
+ if (filter_matches_library(options.plt_filter, lib)
|
|
+ && populate_plt(proc, filename, <e, lib) < 0)
|
|
+ goto fail;
|
|
+
|
|
+ if (filter_matches_library(options.static_filter, lib)
|
|
+ && populate_symtab(proc, filename, <e, lib) < 0)
|
|
+ goto fail;
|
|
+
|
|
+done:
|
|
+ do_close_elf(<e);
|
|
+ return status;
|
|
+
|
|
+fail:
|
|
+ status = -1;
|
|
+ goto done;
|
|
+}
|
|
|
|
- for (i = 0; i < library_num + 1; ++i)
|
|
- do_close_elf(<e[i]);
|
|
+struct library *
|
|
+ltelf_read_main_binary(struct Process *proc, const char *path)
|
|
+{
|
|
+ struct library *lib = malloc(sizeof(*lib));
|
|
+ if (lib == NULL)
|
|
+ return NULL;
|
|
+ library_init(lib, LT_LIBTYPE_MAIN);
|
|
+ library_set_pathname(lib, path, 0);
|
|
+
|
|
+ /* There is a race between running the process and reading its
|
|
+ * binary for internal consumption. So open the binary from
|
|
+ * the /proc filesystem. XXX Note that there is similar race
|
|
+ * for libraries, but there we don't have a nice answer like
|
|
+ * that. Presumably we could read the DSOs from the process
|
|
+ * memory image, but that's not currently done. */
|
|
+ char *fname = pid2name(proc->pid);
|
|
+ if (ltelf_read_library(lib, proc, fname, 0) < 0) {
|
|
+ library_destroy(lib);
|
|
+ free(lib);
|
|
+ return NULL;
|
|
+ }
|
|
|
|
- return library_symbols;
|
|
+ return lib;
|
|
}
|
|
diff --git a/ltrace-elf.h b/ltrace-elf.h
|
|
index 4da8a0a..64d1cb8 100644
|
|
--- a/ltrace-elf.h
|
|
+++ b/ltrace-elf.h
|
|
@@ -3,7 +3,18 @@
|
|
|
|
#include <gelf.h>
|
|
#include <stdlib.h>
|
|
+#include "sysdep.h"
|
|
|
|
+struct Process;
|
|
+struct library;
|
|
+struct library_symbol;
|
|
+
|
|
+/* XXX Ok, the original idea was to separate the low-level ELF data
|
|
+ * from the abstract "struct library" object, but we use some of the
|
|
+ * following extensively in the back end. Not all though. So what we
|
|
+ * use should be move to struct library, and the rest of this
|
|
+ * structure maybe could be safely hidden in .c. How to integrate the
|
|
+ * arch-specific bits into struct library is unclear as of now. */
|
|
struct ltelf {
|
|
int fd;
|
|
Elf *elf;
|
|
@@ -12,46 +23,59 @@ struct ltelf {
|
|
size_t dynsym_count;
|
|
const char *dynstr;
|
|
GElf_Addr plt_addr;
|
|
+ GElf_Word plt_flags;
|
|
size_t plt_size;
|
|
Elf_Data *relplt;
|
|
+ Elf_Data *plt_data;
|
|
size_t relplt_count;
|
|
Elf_Data *symtab;
|
|
const char *strtab;
|
|
+ const char *soname;
|
|
size_t symtab_count;
|
|
Elf_Data *opd;
|
|
GElf_Addr *opd_addr;
|
|
size_t opd_size;
|
|
- Elf32_Word *hash;
|
|
- int hash_type;
|
|
- int lte_flags;
|
|
GElf_Addr dyn_addr;
|
|
size_t dyn_sz;
|
|
+ size_t relplt_size;
|
|
+ GElf_Addr bias;
|
|
+ GElf_Addr entry_addr;
|
|
GElf_Addr base_addr;
|
|
-#ifdef __mips__
|
|
- size_t pltgot_addr;
|
|
- size_t mips_local_gotno;
|
|
- size_t mips_gotsym;
|
|
-#endif // __mips__
|
|
- GElf_Addr plt_stub_vma;
|
|
+ struct arch_ltelf_data arch;
|
|
};
|
|
|
|
-#define ELF_MAX_SEGMENTS 50
|
|
-#define LTE_HASH_MALLOCED 1
|
|
-#define LTE_PLT_EXECUTABLE 2
|
|
+int open_elf(struct ltelf *lte, const char *filename);
|
|
|
|
-#define PLTS_ARE_EXECUTABLE(lte) ((lte->lte_flags & LTE_PLT_EXECUTABLE) != 0)
|
|
+/* XXX is it possible to put breakpoints in VDSO and VSYSCALL
|
|
+ * pseudo-libraries? For now we assume that all libraries can be
|
|
+ * opened via a filesystem. BASE is ignored for ET_EXEC files. */
|
|
+int ltelf_read_library(struct library *lib, struct Process *proc,
|
|
+ const char *filename, GElf_Addr bias);
|
|
|
|
-extern size_t library_num;
|
|
-extern char *library[MAX_LIBRARIES];
|
|
+/* Create a library object representing the main binary. The entry
|
|
+ * point address is stored to *ENTRYP. */
|
|
+struct library *ltelf_read_main_binary(struct Process *proc, const char *path);
|
|
|
|
-extern int open_elf(struct ltelf *lte, const char *filename);
|
|
-extern struct library_symbol *read_elf(Process *proc, GElf_Addr *entryp);
|
|
+GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *);
|
|
|
|
-extern GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *);
|
|
+Elf_Data *elf_loaddata(Elf_Scn *scn, GElf_Shdr *shdr);
|
|
+int elf_get_section_covering(struct ltelf *lte, GElf_Addr addr,
|
|
+ Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr);
|
|
+int elf_get_section_type(struct ltelf *lte, GElf_Word type,
|
|
+ Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr);
|
|
+int elf_get_section_named(struct ltelf *lte, const char *name,
|
|
+ Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr);
|
|
|
|
-#ifndef SHT_GNU_HASH
|
|
-#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */
|
|
-#endif
|
|
+/* Read, respectively, 2, 4, or 8 bytes from Elf data at given OFFSET,
|
|
+ * and store it in *RETP. Returns 0 on success or a negative value if
|
|
+ * there's not enough data. */
|
|
+int elf_read_u16(Elf_Data *data, GElf_Xword offset, uint16_t *retp);
|
|
+int elf_read_u32(Elf_Data *data, GElf_Xword offset, uint32_t *retp);
|
|
+int elf_read_u64(Elf_Data *data, GElf_Xword offset, uint64_t *retp);
|
|
+
|
|
+int default_elf_add_plt_entry(struct Process *proc, struct ltelf *lte,
|
|
+ const char *a_name, GElf_Rela *rela, size_t ndx,
|
|
+ struct library_symbol **ret);
|
|
|
|
#if __WORDSIZE == 32
|
|
#define PRI_ELF_ADDR PRIx32
|
|
diff --git a/ltrace.1 b/ltrace.1
|
|
index 4d320e6..3a908be 100644
|
|
--- a/ltrace.1
|
|
+++ b/ltrace.1
|
|
@@ -1,3 +1,4 @@
|
|
+.\" Copyright (c) 2012 Petr Machata, Red Hat Inc.
|
|
.\" Copyright (c) 1997-2005 Juan Cespedes <cespedes@debian.org>
|
|
.\" This file is covered by the GNU GPL
|
|
.TH ltrace 1
|
|
@@ -64,22 +65,12 @@ carries upon a traced process
|
|
DEBUG_FUNCTION. Shows every entry to internal functions
|
|
.RE
|
|
.TP
|
|
-.I \-e expr
|
|
-A qualifying expression which modifies which events to trace.
|
|
-The format of the expression is:
|
|
-.br
|
|
-[!]value1[,value2]...
|
|
-.br
|
|
-where the values are the functions to trace. Using an exclamation
|
|
-mark negates the set of values. For example
|
|
-.I \-e printf
|
|
-means to trace only the printf library call. By contrast,
|
|
-.I \-e !printf
|
|
-means to trace every library call except printf.
|
|
-.IP
|
|
-Note that some shells use the exclamation point for history
|
|
-expansion; even inside quoted arguments. If so, you must escape
|
|
-the exclamation point with a backslash.
|
|
+.I \-e filter
|
|
+A qualifying expression which modifies which library calls to trace.
|
|
+The format of the filter expression is described in the section
|
|
+\fBFILTER EXPRESSIONS\fR. If more than one \-e option appears on the
|
|
+command line, the library calls that match any of them are traced. If
|
|
+no \-e is given, \fB@MAIN\fR is assumed as a default.
|
|
.TP
|
|
.I \-f
|
|
Trace child processes as they are created by
|
|
@@ -175,16 +166,72 @@ is set at
|
|
which must be an external function. By default, '_start' is used.
|
|
NOTE: this flag is only available on the architectures that need it.
|
|
.TP
|
|
-.I \-x extern
|
|
-Trace the external function
|
|
-.IR extern .
|
|
-This option will search the symbol table and lib-dl loaded libraries when
|
|
-attempting to match the given symbol name.
|
|
-This option may be repeated.
|
|
+.I \-x filter
|
|
+A qualifying expression which modifies which symbol table entry points
|
|
+to trace. The format of the filter expression is described in the
|
|
+section \fBFILTER EXPRESSIONS\fR. If more than one \-x option appears
|
|
+on the command line, the symbols that match any of them are traced.
|
|
+No entry points are traced if no \-x is given.
|
|
.TP
|
|
.I \-V, \-\-version
|
|
Show the version number of ltrace and exit.
|
|
|
|
+.SH FILTER EXPRESSIONS
|
|
+
|
|
+Filter expression is a chain of glob- or regexp-based rules that are
|
|
+used to pick symbols for tracing from libraries that the process uses.
|
|
+Most of it is intuitive, so as an example, the following would trace
|
|
+calls to malloc and free, except those done by libc:
|
|
+
|
|
+-e malloc+free-@libc.so*
|
|
+
|
|
+This reads: trace malloc and free, but don't trace anything that comes
|
|
+from libc. Semi-formally, the syntax of the above example looks
|
|
+approximately like this:
|
|
+
|
|
+{[+-][\fIsymbol pattern\fR][@\fIlibrary pattern\fR]}
|
|
+
|
|
+\fISymbol pattern\fR is used to match symbol names, \fIlibrary
|
|
+pattern\fR to match library SONAMEs. Both are implicitly globs, but
|
|
+can be regular expressions as well (see below). The glob syntax
|
|
+supports meta-characters \fB*\fR and \fB?\fR and character classes,
|
|
+similarly to what basic bash globs support. \fB^\fR and \fB$\fR are
|
|
+recognized to mean, respectively, start and end of given name.
|
|
+
|
|
+Both \fIsymbol pattern\fR and \fIlibrary pattern\fR have to match the
|
|
+whole name. If you want to match only a part of name, surround it
|
|
+with one or two *'s as appropriate. The exception is if the pattern
|
|
+is not mentioned at all, in which case it's as if the corresponding
|
|
+pattern were \fB*\fR. (So \fBmalloc\fR is really \fBmalloc@*\fR and
|
|
+\fB@libc.*\fR is really \fB*@libc.*\fR.)
|
|
+
|
|
+In libraries that don't have an explicit SONAME, basename is taken for
|
|
+SONAME. That holds for main binary as well: \fB/bin/echo\fR has an
|
|
+implicit SONAME of \fBecho\fR. In addition to that, special library
|
|
+pattern \fBMAIN\fR always matches symbols in the main binary and never
|
|
+a library with actual SONAME \fBMAIN\fR (use e.g. \fB^MAIN\fR or
|
|
+\fB[M]AIN\fR for that).
|
|
+
|
|
+If the symbol or library pattern is surrounded in slashes (/like
|
|
+this/), then it is considered a regular expression instead. As a
|
|
+shorthand, instead of writing \fB/x/@/y/\fR, you can write
|
|
+\fB/x@y/\fR.
|
|
+
|
|
+If the library pattern starts with a slash, it is not a SONAME
|
|
+expression, but a path expression, and is matched against the library
|
|
+path name.
|
|
+
|
|
+The first rule may lack a sign, in which case \fB+\fR is assumed. If,
|
|
+on the other hand, the first rule has a \fB-\fR sign, it is as if
|
|
+there was another rule \fB@*\fR in front of it.
|
|
+
|
|
+The above rules are used to construct the set of traced symbols. Each
|
|
+candidate symbol is passed through the chain of above rules.
|
|
+Initially, the symbol is \fIunmarked\fR. If it symbol matches a
|
|
+\fB+\fR rule, it becomes \fImarked\fR, if it matches a \fB-\fR rule,
|
|
+it becomes \fIunmarked\fR. If, after applying all rules, the symbol
|
|
+is \fImarked\fR, it will be traced.
|
|
+
|
|
.SH BUGS
|
|
It has most of the bugs stated in
|
|
.BR strace(1) .
|
|
@@ -212,6 +259,8 @@ Personal config file, overrides
|
|
|
|
.SH AUTHOR
|
|
Juan Cespedes <cespedes@debian.org>
|
|
+.br
|
|
+Petr Machata <pmachata@redhat.com>
|
|
|
|
.SH "SEE ALSO"
|
|
.BR strace(1) ,
|
|
diff --git a/ltrace.h b/ltrace.h
|
|
index 194704d..fe3d6ed 100644
|
|
--- a/ltrace.h
|
|
+++ b/ltrace.h
|
|
@@ -1,3 +1,6 @@
|
|
+#ifndef _LTRACE_H_
|
|
+#define _LTRACE_H_
|
|
+
|
|
typedef enum Event_type Event_type;
|
|
enum Event_type {
|
|
EVENT_NONE=0,
|
|
@@ -18,11 +21,10 @@ enum Event_type {
|
|
EVENT_MAX
|
|
};
|
|
|
|
-typedef struct Process Process;
|
|
typedef struct Event Event;
|
|
struct Event {
|
|
struct Event * next;
|
|
- Process * proc;
|
|
+ struct Process * proc;
|
|
Event_type type;
|
|
union {
|
|
int ret_val; /* EVENT_EXIT */
|
|
@@ -38,3 +40,5 @@ typedef void (*callback_func) (Event *);
|
|
extern void ltrace_init(int argc, char **argv);
|
|
extern void ltrace_add_callback(callback_func f, Event_type type);
|
|
extern void ltrace_main(void);
|
|
+
|
|
+#endif /* _LTRACE_H_ */
|
|
diff --git a/options.c b/options.c
|
|
index 74c28bd..d5edc1a 100644
|
|
--- a/options.c
|
|
+++ b/options.c
|
|
@@ -1,16 +1,19 @@
|
|
#include "config.h"
|
|
|
|
-#include <string.h>
|
|
-#include <stdlib.h>
|
|
-#include <unistd.h>
|
|
-#include <fcntl.h>
|
|
-#include <errno.h>
|
|
-#include <limits.h>
|
|
#include <sys/ioctl.h>
|
|
-
|
|
+#include <assert.h>
|
|
+#include <errno.h>
|
|
+#include <fcntl.h>
|
|
#include <getopt.h>
|
|
+#include <limits.h>
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
+#include "filter.h"
|
|
+#include "glob.h"
|
|
|
|
#ifndef SYSCONFDIR
|
|
#define SYSCONFDIR "/etc"
|
|
@@ -23,7 +26,6 @@ struct options_t options = {
|
|
.align = DEFAULT_ALIGN, /* alignment column for results */
|
|
.user = NULL, /* username to run command as */
|
|
.syscalls = 0, /* display syscalls */
|
|
- .libcalls = 1, /* display library calls */
|
|
#ifdef USE_DEMANGLE
|
|
.demangle = 0, /* Demangle low-level symbol names */
|
|
#endif
|
|
@@ -36,8 +38,6 @@ struct options_t options = {
|
|
.follow = 0, /* trace child processes */
|
|
};
|
|
|
|
-char *library[MAX_LIBRARIES];
|
|
-size_t library_num = 0;
|
|
static char *progname; /* Program name (`ltrace') */
|
|
int opt_i = 0; /* instruction pointer */
|
|
int opt_r = 0; /* print relative timestamp */
|
|
@@ -47,14 +47,6 @@ int opt_T = 0; /* show the time spent inside each call */
|
|
/* List of pids given to option -p: */
|
|
struct opt_p_t *opt_p = NULL; /* attach to process with a given pid */
|
|
|
|
-/* List of function names given to option -e: */
|
|
-struct opt_e_t *opt_e = NULL;
|
|
-int opt_e_enable = 1;
|
|
-
|
|
-/* List of global function names given to -x: */
|
|
-struct opt_x_t *opt_x = NULL;
|
|
-unsigned int opt_x_cnt = 0;
|
|
-
|
|
/* List of filenames give to option -F: */
|
|
struct opt_F_t *opt_F = NULL; /* alternate configuration file(s) */
|
|
|
|
@@ -181,11 +173,221 @@ guess_cols(void) {
|
|
}
|
|
}
|
|
|
|
+static void
|
|
+add_filter_rule(struct filter *filt, const char *expr,
|
|
+ enum filter_rule_type type,
|
|
+ const char *a_sym, int sym_re_p,
|
|
+ const char *a_lib, int lib_re_p)
|
|
+{
|
|
+ struct filter_rule *rule = malloc(sizeof(*rule));
|
|
+ struct filter_lib_matcher *matcher = malloc(sizeof(*matcher));
|
|
+
|
|
+ if (rule == NULL || matcher == NULL) {
|
|
+ fprintf(stderr, "rule near '%s' will be ignored: %s\n",
|
|
+ expr, strerror(errno));
|
|
+ fail:
|
|
+ free(rule);
|
|
+ free(matcher);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ regex_t symbol_re;
|
|
+ int status;
|
|
+ {
|
|
+ /* Add ^ to the start of expression and $ to the end, so that
|
|
+ * we match the whole symbol name. Let the user write the "*"
|
|
+ * explicitly if they wish. */
|
|
+ char sym[strlen(a_sym) + 3];
|
|
+ sprintf(sym, "^%s$", a_sym);
|
|
+ status = (sym_re_p ? regcomp : globcomp)(&symbol_re, sym, 0);
|
|
+ if (status != 0) {
|
|
+ char buf[100];
|
|
+ regerror(status, &symbol_re, buf, sizeof buf);
|
|
+ fprintf(stderr, "rule near '%s' will be ignored: %s\n",
|
|
+ expr, buf);
|
|
+ goto fail;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (strcmp(a_lib, "MAIN") == 0) {
|
|
+ filter_lib_matcher_main_init(matcher);
|
|
+ } else {
|
|
+ /* Add ^ and $ to the library expression as well. */
|
|
+ char lib[strlen(a_lib) + 3];
|
|
+ sprintf(lib, "^%s$", a_lib);
|
|
+
|
|
+ enum filter_lib_matcher_type type
|
|
+ = lib[0] == '/' ? FLM_PATHNAME : FLM_SONAME;
|
|
+
|
|
+ regex_t lib_re;
|
|
+ status = (lib_re_p ? regcomp : globcomp)(&lib_re, lib, 0);
|
|
+ if (status != 0) {
|
|
+ char buf[100];
|
|
+ regerror(status, &lib_re, buf, sizeof buf);
|
|
+ fprintf(stderr, "rule near '%s' will be ignored: %s\n",
|
|
+ expr, buf);
|
|
+
|
|
+ regfree(&symbol_re);
|
|
+ goto fail;
|
|
+ }
|
|
+ filter_lib_matcher_name_init(matcher, type, lib_re);
|
|
+ }
|
|
+
|
|
+ filter_rule_init(rule, type, matcher, symbol_re);
|
|
+ filter_add_rule(filt, rule);
|
|
+}
|
|
+
|
|
+static int
|
|
+parse_filter(struct filter *filt, char *expr)
|
|
+{
|
|
+ /* Filter is a chain of sym@lib rules separated by '-'. If
|
|
+ * the filter expression starts with '-', the missing initial
|
|
+ * rule is implicitly *@*. */
|
|
+
|
|
+ enum filter_rule_type type = FR_ADD;
|
|
+
|
|
+ while (*expr != 0) {
|
|
+ size_t s = strcspn(expr, "@-+");
|
|
+ char *symname = expr;
|
|
+ char *libname;
|
|
+ char *next = expr + s + 1;
|
|
+ enum filter_rule_type this_type = type;
|
|
+
|
|
+ if (expr[s] == 0) {
|
|
+ libname = "*";
|
|
+ expr = next - 1;
|
|
+
|
|
+ } else if (expr[s] == '-' || expr[s] == '+') {
|
|
+ type = expr[s] == '-' ? FR_SUBTRACT : FR_ADD;
|
|
+ expr[s] = 0;
|
|
+ libname = "*";
|
|
+ expr = next;
|
|
+
|
|
+ } else {
|
|
+ assert(expr[s] == '@');
|
|
+ expr[s] = 0;
|
|
+ s = strcspn(next, "-+");
|
|
+ if (s == 0) {
|
|
+ libname = "*";
|
|
+ expr = next;
|
|
+ } else if (next[s] == 0) {
|
|
+ expr = next + s;
|
|
+ libname = next;
|
|
+ } else {
|
|
+ assert(next[s] == '-' || next[s] == '+');
|
|
+ type = next[s] == '-' ? FR_SUBTRACT : FR_ADD;
|
|
+ next[s] = 0;
|
|
+ expr = next + s + 1;
|
|
+ libname = next;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ assert(*libname != 0);
|
|
+ char *symend = symname + strlen(symname) - 1;
|
|
+ char *libend = libname + strlen(libname) - 1;
|
|
+ int sym_is_re = 0;
|
|
+ int lib_is_re = 0;
|
|
+
|
|
+ /*
|
|
+ * /xxx/@... and ...@/xxx/ means that xxx are regular
|
|
+ * expressions. They are globs otherwise.
|
|
+ *
|
|
+ * /xxx@yyy/ is the same as /xxx/@/yyy/
|
|
+ *
|
|
+ * @/xxx matches library path name
|
|
+ * @.xxx matches library relative path name
|
|
+ */
|
|
+ if (symname[0] == '/') {
|
|
+ if (symname != symend && symend[0] == '/') {
|
|
+ ++symname;
|
|
+ *symend-- = 0;
|
|
+ sym_is_re = 1;
|
|
+
|
|
+ } else {
|
|
+ sym_is_re = 1;
|
|
+ lib_is_re = 1;
|
|
+ ++symname;
|
|
+
|
|
+ /* /XXX@YYY/ is the same as
|
|
+ * /XXX/@/YYY/. */
|
|
+ if (libend[0] != '/')
|
|
+ fprintf(stderr, "unmatched '/'"
|
|
+ " in symbol name\n");
|
|
+ else
|
|
+ *libend-- = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* If libname ends in '/', then we expect '/' in the
|
|
+ * beginning too. Otherwise the initial '/' is part
|
|
+ * of absolute file name. */
|
|
+ if (!lib_is_re && libend[0] == '/') {
|
|
+ lib_is_re = 1;
|
|
+ *libend-- = 0;
|
|
+ if (libname != libend && libname[0] == '/')
|
|
+ ++libname;
|
|
+ else
|
|
+ fprintf(stderr, "unmatched '/'"
|
|
+ " in library name\n");
|
|
+ }
|
|
+
|
|
+ if (*symname == 0) /* /@AA/ */
|
|
+ symname = "*";
|
|
+ if (*libname == 0) /* /aa@/ */
|
|
+ libname = "*";
|
|
+
|
|
+ add_filter_rule(filt, expr, this_type,
|
|
+ symname, sym_is_re,
|
|
+ libname, lib_is_re);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct filter *
|
|
+recursive_parse_chain(char *expr)
|
|
+{
|
|
+ struct filter *filt = malloc(sizeof(*filt));
|
|
+ if (filt == NULL) {
|
|
+ fprintf(stderr, "(part of) filter will be ignored: '%s': %s\n",
|
|
+ expr, strerror(errno));
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ filter_init(filt);
|
|
+ if (parse_filter(filt, expr) < 0) {
|
|
+ fprintf(stderr, "Filter '%s' will be ignored.\n", expr);
|
|
+ free(filt);
|
|
+ filt = NULL;
|
|
+ }
|
|
+
|
|
+ return filt;
|
|
+}
|
|
+
|
|
+static void
|
|
+parse_filter_chain(const char *expr, struct filter **retp)
|
|
+{
|
|
+ char *str = strdup(expr);
|
|
+ if (str == NULL) {
|
|
+ fprintf(stderr, "filter '%s' will be ignored: %s\n",
|
|
+ expr, strerror(errno));
|
|
+ return;
|
|
+ }
|
|
+ /* Support initial '!' for backward compatibility. */
|
|
+ if (str[0] == '!')
|
|
+ str[0] = '-';
|
|
+
|
|
+ struct filter **tailp;
|
|
+ for (tailp = retp; *tailp != NULL; tailp = &(*tailp)->next)
|
|
+ ;
|
|
+ *tailp = recursive_parse_chain(str);
|
|
+}
|
|
+
|
|
char **
|
|
-process_options(int argc, char **argv) {
|
|
+process_options(int argc, char **argv)
|
|
+{
|
|
progname = argv[0];
|
|
options.output = stderr;
|
|
- options.no_plt = 0;
|
|
options.no_signals = 0;
|
|
#if defined(HAVE_LIBUNWIND)
|
|
options.bt_depth = -1;
|
|
@@ -193,6 +395,8 @@ process_options(int argc, char **argv) {
|
|
|
|
guess_cols();
|
|
|
|
+ int libcalls = 1;
|
|
+
|
|
while (1) {
|
|
int c;
|
|
char *p;
|
|
@@ -209,14 +413,13 @@ process_options(int argc, char **argv) {
|
|
{"library", 1, 0, 'l'},
|
|
{"output", 1, 0, 'o'},
|
|
{"version", 0, 0, 'V'},
|
|
- {"no-plt", 0, 0, 'g'},
|
|
{"no-signals", 0, 0, 'b'},
|
|
#if defined(HAVE_LIBUNWIND)
|
|
{"where", 1, 0, 'w'},
|
|
#endif /* defined(HAVE_LIBUNWIND) */
|
|
{0, 0, 0, 0}
|
|
};
|
|
- c = getopt_long(argc, argv, "+cfhiLrStTVgb"
|
|
+ c = getopt_long(argc, argv, "+cfhiLrStTVb"
|
|
# ifdef USE_DEMANGLE
|
|
"C"
|
|
# endif
|
|
@@ -258,39 +461,11 @@ process_options(int argc, char **argv) {
|
|
err_usage();
|
|
}
|
|
break;
|
|
+
|
|
case 'e':
|
|
- {
|
|
- char *str_e = strdup(optarg);
|
|
- if (!str_e) {
|
|
- perror("ltrace: strdup");
|
|
- exit(1);
|
|
- }
|
|
- if (str_e[0] == '!') {
|
|
- opt_e_enable = 0;
|
|
- str_e++;
|
|
- }
|
|
- while (*str_e) {
|
|
- struct opt_e_t *tmp;
|
|
- char *str2 = strchr(str_e, ',');
|
|
- if (str2) {
|
|
- *str2 = '\0';
|
|
- }
|
|
- tmp = malloc(sizeof(struct opt_e_t));
|
|
- if (!tmp) {
|
|
- perror("ltrace: malloc");
|
|
- exit(1);
|
|
- }
|
|
- tmp->name = str_e;
|
|
- tmp->next = opt_e;
|
|
- opt_e = tmp;
|
|
- if (str2) {
|
|
- str_e = str2 + 1;
|
|
- } else {
|
|
- break;
|
|
- }
|
|
- }
|
|
- break;
|
|
- }
|
|
+ parse_filter_chain(optarg, &options.plt_filter);
|
|
+ break;
|
|
+
|
|
case 'f':
|
|
options.follow = 1;
|
|
break;
|
|
@@ -306,9 +481,6 @@ process_options(int argc, char **argv) {
|
|
opt_F = tmp;
|
|
break;
|
|
}
|
|
- case 'g':
|
|
- options.no_plt = 1;
|
|
- break;
|
|
case 'h':
|
|
usage();
|
|
exit(0);
|
|
@@ -316,16 +488,11 @@ process_options(int argc, char **argv) {
|
|
opt_i++;
|
|
break;
|
|
case 'l':
|
|
- if (library_num == MAX_LIBRARIES) {
|
|
- fprintf(stderr,
|
|
- "Too many libraries. Maximum is %i.\n",
|
|
- MAX_LIBRARIES);
|
|
- exit(1);
|
|
- }
|
|
- library[library_num++] = optarg;
|
|
+ // XXX TODO
|
|
+ fprintf(stderr, "-l support not yet implemented\n");
|
|
break;
|
|
case 'L':
|
|
- options.libcalls = 0;
|
|
+ libcalls = 0;
|
|
break;
|
|
case 'n':
|
|
options.indent = atoi(optarg);
|
|
@@ -334,7 +501,7 @@ process_options(int argc, char **argv) {
|
|
options.output = fopen(optarg, "w");
|
|
if (!options.output) {
|
|
fprintf(stderr,
|
|
- "Can't open %s for output: %s\n",
|
|
+ "can't open %s for writing: %s\n",
|
|
optarg, strerror(errno));
|
|
exit(1);
|
|
}
|
|
@@ -393,31 +560,8 @@ process_options(int argc, char **argv) {
|
|
/* Fall Thru */
|
|
|
|
case 'x':
|
|
- {
|
|
- struct opt_x_t *p = opt_x;
|
|
-
|
|
- /* First, check for duplicate. */
|
|
- while (p && strcmp(p->name, optarg)) {
|
|
- p = p->next;
|
|
- }
|
|
- if (p) {
|
|
- break;
|
|
- }
|
|
-
|
|
- /* If not duplicate, add to list. */
|
|
- p = malloc(sizeof(struct opt_x_t));
|
|
- if (!p) {
|
|
- perror("ltrace: malloc");
|
|
- exit(1);
|
|
- }
|
|
- opt_x_cnt++;
|
|
- p->name = optarg;
|
|
- p->found = 0;
|
|
- p->next = opt_x;
|
|
- p->hash = ~(0UL);
|
|
- opt_x = p;
|
|
- break;
|
|
- }
|
|
+ parse_filter_chain(optarg, &options.static_filter);
|
|
+ break;
|
|
|
|
default:
|
|
err_usage();
|
|
@@ -449,6 +593,14 @@ process_options(int argc, char **argv) {
|
|
opt_F = egg;
|
|
}
|
|
|
|
+ /* Set default filter. Use @MAIN for now, as that's what
|
|
+ * ltrace used to have in the past. XXX Maybe we should make
|
|
+ * this "*" instead. */
|
|
+ if (options.plt_filter == NULL && libcalls) {
|
|
+ parse_filter_chain("@MAIN", &options.plt_filter);
|
|
+ options.hide_caller = 1;
|
|
+ }
|
|
+
|
|
if (!opt_p && argc < 1) {
|
|
fprintf(stderr, "%s: too few arguments\n", progname);
|
|
err_usage();
|
|
diff --git a/options.h b/options.h
|
|
index 9a00629..3ffee71 100644
|
|
--- a/options.h
|
|
+++ b/options.h
|
|
@@ -1,11 +1,12 @@
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
|
|
+struct filter;
|
|
+
|
|
struct options_t {
|
|
int align; /* -a: default alignment column for results */
|
|
char * user; /* -u: username to run command as */
|
|
int syscalls; /* -S: display system calls */
|
|
- int libcalls; /* -L: display library calls */
|
|
int demangle; /* -C: demangle low-level names into user-level names */
|
|
int indent; /* -n: indent trace output according to program flow */
|
|
FILE *output; /* output to a specific file */
|
|
@@ -14,11 +15,13 @@ struct options_t {
|
|
size_t arraylen; /* default maximum # of array elements printed */
|
|
size_t strlen; /* default maximum # of bytes printed in strings */
|
|
int follow; /* trace child processes */
|
|
- int no_plt; /* set bps on PLT entries */
|
|
int no_signals; /* don't print signals */
|
|
#if defined(HAVE_LIBUNWIND)
|
|
int bt_depth; /* how may levels of stack frames to show */
|
|
#endif /* defined(HAVE_LIBUNWIND) */
|
|
+ struct filter *plt_filter;
|
|
+ struct filter *static_filter;
|
|
+ int hide_caller; /* Whether caller library should be hidden. */
|
|
};
|
|
extern struct options_t options;
|
|
|
|
@@ -32,31 +35,13 @@ struct opt_p_t {
|
|
struct opt_p_t *next;
|
|
};
|
|
|
|
-struct opt_e_t {
|
|
- char *name;
|
|
- struct opt_e_t *next;
|
|
-};
|
|
-
|
|
struct opt_F_t {
|
|
char *filename;
|
|
struct opt_F_t *next;
|
|
};
|
|
|
|
-struct opt_x_t {
|
|
- char *name;
|
|
- int found;
|
|
- unsigned long hash;
|
|
- struct opt_x_t *next;
|
|
-};
|
|
-
|
|
extern struct opt_p_t *opt_p; /* attach to process with a given pid */
|
|
|
|
-extern struct opt_e_t *opt_e; /* list of function names to display */
|
|
-extern int opt_e_enable; /* 0 if '!' is used, 1 otherwise */
|
|
-
|
|
extern struct opt_F_t *opt_F; /* alternate configuration file(s) */
|
|
|
|
-extern struct opt_x_t *opt_x; /* list of functions to break at */
|
|
-extern unsigned int opt_x_cnt;
|
|
-
|
|
extern char **process_options(int argc, char **argv);
|
|
diff --git a/output.c b/output.c
|
|
index 1e2e709..ac8c9d0 100644
|
|
--- a/output.c
|
|
+++ b/output.c
|
|
@@ -9,6 +9,8 @@
|
|
#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
+#include "proc.h"
|
|
+#include "library.h"
|
|
|
|
/* TODO FIXME XXX: include in common.h: */
|
|
extern struct timeval current_time_spent;
|
|
@@ -153,7 +155,10 @@ tabto(int col) {
|
|
}
|
|
|
|
void
|
|
-output_left(enum tof type, Process *proc, char const *function_name) {
|
|
+output_left(enum tof type, struct Process *proc,
|
|
+ struct library_symbol *libsym)
|
|
+{
|
|
+ const char *function_name = libsym->name;
|
|
Function *func;
|
|
static arg_type_info *arg_unknown = NULL;
|
|
if (arg_unknown == NULL)
|
|
@@ -169,10 +174,15 @@ output_left(enum tof type, Process *proc, char const *function_name) {
|
|
current_proc = proc;
|
|
current_depth = proc->callstack_depth;
|
|
begin_of_line(type, proc);
|
|
+ if (!options.hide_caller && libsym->lib != NULL
|
|
+ && libsym->plt_type != LS_TOPLT_NONE)
|
|
+ current_column += fprintf(options.output, "%s->",
|
|
+ libsym->lib->soname);
|
|
#ifdef USE_DEMANGLE
|
|
current_column +=
|
|
- fprintf(options.output, "%s(",
|
|
- options.demangle ? my_demangle(function_name) : function_name);
|
|
+ fprintf(options.output, "%s(",
|
|
+ (options.demangle
|
|
+ ? my_demangle(function_name) : function_name));
|
|
#else
|
|
current_column += fprintf(options.output, "%s(", function_name);
|
|
#endif
|
|
@@ -210,7 +220,9 @@ output_left(enum tof type, Process *proc, char const *function_name) {
|
|
}
|
|
|
|
void
|
|
-output_right(enum tof type, Process *proc, char *function_name) {
|
|
+output_right(enum tof type, struct Process *proc, struct library_symbol *libsym)
|
|
+{
|
|
+ const char *function_name = libsym->name;
|
|
Function *func = name2func(function_name);
|
|
static arg_type_info *arg_unknown = NULL;
|
|
if (arg_unknown == NULL)
|
|
diff --git a/output.h b/output.h
|
|
index fa840c7..714078f 100644
|
|
--- a/output.h
|
|
+++ b/output.h
|
|
@@ -1,3 +1,7 @@
|
|
-void output_line(Process *proc, char *fmt, ...);
|
|
-void output_left(enum tof type, Process *proc, char const *function_name);
|
|
-void output_right(enum tof type, Process *proc, char *function_name);
|
|
+struct Process;
|
|
+struct library_symbol;
|
|
+void output_line(struct Process *proc, char *fmt, ...);
|
|
+void output_left(enum tof type, struct Process *proc,
|
|
+ struct library_symbol *libsym);
|
|
+void output_right(enum tof type, struct Process *proc,
|
|
+ struct library_symbol *libsym);
|
|
diff --git a/proc.c b/proc.c
|
|
index 47086b4..51833fe 100644
|
|
--- a/proc.c
|
|
+++ b/proc.c
|
|
@@ -11,45 +11,296 @@
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
-#include <error.h>
|
|
|
|
#include "common.h"
|
|
#include "breakpoint.h"
|
|
+#include "proc.h"
|
|
|
|
-Process *
|
|
-open_program(char *filename, pid_t pid, int enable) {
|
|
- Process *proc;
|
|
- assert(pid != 0);
|
|
- proc = calloc(sizeof(Process), 1);
|
|
- if (!proc) {
|
|
- perror("malloc");
|
|
- exit(1);
|
|
+#ifndef ARCH_HAVE_PROCESS_DATA
|
|
+int
|
|
+arch_process_init(struct Process *proc)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_process_destroy(struct Process *proc)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_process_clone(struct Process *retp, struct Process *proc)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+arch_process_exec(struct Process *proc)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifndef ARCH_HAVE_DYNLINK_DONE
|
|
+void
|
|
+arch_dynlink_done(struct Process *proc)
|
|
+{
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void add_process(struct Process *proc, int was_exec);
|
|
+
|
|
+static int
|
|
+process_bare_init(struct Process *proc, const char *filename,
|
|
+ pid_t pid, int was_exec)
|
|
+{
|
|
+ if (!was_exec) {
|
|
+ memset(proc, 0, sizeof(*proc));
|
|
+
|
|
+ proc->filename = strdup(filename);
|
|
+ if (proc->filename == NULL) {
|
|
+ fail:
|
|
+ free(proc->filename);
|
|
+ if (proc->breakpoints != NULL)
|
|
+ dict_clear(proc->breakpoints);
|
|
+ return -1;
|
|
+ }
|
|
}
|
|
|
|
- proc->filename = strdup(filename);
|
|
+ /* Add process so that we know who the leader is. */
|
|
proc->pid = pid;
|
|
+ add_process(proc, was_exec);
|
|
+ if (proc->leader == NULL)
|
|
+ goto fail;
|
|
+
|
|
+ if (proc->leader == proc) {
|
|
+ proc->breakpoints = dict_init(target_address_hash,
|
|
+ target_address_cmp);
|
|
+ if (proc->breakpoints == NULL)
|
|
+ goto fail;
|
|
+ } else {
|
|
+ proc->breakpoints = NULL;
|
|
+ }
|
|
+
|
|
#if defined(HAVE_LIBUNWIND)
|
|
proc->unwind_priv = _UPT_create(pid);
|
|
proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0);
|
|
#endif /* defined(HAVE_LIBUNWIND) */
|
|
|
|
- add_process(proc);
|
|
- if (proc->leader == NULL) {
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void
|
|
+process_bare_destroy(struct Process *proc, int was_exec)
|
|
+{
|
|
+ dict_clear(proc->breakpoints);
|
|
+ if (!was_exec) {
|
|
+ free(proc->filename);
|
|
+ remove_process(proc);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int
|
|
+process_init_main(struct Process *proc)
|
|
+{
|
|
+ target_address_t entry;
|
|
+ target_address_t interp_bias;
|
|
+ if (process_get_entry(proc, &entry, &interp_bias) < 0) {
|
|
+ fprintf(stderr, "Couldn't get entry points of process %d\n",
|
|
+ proc->pid);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (breakpoints_init(proc) < 0) {
|
|
+ fprintf(stderr, "failed to init breakpoints %d\n",
|
|
+ proc->pid);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+process_init(struct Process *proc, const char *filename, pid_t pid)
|
|
+{
|
|
+ if (process_bare_init(proc, filename, pid, 0) < 0) {
|
|
+ fail:
|
|
+ fprintf(stderr, "failed to initialize process %d: %s\n",
|
|
+ pid, strerror(errno));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (arch_process_init(proc) < 0) {
|
|
+ process_bare_destroy(proc, 0);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (proc->leader != proc)
|
|
+ return 0;
|
|
+ if (process_init_main(proc) < 0) {
|
|
+ process_bare_destroy(proc, 0);
|
|
+ goto fail;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static enum callback_status
|
|
+destroy_breakpoint_cb(struct Process *proc, struct breakpoint *bp, void *data)
|
|
+{
|
|
+ breakpoint_destroy(bp);
|
|
+ free(bp);
|
|
+ return CBS_CONT;
|
|
+}
|
|
+
|
|
+static void
|
|
+private_process_destroy(struct Process *proc, int keep_filename)
|
|
+{
|
|
+ if (!keep_filename)
|
|
+ free(proc->filename);
|
|
+
|
|
+ /* Libraries and symbols. This is only relevant in
|
|
+ * leader. */
|
|
+ struct library *lib;
|
|
+ for (lib = proc->libraries; lib != NULL; ) {
|
|
+ struct library *next = lib->next;
|
|
+ library_destroy(lib);
|
|
+ free(lib);
|
|
+ lib = next;
|
|
+ }
|
|
+ proc->libraries = NULL;
|
|
+
|
|
+ /* Breakpoints. */
|
|
+ if (proc->breakpoints != NULL) {
|
|
+ proc_each_breakpoint(proc, NULL, destroy_breakpoint_cb, NULL);
|
|
+ dict_clear(proc->breakpoints);
|
|
+ proc->breakpoints = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+process_destroy(struct Process *proc)
|
|
+{
|
|
+ private_process_destroy(proc, 0);
|
|
+ arch_process_destroy(proc);
|
|
+}
|
|
+
|
|
+int
|
|
+process_exec(struct Process *proc)
|
|
+{
|
|
+ /* Call exec first, before we destroy the main state. */
|
|
+ if (arch_process_exec(proc) < 0)
|
|
+ return -1;
|
|
+
|
|
+ private_process_destroy(proc, 1);
|
|
+ if (process_bare_init(proc, NULL, proc->pid, 1) < 0)
|
|
+ return -1;
|
|
+ if (process_init_main(proc) < 0) {
|
|
+ process_bare_destroy(proc, 1);
|
|
+ return -1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct Process *
|
|
+open_program(const char *filename, pid_t pid)
|
|
+{
|
|
+ assert(pid != 0);
|
|
+ struct Process *proc = malloc(sizeof(*proc));
|
|
+ if (proc == NULL || process_init(proc, filename, pid) < 0) {
|
|
free(proc);
|
|
return NULL;
|
|
}
|
|
+ return proc;
|
|
+}
|
|
|
|
- if (proc->leader == proc) {
|
|
- trace_set_options(proc, proc->pid);
|
|
- if (breakpoints_init(proc, enable)) {
|
|
- fprintf(stderr, "failed to init breakpoints %d\n",
|
|
- proc->pid);
|
|
- remove_process(proc);
|
|
- return NULL;
|
|
+struct clone_single_bp_data {
|
|
+ struct Process *old_proc;
|
|
+ struct Process *new_proc;
|
|
+ int error;
|
|
+};
|
|
+
|
|
+static void
|
|
+clone_single_bp(void *key, void *value, void *u)
|
|
+{
|
|
+ struct breakpoint *bp = value;
|
|
+ struct clone_single_bp_data *data = u;
|
|
+
|
|
+ data->error = 0;
|
|
+ struct breakpoint *clone = malloc(sizeof(*clone));
|
|
+ if (clone == NULL
|
|
+ || breakpoint_clone(clone, data->new_proc,
|
|
+ bp, data->old_proc) < 0) {
|
|
+ fail:
|
|
+ free(clone);
|
|
+ data->error = -1;
|
|
+ }
|
|
+ if (proc_add_breakpoint(data->new_proc->leader, clone) < 0) {
|
|
+ breakpoint_destroy(clone);
|
|
+ goto fail;
|
|
+ }
|
|
+}
|
|
+
|
|
+int
|
|
+process_clone(struct Process *retp, struct Process *proc, pid_t pid)
|
|
+{
|
|
+ if (process_bare_init(retp, proc->filename, pid, 0) < 0) {
|
|
+ fail:
|
|
+ fprintf(stderr, "failed to clone process %d->%d : %s\n",
|
|
+ proc->pid, pid, strerror(errno));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ retp->tracesysgood = proc->tracesysgood;
|
|
+ retp->e_machine = proc->e_machine;
|
|
+
|
|
+ /* For non-leader processes, that's all we need to do. */
|
|
+ if (retp->leader != retp)
|
|
+ return 0;
|
|
+
|
|
+ /* Clone symbols first so that we can clone and relink
|
|
+ * breakpoints. */
|
|
+ struct library *lib;
|
|
+ struct library **nlibp = &retp->libraries;
|
|
+ for (lib = proc->libraries; lib != NULL; lib = lib->next) {
|
|
+ *nlibp = malloc(sizeof(**nlibp));
|
|
+ if (*nlibp == NULL
|
|
+ || library_clone(*nlibp, lib) < 0) {
|
|
+ fail2:
|
|
+ process_bare_destroy(retp, 0);
|
|
+
|
|
+ /* Error when cloning. Unroll what was done. */
|
|
+ for (lib = retp->libraries; lib != NULL; ) {
|
|
+ struct library *next = lib->next;
|
|
+ library_destroy(lib);
|
|
+ free(lib);
|
|
+ lib = next;
|
|
+ }
|
|
+ goto fail;
|
|
}
|
|
+
|
|
+ nlibp = &(*nlibp)->next;
|
|
}
|
|
|
|
- return proc;
|
|
+ /* Now clone breakpoints. Symbol relinking is done in
|
|
+ * clone_single_bp. */
|
|
+ struct clone_single_bp_data data = {
|
|
+ .old_proc = proc,
|
|
+ .new_proc = retp,
|
|
+ .error = 0,
|
|
+ };
|
|
+ dict_apply_to_all(proc->breakpoints, &clone_single_bp, &data);
|
|
+
|
|
+ /* And finally the call stack. */
|
|
+ memcpy(retp->callstack, proc->callstack, sizeof(retp->callstack));
|
|
+ retp->callstack_depth = proc->callstack_depth;
|
|
+
|
|
+ if (data.error < 0)
|
|
+ goto fail2;
|
|
+
|
|
+ if (arch_process_clone(retp, proc) < 0)
|
|
+ goto fail2;
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static int
|
|
@@ -67,19 +318,19 @@ open_one_pid(pid_t pid)
|
|
return -1;
|
|
}
|
|
|
|
- proc = open_program(filename, pid, 0);
|
|
+ proc = open_program(filename, pid);
|
|
if (proc == NULL)
|
|
return -1;
|
|
- trace_set_options(proc, pid);
|
|
+ trace_set_options(proc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
+static enum callback_status
|
|
start_one_pid(Process * proc, void * data)
|
|
{
|
|
continue_process(proc->pid);
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
void
|
|
@@ -134,20 +385,21 @@ open_pid(pid_t pid)
|
|
old_ntasks = ntasks;
|
|
}
|
|
|
|
- /* Done. Now initialize breakpoints and then continue
|
|
- * everyone. */
|
|
- Process * leader;
|
|
- leader = pid2proc(pid)->leader;
|
|
- enable_all_breakpoints(leader);
|
|
+ struct Process *leader = pid2proc(pid)->leader;
|
|
|
|
- each_task(pid2proc(pid)->leader, start_one_pid, NULL);
|
|
+ /* XXX Is there a way to figure out whether _start has
|
|
+ * actually already been hit? */
|
|
+ arch_dynlink_done(leader);
|
|
+
|
|
+ /* Done. Continue everyone. */
|
|
+ each_task(leader, NULL, start_one_pid, NULL);
|
|
}
|
|
|
|
-static enum pcb_status
|
|
+static enum callback_status
|
|
find_proc(Process * proc, void * data)
|
|
{
|
|
pid_t pid = (pid_t)(uintptr_t)data;
|
|
- return proc->pid == pid ? pcb_stop : pcb_cont;
|
|
+ return proc->pid == pid ? CBS_STOP : CBS_CONT;
|
|
}
|
|
|
|
Process *
|
|
@@ -179,41 +431,60 @@ unlist_process(Process * proc)
|
|
}
|
|
}
|
|
|
|
-Process *
|
|
-each_process(Process * proc,
|
|
- enum pcb_status (* cb)(Process * proc, void * data),
|
|
- void * data)
|
|
+struct Process *
|
|
+each_process(struct Process *start_after,
|
|
+ enum callback_status(*cb)(struct Process *proc, void *data),
|
|
+ void *data)
|
|
{
|
|
- Process * it = proc ?: list_of_processes;
|
|
- for (; it != NULL; ) {
|
|
+ struct Process *it = start_after == NULL ? list_of_processes
|
|
+ : start_after->next;
|
|
+
|
|
+ while (it != NULL) {
|
|
/* Callback might call remove_process. */
|
|
- Process * next = it->next;
|
|
- if ((*cb) (it, data) == pcb_stop)
|
|
+ struct Process *next = it->next;
|
|
+ switch ((*cb)(it, data)) {
|
|
+ case CBS_FAIL:
|
|
+ /* XXX handle me */
|
|
+ case CBS_STOP:
|
|
return it;
|
|
+ case CBS_CONT:
|
|
+ break;
|
|
+ }
|
|
it = next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Process *
|
|
-each_task(Process * it, enum pcb_status (* cb)(Process * proc, void * data),
|
|
- void * data)
|
|
+each_task(struct Process *proc, struct Process *start_after,
|
|
+ enum callback_status(*cb)(struct Process *proc, void *data),
|
|
+ void *data)
|
|
{
|
|
+ assert(proc != NULL);
|
|
+ struct Process *it = start_after == NULL ? proc->leader
|
|
+ : start_after->next;
|
|
+
|
|
if (it != NULL) {
|
|
- Process * leader = it->leader;
|
|
- for (; it != NULL && it->leader == leader; ) {
|
|
+ struct Process *leader = it->leader;
|
|
+ while (it != NULL && it->leader == leader) {
|
|
/* Callback might call remove_process. */
|
|
- Process * next = it->next;
|
|
- if ((*cb) (it, data) == pcb_stop)
|
|
+ struct Process *next = it->next;
|
|
+ switch ((*cb)(it, data)) {
|
|
+ case CBS_FAIL:
|
|
+ /* XXX handle me */
|
|
+ case CBS_STOP:
|
|
return it;
|
|
+ case CBS_CONT:
|
|
+ break;
|
|
+ }
|
|
it = next;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
-void
|
|
-add_process(Process * proc)
|
|
+static void
|
|
+add_process(struct Process *proc, int was_exec)
|
|
{
|
|
Process ** leaderp = &list_of_processes;
|
|
if (proc->pid) {
|
|
@@ -231,8 +502,11 @@ add_process(Process * proc)
|
|
leaderp = &leader->next;
|
|
}
|
|
}
|
|
- proc->next = *leaderp;
|
|
- *leaderp = proc;
|
|
+
|
|
+ if (!was_exec) {
|
|
+ proc->next = *leaderp;
|
|
+ *leaderp = proc;
|
|
+ }
|
|
}
|
|
|
|
void
|
|
@@ -252,13 +526,13 @@ change_process_leader(Process * proc, Process * leader)
|
|
*leaderp = proc;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-clear_leader(Process * proc, void * data)
|
|
+static enum callback_status
|
|
+clear_leader(struct Process *proc, void *data)
|
|
{
|
|
debug(DEBUG_FUNCTION, "detach_task %d from leader %d",
|
|
proc->pid, proc->leader->pid);
|
|
proc->leader = NULL;
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
static enum ecb_status
|
|
@@ -284,15 +558,16 @@ remove_process(Process *proc)
|
|
debug(DEBUG_FUNCTION, "remove_proc(pid=%d)", proc->pid);
|
|
|
|
if (proc->leader == proc)
|
|
- each_task(proc, &clear_leader, NULL);
|
|
+ each_task(proc, NULL, &clear_leader, NULL);
|
|
|
|
unlist_process(proc);
|
|
delete_events_for(proc);
|
|
+ process_destroy(proc);
|
|
free(proc);
|
|
}
|
|
|
|
void
|
|
-install_event_handler(Process * proc, Event_Handler * handler)
|
|
+install_event_handler(Process *proc, struct event_handler *handler)
|
|
{
|
|
debug(DEBUG_FUNCTION, "install_event_handler(pid=%d, %p)", proc->pid, handler);
|
|
assert(proc->event_handler == NULL);
|
|
@@ -302,7 +577,7 @@ install_event_handler(Process * proc, Event_Handler * handler)
|
|
void
|
|
destroy_event_handler(Process * proc)
|
|
{
|
|
- Event_Handler * handler = proc->event_handler;
|
|
+ struct event_handler *handler = proc->event_handler;
|
|
debug(DEBUG_FUNCTION, "destroy_event_handler(pid=%d, %p)", proc->pid, handler);
|
|
assert(handler != NULL);
|
|
if (handler->destroy != NULL)
|
|
@@ -310,3 +585,197 @@ destroy_event_handler(Process * proc)
|
|
free(handler);
|
|
proc->event_handler = NULL;
|
|
}
|
|
+
|
|
+static enum callback_status
|
|
+breakpoint_for_symbol(struct library_symbol *libsym, void *data)
|
|
+{
|
|
+ struct Process *proc = data;
|
|
+ assert(proc->leader == proc);
|
|
+
|
|
+ /* If there is an artificial breakpoint on the same address,
|
|
+ * its libsym will be NULL, and we can smuggle our libsym
|
|
+ * there. That artificial breakpoint is there presumably for
|
|
+ * the callbacks, which we don't touch. If there is a real
|
|
+ * breakpoint, then this is a bug. ltrace-elf.c should filter
|
|
+ * symbols and ignore extra symbol aliases.
|
|
+ *
|
|
+ * The other direction is more complicated and currently not
|
|
+ * supported. If a breakpoint has custom callbacks, it might
|
|
+ * be also custom-allocated, and we would really need to swap
|
|
+ * the two: delete the one now in the dictionary, swap values
|
|
+ * around, and put the new breakpoint back in. */
|
|
+ struct breakpoint *bp = dict_find_entry(proc->breakpoints,
|
|
+ libsym->enter_addr);
|
|
+ if (bp != NULL) {
|
|
+ assert(bp->libsym == NULL);
|
|
+ bp->libsym = libsym;
|
|
+ return CBS_CONT;
|
|
+ }
|
|
+
|
|
+ bp = malloc(sizeof(*bp));
|
|
+ if (bp == NULL
|
|
+ || breakpoint_init(bp, proc, libsym->enter_addr, libsym) < 0) {
|
|
+ fail:
|
|
+ free(bp);
|
|
+ return CBS_FAIL;
|
|
+ }
|
|
+ if (proc_add_breakpoint(proc, bp) < 0) {
|
|
+ breakpoint_destroy(bp);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (breakpoint_turn_on(bp, proc) < 0) {
|
|
+ proc_remove_breakpoint(proc, bp);
|
|
+ breakpoint_destroy(bp);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ return CBS_CONT;
|
|
+}
|
|
+
|
|
+void
|
|
+proc_add_library(struct Process *proc, struct library *lib)
|
|
+{
|
|
+ assert(lib->next == NULL);
|
|
+ lib->next = proc->libraries;
|
|
+ proc->libraries = lib;
|
|
+ debug(DEBUG_PROCESS, "added library %s@%p (%s) to %d",
|
|
+ lib->soname, lib->base, lib->pathname, proc->pid);
|
|
+
|
|
+ struct library_symbol *libsym = NULL;
|
|
+ while ((libsym = library_each_symbol(lib, libsym, breakpoint_for_symbol,
|
|
+ proc)) != NULL)
|
|
+ fprintf(stderr, "couldn't insert breakpoint for %s to %d: %s",
|
|
+ libsym->name, proc->pid, strerror(errno));
|
|
+}
|
|
+
|
|
+int
|
|
+proc_remove_library(struct Process *proc, struct library *lib)
|
|
+{
|
|
+ struct library **libp;
|
|
+ for (libp = &proc->libraries; *libp != NULL; libp = &(*libp)->next)
|
|
+ if (*libp == lib) {
|
|
+ *libp = lib->next;
|
|
+ return 0;
|
|
+ }
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+struct library *
|
|
+proc_each_library(struct Process *proc, struct library *it,
|
|
+ enum callback_status (*cb)(struct Process *proc,
|
|
+ struct library *lib, void *data),
|
|
+ void *data)
|
|
+{
|
|
+ if (it == NULL)
|
|
+ it = proc->libraries;
|
|
+
|
|
+ while (it != NULL) {
|
|
+ struct library *next = it->next;
|
|
+
|
|
+ switch (cb(proc, it, data)) {
|
|
+ case CBS_FAIL:
|
|
+ /* XXX handle me */
|
|
+ case CBS_STOP:
|
|
+ return it;
|
|
+ case CBS_CONT:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ it = next;
|
|
+ }
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void
|
|
+check_leader(struct Process *proc)
|
|
+{
|
|
+ /* Only the group leader should be getting the breakpoints and
|
|
+ * thus have ->breakpoint initialized. */
|
|
+ assert(proc->leader != NULL);
|
|
+ assert(proc->leader == proc);
|
|
+ assert(proc->breakpoints != NULL);
|
|
+}
|
|
+
|
|
+int
|
|
+proc_add_breakpoint(struct Process *proc, struct breakpoint *bp)
|
|
+{
|
|
+ debug(DEBUG_FUNCTION, "proc_add_breakpoint(pid=%d, %s@%p)",
|
|
+ proc->pid, breakpoint_name(bp), bp->addr);
|
|
+ check_leader(proc);
|
|
+
|
|
+ /* XXX We might merge bp->libsym instead of the following
|
|
+ * assert, but that's not necessary right now. Read the
|
|
+ * comment in breakpoint_for_symbol. */
|
|
+ assert(dict_find_entry(proc->breakpoints, bp->addr) == NULL);
|
|
+
|
|
+ if (dict_enter(proc->breakpoints, bp->addr, bp) < 0) {
|
|
+ fprintf(stderr,
|
|
+ "couldn't enter breakpoint %s@%p to dictionary: %s\n",
|
|
+ breakpoint_name(bp), bp->addr, strerror(errno));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+proc_remove_breakpoint(struct Process *proc, struct breakpoint *bp)
|
|
+{
|
|
+ debug(DEBUG_FUNCTION, "proc_remove_breakpoint(pid=%d, %s@%p)",
|
|
+ proc->pid, breakpoint_name(bp), bp->addr);
|
|
+ check_leader(proc);
|
|
+ struct breakpoint *removed = dict_remove(proc->breakpoints, bp->addr);
|
|
+ assert(removed == bp);
|
|
+}
|
|
+
|
|
+/* Dict doesn't support iteration restarts, so here's this contraption
|
|
+ * for now. XXX add restarts to dict. */
|
|
+struct each_breakpoint_data
|
|
+{
|
|
+ void *start;
|
|
+ void *end;
|
|
+ struct Process *proc;
|
|
+ enum callback_status (*cb)(struct Process *proc,
|
|
+ struct breakpoint *bp,
|
|
+ void *data);
|
|
+ void *cb_data;
|
|
+};
|
|
+
|
|
+static void
|
|
+each_breakpoint_cb(void *key, void *value, void *d)
|
|
+{
|
|
+ struct each_breakpoint_data *data = d;
|
|
+ if (data->end != NULL)
|
|
+ return;
|
|
+ if (data->start == key)
|
|
+ data->start = NULL;
|
|
+
|
|
+ if (data->start == NULL) {
|
|
+ switch (data->cb(data->proc, value, data->cb_data)) {
|
|
+ case CBS_FAIL:
|
|
+ /* XXX handle me */
|
|
+ case CBS_STOP:
|
|
+ data->end = key;
|
|
+ case CBS_CONT:
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void *
|
|
+proc_each_breakpoint(struct Process *proc, void *start,
|
|
+ enum callback_status (*cb)(struct Process *proc,
|
|
+ struct breakpoint *bp,
|
|
+ void *data), void *data)
|
|
+{
|
|
+ struct each_breakpoint_data dd = {
|
|
+ .start = start,
|
|
+ .proc = proc,
|
|
+ .cb = cb,
|
|
+ .cb_data = data,
|
|
+ };
|
|
+ dict_apply_to_all(proc->breakpoints, &each_breakpoint_cb, &dd);
|
|
+ return dd.end;
|
|
+}
|
|
diff --git a/proc.h b/proc.h
|
|
new file mode 100644
|
|
index 0000000..443bd8e
|
|
--- /dev/null
|
|
+++ b/proc.h
|
|
@@ -0,0 +1,234 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2010,2011,2012 Petr Machata, Red Hat Inc.
|
|
+ * Copyright (C) 2010 Joe Damato
|
|
+ * Copyright (C) 1998,2001,2008,2009 Juan Cespedes
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#ifndef _PROC_H_
|
|
+#define _PROC_H_
|
|
+
|
|
+#if defined(HAVE_LIBUNWIND)
|
|
+# include <libunwind.h>
|
|
+#endif /* defined(HAVE_LIBUNWIND) */
|
|
+
|
|
+#include "ltrace.h"
|
|
+#include "dict.h"
|
|
+#include "sysdep.h"
|
|
+
|
|
+struct library;
|
|
+struct breakpoint;
|
|
+
|
|
+/* XXX Move this somewhere where it makes sense. When the mess in
|
|
+ * common.h is disentangled, that would actually be a good place for
|
|
+ * this. */
|
|
+enum callback_status {
|
|
+ CBS_STOP, /* The iteration should stop. */
|
|
+ CBS_CONT, /* The iteration should continue. */
|
|
+ CBS_FAIL, /* There was an error. The iteration should stop
|
|
+ * and return error. */
|
|
+};
|
|
+
|
|
+struct event_handler {
|
|
+ /* Event handler that overrides the default one. Should
|
|
+ * return NULL if the event was handled, otherwise the
|
|
+ * returned event is passed to the default handler. */
|
|
+ Event *(*on_event)(struct event_handler *self, Event *event);
|
|
+
|
|
+ /* Called when the event handler removal is requested. */
|
|
+ void (*destroy)(struct event_handler *self);
|
|
+};
|
|
+
|
|
+enum process_state {
|
|
+ STATE_ATTACHED = 0,
|
|
+ STATE_BEING_CREATED,
|
|
+ STATE_IGNORED /* ignore this process (it's a fork and no -f was used) */
|
|
+};
|
|
+
|
|
+struct callstack_element {
|
|
+ union {
|
|
+ int syscall;
|
|
+ struct library_symbol * libfunc;
|
|
+ } c_un;
|
|
+ int is_syscall;
|
|
+ void * return_addr;
|
|
+ struct timeval time_spent;
|
|
+ void * arch_ptr;
|
|
+};
|
|
+
|
|
+/* XXX We should get rid of this. */
|
|
+#define MAX_CALLDEPTH 64
|
|
+
|
|
+/* XXX We would rather have this all organized a little differently,
|
|
+ * have Process for the whole group and Task for what's there for
|
|
+ * per-thread stuff. But for now this is the less invasive way of
|
|
+ * structuring it. */
|
|
+typedef struct Process Process;
|
|
+struct Process {
|
|
+ enum process_state state;
|
|
+ Process * parent; /* needed by STATE_BEING_CREATED */
|
|
+ char * filename;
|
|
+ pid_t pid;
|
|
+
|
|
+ /* Dictionary of breakpoints (which is a mapping
|
|
+ * address->breakpoint). This is NULL for non-leader
|
|
+ * processes. XXX note that we store addresses (keys) by
|
|
+ * value. That assumes that target_address_t fits in host
|
|
+ * pointer. */
|
|
+ Dict * breakpoints;
|
|
+
|
|
+ int mask_32bit; /* 1 if 64-bit ltrace is tracing 32-bit process */
|
|
+ unsigned int personality;
|
|
+ int tracesysgood; /* signal indicating a PTRACE_SYSCALL trap */
|
|
+
|
|
+ int callstack_depth;
|
|
+ struct callstack_element callstack[MAX_CALLDEPTH];
|
|
+
|
|
+ /* Linked list of libraries in backwards order of mapping.
|
|
+ * The last element is the executed binary itself. */
|
|
+ struct library *libraries;
|
|
+
|
|
+ /* Arch-dependent: */
|
|
+ void *debug; /* arch-dep process debug struct XXX move to
|
|
+ * os_process_data after it's invented. */
|
|
+ void * instruction_pointer;
|
|
+ void * stack_pointer; /* To get return addr, args... */
|
|
+ void * return_addr;
|
|
+ void * arch_ptr;
|
|
+ short e_machine;
|
|
+#ifdef __arm__
|
|
+ int thumb_mode; /* ARM execution mode: 0: ARM, 1: Thumb */
|
|
+#endif
|
|
+
|
|
+#if defined(HAVE_LIBUNWIND)
|
|
+ /* libunwind address space */
|
|
+ unw_addr_space_t unwind_as;
|
|
+ void *unwind_priv;
|
|
+#endif /* defined(HAVE_LIBUNWIND) */
|
|
+
|
|
+ /* Set in leader. */
|
|
+ struct event_handler *event_handler;
|
|
+
|
|
+ /**
|
|
+ * Process chaining.
|
|
+ **/
|
|
+ Process * next;
|
|
+
|
|
+ /* LEADER points to the leader thread of the POSIX.1 process.
|
|
+ If X->LEADER == X, then X is the leader thread and the
|
|
+ Process structures chained by NEXT represent other threads,
|
|
+ up until, but not including, the next leader thread.
|
|
+ LEADER may be NULL after the leader has already exited. In
|
|
+ that case this process is waiting to be collected. */
|
|
+ Process * leader;
|
|
+
|
|
+ struct arch_process_data arch;
|
|
+};
|
|
+
|
|
+/* Initialize a process given a path to binary FILENAME, with a PID,
|
|
+ * and add the process to an internal chain of traced processes. */
|
|
+int process_init(struct Process *proc, const char *filename, pid_t pid);
|
|
+
|
|
+/* PROC underwent an exec. This is a bit like process_destroy
|
|
+ * followed by process_init, except that some state is kept and the
|
|
+ * process doesn't lose it's place in the list of processes. */
|
|
+int process_exec(struct Process *proc);
|
|
+
|
|
+/* Release any memory allocated for PROC (but not PROC itself). Does
|
|
+ * NOT remove PROC from internal chain.
|
|
+ *
|
|
+ * XXX clearly this init/destroy pair is different than others and
|
|
+ * should be fixed. process_init should presumably be separate from
|
|
+ * process_add. */
|
|
+void process_destroy(struct Process *proc);
|
|
+
|
|
+struct Process *open_program(const char *filename, pid_t pid);
|
|
+void open_pid(pid_t pid);
|
|
+Process * pid2proc(pid_t pid);
|
|
+
|
|
+/* Clone the contents of PROC into the memory referenced by RETP.
|
|
+ * Returns 0 on success or a negative value on failure. */
|
|
+int process_clone(struct Process *retp, struct Process *proc, pid_t pid);
|
|
+
|
|
+/* Iterate through the processes that ltrace currently traces. CB is
|
|
+ * called for each process. Tasks are considered to be processes for
|
|
+ * the purpose of this iterator.
|
|
+ *
|
|
+ * Notes on this iteration interface: The iteration starts after the
|
|
+ * process designated by START_AFTER, or at the first process if
|
|
+ * START_AFTER is NULL. DATA is passed verbatim to CB. If CB returns
|
|
+ * CBS_STOP, the iteration stops and the current iterator is returned.
|
|
+ * That iterator can then be used to restart the iteration. NULL is
|
|
+ * returned when iteration ends.
|
|
+ *
|
|
+ * There's no provision for returning error states. Errors need to be
|
|
+ * signaled to the caller via DATA, together with any other data that
|
|
+ * the callback needs. */
|
|
+Process *each_process(Process *start_after,
|
|
+ enum callback_status (*cb)(struct Process *proc,
|
|
+ void *data),
|
|
+ void *data);
|
|
+
|
|
+/* Iterate through list of tasks of given process PROC. Restarts are
|
|
+ * supported via START_AFTER (see each_process for details of
|
|
+ * iteration interface). */
|
|
+Process *each_task(struct Process *proc, struct Process *start_after,
|
|
+ enum callback_status (*cb)(struct Process *proc,
|
|
+ void *data),
|
|
+ void *data);
|
|
+
|
|
+void change_process_leader(Process *proc, Process *leader);
|
|
+
|
|
+/* Remove process from the list of traced processes, drop any events
|
|
+ * in the event queue, destroy it and free memory. */
|
|
+void remove_process(struct Process *proc);
|
|
+
|
|
+void install_event_handler(Process *proc, struct event_handler *handler);
|
|
+void destroy_event_handler(Process *proc);
|
|
+
|
|
+/* Add a library LIB to the list of PROC's libraries. */
|
|
+void proc_add_library(struct Process *proc, struct library *lib);
|
|
+
|
|
+/* Remove LIB from list of PROC's libraries. Returns 0 if the library
|
|
+ * was found and unlinked, otherwise returns a negative value. */
|
|
+int proc_remove_library(struct Process *proc, struct library *lib);
|
|
+
|
|
+/* Iterate through the libraries of PROC. See each_process for
|
|
+ * detailed description of the iteration interface. */
|
|
+struct library *proc_each_library(struct Process *proc, struct library *start,
|
|
+ enum callback_status (*cb)(struct Process *p,
|
|
+ struct library *l,
|
|
+ void *data),
|
|
+ void *data);
|
|
+
|
|
+/* Insert BP into PROC. */
|
|
+int proc_add_breakpoint(struct Process *proc, struct breakpoint *bp);
|
|
+
|
|
+/* Remove BP from PROC. This has no reason to fail in runtime. If it
|
|
+ * does not find BP in PROC, it's hard error guarded by assertion. */
|
|
+void proc_remove_breakpoint(struct Process *proc, struct breakpoint *bp);
|
|
+
|
|
+/* Iterate through the libraries of PROC. See each_process for
|
|
+ * detailed description of the iteration interface. */
|
|
+void *proc_each_breakpoint(struct Process *proc, void *start,
|
|
+ enum callback_status (*cb)(struct Process *proc,
|
|
+ struct breakpoint *bp,
|
|
+ void *data),
|
|
+ void *data);
|
|
+
|
|
+#endif /* _PROC_H_ */
|
|
diff --git a/sysdeps/linux-gnu/Makefile.am b/sysdeps/linux-gnu/Makefile.am
|
|
index bd52092..e6fd7ef 100644
|
|
--- a/sysdeps/linux-gnu/Makefile.am
|
|
+++ b/sysdeps/linux-gnu/Makefile.am
|
|
@@ -28,7 +28,8 @@ ___libos_la_LIBADD = \
|
|
noinst_HEADERS = \
|
|
arch_syscallent.h \
|
|
signalent1.h \
|
|
- syscallent1.h
|
|
+ syscallent1.h \
|
|
+ trace.h
|
|
|
|
EXTRA_DIST = \
|
|
arch_mksyscallent \
|
|
diff --git a/sysdeps/linux-gnu/alpha/plt.c b/sysdeps/linux-gnu/alpha/plt.c
|
|
index 83337b2..8ef456e 100644
|
|
--- a/sysdeps/linux-gnu/alpha/plt.c
|
|
+++ b/sysdeps/linux-gnu/alpha/plt.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
GElf_Addr
|
|
diff --git a/sysdeps/linux-gnu/alpha/regs.c b/sysdeps/linux-gnu/alpha/regs.c
|
|
index 9554e48..3c02a5d 100644
|
|
--- a/sysdeps/linux-gnu/alpha/regs.c
|
|
+++ b/sysdeps/linux-gnu/alpha/regs.c
|
|
@@ -4,6 +4,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
diff --git a/sysdeps/linux-gnu/alpha/trace.c b/sysdeps/linux-gnu/alpha/trace.c
|
|
index e4d4063..18fe395 100644
|
|
--- a/sysdeps/linux-gnu/alpha/trace.c
|
|
+++ b/sysdeps/linux-gnu/alpha/trace.c
|
|
@@ -6,6 +6,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
#include "debug.h"
|
|
|
|
diff --git a/sysdeps/linux-gnu/arm/arch.h b/sysdeps/linux-gnu/arm/arch.h
|
|
index 8f2dfb3..d50e439 100644
|
|
--- a/sysdeps/linux-gnu/arm/arch.h
|
|
+++ b/sysdeps/linux-gnu/arm/arch.h
|
|
@@ -9,3 +9,8 @@
|
|
|
|
#define LT_ELFCLASS ELFCLASS32
|
|
#define LT_ELF_MACHINE EM_ARM
|
|
+
|
|
+#define ARCH_HAVE_BREAKPOINT_DATA
|
|
+struct arch_breakpoint_data {
|
|
+ int thumb_mode;
|
|
+};
|
|
diff --git a/sysdeps/linux-gnu/arm/breakpoint.c b/sysdeps/linux-gnu/arm/breakpoint.c
|
|
index 493f973..324ff07 100644
|
|
--- a/sysdeps/linux-gnu/arm/breakpoint.c
|
|
+++ b/sysdeps/linux-gnu/arm/breakpoint.c
|
|
@@ -82,3 +82,29 @@ arch_disable_breakpoint(pid_t pid, const struct breakpoint *sbp)
|
|
ptrace(PTRACE_POKETEXT, pid, sbp->addr + i * sizeof(long), a);
|
|
}
|
|
}
|
|
+
|
|
+int
|
|
+arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp)
|
|
+{
|
|
+ /* XXX That uintptr_t cast is there temporarily until
|
|
+ * target_address_t becomes integral type. */
|
|
+ int thumb_mode = ((uintptr_t)sbp->addr) & 1;
|
|
+ if (thumb_mode)
|
|
+ sbp->addr = (void *)((uintptr_t)sbp->addr & ~1);
|
|
+ sbp->arch.thumb_mode = thumb_mode | proc->thumb_mode;
|
|
+ /* XXX This doesn't seem like it belongs here. */
|
|
+ proc->thumb_mode = 0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_breakpoint_destroy(struct breakpoint *sbp)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp)
|
|
+{
|
|
+ retp->arch.thumb_mode = sbp->arch.thumb_mode;
|
|
+ return 0;
|
|
+}
|
|
diff --git a/sysdeps/linux-gnu/arm/plt.c b/sysdeps/linux-gnu/arm/plt.c
|
|
index 76f4f4c..fb98d7b 100644
|
|
--- a/sysdeps/linux-gnu/arm/plt.c
|
|
+++ b/sysdeps/linux-gnu/arm/plt.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
static int
|
|
diff --git a/sysdeps/linux-gnu/arm/regs.c b/sysdeps/linux-gnu/arm/regs.c
|
|
index b8aed6e..22bc4bf 100644
|
|
--- a/sysdeps/linux-gnu/arm/regs.c
|
|
+++ b/sysdeps/linux-gnu/arm/regs.c
|
|
@@ -4,6 +4,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
@@ -39,9 +40,15 @@ void *
|
|
get_return_addr(Process *proc, void *stack_pointer) {
|
|
long addr = ptrace(PTRACE_PEEKUSER, proc->pid, off_lr, 0);
|
|
|
|
+ /* Remember & unset the thumb mode bit. XXX This is really a
|
|
+ * bit of a hack, as we assume that the following
|
|
+ * insert_breakpoint call will be related to this address.
|
|
+ * This interface should really be get_return_breakpoint, or
|
|
+ * maybe install_return_breakpoint. */
|
|
proc->thumb_mode = addr & 1;
|
|
if (proc->thumb_mode)
|
|
addr &= ~1;
|
|
+
|
|
return (void *)addr;
|
|
}
|
|
|
|
diff --git a/sysdeps/linux-gnu/arm/trace.c b/sysdeps/linux-gnu/arm/trace.c
|
|
index 39b8264..f465b72 100644
|
|
--- a/sysdeps/linux-gnu/arm/trace.c
|
|
+++ b/sysdeps/linux-gnu/arm/trace.c
|
|
@@ -7,6 +7,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
#include "output.h"
|
|
#include "ptrace.h"
|
|
diff --git a/sysdeps/linux-gnu/breakpoint.c b/sysdeps/linux-gnu/breakpoint.c
|
|
index b98374b..e05e730 100644
|
|
--- a/sysdeps/linux-gnu/breakpoint.c
|
|
+++ b/sysdeps/linux-gnu/breakpoint.c
|
|
@@ -1,11 +1,15 @@
|
|
#include "config.h"
|
|
|
|
#include <sys/ptrace.h>
|
|
+#include <errno.h>
|
|
#include <string.h>
|
|
+#include <stdio.h>
|
|
|
|
#include "common.h"
|
|
-#include "arch.h"
|
|
+#include "sysdep.h"
|
|
#include "breakpoint.h"
|
|
+#include "proc.h"
|
|
+#include "library.h"
|
|
|
|
#ifdef ARCH_HAVE_ENABLE_BREAKPOINT
|
|
extern void arch_enable_breakpoint(pid_t, struct breakpoint *);
|
|
@@ -16,15 +20,20 @@ arch_enable_breakpoint(pid_t pid, struct breakpoint *sbp)
|
|
static unsigned char break_insn[] = BREAKPOINT_VALUE;
|
|
unsigned int i, j;
|
|
|
|
- if (sbp->libsym) {
|
|
- debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p, symbol=%s", pid, sbp->addr, sbp->libsym->name);
|
|
- } else {
|
|
- debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p", pid, sbp->addr);
|
|
- }
|
|
+ debug(DEBUG_PROCESS,
|
|
+ "arch_enable_breakpoint: pid=%d, addr=%p, symbol=%s",
|
|
+ pid, sbp->addr, breakpoint_name(sbp));
|
|
|
|
for (i = 0; i < 1 + ((BREAKPOINT_LENGTH - 1) / sizeof(long)); i++) {
|
|
long a = ptrace(PTRACE_PEEKTEXT, pid,
|
|
sbp->addr + i * sizeof(long), 0);
|
|
+ if (a == -1 && errno) {
|
|
+ fprintf(stderr, "enable_breakpoint"
|
|
+ " pid=%d, addr=%p, symbol=%s: %s\n",
|
|
+ pid, sbp->addr, breakpoint_name(sbp),
|
|
+ strerror(errno));
|
|
+ return;
|
|
+ }
|
|
for (j = 0;
|
|
j < sizeof(long)
|
|
&& i * sizeof(long) + j < BREAKPOINT_LENGTH; j++) {
|
|
@@ -33,7 +42,15 @@ arch_enable_breakpoint(pid_t pid, struct breakpoint *sbp)
|
|
sbp->orig_value[i * sizeof(long) + j] = bytes[j];
|
|
bytes[j] = break_insn[i * sizeof(long) + j];
|
|
}
|
|
- ptrace(PTRACE_POKETEXT, pid, sbp->addr + i * sizeof(long), a);
|
|
+ a = ptrace(PTRACE_POKETEXT, pid,
|
|
+ sbp->addr + i * sizeof(long), a);
|
|
+ if (a == -1) {
|
|
+ fprintf(stderr, "enable_breakpoint"
|
|
+ " pid=%d, addr=%p, symbol=%s: %s\n",
|
|
+ pid, sbp->addr, breakpoint_name(sbp),
|
|
+ strerror(errno));
|
|
+ return;
|
|
+ }
|
|
}
|
|
}
|
|
#endif /* ARCH_HAVE_ENABLE_BREAKPOINT */
|
|
@@ -41,11 +58,8 @@ arch_enable_breakpoint(pid_t pid, struct breakpoint *sbp)
|
|
void
|
|
enable_breakpoint(Process *proc, struct breakpoint *sbp)
|
|
{
|
|
- if (sbp->libsym) {
|
|
- debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p, symbol=%s", proc->pid, sbp->addr, sbp->libsym->name);
|
|
- } else {
|
|
- debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p", proc->pid, sbp->addr);
|
|
- }
|
|
+ debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p, symbol=%s",
|
|
+ proc->pid, sbp->addr, breakpoint_name(sbp));
|
|
arch_enable_breakpoint(proc->pid, sbp);
|
|
}
|
|
|
|
@@ -57,16 +71,19 @@ arch_disable_breakpoint(pid_t pid, const struct breakpoint *sbp)
|
|
{
|
|
unsigned int i, j;
|
|
|
|
- if (sbp->libsym) {
|
|
- debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p, symbol=%s", pid, sbp->addr, sbp->libsym->name);
|
|
- } else {
|
|
- debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p", pid, sbp->addr);
|
|
- }
|
|
+ debug(DEBUG_PROCESS,
|
|
+ "arch_disable_breakpoint: pid=%d, addr=%p, symbol=%s",
|
|
+ pid, sbp->addr, breakpoint_name(sbp));
|
|
|
|
for (i = 0; i < 1 + ((BREAKPOINT_LENGTH - 1) / sizeof(long)); i++) {
|
|
- long a =
|
|
- ptrace(PTRACE_PEEKTEXT, pid, sbp->addr + i * sizeof(long),
|
|
- 0);
|
|
+ long a = ptrace(PTRACE_PEEKTEXT, pid,
|
|
+ sbp->addr + i * sizeof(long), 0);
|
|
+ if (a == -1 && errno) {
|
|
+ fprintf(stderr,
|
|
+ "disable_breakpoint pid=%d, addr=%p: %s\n",
|
|
+ pid, sbp->addr, strerror(errno));
|
|
+ return;
|
|
+ }
|
|
for (j = 0;
|
|
j < sizeof(long)
|
|
&& i * sizeof(long) + j < BREAKPOINT_LENGTH; j++) {
|
|
@@ -74,7 +91,14 @@ arch_disable_breakpoint(pid_t pid, const struct breakpoint *sbp)
|
|
|
|
bytes[j] = sbp->orig_value[i * sizeof(long) + j];
|
|
}
|
|
- ptrace(PTRACE_POKETEXT, pid, sbp->addr + i * sizeof(long), a);
|
|
+ a = ptrace(PTRACE_POKETEXT, pid,
|
|
+ sbp->addr + i * sizeof(long), a);
|
|
+ if (a == -1 && errno) {
|
|
+ fprintf(stderr,
|
|
+ "disable_breakpoint pid=%d, addr=%p: %s\n",
|
|
+ pid, sbp->addr, strerror(errno));
|
|
+ return;
|
|
+ }
|
|
}
|
|
}
|
|
#endif /* ARCH_HAVE_DISABLE_BREAKPOINT */
|
|
@@ -82,10 +106,7 @@ arch_disable_breakpoint(pid_t pid, const struct breakpoint *sbp)
|
|
void
|
|
disable_breakpoint(Process *proc, struct breakpoint *sbp)
|
|
{
|
|
- if (sbp->libsym) {
|
|
- debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p, symbol=%s", proc->pid, sbp->addr, sbp->libsym->name);
|
|
- } else {
|
|
- debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p", proc->pid, sbp->addr);
|
|
- }
|
|
+ debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p, symbol=%s",
|
|
+ proc->pid, sbp->addr, breakpoint_name(sbp));
|
|
arch_disable_breakpoint(proc->pid, sbp);
|
|
}
|
|
diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c
|
|
index 0167049..91d873e 100644
|
|
--- a/sysdeps/linux-gnu/events.c
|
|
+++ b/sysdeps/linux-gnu/events.c
|
|
@@ -13,6 +13,7 @@
|
|
|
|
#include "common.h"
|
|
#include "breakpoint.h"
|
|
+#include "proc.h"
|
|
|
|
static Event event;
|
|
|
|
@@ -21,10 +22,10 @@ static Event event;
|
|
static Event * delayed_events = NULL;
|
|
static Event * end_delayed_events = NULL;
|
|
|
|
-static enum pcb_status
|
|
+static enum callback_status
|
|
first (Process * proc, void * data)
|
|
{
|
|
- return pcb_stop;
|
|
+ return CBS_STOP;
|
|
}
|
|
|
|
void
|
|
@@ -174,14 +175,6 @@ next_event(void)
|
|
get_arch_dep(event.proc);
|
|
debug(3, "event from pid %u", pid);
|
|
Process *leader = event.proc->leader;
|
|
- if (leader == event.proc) {
|
|
- if (!event.proc->libdl_hooked) {
|
|
- /* debug struct may not have been written yet.. */
|
|
- if (linkmap_init(event.proc, &main_lte) == 0) {
|
|
- event.proc->libdl_hooked = 1;
|
|
- }
|
|
- }
|
|
- }
|
|
|
|
/* The process should be stopped after the waitpid call. But
|
|
* when the whole thread group is terminated, we see
|
|
diff --git a/sysdeps/linux-gnu/i386/plt.c b/sysdeps/linux-gnu/i386/plt.c
|
|
index b53ff44..daaf15a 100644
|
|
--- a/sysdeps/linux-gnu/i386/plt.c
|
|
+++ b/sysdeps/linux-gnu/i386/plt.c
|
|
@@ -1,12 +1,16 @@
|
|
#include <gelf.h>
|
|
-#include "common.h"
|
|
+#include "proc.h"
|
|
+#include "library.h"
|
|
+#include "ltrace-elf.h"
|
|
|
|
GElf_Addr
|
|
-arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) {
|
|
+arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela)
|
|
+{
|
|
return lte->plt_addr + (ndx + 1) * 16;
|
|
}
|
|
|
|
void *
|
|
-sym2addr(Process *proc, struct library_symbol *sym) {
|
|
+sym2addr(struct Process *proc, struct library_symbol *sym)
|
|
+{
|
|
return sym->enter_addr;
|
|
}
|
|
diff --git a/sysdeps/linux-gnu/i386/regs.c b/sysdeps/linux-gnu/i386/regs.c
|
|
index 6777f17..a1584ac 100644
|
|
--- a/sysdeps/linux-gnu/i386/regs.c
|
|
+++ b/sysdeps/linux-gnu/i386/regs.c
|
|
@@ -4,7 +4,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
-#include "common.h"
|
|
+#include "proc.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
# define PTRACE_PEEKUSER PTRACE_PEEKUSR
|
|
diff --git a/sysdeps/linux-gnu/i386/trace.c b/sysdeps/linux-gnu/i386/trace.c
|
|
index 76f1105..f0c1e50 100644
|
|
--- a/sysdeps/linux-gnu/i386/trace.c
|
|
+++ b/sysdeps/linux-gnu/i386/trace.c
|
|
@@ -1,12 +1,14 @@
|
|
#include "config.h"
|
|
|
|
-#include <stdlib.h>
|
|
+#include <sys/ptrace.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
-#include <signal.h>
|
|
-#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
+#include <errno.h>
|
|
+#include <signal.h>
|
|
+#include <stdlib.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
@@ -24,20 +26,32 @@ get_arch_dep(Process *proc) {
|
|
/* Returns 1 if syscall, 2 if sysret, 0 otherwise.
|
|
*/
|
|
int
|
|
-syscall_p(Process *proc, int status, int *sysnum) {
|
|
+syscall_p(struct Process *proc, int status, int *sysnum)
|
|
+{
|
|
if (WIFSTOPPED(status)
|
|
&& WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) {
|
|
+ struct callstack_element *elem = NULL;
|
|
+ if (proc->callstack_depth > 0)
|
|
+ elem = proc->callstack + proc->callstack_depth - 1;
|
|
+
|
|
*sysnum = ptrace(PTRACE_PEEKUSER, proc->pid, 4 * ORIG_EAX, 0);
|
|
+ if (*sysnum == -1) {
|
|
+ if (errno)
|
|
+ return -1;
|
|
+ /* Otherwise, ORIG_EAX == -1 means that the
|
|
+ * system call should not be restarted. In
|
|
+ * that case rely on what we have on
|
|
+ * stack. */
|
|
+ if (elem != NULL && elem->is_syscall)
|
|
+ *sysnum = elem->c_un.syscall;
|
|
+ }
|
|
|
|
- if (proc->callstack_depth > 0 &&
|
|
- proc->callstack[proc->callstack_depth - 1].is_syscall &&
|
|
- proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) {
|
|
+ if (elem != NULL && elem->is_syscall
|
|
+ && elem->c_un.syscall == *sysnum)
|
|
return 2;
|
|
- }
|
|
|
|
- if (*sysnum >= 0) {
|
|
+ if (*sysnum >= 0)
|
|
return 1;
|
|
- }
|
|
}
|
|
return 0;
|
|
}
|
|
diff --git a/sysdeps/linux-gnu/ia64/plt.c b/sysdeps/linux-gnu/ia64/plt.c
|
|
index 7fd451b..323df65 100644
|
|
--- a/sysdeps/linux-gnu/ia64/plt.c
|
|
+++ b/sysdeps/linux-gnu/ia64/plt.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
/* A bundle is 128 bits */
|
|
diff --git a/sysdeps/linux-gnu/ia64/regs.c b/sysdeps/linux-gnu/ia64/regs.c
|
|
index 3f5d951..64c0164 100644
|
|
--- a/sysdeps/linux-gnu/ia64/regs.c
|
|
+++ b/sysdeps/linux-gnu/ia64/regs.c
|
|
@@ -8,6 +8,7 @@
|
|
#include <asm/rse.h>
|
|
|
|
#include <stddef.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
void *
|
|
diff --git a/sysdeps/linux-gnu/ia64/trace.c b/sysdeps/linux-gnu/ia64/trace.c
|
|
index 079ed55..385fac1 100644
|
|
--- a/sysdeps/linux-gnu/ia64/trace.c
|
|
+++ b/sysdeps/linux-gnu/ia64/trace.c
|
|
@@ -11,6 +11,7 @@
|
|
#include <asm/rse.h>
|
|
#include <errno.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
/* What we think of as a bundle, ptrace thinks of it as two unsigned
|
|
diff --git a/sysdeps/linux-gnu/m68k/plt.c b/sysdeps/linux-gnu/m68k/plt.c
|
|
index 508d7fc..a1c2604 100644
|
|
--- a/sysdeps/linux-gnu/m68k/plt.c
|
|
+++ b/sysdeps/linux-gnu/m68k/plt.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
GElf_Addr
|
|
diff --git a/sysdeps/linux-gnu/m68k/regs.c b/sysdeps/linux-gnu/m68k/regs.c
|
|
index 959a60e..1542b5a 100644
|
|
--- a/sysdeps/linux-gnu/m68k/regs.c
|
|
+++ b/sysdeps/linux-gnu/m68k/regs.c
|
|
@@ -4,6 +4,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
diff --git a/sysdeps/linux-gnu/m68k/trace.c b/sysdeps/linux-gnu/m68k/trace.c
|
|
index 2f89fdf..c63702d 100644
|
|
--- a/sysdeps/linux-gnu/m68k/trace.c
|
|
+++ b/sysdeps/linux-gnu/m68k/trace.c
|
|
@@ -6,6 +6,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
diff --git a/sysdeps/linux-gnu/mipsel/arch.h b/sysdeps/linux-gnu/mipsel/arch.h
|
|
index dd0ca35..f7e2316 100644
|
|
--- a/sysdeps/linux-gnu/mipsel/arch.h
|
|
+++ b/sysdeps/linux-gnu/mipsel/arch.h
|
|
@@ -1,3 +1,8 @@
|
|
+#ifndef LTRACE_MIPS_ARCH_H
|
|
+#define LTRACE_MIPS_ARCH_H
|
|
+
|
|
+#include <stddef.h>
|
|
+
|
|
#define BREAKPOINT_VALUE { 0x0d, 0x00, 0x00, 0x00 }
|
|
#define BREAKPOINT_LENGTH 4
|
|
#define DECR_PC_AFTER_BREAK 0
|
|
@@ -7,3 +12,12 @@
|
|
|
|
#define PLTs_INIT_BY_HERE "_start"
|
|
#define E_ENTRY_NAME "_start"
|
|
+
|
|
+#define ARCH_HAVE_LTELF_DATA
|
|
+struct arch_ltelf_data {
|
|
+ size_t pltgot_addr;
|
|
+ size_t mips_local_gotno;
|
|
+ size_t mips_gotsym;
|
|
+};
|
|
+
|
|
+#endif /* LTRACE_MIPS_ARCH_H */
|
|
diff --git a/sysdeps/linux-gnu/mipsel/plt.c b/sysdeps/linux-gnu/mipsel/plt.c
|
|
index 57dfb9a..3ffaddf 100644
|
|
--- a/sysdeps/linux-gnu/mipsel/plt.c
|
|
+++ b/sysdeps/linux-gnu/mipsel/plt.c
|
|
@@ -1,6 +1,8 @@
|
|
#include "debug.h"
|
|
#include <gelf.h>
|
|
#include <sys/ptrace.h>
|
|
+#include <error.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
/**
|
|
@@ -69,4 +71,62 @@ sym2addr(Process *proc, struct library_symbol *sym) {
|
|
return (void *)ret;;
|
|
}
|
|
|
|
+/**
|
|
+ MIPS ABI Supplement:
|
|
+
|
|
+ DT_PLTGOT This member holds the address of the .got section.
|
|
+
|
|
+ DT_MIPS_SYMTABNO This member holds the number of entries in the
|
|
+ .dynsym section.
|
|
+
|
|
+ DT_MIPS_LOCAL_GOTNO This member holds the number of local global
|
|
+ offset table entries.
|
|
+
|
|
+ DT_MIPS_GOTSYM This member holds the index of the first dyamic
|
|
+ symbol table entry that corresponds to an entry in the gobal offset
|
|
+ table.
|
|
+
|
|
+ */
|
|
+int
|
|
+arch_elf_init(struct ltelf *lte)
|
|
+{
|
|
+ Elf_Scn *scn;
|
|
+ GElf_Shdr shdr;
|
|
+ if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0
|
|
+ || scn == NULL) {
|
|
+ fail:
|
|
+ error(0, 0, "Couldn't get SHT_DYNAMIC: %s",
|
|
+ elf_errmsg(-1));
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ Elf_Data *data = elf_loaddata(scn, &shdr);
|
|
+ if (data == NULL)
|
|
+ goto fail;
|
|
+
|
|
+ size_t j;
|
|
+ for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) {
|
|
+ GElf_Dyn dyn;
|
|
+ if (gelf_getdyn(data, j, &dyn) == NULL)
|
|
+ goto fail;
|
|
+
|
|
+ if(dyn.d_tag == DT_PLTGOT) {
|
|
+ lte->arch.pltgot_addr = dyn.d_un.d_ptr;
|
|
+ }
|
|
+ if(dyn.d_tag == DT_MIPS_LOCAL_GOTNO){
|
|
+ lte->arch.mips_local_gotno = dyn.d_un.d_val;
|
|
+ }
|
|
+ if(dyn.d_tag == DT_MIPS_GOTSYM){
|
|
+ lte->arch.mips_gotsym = dyn.d_un.d_val;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_elf_destroy(struct ltelf *lte)
|
|
+{
|
|
+}
|
|
+
|
|
/**@}*/
|
|
diff --git a/sysdeps/linux-gnu/mipsel/regs.c b/sysdeps/linux-gnu/mipsel/regs.c
|
|
index badbb10..a8a9b10 100644
|
|
--- a/sysdeps/linux-gnu/mipsel/regs.c
|
|
+++ b/sysdeps/linux-gnu/mipsel/regs.c
|
|
@@ -5,6 +5,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
#include "mipsel.h"
|
|
|
|
diff --git a/sysdeps/linux-gnu/mipsel/trace.c b/sysdeps/linux-gnu/mipsel/trace.c
|
|
index 6553967..4b999e4 100644
|
|
--- a/sysdeps/linux-gnu/mipsel/trace.c
|
|
+++ b/sysdeps/linux-gnu/mipsel/trace.c
|
|
@@ -6,6 +6,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
#include "debug.h"
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
#include "mipsel.h"
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
diff --git a/sysdeps/linux-gnu/ppc/arch.h b/sysdeps/linux-gnu/ppc/arch.h
|
|
index 64c1821..6e258e7 100644
|
|
--- a/sysdeps/linux-gnu/ppc/arch.h
|
|
+++ b/sysdeps/linux-gnu/ppc/arch.h
|
|
@@ -1,3 +1,8 @@
|
|
+#ifndef LTRACE_PPC_ARCH_H
|
|
+#define LTRACE_PPC_ARCH_H
|
|
+
|
|
+#include <gelf.h>
|
|
+
|
|
#define BREAKPOINT_VALUE { 0x7f, 0xe0, 0x00, 0x08 }
|
|
#define BREAKPOINT_LENGTH 4
|
|
#define DECR_PC_AFTER_BREAK 0
|
|
@@ -13,13 +18,68 @@
|
|
|
|
#define PLT_REINITALISATION_BP "_start"
|
|
|
|
-/* Start of arch-specific functions. */
|
|
#define ARCH_ENDIAN_BIG
|
|
#define ARCH_HAVE_ATOMIC_SINGLESTEP
|
|
+#define ARCH_HAVE_ADD_PLT_ENTRY
|
|
+#define ARCH_HAVE_TRANSLATE_ADDRESS
|
|
+#define ARCH_HAVE_DYNLINK_DONE
|
|
|
|
-#define PPC_NOP { 0x60, 0x00, 0x00, 0x00 }
|
|
-#define PPC_NOP_LENGTH 4
|
|
+struct library_symbol;
|
|
|
|
-#if (PPC_NOP_LENGTH != BREAKPOINT_LENGTH)
|
|
-#error "Length of the breakpoint value not equal to the length of a nop instruction"
|
|
-#endif
|
|
+#define ARCH_HAVE_LTELF_DATA
|
|
+struct arch_ltelf_data {
|
|
+ GElf_Addr plt_stub_vma;
|
|
+ struct library_symbol *stubs;
|
|
+ Elf_Data *opd_data;
|
|
+ GElf_Addr opd_base;
|
|
+ GElf_Xword opd_size;
|
|
+ int secure_plt;
|
|
+};
|
|
+
|
|
+#define ARCH_HAVE_LIBRARY_DATA
|
|
+struct arch_library_data {
|
|
+ int bss_plt_prelinked;
|
|
+};
|
|
+
|
|
+enum ppc64_plt_type {
|
|
+ /* Either a non-PLT symbol, or PPC32 symbol. */
|
|
+ PPC_DEFAULT = 0,
|
|
+
|
|
+ /* PPC64 STUB, never resolved. */
|
|
+ PPC64_PLT_STUB,
|
|
+
|
|
+ /* Unresolved PLT symbol (.plt contains PLT address). */
|
|
+ PPC_PLT_UNRESOLVED,
|
|
+
|
|
+ /* Resolved PLT symbol. The corresponding .plt slot contained
|
|
+ * target address, which was changed to the address of
|
|
+ * corresponding PLT entry. The original is now saved in
|
|
+ * RESOLVED_VALUE. */
|
|
+ PPC_PLT_RESOLVED,
|
|
+};
|
|
+
|
|
+#define ARCH_HAVE_LIBRARY_SYMBOL_DATA
|
|
+struct arch_library_symbol_data {
|
|
+ enum ppc64_plt_type type;
|
|
+ GElf_Addr resolved_value;
|
|
+
|
|
+ /* Address of corresponding slot in .plt. */
|
|
+ GElf_Addr plt_slot_addr;
|
|
+};
|
|
+
|
|
+#define ARCH_HAVE_BREAKPOINT_DATA
|
|
+struct arch_breakpoint_data {
|
|
+ /* We need this just for arch_breakpoint_init. */
|
|
+};
|
|
+
|
|
+#define ARCH_HAVE_PROCESS_DATA
|
|
+struct arch_process_data {
|
|
+ /* Breakpoint that hits when the dynamic linker is about to
|
|
+ * update a .plt slot. NULL before that address is known. */
|
|
+ struct breakpoint *dl_plt_update_bp;
|
|
+
|
|
+ /* PLT update breakpoint looks here for the handler. */
|
|
+ struct process_stopping_handler *handler;
|
|
+};
|
|
+
|
|
+#endif /* LTRACE_PPC_ARCH_H */
|
|
diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c
|
|
index 668f63d..3b6a25f 100644
|
|
--- a/sysdeps/linux-gnu/ppc/plt.c
|
|
+++ b/sysdeps/linux-gnu/ppc/plt.c
|
|
@@ -1,55 +1,986 @@
|
|
#include <gelf.h>
|
|
#include <sys/ptrace.h>
|
|
+#include <errno.h>
|
|
+#include <error.h>
|
|
+#include <inttypes.h>
|
|
+#include <assert.h>
|
|
+#include <string.h>
|
|
+
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
+#include "library.h"
|
|
+#include "breakpoint.h"
|
|
+#include "linux-gnu/trace.h"
|
|
+
|
|
+/* There are two PLT types on 32-bit PPC: old-style, BSS PLT, and
|
|
+ * new-style "secure" PLT. We can tell one from the other by the
|
|
+ * flags on the .plt section. If it's +X (executable), it's BSS PLT,
|
|
+ * otherwise it's secure.
|
|
+ *
|
|
+ * BSS PLT works the same way as most architectures: the .plt section
|
|
+ * contains trampolines and we put breakpoints to those. If not
|
|
+ * prelinked, .plt contains zeroes, and dynamic linker fills in the
|
|
+ * initial set of trampolines, which means that we need to delay
|
|
+ * enabling breakpoints until after binary entry point is hit.
|
|
+ * Additionally, after first call, dynamic linker updates .plt with
|
|
+ * branch to resolved address. That means that on first hit, we must
|
|
+ * do something similar to the PPC64 gambit described below.
|
|
+ *
|
|
+ * With secure PLT, the .plt section doesn't contain instructions but
|
|
+ * addresses. The real PLT table is stored in .text. Addresses of
|
|
+ * those PLT entries can be computed, and apart from the fact that
|
|
+ * they are in .text, they are ordinary PLT entries.
|
|
+ *
|
|
+ * 64-bit PPC is more involved. Program linker creates for each
|
|
+ * library call a _stub_ symbol named xxxxxxxx.plt_call.<callee>
|
|
+ * (where xxxxxxxx is a hexadecimal number). That stub does the call
|
|
+ * dispatch: it loads an address of a function to call from the
|
|
+ * section .plt, and branches. PLT entries themselves are essentially
|
|
+ * a curried call to the resolver. When the symbol is resolved, the
|
|
+ * resolver updates the value stored in .plt, and the next time
|
|
+ * around, the stub calls the library function directly. So we make
|
|
+ * at most one trip (none if the binary is prelinked) through each PLT
|
|
+ * entry, and correspondingly that is useless as a breakpoint site.
|
|
+ *
|
|
+ * Note the three confusing terms: stubs (that play the role of PLT
|
|
+ * entries), PLT entries, .plt section.
|
|
+ *
|
|
+ * We first check symbol tables and see if we happen to have stub
|
|
+ * symbols available. If yes we just put breakpoints to those, and
|
|
+ * treat them as usual breakpoints. The only tricky part is realizing
|
|
+ * that there can be more than one breakpoint per symbol.
|
|
+ *
|
|
+ * The case that we don't have the stub symbols available is harder.
|
|
+ * The following scheme uses two kinds of PLT breakpoints: unresolved
|
|
+ * and resolved (to some address). When the process starts (or when
|
|
+ * we attach), we distribute unresolved PLT breakpoints to the PLT
|
|
+ * entries (not stubs). Then we look in .plt, and for each entry
|
|
+ * whose value is different than the corresponding PLT entry address,
|
|
+ * we assume it was already resolved, and convert the breakpoint to
|
|
+ * resolved. We also rewrite the resolved value in .plt back to the
|
|
+ * PLT address.
|
|
+ *
|
|
+ * When a PLT entry hits a resolved breakpoint (which happens because
|
|
+ * we rewrite .plt with the original unresolved addresses), we move
|
|
+ * the instruction pointer to the corresponding address and continue
|
|
+ * the process as if nothing happened.
|
|
+ *
|
|
+ * When unresolved PLT entry is called for the first time, we need to
|
|
+ * catch the new value that the resolver will write to a .plt slot.
|
|
+ * We also need to prevent another thread from racing through and
|
|
+ * taking the branch without ltrace noticing. So when unresolved PLT
|
|
+ * entry hits, we have to stop all threads. We then single-step
|
|
+ * through the resolver, until the .plt slot changes. When it does,
|
|
+ * we treat it the same way as above: convert the PLT breakpoint to
|
|
+ * resolved, and rewrite the .plt value back to PLT address. We then
|
|
+ * start all threads again.
|
|
+ *
|
|
+ * As an optimization, we remember the address where the address was
|
|
+ * resolved, and put a breakpoint there. The next time around (when
|
|
+ * the next PLT entry is to be resolved), instead of single-stepping
|
|
+ * through half the dynamic linker, we just let the thread run and hit
|
|
+ * this breakpoint. When it hits, we know the PLT entry was resolved.
|
|
+ *
|
|
+ * XXX TODO If we have hardware watch point, we might put a read watch
|
|
+ * on .plt slot, and discover the offenders this way. I don't know
|
|
+ * the details, but I assume at most a handful (like, one or two, if
|
|
+ * available at all) addresses may be watched at a time, and thus this
|
|
+ * would be used as an amendment of the above rather than full-on
|
|
+ * solution to PLT tracing on PPC.
|
|
+ */
|
|
+
|
|
+#define PPC_PLT_STUB_SIZE 16
|
|
+#define PPC64_PLT_STUB_SIZE 8 //xxx
|
|
+
|
|
+static inline int
|
|
+host_powerpc64()
|
|
+{
|
|
+#ifdef __powerpc64__
|
|
+ return 1;
|
|
+#else
|
|
+ return 0;
|
|
+#endif
|
|
+}
|
|
+
|
|
+int
|
|
+read_target_4(struct Process *proc, target_address_t addr, uint32_t *lp)
|
|
+{
|
|
+ unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0);
|
|
+ if (l == -1UL && errno)
|
|
+ return -1;
|
|
+#ifdef __powerpc64__
|
|
+ l >>= 32;
|
|
+#endif
|
|
+ *lp = l;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+read_target_8(struct Process *proc, target_address_t addr, uint64_t *lp)
|
|
+{
|
|
+ unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0);
|
|
+ if (l == -1UL && errno)
|
|
+ return -1;
|
|
+ if (host_powerpc64()) {
|
|
+ *lp = l;
|
|
+ } else {
|
|
+ unsigned long l2 = ptrace(PTRACE_PEEKTEXT, proc->pid,
|
|
+ addr + 4, 0);
|
|
+ if (l2 == -1UL && errno)
|
|
+ return -1;
|
|
+ *lp = ((uint64_t)l << 32) | l2;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+read_target_long(struct Process *proc, target_address_t addr, uint64_t *lp)
|
|
+{
|
|
+ if (proc->e_machine == EM_PPC) {
|
|
+ uint32_t w;
|
|
+ int ret = read_target_4(proc, addr, &w);
|
|
+ if (ret >= 0)
|
|
+ *lp = (uint64_t)w;
|
|
+ return ret;
|
|
+ } else {
|
|
+ return read_target_8(proc, addr, lp);
|
|
+ }
|
|
+}
|
|
+
|
|
+static enum callback_status
|
|
+reenable_breakpoint(struct Process *proc, struct breakpoint *bp, void *data)
|
|
+{
|
|
+ /* We don't need to re-enable non-PLT breakpoints and
|
|
+ * breakpoints that are not PPC32 BSS unprelinked. */
|
|
+ if (bp->libsym == NULL
|
|
+ || bp->libsym->plt_type == LS_TOPLT_NONE
|
|
+ || bp->libsym->lib->arch.bss_plt_prelinked != 0)
|
|
+ return CBS_CONT;
|
|
+
|
|
+ debug(DEBUG_PROCESS, "pid=%d reenable_breakpoint %s",
|
|
+ proc->pid, breakpoint_name(bp));
|
|
+
|
|
+ assert(proc->e_machine == EM_PPC);
|
|
+ uint64_t l;
|
|
+ if (read_target_8(proc, bp->addr, &l) < 0) {
|
|
+ error(0, errno, "couldn't read PLT value for %s(%p)",
|
|
+ breakpoint_name(bp), bp->addr);
|
|
+ return CBS_CONT;
|
|
+ }
|
|
+
|
|
+ /* XXX double cast */
|
|
+ bp->libsym->arch.plt_slot_addr = (GElf_Addr)(uintptr_t)bp->addr;
|
|
+
|
|
+ /* If necessary, re-enable the breakpoint if it was
|
|
+ * overwritten by the dynamic linker. */
|
|
+ union {
|
|
+ uint32_t insn;
|
|
+ char buf[4];
|
|
+ } u = { .buf = BREAKPOINT_VALUE };
|
|
+ if (l >> 32 == u.insn)
|
|
+ debug(DEBUG_PROCESS, "pid=%d, breakpoint still present"
|
|
+ " at %p, avoiding reenable", proc->pid, bp->addr);
|
|
+ else
|
|
+ enable_breakpoint(proc, bp);
|
|
+
|
|
+ bp->libsym->arch.resolved_value = l;
|
|
+
|
|
+ return CBS_CONT;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_dynlink_done(struct Process *proc)
|
|
+{
|
|
+ /* On PPC32, .plt of objects that use BSS PLT are overwritten
|
|
+ * by the dynamic linker (unless that object was prelinked).
|
|
+ * We need to re-enable breakpoints in those objects. */
|
|
+ proc_each_breakpoint(proc, NULL, reenable_breakpoint, NULL);
|
|
+}
|
|
|
|
GElf_Addr
|
|
-arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) {
|
|
- return rela->r_offset;
|
|
+arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela)
|
|
+{
|
|
+ if (lte->ehdr.e_machine == EM_PPC && lte->arch.secure_plt) {
|
|
+ assert(lte->arch.plt_stub_vma != 0);
|
|
+ return lte->arch.plt_stub_vma + PPC_PLT_STUB_SIZE * ndx;
|
|
+
|
|
+ } else if (lte->ehdr.e_machine == EM_PPC) {
|
|
+ return rela->r_offset;
|
|
+
|
|
+ } else {
|
|
+ /* If we get here, we don't have stub symbols. In
|
|
+ * that case we put brakpoints to PLT entries the same
|
|
+ * as the PPC32 secure PLT case does. */
|
|
+ assert(lte->arch.plt_stub_vma != 0);
|
|
+ return lte->arch.plt_stub_vma + PPC64_PLT_STUB_SIZE * ndx;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* This entry point is called when ltelf is not available
|
|
+ * anymore--during runtime. At that point we don't have to concern
|
|
+ * ourselves with bias, as the values in OPD have been resolved
|
|
+ * already. */
|
|
+int
|
|
+arch_translate_address_dyn(struct Process *proc,
|
|
+ target_address_t addr, target_address_t *ret)
|
|
+{
|
|
+ if (proc->e_machine == EM_PPC64) {
|
|
+ uint64_t value;
|
|
+ if (read_target_8(proc, addr, &value) < 0) {
|
|
+ error(0, errno, "dynamic .opd translation of %p", addr);
|
|
+ return -1;
|
|
+ }
|
|
+ *ret = (target_address_t)value;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ *ret = addr;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+arch_translate_address(struct ltelf *lte,
|
|
+ target_address_t addr, target_address_t *ret)
|
|
+{
|
|
+ if (lte->ehdr.e_machine == EM_PPC64) {
|
|
+ GElf_Xword offset = (GElf_Addr)addr - lte->arch.opd_base;
|
|
+ uint64_t value;
|
|
+ if (elf_read_u64(lte->arch.opd_data, offset, &value) < 0) {
|
|
+ error(0, 0, "static .opd translation of %p: %s", addr,
|
|
+ elf_errmsg(-1));
|
|
+ return -1;
|
|
+ }
|
|
+ *ret = (target_address_t)(value + lte->bias);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ *ret = addr;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+load_opd_data(struct ltelf *lte, struct library *lib)
|
|
+{
|
|
+ Elf_Scn *sec;
|
|
+ GElf_Shdr shdr;
|
|
+ if (elf_get_section_named(lte, ".opd", &sec, &shdr) < 0) {
|
|
+ fail:
|
|
+ fprintf(stderr, "couldn't find .opd data\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ lte->arch.opd_data = elf_rawdata(sec, NULL);
|
|
+ if (lte->arch.opd_data == NULL)
|
|
+ goto fail;
|
|
+
|
|
+ lte->arch.opd_base = shdr.sh_addr + lte->bias;
|
|
+ lte->arch.opd_size = shdr.sh_size;
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
void *
|
|
-sym2addr(Process *proc, struct library_symbol *sym) {
|
|
- void *addr = sym->enter_addr;
|
|
- long pt_ret;
|
|
+sym2addr(struct Process *proc, struct library_symbol *sym)
|
|
+{
|
|
+ return sym->enter_addr;
|
|
+}
|
|
+
|
|
+static GElf_Addr
|
|
+get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot, Elf_Data *plt_data)
|
|
+{
|
|
+ Elf_Scn *ppcgot_sec = NULL;
|
|
+ GElf_Shdr ppcgot_shdr;
|
|
+ if (ppcgot != 0
|
|
+ && elf_get_section_covering(lte, ppcgot,
|
|
+ &ppcgot_sec, &ppcgot_shdr) < 0)
|
|
+ error(0, 0, "DT_PPC_GOT=%#"PRIx64", but no such section found",
|
|
+ ppcgot);
|
|
+
|
|
+ if (ppcgot_sec != NULL) {
|
|
+ Elf_Data *data = elf_loaddata(ppcgot_sec, &ppcgot_shdr);
|
|
+ if (data == NULL || data->d_size < 8 ) {
|
|
+ error(0, 0, "couldn't read GOT data");
|
|
+ } else {
|
|
+ // where PPCGOT begins in .got
|
|
+ size_t offset = ppcgot - ppcgot_shdr.sh_addr;
|
|
+ assert(offset % 4 == 0);
|
|
+ uint32_t glink_vma;
|
|
+ if (elf_read_u32(data, offset + 4, &glink_vma) < 0) {
|
|
+ error(0, 0, "couldn't read glink VMA address"
|
|
+ " at %zd@GOT", offset);
|
|
+ return 0;
|
|
+ }
|
|
+ if (glink_vma != 0) {
|
|
+ debug(1, "PPC GOT glink_vma address: %#" PRIx32,
|
|
+ glink_vma);
|
|
+ return (GElf_Addr)glink_vma;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (plt_data != NULL) {
|
|
+ uint32_t glink_vma;
|
|
+ if (elf_read_u32(plt_data, 0, &glink_vma) < 0) {
|
|
+ error(0, 0, "couldn't read glink VMA address");
|
|
+ return 0;
|
|
+ }
|
|
+ debug(1, ".plt glink_vma address: %#" PRIx32, glink_vma);
|
|
+ return (GElf_Addr)glink_vma;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+load_dynamic_entry(struct ltelf *lte, int tag, GElf_Addr *valuep)
|
|
+{
|
|
+ Elf_Scn *scn;
|
|
+ GElf_Shdr shdr;
|
|
+ if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0
|
|
+ || scn == NULL) {
|
|
+ fail:
|
|
+ error(0, 0, "Couldn't get SHT_DYNAMIC: %s",
|
|
+ elf_errmsg(-1));
|
|
+ return -1;
|
|
+ }
|
|
|
|
- debug(3, 0);
|
|
+ Elf_Data *data = elf_loaddata(scn, &shdr);
|
|
+ if (data == NULL)
|
|
+ goto fail;
|
|
|
|
- if (sym->plt_type != LS_TOPLT_POINT) {
|
|
- return addr;
|
|
+ size_t j;
|
|
+ for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) {
|
|
+ GElf_Dyn dyn;
|
|
+ if (gelf_getdyn(data, j, &dyn) == NULL)
|
|
+ goto fail;
|
|
+
|
|
+ if(dyn.d_tag == tag) {
|
|
+ *valuep = dyn.d_un.d_ptr;
|
|
+ return 0;
|
|
+ }
|
|
}
|
|
|
|
- if (proc->pid == 0) {
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static int
|
|
+load_ppcgot(struct ltelf *lte, GElf_Addr *ppcgotp)
|
|
+{
|
|
+ return load_dynamic_entry(lte, DT_PPC_GOT, ppcgotp);
|
|
+}
|
|
+
|
|
+static int
|
|
+load_ppc64_glink(struct ltelf *lte, GElf_Addr *glinkp)
|
|
+{
|
|
+ return load_dynamic_entry(lte, DT_PPC64_GLINK, glinkp);
|
|
+}
|
|
+
|
|
+static int
|
|
+nonzero_data(Elf_Data *data)
|
|
+{
|
|
+ /* We are not supposed to get here if there's no PLT. */
|
|
+ assert(data != NULL);
|
|
+
|
|
+ unsigned char *buf = data->d_buf;
|
|
+ if (buf == NULL)
|
|
return 0;
|
|
+
|
|
+ size_t i;
|
|
+ for (i = 0; i < data->d_size; ++i)
|
|
+ if (buf[i] != 0)
|
|
+ return 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+arch_elf_init(struct ltelf *lte, struct library *lib)
|
|
+{
|
|
+ if (lte->ehdr.e_machine == EM_PPC64
|
|
+ && load_opd_data(lte, lib) < 0)
|
|
+ return -1;
|
|
+
|
|
+ lte->arch.secure_plt = !(lte->plt_flags & SHF_EXECINSTR);
|
|
+
|
|
+ /* For PPC32 BSS, it is important whether the binary was
|
|
+ * prelinked. If .plt section is NODATA, or if it contains
|
|
+ * zeroes, then this library is not prelinked, and we need to
|
|
+ * delay breakpoints. */
|
|
+ if (lte->ehdr.e_machine == EM_PPC && !lte->arch.secure_plt)
|
|
+ lib->arch.bss_plt_prelinked = nonzero_data(lte->plt_data);
|
|
+ else
|
|
+ /* For cases where it's irrelevant, initialize the
|
|
+ * value to something conspicuous. */
|
|
+ lib->arch.bss_plt_prelinked = -1;
|
|
+
|
|
+ if (lte->ehdr.e_machine == EM_PPC && lte->arch.secure_plt) {
|
|
+ GElf_Addr ppcgot;
|
|
+ if (load_ppcgot(lte, &ppcgot) < 0) {
|
|
+ error(0, 0, "couldn't find DT_PPC_GOT");
|
|
+ return -1;
|
|
+ }
|
|
+ GElf_Addr glink_vma = get_glink_vma(lte, ppcgot, lte->plt_data);
|
|
+
|
|
+ assert (lte->relplt_size % 12 == 0);
|
|
+ size_t count = lte->relplt_size / 12; // size of RELA entry
|
|
+ lte->arch.plt_stub_vma = glink_vma
|
|
+ - (GElf_Addr)count * PPC_PLT_STUB_SIZE;
|
|
+ debug(1, "stub_vma is %#" PRIx64, lte->arch.plt_stub_vma);
|
|
+
|
|
+ } else if (lte->ehdr.e_machine == EM_PPC64) {
|
|
+ GElf_Addr glink_vma;
|
|
+ if (load_ppc64_glink(lte, &glink_vma) < 0) {
|
|
+ error(0, 0, "couldn't find DT_PPC64_GLINK");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* The first glink stub starts at offset 32. */
|
|
+ lte->arch.plt_stub_vma = glink_vma + 32;
|
|
}
|
|
|
|
- if (options.debug >= 3) {
|
|
- xinfdump(proc->pid, (void *)(((long)addr-32)&0xfffffff0),
|
|
- sizeof(void*)*8);
|
|
+ /* On PPC64, look for stub symbols in symbol table. These are
|
|
+ * called: xxxxxxxx.plt_call.callee_name@version+addend. */
|
|
+ if (lte->ehdr.e_machine == EM_PPC64
|
|
+ && lte->symtab != NULL && lte->strtab != NULL) {
|
|
+
|
|
+ /* N.B. We can't simply skip the symbols that we fail
|
|
+ * to read or malloc. There may be more than one stub
|
|
+ * per symbol name, and if we failed in one but
|
|
+ * succeeded in another, the PLT enabling code would
|
|
+ * have no way to tell that something is missing. We
|
|
+ * could work around that, of course, but it doesn't
|
|
+ * seem worth the trouble. So if anything fails, we
|
|
+ * just pretend that we don't have stub symbols at
|
|
+ * all, as if the binary is stripped. */
|
|
+
|
|
+ size_t i;
|
|
+ for (i = 0; i < lte->symtab_count; ++i) {
|
|
+ GElf_Sym sym;
|
|
+ if (gelf_getsym(lte->symtab, i, &sym) == NULL) {
|
|
+ struct library_symbol *sym, *next;
|
|
+ fail:
|
|
+ for (sym = lte->arch.stubs; sym != NULL; ) {
|
|
+ next = sym->next;
|
|
+ library_symbol_destroy(sym);
|
|
+ free(sym);
|
|
+ sym = next;
|
|
+ }
|
|
+ lte->arch.stubs = NULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ const char *name = lte->strtab + sym.st_name;
|
|
+
|
|
+#define STUBN ".plt_call."
|
|
+ if ((name = strstr(name, STUBN)) == NULL)
|
|
+ continue;
|
|
+ name += sizeof(STUBN) - 1;
|
|
+#undef STUBN
|
|
+
|
|
+ size_t len;
|
|
+ const char *ver = strchr(name, '@');
|
|
+ if (ver != NULL) {
|
|
+ len = ver - name;
|
|
+
|
|
+ } else {
|
|
+ /* If there is "+" at all, check that
|
|
+ * the symbol name ends in "+0". */
|
|
+ const char *add = strrchr(name, '+');
|
|
+ if (add != NULL) {
|
|
+ assert(strcmp(add, "+0") == 0);
|
|
+ len = add - name;
|
|
+ } else {
|
|
+ len = strlen(name);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ char *sym_name = strndup(name, len);
|
|
+ struct library_symbol *libsym = malloc(sizeof(*libsym));
|
|
+ if (sym_name == NULL || libsym == NULL) {
|
|
+ fail2:
|
|
+ free(sym_name);
|
|
+ free(libsym);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ target_address_t addr = (target_address_t)
|
|
+ (uintptr_t)sym.st_value + lte->bias;
|
|
+ if (library_symbol_init(libsym, addr, sym_name, 1,
|
|
+ LS_TOPLT_EXEC) < 0)
|
|
+ goto fail2;
|
|
+ libsym->arch.type = PPC64_PLT_STUB;
|
|
+ libsym->next = lte->arch.stubs;
|
|
+ lte->arch.stubs = libsym;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+read_plt_slot_value(struct Process *proc, GElf_Addr addr, GElf_Addr *valp)
|
|
+{
|
|
+ /* On PPC64, we read from .plt, which contains 8 byte
|
|
+ * addresses. On PPC32 we read from .plt, which contains 4
|
|
+ * byte instructions, but the PLT is two instructions, and
|
|
+ * either can change. */
|
|
+ uint64_t l;
|
|
+ /* XXX double cast. */
|
|
+ if (read_target_8(proc, (target_address_t)(uintptr_t)addr, &l) < 0) {
|
|
+ error(0, errno, "ptrace .plt slot value @%#" PRIx64, addr);
|
|
+ return -1;
|
|
}
|
|
|
|
- // On a PowerPC-64 system, a plt is three 64-bit words: the first is the
|
|
- // 64-bit address of the routine. Before the PLT has been initialized,
|
|
- // this will be 0x0. In fact, the symbol table won't have the plt's
|
|
- // address even. Ater the PLT has been initialized, but before it has
|
|
- // been resolved, the first word will be the address of the function in
|
|
- // the dynamic linker that will reslove the PLT. After the PLT is
|
|
- // resolved, this will will be the address of the routine whose symbol
|
|
- // is in the symbol table.
|
|
+ *valp = (GElf_Addr)l;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+unresolve_plt_slot(struct Process *proc, GElf_Addr addr, GElf_Addr value)
|
|
+{
|
|
+ /* We only modify plt_entry[0], which holds the resolved
|
|
+ * address of the routine. We keep the TOC and environment
|
|
+ * pointers intact. Hence the only adjustment that we need to
|
|
+ * do is to IP. */
|
|
+ if (ptrace(PTRACE_POKETEXT, proc->pid, addr, value) < 0) {
|
|
+ error(0, errno, "unresolve .plt slot");
|
|
+ return -1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void
|
|
+mark_as_resolved(struct library_symbol *libsym, GElf_Addr value)
|
|
+{
|
|
+ libsym->arch.type = PPC_PLT_RESOLVED;
|
|
+ libsym->arch.resolved_value = value;
|
|
+}
|
|
|
|
- // On a PowerPC-32 system, there are two types of PLTs: secure (new) and
|
|
- // non-secure (old). For the secure case, the PLT is simply a pointer
|
|
- // and we can treat it much as we do for the PowerPC-64 case. For the
|
|
- // non-secure case, the PLT is executable code and we can put the
|
|
- // break-point right in the PLT.
|
|
+enum plt_status
|
|
+arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte,
|
|
+ const char *a_name, GElf_Rela *rela, size_t ndx,
|
|
+ struct library_symbol **ret)
|
|
+{
|
|
+ if (lte->ehdr.e_machine == EM_PPC)
|
|
+ return plt_default;
|
|
|
|
- pt_ret = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0);
|
|
+ /* PPC64. If we have stubs, we return a chain of breakpoint
|
|
+ * sites, one for each stub that corresponds to this PLT
|
|
+ * entry. */
|
|
+ struct library_symbol *chain = NULL;
|
|
+ struct library_symbol **symp;
|
|
+ for (symp = <e->arch.stubs; *symp != NULL; ) {
|
|
+ struct library_symbol *sym = *symp;
|
|
+ if (strcmp(sym->name, a_name) != 0) {
|
|
+ symp = &(*symp)->next;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* Re-chain the symbol from stubs to CHAIN. */
|
|
+ *symp = sym->next;
|
|
+ sym->next = chain;
|
|
+ chain = sym;
|
|
+ }
|
|
+
|
|
+ if (chain != NULL) {
|
|
+ *ret = chain;
|
|
+ return plt_ok;
|
|
+ }
|
|
+
|
|
+ /* We don't have stub symbols. Find corresponding .plt slot,
|
|
+ * and check whether it contains the corresponding PLT address
|
|
+ * (or 0 if the dynamic linker hasn't run yet). N.B. we don't
|
|
+ * want read this from ELF file, but from process image. That
|
|
+ * makes a difference if we are attaching to a running
|
|
+ * process. */
|
|
+
|
|
+ GElf_Addr plt_entry_addr = arch_plt_sym_val(lte, ndx, rela);
|
|
+ GElf_Addr plt_slot_addr = rela->r_offset;
|
|
+ assert(plt_slot_addr >= lte->plt_addr
|
|
+ || plt_slot_addr < lte->plt_addr + lte->plt_size);
|
|
+
|
|
+ GElf_Addr plt_slot_value;
|
|
+ if (read_plt_slot_value(proc, plt_slot_addr, &plt_slot_value) < 0)
|
|
+ return plt_fail;
|
|
+
|
|
+ char *name = strdup(a_name);
|
|
+ struct library_symbol *libsym = malloc(sizeof(*libsym));
|
|
+ if (name == NULL || libsym == NULL) {
|
|
+ error(0, errno, "allocation for .plt slot");
|
|
+ fail:
|
|
+ free(name);
|
|
+ free(libsym);
|
|
+ return plt_fail;
|
|
+ }
|
|
+
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ if (library_symbol_init(libsym,
|
|
+ (target_address_t)(uintptr_t)plt_entry_addr,
|
|
+ name, 1, LS_TOPLT_EXEC) < 0)
|
|
+ goto fail;
|
|
+ libsym->arch.plt_slot_addr = plt_slot_addr;
|
|
+
|
|
+ if (plt_slot_value == plt_entry_addr || plt_slot_value == 0) {
|
|
+ libsym->arch.type = PPC_PLT_UNRESOLVED;
|
|
+ libsym->arch.resolved_value = plt_entry_addr;
|
|
|
|
- if (proc->mask_32bit) {
|
|
- // Assume big-endian.
|
|
- addr = (void *)((pt_ret >> 32) & 0xffffffff);
|
|
} else {
|
|
- addr = (void *)pt_ret;
|
|
+ /* Unresolve the .plt slot. If the binary was
|
|
+ * prelinked, this makes the code invalid, because in
|
|
+ * case of prelinked binary, the dynamic linker
|
|
+ * doesn't update .plt[0] and .plt[1] with addresses
|
|
+ * of the resover. But we don't care, we will never
|
|
+ * need to enter the resolver. That just means that
|
|
+ * we have to un-un-resolve this back before we
|
|
+ * detach. */
|
|
+
|
|
+ if (unresolve_plt_slot(proc, plt_slot_addr, plt_entry_addr) < 0) {
|
|
+ library_symbol_destroy(libsym);
|
|
+ goto fail;
|
|
+ }
|
|
+ mark_as_resolved(libsym, plt_slot_value);
|
|
}
|
|
|
|
- return addr;
|
|
+ *ret = libsym;
|
|
+ return plt_ok;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_elf_destroy(struct ltelf *lte)
|
|
+{
|
|
+ struct library_symbol *sym;
|
|
+ for (sym = lte->arch.stubs; sym != NULL; ) {
|
|
+ struct library_symbol *next = sym->next;
|
|
+ library_symbol_destroy(sym);
|
|
+ free(sym);
|
|
+ sym = next;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+dl_plt_update_bp_on_hit(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ debug(DEBUG_PROCESS, "pid=%d dl_plt_update_bp_on_hit %s(%p)",
|
|
+ proc->pid, breakpoint_name(bp), bp->addr);
|
|
+ struct process_stopping_handler *self = proc->arch.handler;
|
|
+ assert(self != NULL);
|
|
+
|
|
+ struct library_symbol *libsym = self->breakpoint_being_enabled->libsym;
|
|
+ GElf_Addr value;
|
|
+ if (read_plt_slot_value(proc, libsym->arch.plt_slot_addr, &value) < 0)
|
|
+ return;
|
|
+
|
|
+ /* On PPC64, we rewrite the slot value. */
|
|
+ if (proc->e_machine == EM_PPC64)
|
|
+ unresolve_plt_slot(proc, libsym->arch.plt_slot_addr,
|
|
+ libsym->arch.resolved_value);
|
|
+ /* We mark the breakpoint as resolved on both arches. */
|
|
+ mark_as_resolved(libsym, value);
|
|
+
|
|
+ /* cb_on_all_stopped looks if HANDLER is set to NULL as a way
|
|
+ * to check that this was run. It's an error if it
|
|
+ * wasn't. */
|
|
+ proc->arch.handler = NULL;
|
|
+
|
|
+ breakpoint_turn_off(bp, proc);
|
|
+}
|
|
+
|
|
+static void
|
|
+cb_on_all_stopped(struct process_stopping_handler *self)
|
|
+{
|
|
+ /* Put that in for dl_plt_update_bp_on_hit to see. */
|
|
+ assert(self->task_enabling_breakpoint->arch.handler == NULL);
|
|
+ self->task_enabling_breakpoint->arch.handler = self;
|
|
+
|
|
+ linux_ptrace_disable_and_continue(self);
|
|
+}
|
|
+
|
|
+static enum callback_status
|
|
+cb_keep_stepping_p(struct process_stopping_handler *self)
|
|
+{
|
|
+ struct Process *proc = self->task_enabling_breakpoint;
|
|
+ struct library_symbol *libsym = self->breakpoint_being_enabled->libsym;
|
|
+
|
|
+ GElf_Addr value;
|
|
+ if (read_plt_slot_value(proc, libsym->arch.plt_slot_addr, &value) < 0)
|
|
+ return CBS_FAIL;
|
|
+
|
|
+ /* In UNRESOLVED state, the RESOLVED_VALUE in fact contains
|
|
+ * the PLT entry value. */
|
|
+ if (value == libsym->arch.resolved_value)
|
|
+ return CBS_CONT;
|
|
+
|
|
+ debug(DEBUG_PROCESS, "pid=%d PLT got resolved to value %#"PRIx64,
|
|
+ proc->pid, value);
|
|
+
|
|
+ /* The .plt slot got resolved! We can migrate the breakpoint
|
|
+ * to RESOLVED and stop single-stepping. */
|
|
+ if (proc->e_machine == EM_PPC64
|
|
+ && unresolve_plt_slot(proc, libsym->arch.plt_slot_addr,
|
|
+ libsym->arch.resolved_value) < 0)
|
|
+ return CBS_FAIL;
|
|
+
|
|
+ /* Resolving on PPC64 consists of overwriting a doubleword in
|
|
+ * .plt. That doubleword is than read back by a stub, and
|
|
+ * jumped on. Hopefully we can assume that double word update
|
|
+ * is done on a single place only, as it contains a final
|
|
+ * address. We still need to look around for any sync
|
|
+ * instruction, but essentially it is safe to optimize away
|
|
+ * the single stepping next time and install a post-update
|
|
+ * breakpoint.
|
|
+ *
|
|
+ * The situation on PPC32 BSS is more complicated. The
|
|
+ * dynamic linker here updates potentially several
|
|
+ * instructions (XXX currently we assume two) and the rules
|
|
+ * are more complicated. Sometimes it's enough to adjust just
|
|
+ * one of the addresses--the logic for generating optimal
|
|
+ * dispatch depends on relative addresses of the .plt entry
|
|
+ * and the jump destination. We can't assume that the some
|
|
+ * instruction block does the update every time. So on PPC32,
|
|
+ * we turn the optimization off and just step through it each
|
|
+ * time. */
|
|
+ if (proc->e_machine == EM_PPC)
|
|
+ goto done;
|
|
+
|
|
+ /* Install breakpoint to the address where the change takes
|
|
+ * place. If we fail, then that just means that we'll have to
|
|
+ * singlestep the next time around as well. */
|
|
+ struct Process *leader = proc->leader;
|
|
+ if (leader == NULL || leader->arch.dl_plt_update_bp != NULL)
|
|
+ goto done;
|
|
+
|
|
+ /* We need to install to the next instruction. ADDR points to
|
|
+ * a store instruction, so moving the breakpoint one
|
|
+ * instruction forward is safe. */
|
|
+ target_address_t addr = get_instruction_pointer(proc) + 4;
|
|
+ leader->arch.dl_plt_update_bp = insert_breakpoint(proc, addr, NULL);
|
|
+ if (leader->arch.dl_plt_update_bp == NULL)
|
|
+ goto done;
|
|
+
|
|
+ static struct bp_callbacks dl_plt_update_cbs = {
|
|
+ .on_hit = dl_plt_update_bp_on_hit,
|
|
+ };
|
|
+ leader->arch.dl_plt_update_bp->cbs = &dl_plt_update_cbs;
|
|
+
|
|
+ /* Turn it off for now. We will turn it on again when we hit
|
|
+ * the PLT entry that needs this. */
|
|
+ breakpoint_turn_off(leader->arch.dl_plt_update_bp, proc);
|
|
+
|
|
+done:
|
|
+ mark_as_resolved(libsym, value);
|
|
+
|
|
+ return CBS_STOP;
|
|
+}
|
|
+
|
|
+static void
|
|
+jump_to_entry_point(struct Process *proc, struct breakpoint *bp)
|
|
+{
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ target_address_t rv = (target_address_t)
|
|
+ (uintptr_t)bp->libsym->arch.resolved_value;
|
|
+ set_instruction_pointer(proc, rv);
|
|
+}
|
|
+
|
|
+static void
|
|
+ppc_plt_bp_continue(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ switch (bp->libsym->arch.type) {
|
|
+ struct Process *leader;
|
|
+ void (*on_all_stopped)(struct process_stopping_handler *);
|
|
+ enum callback_status (*keep_stepping_p)
|
|
+ (struct process_stopping_handler *);
|
|
+
|
|
+ case PPC_DEFAULT:
|
|
+ assert(proc->e_machine == EM_PPC);
|
|
+ assert(bp->libsym != NULL);
|
|
+ assert(bp->libsym->lib->arch.bss_plt_prelinked == 0);
|
|
+ /* fall-through */
|
|
+
|
|
+ case PPC_PLT_UNRESOLVED:
|
|
+ on_all_stopped = NULL;
|
|
+ keep_stepping_p = NULL;
|
|
+ leader = proc->leader;
|
|
+
|
|
+ if (leader != NULL && leader->arch.dl_plt_update_bp != NULL
|
|
+ && breakpoint_turn_on(leader->arch.dl_plt_update_bp,
|
|
+ proc) >= 0)
|
|
+ on_all_stopped = cb_on_all_stopped;
|
|
+ else
|
|
+ keep_stepping_p = cb_keep_stepping_p;
|
|
+
|
|
+ if (process_install_stopping_handler
|
|
+ (proc, bp, on_all_stopped, keep_stepping_p, NULL) < 0) {
|
|
+ error(0, 0, "ppc_plt_bp_continue: couldn't install"
|
|
+ " event handler");
|
|
+ continue_after_breakpoint(proc, bp);
|
|
+ }
|
|
+ return;
|
|
+
|
|
+ case PPC_PLT_RESOLVED:
|
|
+ if (proc->e_machine == EM_PPC) {
|
|
+ continue_after_breakpoint(proc, bp);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ jump_to_entry_point(proc, bp);
|
|
+ continue_process(proc->pid);
|
|
+ return;
|
|
+
|
|
+ case PPC64_PLT_STUB:
|
|
+ /* These should never hit here. */
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ assert(bp->libsym->arch.type != bp->libsym->arch.type);
|
|
+ abort();
|
|
+}
|
|
+
|
|
+/* When a process is in a PLT stub, it may have already read the data
|
|
+ * in .plt that we changed. If we detach now, it will jump to PLT
|
|
+ * entry and continue to the dynamic linker, where it will SIGSEGV,
|
|
+ * because zeroth .plt slot is not filled in prelinked binaries, and
|
|
+ * the dynamic linker needs that data. Moreover, the process may
|
|
+ * actually have hit the breakpoint already. This functions tries to
|
|
+ * detect both cases and do any fix-ups necessary to mend this
|
|
+ * situation. */
|
|
+static enum callback_status
|
|
+detach_task_cb(struct Process *task, void *data)
|
|
+{
|
|
+ struct breakpoint *bp = data;
|
|
+
|
|
+ if (get_instruction_pointer(task) == bp->addr) {
|
|
+ debug(DEBUG_PROCESS, "%d at %p, which is PLT slot",
|
|
+ task->pid, bp->addr);
|
|
+ jump_to_entry_point(task, bp);
|
|
+ return CBS_CONT;
|
|
+ }
|
|
+
|
|
+ /* XXX There's still a window of several instructions where we
|
|
+ * might catch the task inside a stub such that it has already
|
|
+ * read destination address from .plt, but hasn't jumped yet,
|
|
+ * thus avoiding the breakpoint. */
|
|
+
|
|
+ return CBS_CONT;
|
|
+}
|
|
+
|
|
+static void
|
|
+ppc_plt_bp_retract(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ /* On PPC64, we rewrite .plt with PLT entry addresses. This
|
|
+ * needs to be undone. Unfortunately, the program may have
|
|
+ * made decisions based on that value */
|
|
+ if (proc->e_machine == EM_PPC64
|
|
+ && bp->libsym != NULL
|
|
+ && bp->libsym->arch.type == PPC_PLT_RESOLVED) {
|
|
+ each_task(proc->leader, NULL, detach_task_cb, bp);
|
|
+ unresolve_plt_slot(proc, bp->libsym->arch.plt_slot_addr,
|
|
+ bp->libsym->arch.resolved_value);
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_init(struct library *lib)
|
|
+{
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_destroy(struct library *lib)
|
|
+{
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_clone(struct library *retp, struct library *lib)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_library_symbol_init(struct library_symbol *libsym)
|
|
+{
|
|
+ /* We set type explicitly in the code above, where we have the
|
|
+ * necessary context. This is for calls from ltrace-elf.c and
|
|
+ * such. */
|
|
+ libsym->arch.type = PPC_DEFAULT;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_library_symbol_destroy(struct library_symbol *libsym)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_library_symbol_clone(struct library_symbol *retp,
|
|
+ struct library_symbol *libsym)
|
|
+{
|
|
+ retp->arch = libsym->arch;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* For some symbol types, we need to set up custom callbacks. XXX we
|
|
+ * don't need PROC here, we can store the data in BP if it is of
|
|
+ * interest to us. */
|
|
+int
|
|
+arch_breakpoint_init(struct Process *proc, struct breakpoint *bp)
|
|
+{
|
|
+ /* Artificial and entry-point breakpoints are plain. */
|
|
+ if (bp->libsym == NULL || bp->libsym->plt_type != LS_TOPLT_EXEC)
|
|
+ return 0;
|
|
+
|
|
+ /* On PPC, secure PLT and prelinked BSS PLT are plain. */
|
|
+ if (proc->e_machine == EM_PPC
|
|
+ && bp->libsym->lib->arch.bss_plt_prelinked != 0)
|
|
+ return 0;
|
|
+
|
|
+ /* On PPC64, stub PLT breakpoints are plain. */
|
|
+ if (proc->e_machine == EM_PPC64
|
|
+ && bp->libsym->arch.type == PPC64_PLT_STUB)
|
|
+ return 0;
|
|
+
|
|
+ static struct bp_callbacks cbs = {
|
|
+ .on_continue = ppc_plt_bp_continue,
|
|
+ .on_retract = ppc_plt_bp_retract,
|
|
+ };
|
|
+ breakpoint_set_callbacks(bp, &cbs);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_breakpoint_destroy(struct breakpoint *bp)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp)
|
|
+{
|
|
+ retp->arch = sbp->arch;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+arch_process_init(struct Process *proc)
|
|
+{
|
|
+ proc->arch.dl_plt_update_bp = NULL;
|
|
+ proc->arch.handler = NULL;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void
|
|
+arch_process_destroy(struct Process *proc)
|
|
+{
|
|
+}
|
|
+
|
|
+int
|
|
+arch_process_clone(struct Process *retp, struct Process *proc)
|
|
+{
|
|
+ retp->arch = proc->arch;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
+arch_process_exec(struct Process *proc)
|
|
+{
|
|
+ return arch_process_init(proc);
|
|
}
|
|
diff --git a/sysdeps/linux-gnu/ppc/regs.c b/sysdeps/linux-gnu/ppc/regs.c
|
|
index eca58ff..26e962f 100644
|
|
--- a/sysdeps/linux-gnu/ppc/regs.c
|
|
+++ b/sysdeps/linux-gnu/ppc/regs.c
|
|
@@ -3,7 +3,10 @@
|
|
#include <sys/types.h>
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
+#include <errno.h>
|
|
+#include <error.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
@@ -20,8 +23,10 @@ get_instruction_pointer(Process *proc) {
|
|
}
|
|
|
|
void
|
|
-set_instruction_pointer(Process *proc, void *addr) {
|
|
- ptrace(PTRACE_POKEUSER, proc->pid, sizeof(long)*PT_NIP, addr);
|
|
+set_instruction_pointer(Process *proc, void *addr)
|
|
+{
|
|
+ if (ptrace(PTRACE_POKEUSER, proc->pid, sizeof(long)*PT_NIP, addr) != 0)
|
|
+ error(0, errno, "set_instruction_pointer");
|
|
}
|
|
|
|
void *
|
|
diff --git a/sysdeps/linux-gnu/ppc/syscallent.h b/sysdeps/linux-gnu/ppc/syscallent.h
|
|
index 5ce5739..7537b3d 100644
|
|
--- a/sysdeps/linux-gnu/ppc/syscallent.h
|
|
+++ b/sysdeps/linux-gnu/ppc/syscallent.h
|
|
@@ -1,4 +1,4 @@
|
|
-"0", /* 0 */
|
|
+ "restart_syscall", /* 0 */
|
|
"exit", /* 1 */
|
|
"fork", /* 2 */
|
|
"read", /* 3 */
|
|
@@ -177,8 +177,8 @@
|
|
"rt_sigtimedwait", /* 176 */
|
|
"rt_sigqueueinfo", /* 177 */
|
|
"rt_sigsuspend", /* 178 */
|
|
- "pread", /* 179 */
|
|
- "pwrite", /* 180 */
|
|
+ "pread64", /* 179 */
|
|
+ "pwrite64", /* 180 */
|
|
"chown", /* 181 */
|
|
"getcwd", /* 182 */
|
|
"capget", /* 183 */
|
|
@@ -254,6 +254,12 @@
|
|
"fstatfs64", /* 253 */
|
|
"fadvise64_64", /* 254 */
|
|
"rtas", /* 255 */
|
|
+ "sys_debug_setcontext", /* 256 */
|
|
+ "server", /* 257 */
|
|
+ "migrate_pages", /* 258 */
|
|
+ "mbind", /* 259 */
|
|
+ "get_mempolicy", /* 260 */
|
|
+ "set_mempolicy", /* 261 */
|
|
"mq_open", /* 262 */
|
|
"mq_unlink", /* 263 */
|
|
"mq_timedsend", /* 264 */
|
|
@@ -270,3 +276,78 @@
|
|
"inotify_init", /* 275 */
|
|
"inotify_add_watch", /* 276 */
|
|
"inotify_rm_watch", /* 277 */
|
|
+ "spu_run", /* 278 */
|
|
+ "spu_create", /* 279 */
|
|
+ "pselect6", /* 280 */
|
|
+ "ppoll", /* 281 */
|
|
+ "unshare", /* 282 */
|
|
+ "splice", /* 283 */
|
|
+ "tee", /* 284 */
|
|
+ "vmsplice", /* 285 */
|
|
+ "openat", /* 286 */
|
|
+ "mkdirat", /* 287 */
|
|
+ "mknodat", /* 288 */
|
|
+ "fchownat", /* 289 */
|
|
+ "futimesat", /* 290 */
|
|
+ "fstatat64", /* 291 */
|
|
+ "unlinkat", /* 292 */
|
|
+ "renameat", /* 293 */
|
|
+ "linkat", /* 294 */
|
|
+ "symlinkat", /* 295 */
|
|
+ "readlinkat", /* 296 */
|
|
+ "fchmodat", /* 297 */
|
|
+ "faccessat", /* 298 */
|
|
+ "get_robust_list", /* 299 */
|
|
+ "set_robust_list", /* 300 */
|
|
+ "move_pages", /* 301 */
|
|
+ "getcpu", /* 302 */
|
|
+ "epoll_pwait", /* 303 */
|
|
+ "utimensat", /* 304 */
|
|
+ "signalfd", /* 305 */
|
|
+ "timerfd_create", /* 306 */
|
|
+ "eventfd", /* 307 */
|
|
+ "sync_file_range2", /* 308 */
|
|
+ "fallocate", /* 309 */
|
|
+ "subpage_prot", /* 310 */
|
|
+ "timerfd_settime", /* 311 */
|
|
+ "timerfd_gettime", /* 312 */
|
|
+ "signalfd4", /* 313 */
|
|
+ "eventfd2", /* 314 */
|
|
+ "epoll_create1", /* 315 */
|
|
+ "dup3", /* 316 */
|
|
+ "pipe2", /* 317 */
|
|
+ "inotify_init1", /* 318 */
|
|
+ "perf_event_open", /* 319 */
|
|
+ "preadv", /* 320 */
|
|
+ "pwritev", /* 321 */
|
|
+ "rt_tgsigqueueinfo", /* 322 */
|
|
+ "fanotify_init", /* 323 */
|
|
+ "fanotify_mark", /* 324 */
|
|
+ "prlimit64", /* 325 */
|
|
+ "socket", /* 326 */
|
|
+ "bind", /* 327 */
|
|
+ "connect", /* 328 */
|
|
+ "listen", /* 329 */
|
|
+ "accept", /* 330 */
|
|
+ "getsockname", /* 331 */
|
|
+ "getpeername", /* 332 */
|
|
+ "socketpair", /* 333 */
|
|
+ "send", /* 334 */
|
|
+ "sendto", /* 335 */
|
|
+ "recv", /* 336 */
|
|
+ "recvfrom", /* 337 */
|
|
+ "shutdown", /* 338 */
|
|
+ "setsockopt", /* 339 */
|
|
+ "getsockopt", /* 340 */
|
|
+ "sendmsg", /* 341 */
|
|
+ "recvmsg", /* 342 */
|
|
+ "recvmmsg", /* 343 */
|
|
+ "accept4", /* 344 */
|
|
+ "name_to_handle_at", /* 345 */
|
|
+ "open_by_handle_at", /* 346 */
|
|
+ "clock_adjtime", /* 347 */
|
|
+ "syncfs", /* 348 */
|
|
+ "sendmmsg", /* 349 */
|
|
+ "setns", /* 350 */
|
|
+ "process_vm_readv", /* 351 */
|
|
+ "process_writev", /* 352 */
|
|
diff --git a/sysdeps/linux-gnu/ppc/trace.c b/sysdeps/linux-gnu/ppc/trace.c
|
|
index 05993de..742785a 100644
|
|
--- a/sysdeps/linux-gnu/ppc/trace.c
|
|
+++ b/sysdeps/linux-gnu/ppc/trace.c
|
|
@@ -9,6 +9,7 @@
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
#include "ptrace.h"
|
|
#include "breakpoint.h"
|
|
@@ -209,30 +211,46 @@ arch_umovelong (Process *proc, void *addr, long *result, arg_type_info *info) {
|
|
#define STWCX_INSTRUCTION 0x7c00012d
|
|
#define STDCX_INSTRUCTION 0x7c0001ad
|
|
#define BC_MASK 0xfc000000
|
|
-#define BC_INSTRUCTION 0x40000000
|
|
+#define BC_INSN 0x40000000
|
|
+#define BRANCH_MASK 0xfc000000
|
|
+
|
|
+/* In plt.h. XXX make this official interface. */
|
|
+int read_target_4(struct Process *proc, target_address_t addr, uint32_t *lp);
|
|
|
|
int
|
|
arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp,
|
|
int (*add_cb)(void *addr, void *data),
|
|
void *add_cb_data)
|
|
{
|
|
- void *addr = sbp->addr;
|
|
- debug(1, "pid=%d addr=%p", proc->pid, addr);
|
|
+ target_address_t ip = get_instruction_pointer(proc);
|
|
+ struct breakpoint *other = address2bpstruct(proc->leader, ip);
|
|
+
|
|
+ debug(1, "arch_atomic_singlestep pid=%d addr=%p %s(%p)",
|
|
+ proc->pid, ip, breakpoint_name(sbp), sbp->addr);
|
|
|
|
/* If the original instruction was lwarx/ldarx, we can't
|
|
* single-step over it, instead we have to execute the whole
|
|
* atomic block at once. */
|
|
union {
|
|
uint32_t insn;
|
|
- char buf[4];
|
|
+ char buf[BREAKPOINT_LENGTH];
|
|
} u;
|
|
- memcpy(u.buf, sbp->orig_value, BREAKPOINT_LENGTH);
|
|
+ if (other != NULL) {
|
|
+ memcpy(u.buf, sbp->orig_value, BREAKPOINT_LENGTH);
|
|
+ } else if (read_target_4(proc, ip, &u.insn) < 0) {
|
|
+ fprintf(stderr, "couldn't read instruction at IP %p\n", ip);
|
|
+ /* Do the normal singlestep. */
|
|
+ return 1;
|
|
+ }
|
|
|
|
if ((u.insn & LWARX_MASK) != LWARX_INSTRUCTION
|
|
&& (u.insn & LWARX_MASK) != LDARX_INSTRUCTION)
|
|
return 1;
|
|
|
|
+ debug(1, "singlestep over atomic block at %p", ip);
|
|
+
|
|
int insn_count;
|
|
+ target_address_t addr = ip;
|
|
for (insn_count = 0; ; ++insn_count) {
|
|
addr += 4;
|
|
unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0);
|
|
@@ -245,27 +263,39 @@ arch_atomic_singlestep(struct Process *proc, Breakpoint *sbp,
|
|
insn = l;
|
|
#endif
|
|
|
|
- /* If we hit a branch instruction, give up. The
|
|
- * computation could escape that way and we'd have to
|
|
- * treat that case specially. */
|
|
- if ((insn & BC_MASK) == BC_INSTRUCTION) {
|
|
- debug(1, "pid=%d, found branch at %p, giving up",
|
|
- proc->pid, addr);
|
|
- return -1;
|
|
+ /* If a conditional branch is found, put a breakpoint
|
|
+ * in its destination address. */
|
|
+ if ((insn & BRANCH_MASK) == BC_INSN) {
|
|
+ int immediate = ((insn & 0xfffc) ^ 0x8000) - 0x8000;
|
|
+ int absolute = insn & 2;
|
|
+
|
|
+ /* XXX drop the following casts. */
|
|
+ target_address_t branch_addr;
|
|
+ if (absolute)
|
|
+ branch_addr = (void *)(uintptr_t)immediate;
|
|
+ else
|
|
+ branch_addr = addr + (uintptr_t)immediate;
|
|
+
|
|
+ debug(1, "pid=%d, branch in atomic block from %p to %p",
|
|
+ proc->pid, addr, branch_addr);
|
|
+ if (add_cb(branch_addr, add_cb_data) < 0)
|
|
+ return -1;
|
|
}
|
|
|
|
+ /* Assume that the atomic sequence ends with a
|
|
+ * stwcx/stdcx instruction. */
|
|
if ((insn & STWCX_MASK) == STWCX_INSTRUCTION
|
|
|| (insn & STWCX_MASK) == STDCX_INSTRUCTION) {
|
|
- debug(1, "pid=%d, found end of atomic block at %p",
|
|
- proc->pid, addr);
|
|
+ debug(1, "pid=%d, found end of atomic block %p at %p",
|
|
+ proc->pid, ip, addr);
|
|
break;
|
|
}
|
|
|
|
/* Arbitrary cut-off. If we didn't find the
|
|
* terminating instruction by now, just give up. */
|
|
if (insn_count > 16) {
|
|
- debug(1, "pid=%d, couldn't find end of atomic block",
|
|
- proc->pid);
|
|
+ fprintf(stderr, "[%d] couldn't find end of atomic block"
|
|
+ " at %p\n", proc->pid, ip);
|
|
return -1;
|
|
}
|
|
}
|
|
diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c
|
|
index 3350117..b5123fe 100644
|
|
--- a/sysdeps/linux-gnu/proc.c
|
|
+++ b/sysdeps/linux-gnu/proc.c
|
|
@@ -1,23 +1,24 @@
|
|
#define _GNU_SOURCE /* For getline. */
|
|
#include "config.h"
|
|
|
|
-#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
+#include <sys/syscall.h>
|
|
+#include <sys/types.h>
|
|
+#include <ctype.h>
|
|
+#include <dirent.h>
|
|
+#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <link.h>
|
|
+#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
-#include <signal.h>
|
|
#include <unistd.h>
|
|
-#include <dirent.h>
|
|
-#include <ctype.h>
|
|
-#include <errno.h>
|
|
-#include <sys/syscall.h>
|
|
-#include <error.h>
|
|
|
|
#include "common.h"
|
|
#include "breakpoint.h"
|
|
+#include "proc.h"
|
|
+#include "library.h"
|
|
|
|
/* /proc/pid doesn't exist just after the fork, and sometimes `ltrace'
|
|
* couldn't open it to find the executable. So it may be necessary to
|
|
@@ -77,27 +78,28 @@ find_line_starting(FILE * file, const char * prefix, size_t len)
|
|
}
|
|
|
|
static void
|
|
-each_line_starting(FILE * file, const char *prefix,
|
|
- enum pcb_status (*cb)(const char * line, const char * prefix,
|
|
- void * data),
|
|
- void * data)
|
|
+each_line_starting(FILE *file, const char *prefix,
|
|
+ enum callback_status (*cb)(const char *line,
|
|
+ const char *prefix,
|
|
+ void *data),
|
|
+ void *data)
|
|
{
|
|
size_t len = strlen(prefix);
|
|
char * line;
|
|
while ((line = find_line_starting(file, prefix, len)) != NULL) {
|
|
- enum pcb_status st = (*cb)(line, prefix, data);
|
|
+ enum callback_status st = (*cb)(line, prefix, data);
|
|
free (line);
|
|
- if (st == pcb_stop)
|
|
+ if (st == CBS_STOP)
|
|
return;
|
|
}
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-process_leader_cb(const char * line, const char * prefix, void * data)
|
|
+static enum callback_status
|
|
+process_leader_cb(const char *line, const char *prefix, void *data)
|
|
{
|
|
pid_t * pidp = data;
|
|
*pidp = atoi(line + strlen(prefix));
|
|
- return pcb_stop;
|
|
+ return CBS_STOP;
|
|
}
|
|
|
|
pid_t
|
|
@@ -113,13 +115,13 @@ process_leader(pid_t pid)
|
|
return tgid;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-process_stopped_cb(const char * line, const char * prefix, void * data)
|
|
+static enum callback_status
|
|
+process_stopped_cb(const char *line, const char *prefix, void *data)
|
|
{
|
|
char c = line[strlen(prefix)];
|
|
// t:tracing stop, T:job control stop
|
|
*(int *)data = (c == 't' || c == 'T');
|
|
- return pcb_stop;
|
|
+ return CBS_STOP;
|
|
}
|
|
|
|
int
|
|
@@ -135,15 +137,15 @@ process_stopped(pid_t pid)
|
|
return is_stopped;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-process_status_cb(const char * line, const char * prefix, void * data)
|
|
+static enum callback_status
|
|
+process_status_cb(const char *line, const char *prefix, void *data)
|
|
{
|
|
const char * status = line + strlen(prefix);
|
|
const char c = *status;
|
|
|
|
#define RETURN(C) do { \
|
|
*(enum process_status *)data = C; \
|
|
- return pcb_stop; \
|
|
+ return CBS_STOP; \
|
|
} while (0)
|
|
|
|
switch (c) {
|
|
@@ -179,7 +181,8 @@ process_status(pid_t pid)
|
|
each_line_starting(file, "State:\t", &process_status_cb, &ret);
|
|
fclose(file);
|
|
if (ret == ps_invalid)
|
|
- error(0, errno, "process_status %d", pid);
|
|
+ fprintf(stderr, "process_status %d: %s", pid,
|
|
+ strerror(errno));
|
|
} else
|
|
/* If the file is not present, the process presumably
|
|
* exited already. */
|
|
@@ -242,51 +245,178 @@ process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n)
|
|
return 0;
|
|
}
|
|
|
|
+/* On native 64-bit system, we need to be careful when handling cross
|
|
+ * tracing. This select appropriate pointer depending on host and
|
|
+ * target architectures. XXX Really we should abstract this into the
|
|
+ * ABI object, as theorized about somewhere on pmachata/revamp
|
|
+ * branch. */
|
|
+static void *
|
|
+select_32_64(struct Process *proc, void *p32, void *p64)
|
|
+{
|
|
+ if (sizeof(long) == 4 || proc->mask_32bit)
|
|
+ return p32;
|
|
+ else
|
|
+ return p64;
|
|
+}
|
|
+
|
|
+static int
|
|
+fetch_dyn64(struct Process *proc, target_address_t *addr, Elf64_Dyn *ret)
|
|
+{
|
|
+ if (umovebytes(proc, *addr, ret, sizeof(*ret)) != sizeof(*ret))
|
|
+ return -1;
|
|
+ *addr += sizeof(*ret);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int
|
|
-find_dynamic_entry_addr(Process *proc, void *pvAddr, int d_tag, void **addr) {
|
|
- int i = 0, done = 0;
|
|
- ElfW(Dyn) entry;
|
|
+fetch_dyn32(struct Process *proc, target_address_t *addr, Elf64_Dyn *ret)
|
|
+{
|
|
+ Elf32_Dyn dyn;
|
|
+ if (umovebytes(proc, *addr, &dyn, sizeof(dyn)) != sizeof(dyn))
|
|
+ return -1;
|
|
+
|
|
+ *addr += sizeof(dyn);
|
|
+ ret->d_tag = dyn.d_tag;
|
|
+ ret->d_un.d_val = dyn.d_un.d_val;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
|
|
+static int (*
|
|
+dyn_fetcher(struct Process *proc))(struct Process *,
|
|
+ target_address_t *, Elf64_Dyn *)
|
|
+{
|
|
+ return select_32_64(proc, fetch_dyn32, fetch_dyn64);
|
|
+}
|
|
+
|
|
+static int
|
|
+find_dynamic_entry_addr(struct Process *proc, target_address_t src_addr,
|
|
+ int d_tag, target_address_t *ret)
|
|
+{
|
|
debug(DEBUG_FUNCTION, "find_dynamic_entry()");
|
|
|
|
- if (addr == NULL || pvAddr == NULL || d_tag < 0 || d_tag > DT_NUM) {
|
|
+ if (ret == NULL || src_addr == 0 || d_tag < 0 || d_tag > DT_NUM)
|
|
return -1;
|
|
- }
|
|
|
|
- while ((!done) && (i < ELF_MAX_SEGMENTS) &&
|
|
- (sizeof(entry) == umovebytes(proc, pvAddr, &entry, sizeof(entry))) &&
|
|
- (entry.d_tag != DT_NULL)) {
|
|
+ int i = 0;
|
|
+ while (1) {
|
|
+ Elf64_Dyn entry;
|
|
+ if (dyn_fetcher(proc)(proc, &src_addr, &entry) < 0
|
|
+ || entry.d_tag == DT_NULL
|
|
+ || i++ > 100) { /* Arbitrary cut-off so that we
|
|
+ * don't loop forever if the
|
|
+ * binary is corrupted. */
|
|
+ debug(2, "Couldn't find address for dtag!");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
if (entry.d_tag == d_tag) {
|
|
- done = 1;
|
|
- *addr = (void *)entry.d_un.d_val;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ *ret = (target_address_t)(uintptr_t)entry.d_un.d_val;
|
|
+ debug(2, "found address: %p in dtag %d", *ret, d_tag);
|
|
+ return 0;
|
|
}
|
|
- pvAddr += sizeof(entry);
|
|
- i++;
|
|
}
|
|
+}
|
|
|
|
- if (done) {
|
|
- debug(2, "found address: 0x%p in dtag %d\n", *addr, d_tag);
|
|
- return 0;
|
|
+/* Our own type for representing 32-bit linkmap. We can't rely on the
|
|
+ * definition in link.h, because that's only accurate for our host
|
|
+ * architecture, not for target architecture (where the traced process
|
|
+ * runs). */
|
|
+#define LT_LINK_MAP(BITS) \
|
|
+ { \
|
|
+ Elf##BITS##_Addr l_addr; \
|
|
+ Elf##BITS##_Addr l_name; \
|
|
+ Elf##BITS##_Addr l_ld; \
|
|
+ Elf##BITS##_Addr l_next; \
|
|
+ Elf##BITS##_Addr l_prev; \
|
|
}
|
|
- else {
|
|
- debug(2, "Couldn't address for dtag!\n");
|
|
+struct lt_link_map_32 LT_LINK_MAP(32);
|
|
+struct lt_link_map_64 LT_LINK_MAP(64);
|
|
+
|
|
+static int
|
|
+fetch_lm64(struct Process *proc, target_address_t addr,
|
|
+ struct lt_link_map_64 *ret)
|
|
+{
|
|
+ if (umovebytes(proc, addr, ret, sizeof(*ret)) != sizeof(*ret))
|
|
+ return -1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+fetch_lm32(struct Process *proc, target_address_t addr,
|
|
+ struct lt_link_map_64 *ret)
|
|
+{
|
|
+ struct lt_link_map_32 lm;
|
|
+ if (umovebytes(proc, addr, &lm, sizeof(lm)) != sizeof(lm))
|
|
return -1;
|
|
+
|
|
+ ret->l_addr = lm.l_addr;
|
|
+ ret->l_name = lm.l_name;
|
|
+ ret->l_ld = lm.l_ld;
|
|
+ ret->l_next = lm.l_next;
|
|
+ ret->l_prev = lm.l_prev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int (*
|
|
+lm_fetcher(struct Process *proc))(struct Process *,
|
|
+ target_address_t, struct lt_link_map_64 *)
|
|
+{
|
|
+ return select_32_64(proc, fetch_lm32, fetch_lm64);
|
|
+}
|
|
+
|
|
+/* The same as above holds for struct r_debug. */
|
|
+#define LT_R_DEBUG(BITS) \
|
|
+ { \
|
|
+ int r_version; \
|
|
+ Elf##BITS##_Addr r_map; \
|
|
+ Elf##BITS##_Addr r_brk; \
|
|
+ int r_state; \
|
|
+ Elf##BITS##_Addr r_ldbase; \
|
|
}
|
|
+
|
|
+struct lt_r_debug_32 LT_R_DEBUG(32);
|
|
+struct lt_r_debug_64 LT_R_DEBUG(64);
|
|
+
|
|
+static int
|
|
+fetch_rd64(struct Process *proc, target_address_t addr,
|
|
+ struct lt_r_debug_64 *ret)
|
|
+{
|
|
+ if (umovebytes(proc, addr, ret, sizeof(*ret)) != sizeof(*ret))
|
|
+ return -1;
|
|
+ return 0;
|
|
}
|
|
|
|
-struct cb_data {
|
|
- const char *lib_name;
|
|
- struct ltelf *lte;
|
|
- ElfW(Addr) addr;
|
|
- Process *proc;
|
|
-};
|
|
+static int
|
|
+fetch_rd32(struct Process *proc, target_address_t addr,
|
|
+ struct lt_r_debug_64 *ret)
|
|
+{
|
|
+ struct lt_r_debug_32 rd;
|
|
+ if (umovebytes(proc, addr, &rd, sizeof(rd)) != sizeof(rd))
|
|
+ return -1;
|
|
|
|
-static void
|
|
-crawl_linkmap(Process *proc, struct r_debug *dbg, void (*callback)(void *), struct cb_data *data) {
|
|
- struct link_map rlm;
|
|
- char lib_name[BUFSIZ];
|
|
- struct link_map *lm = NULL;
|
|
+ ret->r_version = rd.r_version;
|
|
+ ret->r_map = rd.r_map;
|
|
+ ret->r_brk = rd.r_brk;
|
|
+ ret->r_state = rd.r_state;
|
|
+ ret->r_ldbase = rd.r_ldbase;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
|
|
+static int (*
|
|
+rdebug_fetcher(struct Process *proc))(struct Process *,
|
|
+ target_address_t, struct lt_r_debug_64 *)
|
|
+{
|
|
+ return select_32_64(proc, fetch_rd32, fetch_rd64);
|
|
+}
|
|
+
|
|
+static void
|
|
+crawl_linkmap(struct Process *proc, struct lt_r_debug_64 *dbg)
|
|
+{
|
|
debug (DEBUG_FUNCTION, "crawl_linkmap()");
|
|
|
|
if (!dbg || !dbg->r_map) {
|
|
@@ -294,201 +424,234 @@ crawl_linkmap(Process *proc, struct r_debug *dbg, void (*callback)(void *), stru
|
|
return;
|
|
}
|
|
|
|
- lm = dbg->r_map;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ target_address_t addr = (target_address_t)(uintptr_t)dbg->r_map;
|
|
|
|
- while (lm) {
|
|
- if (umovebytes(proc, lm, &rlm, sizeof(rlm)) != sizeof(rlm)) {
|
|
- debug(2, "Unable to read link map\n");
|
|
+ while (addr != 0) {
|
|
+ struct lt_link_map_64 rlm;
|
|
+ if (lm_fetcher(proc)(proc, addr, &rlm) < 0) {
|
|
+ debug(2, "Unable to read link map");
|
|
return;
|
|
}
|
|
|
|
- lm = rlm.l_next;
|
|
- if (rlm.l_name == NULL) {
|
|
- debug(2, "Invalid library name referenced in dynamic linker map\n");
|
|
+ target_address_t key = addr;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ addr = (target_address_t)(uintptr_t)rlm.l_next;
|
|
+ if (rlm.l_name == 0) {
|
|
+ debug(2, "Name of mapped library is NULL");
|
|
return;
|
|
}
|
|
|
|
- umovebytes(proc, rlm.l_name, lib_name, sizeof(lib_name));
|
|
+ char lib_name[BUFSIZ];
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ umovebytes(proc, (target_address_t)(uintptr_t)rlm.l_name,
|
|
+ lib_name, sizeof(lib_name));
|
|
|
|
- if (lib_name[0] == '\0') {
|
|
- debug(2, "Library name is an empty string");
|
|
+ if (*lib_name == '\0') {
|
|
+ /* VDSO. No associated file, XXX but we might
|
|
+ * load it from the address space of the
|
|
+ * process. */
|
|
continue;
|
|
}
|
|
|
|
- if (callback) {
|
|
- debug(2, "Dispatching callback for: %s, "
|
|
- "Loaded at 0x%" PRI_ELF_ADDR "\n",
|
|
- lib_name, rlm.l_addr);
|
|
- data->addr = rlm.l_addr;
|
|
- data->lib_name = lib_name;
|
|
- callback(data);
|
|
+ /* Do we have that library already? */
|
|
+ if (proc_each_library(proc, NULL, library_with_key_cb, &key))
|
|
+ continue;
|
|
+
|
|
+ struct library *lib = malloc(sizeof(*lib));
|
|
+ if (lib == NULL) {
|
|
+ fail:
|
|
+ if (lib != NULL)
|
|
+ library_destroy(lib);
|
|
+ fprintf(stderr, "Couldn't load ELF object %s: %s\n",
|
|
+ lib_name, strerror(errno));
|
|
+ continue;
|
|
}
|
|
+ library_init(lib, LT_LIBTYPE_DSO);
|
|
+
|
|
+ if (ltelf_read_library(lib, proc, lib_name, rlm.l_addr) < 0)
|
|
+ goto fail;
|
|
+
|
|
+ lib->key = key;
|
|
+ proc_add_library(proc, lib);
|
|
}
|
|
return;
|
|
}
|
|
|
|
-static struct r_debug *
|
|
-load_debug_struct(Process *proc) {
|
|
- struct r_debug *rdbg = NULL;
|
|
+/* A struct stored at proc->debug. */
|
|
+struct debug_struct
|
|
+{
|
|
+ target_address_t debug_addr;
|
|
+ int state;
|
|
+};
|
|
|
|
+static int
|
|
+load_debug_struct(struct Process *proc, struct lt_r_debug_64 *ret)
|
|
+{
|
|
debug(DEBUG_FUNCTION, "load_debug_struct");
|
|
|
|
- rdbg = malloc(sizeof(*rdbg));
|
|
- if (!rdbg) {
|
|
- return NULL;
|
|
- }
|
|
+ struct debug_struct *debug = proc->debug;
|
|
|
|
- if (umovebytes(proc, proc->debug, rdbg, sizeof(*rdbg)) != sizeof(*rdbg)) {
|
|
+ if (rdebug_fetcher(proc)(proc, debug->debug_addr, ret) < 0) {
|
|
debug(2, "This process does not have a debug structure!\n");
|
|
- free(rdbg);
|
|
- return NULL;
|
|
+ return -1;
|
|
}
|
|
|
|
- return rdbg;
|
|
+ return 0;
|
|
}
|
|
|
|
static void
|
|
-linkmap_add_cb(void *data) { //const char *lib_name, ElfW(Addr) addr) {
|
|
- size_t i = 0;
|
|
- struct cb_data *lm_add = data;
|
|
- struct ltelf lte;
|
|
- struct opt_x_t *xptr;
|
|
-
|
|
- debug(DEBUG_FUNCTION, "linkmap_add_cb");
|
|
-
|
|
- /*
|
|
- XXX
|
|
- iterate through library[i]'s to see if this lib is in the list.
|
|
- if not, add it
|
|
- */
|
|
- for(;i < library_num;i++) {
|
|
- if (strcmp(library[i], lm_add->lib_name) == 0) {
|
|
- /* found it, so its not new */
|
|
- return;
|
|
- }
|
|
- }
|
|
-
|
|
- /* new library linked! */
|
|
- debug(2, "New libdl loaded library found: %s\n", lm_add->lib_name);
|
|
-
|
|
- if (library_num < MAX_LIBRARIES) {
|
|
- library[library_num++] = strdup(lm_add->lib_name);
|
|
- memset(<e, 0, sizeof(struct ltelf));
|
|
- lte.base_addr = lm_add->addr;
|
|
- do_init_elf(<e, library[library_num-1]);
|
|
- /* add bps */
|
|
- for (xptr = opt_x; xptr; xptr = xptr->next) {
|
|
- if (xptr->found)
|
|
- continue;
|
|
-
|
|
- GElf_Sym sym;
|
|
- GElf_Addr addr;
|
|
-
|
|
- if (in_load_libraries(xptr->name, <e, 1, &sym)) {
|
|
- debug(2, "found symbol %s @ %#" PRIx64
|
|
- ", adding it.",
|
|
- xptr->name, sym.st_value);
|
|
- addr = sym.st_value;
|
|
- add_library_symbol(addr, xptr->name, &library_symbols, LS_TOPLT_NONE, 0);
|
|
- xptr->found = 1;
|
|
- insert_breakpoint(lm_add->proc,
|
|
- sym2addr(lm_add->proc,
|
|
- library_symbols),
|
|
- library_symbols, 1);
|
|
- }
|
|
- }
|
|
- do_close_elf(<e);
|
|
- }
|
|
-}
|
|
-
|
|
-void
|
|
-arch_check_dbg(Process *proc) {
|
|
- struct r_debug *dbg = NULL;
|
|
- struct cb_data data;
|
|
-
|
|
+rdebug_bp_on_hit(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
debug(DEBUG_FUNCTION, "arch_check_dbg");
|
|
|
|
- if (!(dbg = load_debug_struct(proc))) {
|
|
+ struct lt_r_debug_64 rdbg;
|
|
+ if (load_debug_struct(proc, &rdbg) < 0) {
|
|
debug(2, "Unable to load debug structure!");
|
|
return;
|
|
}
|
|
|
|
- if (dbg->r_state == RT_CONSISTENT) {
|
|
+ struct debug_struct *debug = proc->debug;
|
|
+ if (rdbg.r_state == RT_CONSISTENT) {
|
|
debug(2, "Linkmap is now consistent");
|
|
- if (proc->debug_state == RT_ADD) {
|
|
+ if (debug->state == RT_ADD) {
|
|
debug(2, "Adding DSO to linkmap");
|
|
- data.proc = proc;
|
|
- crawl_linkmap(proc, dbg, linkmap_add_cb, &data);
|
|
- } else if (proc->debug_state == RT_DELETE) {
|
|
+ //data.proc = proc;
|
|
+ crawl_linkmap(proc, &rdbg);
|
|
+ //&data);
|
|
+ } else if (debug->state == RT_DELETE) {
|
|
debug(2, "Removing DSO from linkmap");
|
|
} else {
|
|
debug(2, "Unexpected debug state!");
|
|
}
|
|
}
|
|
|
|
- proc->debug_state = dbg->r_state;
|
|
-
|
|
- return;
|
|
+ debug->state = rdbg.r_state;
|
|
}
|
|
|
|
-static void
|
|
-hook_libdl_cb(void *data) {
|
|
- struct cb_data *hook_data = data;
|
|
- const char *lib_name = NULL;
|
|
- ElfW(Addr) addr;
|
|
- struct ltelf *lte = NULL;
|
|
-
|
|
- debug(DEBUG_FUNCTION, "add_library_cb");
|
|
+int
|
|
+linkmap_init(struct Process *proc, target_address_t dyn_addr)
|
|
+{
|
|
+ debug(DEBUG_FUNCTION, "linkmap_init()");
|
|
|
|
- if (!data) {
|
|
- debug(2, "No callback data");
|
|
- return;
|
|
+ struct debug_struct *debug = malloc(sizeof(*debug));
|
|
+ if (debug == NULL) {
|
|
+ fprintf(stderr, "couldn't allocate debug struct: %s\n",
|
|
+ strerror(errno));
|
|
+ fail:
|
|
+ proc->debug = NULL;
|
|
+ free(debug);
|
|
+ return -1;
|
|
}
|
|
+ proc->debug = debug;
|
|
|
|
- lib_name = hook_data->lib_name;
|
|
- addr = hook_data->addr;
|
|
- lte = hook_data->lte;
|
|
-
|
|
- if (library_num < MAX_LIBRARIES) {
|
|
- library[library_num++] = strdup(lib_name);
|
|
- lte[library_num].base_addr = addr;
|
|
+ if (find_dynamic_entry_addr(proc, dyn_addr, DT_DEBUG,
|
|
+ &debug->debug_addr) == -1) {
|
|
+ debug(2, "Couldn't find debug structure!");
|
|
+ goto fail;
|
|
}
|
|
- else {
|
|
- fprintf (stderr, "MAX LIBS REACHED\n");
|
|
- exit(EXIT_FAILURE);
|
|
+
|
|
+ int status;
|
|
+ struct lt_r_debug_64 rdbg;
|
|
+ if ((status = load_debug_struct(proc, &rdbg)) < 0) {
|
|
+ debug(2, "No debug structure or no memory to allocate one!");
|
|
+ return status;
|
|
}
|
|
-}
|
|
|
|
-int
|
|
-linkmap_init(Process *proc, struct ltelf *lte) {
|
|
- void *dbg_addr = NULL, *dyn_addr = GELF_ADDR_CAST(lte->dyn_addr);
|
|
- struct r_debug *rdbg = NULL;
|
|
- struct cb_data data;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ target_address_t addr = (target_address_t)(uintptr_t)rdbg.r_brk;
|
|
+ if (arch_translate_address_dyn(proc, addr, &addr) < 0)
|
|
+ goto fail;
|
|
|
|
- debug(DEBUG_FUNCTION, "linkmap_init()");
|
|
+ struct breakpoint *rdebug_bp = insert_breakpoint(proc, addr, NULL);
|
|
+ static struct bp_callbacks rdebug_callbacks = {
|
|
+ .on_hit = rdebug_bp_on_hit,
|
|
+ };
|
|
+ rdebug_bp->cbs = &rdebug_callbacks;
|
|
|
|
- if (find_dynamic_entry_addr(proc, dyn_addr, DT_DEBUG, &dbg_addr) == -1) {
|
|
- debug(2, "Couldn't find debug structure!");
|
|
- return -1;
|
|
- }
|
|
+ crawl_linkmap(proc, &rdbg);
|
|
|
|
- proc->debug = dbg_addr;
|
|
+ return 0;
|
|
+}
|
|
|
|
- if (!(rdbg = load_debug_struct(proc))) {
|
|
- debug(2, "No debug structure or no memory to allocate one!");
|
|
+static int
|
|
+fetch_auxv64_entry(int fd, Elf64_auxv_t *ret)
|
|
+{
|
|
+ /* Reaching EOF is as much problem as not reading whole
|
|
+ * entry. */
|
|
+ return read(fd, ret, sizeof(*ret)) == sizeof(*ret) ? 0 : -1;
|
|
+}
|
|
+
|
|
+static int
|
|
+fetch_auxv32_entry(int fd, Elf64_auxv_t *ret)
|
|
+{
|
|
+ Elf32_auxv_t auxv;
|
|
+ if (read(fd, &auxv, sizeof(auxv)) != sizeof(auxv))
|
|
return -1;
|
|
+
|
|
+ ret->a_type = auxv.a_type;
|
|
+ ret->a_un.a_val = auxv.a_un.a_val;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int (*
|
|
+auxv_fetcher(struct Process *proc))(int, Elf64_auxv_t *)
|
|
+{
|
|
+ return select_32_64(proc, fetch_auxv32_entry, fetch_auxv64_entry);
|
|
+}
|
|
+
|
|
+int
|
|
+process_get_entry(struct Process *proc,
|
|
+ target_address_t *entryp,
|
|
+ target_address_t *interp_biasp)
|
|
+{
|
|
+ PROC_PID_FILE(fn, "/proc/%d/auxv", proc->pid);
|
|
+ int fd = open(fn, O_RDONLY);
|
|
+ if (fd == -1) {
|
|
+ fail:
|
|
+ fprintf(stderr, "couldn't read %s: %s", fn, strerror(errno));
|
|
+ done:
|
|
+ if (fd != -1)
|
|
+ close(fd);
|
|
+ return fd == -1 ? -1 : 0;
|
|
}
|
|
|
|
- data.lte = lte;
|
|
+ target_address_t at_entry = 0;
|
|
+ target_address_t at_bias = 0;
|
|
+ while (1) {
|
|
+ Elf64_auxv_t entry;
|
|
+ if (auxv_fetcher(proc)(fd, &entry) < 0)
|
|
+ goto fail;
|
|
+
|
|
+ switch (entry.a_type) {
|
|
+ case AT_BASE:
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ at_bias = (target_address_t)
|
|
+ (uintptr_t)entry.a_un.a_val;
|
|
+ continue;
|
|
|
|
- add_library_symbol(rdbg->r_brk, "", &library_symbols, LS_TOPLT_NONE, 0);
|
|
- insert_breakpoint(proc, sym2addr(proc, library_symbols),
|
|
- library_symbols, 1);
|
|
+ case AT_ENTRY:
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ at_entry = (target_address_t)
|
|
+ (uintptr_t)entry.a_un.a_val;
|
|
+ default:
|
|
+ continue;
|
|
|
|
- crawl_linkmap(proc, rdbg, hook_libdl_cb, &data);
|
|
+ case AT_NULL:
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
|
|
- free(rdbg);
|
|
- return 0;
|
|
+ *entryp = at_entry;
|
|
+ *interp_biasp = at_bias;
|
|
+ goto done;
|
|
}
|
|
|
|
int
|
|
diff --git a/sysdeps/linux-gnu/s390/plt.c b/sysdeps/linux-gnu/s390/plt.c
|
|
index 85a1dd1..754d270 100644
|
|
--- a/sysdeps/linux-gnu/s390/plt.c
|
|
+++ b/sysdeps/linux-gnu/s390/plt.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
GElf_Addr
|
|
diff --git a/sysdeps/linux-gnu/s390/regs.c b/sysdeps/linux-gnu/s390/regs.c
|
|
index 169893e..a45dd9b 100644
|
|
--- a/sysdeps/linux-gnu/s390/regs.c
|
|
+++ b/sysdeps/linux-gnu/s390/regs.c
|
|
@@ -9,6 +9,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
diff --git a/sysdeps/linux-gnu/s390/trace.c b/sysdeps/linux-gnu/s390/trace.c
|
|
index 63935de..8c08f1f 100644
|
|
--- a/sysdeps/linux-gnu/s390/trace.c
|
|
+++ b/sysdeps/linux-gnu/s390/trace.c
|
|
@@ -17,6 +17,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
diff --git a/sysdeps/linux-gnu/sparc/plt.c b/sysdeps/linux-gnu/sparc/plt.c
|
|
index f9e6d80..658e549 100644
|
|
--- a/sysdeps/linux-gnu/sparc/plt.c
|
|
+++ b/sysdeps/linux-gnu/sparc/plt.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
GElf_Addr
|
|
diff --git a/sysdeps/linux-gnu/sparc/regs.c b/sysdeps/linux-gnu/sparc/regs.c
|
|
index 49d2729..d7ee114 100644
|
|
--- a/sysdeps/linux-gnu/sparc/regs.c
|
|
+++ b/sysdeps/linux-gnu/sparc/regs.c
|
|
@@ -2,6 +2,7 @@
|
|
|
|
#include <sys/types.h>
|
|
#include "ptrace.h"
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
void *
|
|
diff --git a/sysdeps/linux-gnu/sparc/trace.c b/sysdeps/linux-gnu/sparc/trace.c
|
|
index 7f05b55..e05c4d3 100644
|
|
--- a/sysdeps/linux-gnu/sparc/trace.c
|
|
+++ b/sysdeps/linux-gnu/sparc/trace.c
|
|
@@ -6,6 +6,7 @@
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include "ptrace.h"
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
|
|
void
|
|
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
|
|
index fe64a28..d5c5262 100644
|
|
--- a/sysdeps/linux-gnu/trace.c
|
|
+++ b/sysdeps/linux-gnu/trace.c
|
|
@@ -17,6 +17,8 @@
|
|
#include "common.h"
|
|
#include "config.h"
|
|
#include "breakpoint.h"
|
|
+#include "proc.h"
|
|
+#include "linux-gnu/trace.h"
|
|
|
|
#include "config.h"
|
|
#ifdef HAVE_LIBSELINUX
|
|
@@ -107,26 +109,21 @@ trace_me(void)
|
|
}
|
|
|
|
/* There's a (hopefully) brief period of time after the child process
|
|
- * exec's when we can't trace it yet. Here we wait for kernel to
|
|
+ * forks when we can't trace it yet. Here we wait for kernel to
|
|
* prepare the process. */
|
|
-void
|
|
+int
|
|
wait_for_proc(pid_t pid)
|
|
{
|
|
- size_t i;
|
|
- for (i = 0; i < 100; ++i) {
|
|
- /* We read from memory address 0, but that shouldn't
|
|
- * be a problem: the reading will just fail. We are
|
|
- * looking for a particular reason of failure. */
|
|
- if (ptrace(PTRACE_PEEKTEXT, pid, 0, 0) != -1
|
|
- || errno != ESRCH)
|
|
- return;
|
|
-
|
|
- usleep(1000);
|
|
+ /* man ptrace: PTRACE_ATTACH attaches to the process specified
|
|
+ in pid. The child is sent a SIGSTOP, but will not
|
|
+ necessarily have stopped by the completion of this call;
|
|
+ use wait() to wait for the child to stop. */
|
|
+ if (waitpid(pid, NULL, __WALL) != pid) {
|
|
+ perror ("trace_pid: waitpid");
|
|
+ return -1;
|
|
}
|
|
|
|
- fprintf(stderr, "\
|
|
-I consistently fail to read a word from the freshly launched process.\n\
|
|
-I'll now try to proceed with tracing, but this shouldn't be happening.\n");
|
|
+ return 0;
|
|
}
|
|
|
|
int
|
|
@@ -139,23 +136,16 @@ trace_pid(pid_t pid)
|
|
if (ptrace(PTRACE_ATTACH, pid, 1, 0) < 0)
|
|
return -1;
|
|
|
|
- /* man ptrace: PTRACE_ATTACH attaches to the process specified
|
|
- in pid. The child is sent a SIGSTOP, but will not
|
|
- necessarily have stopped by the completion of this call;
|
|
- use wait() to wait for the child to stop. */
|
|
- if (waitpid (pid, NULL, __WALL) != pid) {
|
|
- perror ("trace_pid: waitpid");
|
|
- return -1;
|
|
- }
|
|
-
|
|
- return 0;
|
|
+ return wait_for_proc(pid);
|
|
}
|
|
|
|
void
|
|
-trace_set_options(Process *proc, pid_t pid) {
|
|
+trace_set_options(struct Process *proc)
|
|
+{
|
|
if (proc->tracesysgood & 0x80)
|
|
return;
|
|
|
|
+ pid_t pid = proc->pid;
|
|
debug(DEBUG_PROCESS, "trace_set_options: pid=%d", pid);
|
|
|
|
long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK |
|
|
@@ -212,67 +202,6 @@ continue_process(pid_t pid)
|
|
"putting off the continue, events in que.");
|
|
}
|
|
|
|
-/**
|
|
- * This is used for bookkeeping related to PIDs that the event
|
|
- * handlers work with.
|
|
- */
|
|
-struct pid_task {
|
|
- pid_t pid; /* This may be 0 for tasks that exited
|
|
- * mid-handling. */
|
|
- int sigstopped : 1;
|
|
- int got_event : 1;
|
|
- int delivered : 1;
|
|
- int vforked : 1;
|
|
- int sysret : 1;
|
|
-} * pids;
|
|
-
|
|
-struct pid_set {
|
|
- struct pid_task * tasks;
|
|
- size_t count;
|
|
- size_t alloc;
|
|
-};
|
|
-
|
|
-/**
|
|
- * Breakpoint re-enablement. When we hit a breakpoint, we must
|
|
- * disable it, single-step, and re-enable it. That single-step can be
|
|
- * done only by one task in a task group, while others are stopped,
|
|
- * otherwise the processes would race for who sees the breakpoint
|
|
- * disabled and who doesn't. The following is to keep track of it
|
|
- * all.
|
|
- */
|
|
-struct process_stopping_handler
|
|
-{
|
|
- Event_Handler super;
|
|
-
|
|
- /* The task that is doing the re-enablement. */
|
|
- Process * task_enabling_breakpoint;
|
|
-
|
|
- /* The pointer being re-enabled. */
|
|
- struct breakpoint *breakpoint_being_enabled;
|
|
-
|
|
- /* Artificial atomic skip breakpoint, if any needed. */
|
|
- void *atomic_skip_bp_addr;
|
|
-
|
|
- enum {
|
|
- /* We are waiting for everyone to land in t/T. */
|
|
- psh_stopping = 0,
|
|
-
|
|
- /* We are doing the PTRACE_SINGLESTEP. */
|
|
- psh_singlestep,
|
|
-
|
|
- /* We are waiting for all the SIGSTOPs to arrive so
|
|
- * that we can sink them. */
|
|
- psh_sinking,
|
|
-
|
|
- /* This is for tracking the ugly workaround. */
|
|
- psh_ugly_workaround,
|
|
- } state;
|
|
-
|
|
- int exiting;
|
|
-
|
|
- struct pid_set pids;
|
|
-};
|
|
-
|
|
static struct pid_task *
|
|
get_task_info(struct pid_set * pids, pid_t pid)
|
|
{
|
|
@@ -303,8 +232,8 @@ add_task_info(struct pid_set * pids, pid_t pid)
|
|
return task_info;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-task_stopped(Process * task, void * data)
|
|
+static enum callback_status
|
|
+task_stopped(struct Process *task, void *data)
|
|
{
|
|
enum process_status st = process_status(task->pid);
|
|
if (data != NULL)
|
|
@@ -318,48 +247,48 @@ task_stopped(Process * task, void * data)
|
|
case ps_invalid:
|
|
case ps_tracing_stop:
|
|
case ps_zombie:
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
case ps_sleeping:
|
|
case ps_stop:
|
|
case ps_other:
|
|
- return pcb_stop;
|
|
+ return CBS_STOP;
|
|
}
|
|
|
|
abort ();
|
|
}
|
|
|
|
/* Task is blocked if it's stopped, or if it's a vfork parent. */
|
|
-static enum pcb_status
|
|
-task_blocked(Process * task, void * data)
|
|
+static enum callback_status
|
|
+task_blocked(struct 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 CBS_CONT;
|
|
|
|
return task_stopped(task, NULL);
|
|
}
|
|
|
|
-static Event * process_vfork_on_event(Event_Handler * super, Event * event);
|
|
+static Event *process_vfork_on_event(struct event_handler *super, Event *event);
|
|
|
|
-static enum pcb_status
|
|
-task_vforked(Process * task, void * data)
|
|
+static enum callback_status
|
|
+task_vforked(struct Process *task, void *data)
|
|
{
|
|
if (task->event_handler != NULL
|
|
&& task->event_handler->on_event == &process_vfork_on_event)
|
|
- return pcb_stop;
|
|
- return pcb_cont;
|
|
+ return CBS_STOP;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
static int
|
|
-is_vfork_parent(Process * task)
|
|
+is_vfork_parent(struct Process *task)
|
|
{
|
|
- return each_task(task->leader, &task_vforked, NULL) != NULL;
|
|
+ return each_task(task->leader, NULL, &task_vforked, NULL) != NULL;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-send_sigstop(Process * task, void * data)
|
|
+static enum callback_status
|
|
+send_sigstop(struct Process *task, void *data)
|
|
{
|
|
Process * leader = task->leader;
|
|
struct pid_set * pids = data;
|
|
@@ -372,24 +301,24 @@ send_sigstop(Process * task, void * data)
|
|
perror("send_sigstop: add_task_info");
|
|
destroy_event_handler(leader);
|
|
/* Signal failure upwards. */
|
|
- return pcb_stop;
|
|
+ return CBS_STOP;
|
|
}
|
|
|
|
/* This task still has not been attached to. It should be
|
|
stopped by the kernel. */
|
|
if (task->state == STATE_BEING_CREATED)
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
|
|
/* Don't bother sending SIGSTOP if we are already stopped, or
|
|
* 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_stopped(task, &st) == CBS_CONT)
|
|
+ return CBS_CONT;
|
|
if (task_info->sigstopped) {
|
|
if (!task_info->delivered)
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
task_info->delivered = 0;
|
|
}
|
|
|
|
@@ -400,7 +329,7 @@ send_sigstop(Process * task, void * data)
|
|
if (st == ps_sleeping
|
|
&& is_vfork_parent (task)) {
|
|
task_info->vforked = 1;
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
if (task_kill(task->pid, SIGSTOP) >= 0) {
|
|
@@ -410,7 +339,7 @@ send_sigstop(Process * task, void * data)
|
|
fprintf(stderr,
|
|
"Warning: couldn't send SIGSTOP to %d\n", task->pid);
|
|
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
/* On certain kernels, detaching right after a singlestep causes the
|
|
@@ -426,7 +355,7 @@ ugly_workaround(Process * proc)
|
|
if (sbp != NULL)
|
|
enable_breakpoint(proc, sbp);
|
|
else
|
|
- insert_breakpoint(proc, ip, NULL, 1);
|
|
+ insert_breakpoint(proc, ip, NULL);
|
|
ptrace(PTRACE_CONT, proc->pid, 0, 0);
|
|
}
|
|
|
|
@@ -444,9 +373,21 @@ process_stopping_done(struct process_stopping_handler * self, Process * leader)
|
|
continue_process(self->pids.tasks[i].pid);
|
|
continue_process(self->task_enabling_breakpoint->pid);
|
|
destroy_event_handler(leader);
|
|
- } else {
|
|
+ }
|
|
+
|
|
+ if (self->exiting) {
|
|
+ ugly_workaround:
|
|
self->state = psh_ugly_workaround;
|
|
ugly_workaround(self->task_enabling_breakpoint);
|
|
+ } else {
|
|
+ switch ((self->ugly_workaround_p)(self)) {
|
|
+ case CBS_FAIL:
|
|
+ /* xxx handle me */
|
|
+ case CBS_STOP:
|
|
+ break;
|
|
+ case CBS_CONT:
|
|
+ goto ugly_workaround;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -464,21 +405,28 @@ undo_breakpoint(Event * event, void * data)
|
|
return ecb_cont;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-untrace_task(Process * task, void * data)
|
|
+static enum callback_status
|
|
+untrace_task(struct Process *task, void *data)
|
|
{
|
|
if (task != data)
|
|
untrace_pid(task->pid);
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
-static enum pcb_status
|
|
-remove_task(Process * task, void * data)
|
|
+static enum callback_status
|
|
+remove_task(struct Process *task, void *data)
|
|
{
|
|
/* Don't untrace leader just yet. */
|
|
if (task != data)
|
|
remove_process(task);
|
|
- return pcb_cont;
|
|
+ return CBS_CONT;
|
|
+}
|
|
+
|
|
+static enum callback_status
|
|
+retract_breakpoint_cb(struct Process *proc, struct breakpoint *bp, void *data)
|
|
+{
|
|
+ breakpoint_on_retract(bp, proc);
|
|
+ return CBS_CONT;
|
|
}
|
|
|
|
static void
|
|
@@ -486,6 +434,7 @@ detach_process(Process * leader)
|
|
{
|
|
each_qd_event(&undo_breakpoint, leader);
|
|
disable_all_breakpoints(leader);
|
|
+ proc_each_breakpoint(leader, NULL, retract_breakpoint_cb, NULL);
|
|
|
|
/* Now untrace the process, if it was attached to by -p. */
|
|
struct opt_p_t * it;
|
|
@@ -494,11 +443,11 @@ detach_process(Process * leader)
|
|
if (proc == NULL)
|
|
continue;
|
|
if (proc->leader == leader) {
|
|
- each_task(leader, &untrace_task, NULL);
|
|
+ each_task(leader, NULL, &untrace_task, NULL);
|
|
break;
|
|
}
|
|
}
|
|
- each_task(leader, &remove_task, leader);
|
|
+ each_task(leader, NULL, &remove_task, leader);
|
|
destroy_event_handler(leader);
|
|
remove_task(leader, NULL);
|
|
}
|
|
@@ -632,19 +581,56 @@ arch_atomic_singlestep(struct Process *proc, Breakpoint *sbp,
|
|
}
|
|
#endif
|
|
|
|
+static Event *process_stopping_on_event(struct event_handler *super,
|
|
+ Event *event);
|
|
+
|
|
+static void
|
|
+remove_atomic_breakpoints(struct Process *proc)
|
|
+{
|
|
+ struct process_stopping_handler *self
|
|
+ = (void *)proc->leader->event_handler;
|
|
+ assert(self != NULL);
|
|
+ assert(self->super.on_event == process_stopping_on_event);
|
|
+
|
|
+ int ct = sizeof(self->atomic_skip_bp_addrs)
|
|
+ / sizeof(*self->atomic_skip_bp_addrs);
|
|
+ int i;
|
|
+ for (i = 0; i < ct; ++i)
|
|
+ if (self->atomic_skip_bp_addrs[i] != 0) {
|
|
+ delete_breakpoint(proc, self->atomic_skip_bp_addrs[i]);
|
|
+ self->atomic_skip_bp_addrs[i] = 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+atomic_singlestep_bp_on_hit(struct breakpoint *bp, struct Process *proc)
|
|
+{
|
|
+ remove_atomic_breakpoints(proc);
|
|
+}
|
|
+
|
|
static int
|
|
atomic_singlestep_add_bp(void *addr, void *data)
|
|
{
|
|
struct process_stopping_handler *self = data;
|
|
struct Process *proc = self->task_enabling_breakpoint;
|
|
|
|
- /* Only support single address as of now. */
|
|
- assert(self->atomic_skip_bp_addr == NULL);
|
|
-
|
|
- self->atomic_skip_bp_addr = addr + 4;
|
|
- insert_breakpoint(proc->leader, self->atomic_skip_bp_addr, NULL, 1);
|
|
+ int ct = sizeof(self->atomic_skip_bp_addrs)
|
|
+ / sizeof(*self->atomic_skip_bp_addrs);
|
|
+ int i;
|
|
+ for (i = 0; i < ct; ++i)
|
|
+ if (self->atomic_skip_bp_addrs[i] == 0) {
|
|
+ self->atomic_skip_bp_addrs[i] = addr;
|
|
+ static struct bp_callbacks cbs = {
|
|
+ .on_hit = atomic_singlestep_bp_on_hit,
|
|
+ };
|
|
+ struct breakpoint *bp
|
|
+ = insert_breakpoint(proc, addr, NULL);
|
|
+ breakpoint_set_callbacks(bp, &cbs);
|
|
+ return 0;
|
|
+ }
|
|
|
|
- return 0;
|
|
+ assert(!"Too many atomic singlestep breakpoints!");
|
|
+ abort();
|
|
}
|
|
|
|
static int
|
|
@@ -670,30 +656,68 @@ singlestep(struct process_stopping_handler *self) ***
|
|
}
|
|
|
|
static void
|
|
-post_singlestep(struct process_stopping_handler *self, Event **eventp)
|
|
+post_singlestep(struct process_stopping_handler *self,
|
|
+ struct Event **eventp)
|
|
{
|
|
continue_for_sigstop_delivery(&self->pids);
|
|
|
|
- if ((*eventp)->type == EVENT_BREAKPOINT)
|
|
+ if (*eventp != NULL && (*eventp)->type == EVENT_BREAKPOINT)
|
|
*eventp = NULL; // handled
|
|
|
|
- if (self->atomic_skip_bp_addr != 0)
|
|
- delete_breakpoint(self->task_enabling_breakpoint->leader,
|
|
- self->atomic_skip_bp_addr);
|
|
+ struct Process *proc = self->task_enabling_breakpoint;
|
|
|
|
+ remove_atomic_breakpoints(proc);
|
|
self->breakpoint_being_enabled = NULL;
|
|
}
|
|
|
|
static void
|
|
-singlestep_error(struct process_stopping_handler *self, Event **eventp)
|
|
+singlestep_error(struct process_stopping_handler *self)
|
|
{
|
|
struct Process *teb = self->task_enabling_breakpoint;
|
|
struct breakpoint *sbp = self->breakpoint_being_enabled;
|
|
- fprintf(stderr, "%d couldn't singlestep over %s (%p)\n",
|
|
+ fprintf(stderr, "%d couldn't continue when handling %s (%p) at %p\n",
|
|
teb->pid, sbp->libsym != NULL ? sbp->libsym->name : NULL,
|
|
- sbp->addr);
|
|
+ sbp->addr, get_instruction_pointer(teb));
|
|
delete_breakpoint(teb->leader, sbp->addr);
|
|
- post_singlestep(self, eventp);
|
|
+}
|
|
+
|
|
+static void
|
|
+pt_continue(struct process_stopping_handler *self)
|
|
+{
|
|
+ struct Process *teb = self->task_enabling_breakpoint;
|
|
+ debug(1, "PTRACE_CONT");
|
|
+ ptrace(PTRACE_CONT, teb->pid, 0, 0);
|
|
+}
|
|
+
|
|
+static void
|
|
+pt_singlestep(struct process_stopping_handler *self)
|
|
+{
|
|
+ if (singlestep(self) < 0)
|
|
+ singlestep_error(self);
|
|
+}
|
|
+
|
|
+static void
|
|
+disable_and(struct process_stopping_handler *self,
|
|
+ void (*do_this)(struct process_stopping_handler *self))
|
|
+{
|
|
+ struct Process *teb = self->task_enabling_breakpoint;
|
|
+ debug(DEBUG_PROCESS, "all stopped, now singlestep/cont %d", teb->pid);
|
|
+ if (self->breakpoint_being_enabled->enabled)
|
|
+ disable_breakpoint(teb, self->breakpoint_being_enabled);
|
|
+ (do_this)(self);
|
|
+ self->state = psh_singlestep;
|
|
+}
|
|
+
|
|
+void
|
|
+linux_ptrace_disable_and_singlestep(struct process_stopping_handler *self)
|
|
+{
|
|
+ disable_and(self, &pt_singlestep);
|
|
+}
|
|
+
|
|
+void
|
|
+linux_ptrace_disable_and_continue(struct process_stopping_handler *self)
|
|
+{
|
|
+ disable_and(self, &pt_continue);
|
|
}
|
|
|
|
/* This event handler is installed when we are in the process of
|
|
@@ -703,16 +727,15 @@ singlestep_error(struct process_stopping_handler *self, Event **eventp)
|
|
* happens, we let the re-enablement thread to PTRACE_SINGLESTEP,
|
|
* re-enable, and continue everyone. */
|
|
static Event *
|
|
-process_stopping_on_event(Event_Handler * super, Event * event)
|
|
+process_stopping_on_event(struct event_handler *super, Event *event)
|
|
{
|
|
struct process_stopping_handler * self = (void *)super;
|
|
Process * task = event->proc;
|
|
Process * leader = task->leader;
|
|
- struct breakpoint *sbp = self->breakpoint_being_enabled;
|
|
Process * teb = self->task_enabling_breakpoint;
|
|
|
|
debug(DEBUG_PROCESS,
|
|
- "pid %d; event type %d; state %d",
|
|
+ "process_stopping_on_event: pid %d; event type %d; state %d",
|
|
task->pid, event->type, self->state);
|
|
|
|
struct pid_task * task_info = get_task_info(&self->pids, task->pid);
|
|
@@ -741,17 +764,10 @@ process_stopping_on_event(Event_Handler * super, Event * event)
|
|
switch (state) {
|
|
case psh_stopping:
|
|
/* If everyone is stopped, singlestep. */
|
|
- if (each_task(leader, &task_blocked, &self->pids) == NULL) {
|
|
- debug(DEBUG_PROCESS, "all stopped, now SINGLESTEP %d",
|
|
- teb->pid);
|
|
- if (sbp->enabled)
|
|
- disable_breakpoint(teb, sbp);
|
|
- if (singlestep(self) < 0) {
|
|
- singlestep_error(self, &event);
|
|
- goto psh_sinking;
|
|
- }
|
|
-
|
|
- self->state = state = psh_singlestep;
|
|
+ if (each_task(leader, NULL, &task_blocked,
|
|
+ &self->pids) == NULL) {
|
|
+ (self->on_all_stopped)(self);
|
|
+ state = self->state;
|
|
}
|
|
break;
|
|
|
|
@@ -760,18 +776,47 @@ process_stopping_on_event(Event_Handler * super, Event * event)
|
|
* have now stepped, and can re-enable the breakpoint. */
|
|
if (event != NULL && task == teb) {
|
|
|
|
- /* This is not the singlestep that we are waiting for. */
|
|
+ /* If this was caused by a real breakpoint, as
|
|
+ * opposed to a singlestep, assume that it's
|
|
+ * an artificial breakpoint installed for some
|
|
+ * reason for the re-enablement. In that case
|
|
+ * handle it. */
|
|
+ if (event->type == EVENT_BREAKPOINT) {
|
|
+ target_address_t ip
|
|
+ = get_instruction_pointer(task);
|
|
+ struct breakpoint *other
|
|
+ = address2bpstruct(leader, ip);
|
|
+ if (other != NULL)
|
|
+ breakpoint_on_hit(other, task);
|
|
+ }
|
|
+
|
|
+ /* If we got SIGNAL instead of BREAKPOINT,
|
|
+ * then this is not singlestep at all. */
|
|
if (event->type == EVENT_SIGNAL) {
|
|
+ do_singlestep:
|
|
if (singlestep(self) < 0) {
|
|
- singlestep_error(self, &event);
|
|
+ singlestep_error(self);
|
|
+ post_singlestep(self, &event);
|
|
goto psh_sinking;
|
|
}
|
|
break;
|
|
+ } else {
|
|
+ switch ((self->keep_stepping_p)(self)) {
|
|
+ case CBS_FAIL:
|
|
+ /* XXX handle me */
|
|
+ case CBS_STOP:
|
|
+ break;
|
|
+ case CBS_CONT:
|
|
+ /* Sink singlestep event. */
|
|
+ if (event->type == EVENT_BREAKPOINT)
|
|
+ event = NULL;
|
|
+ goto do_singlestep;
|
|
+ }
|
|
}
|
|
|
|
- /* Essentially we don't care what event caused
|
|
- * the thread to stop. We can do the
|
|
- * re-enablement now. */
|
|
+ /* Re-enable the breakpoint that we are
|
|
+ * stepping over. */
|
|
+ struct breakpoint *sbp = self->breakpoint_being_enabled;
|
|
if (sbp->enabled)
|
|
enable_breakpoint(teb, sbp);
|
|
|
|
@@ -812,52 +857,89 @@ process_stopping_on_event(Event_Handler * super, Event * event)
|
|
}
|
|
|
|
static void
|
|
-process_stopping_destroy(Event_Handler * super)
|
|
+process_stopping_destroy(struct event_handler *super)
|
|
{
|
|
struct process_stopping_handler * self = (void *)super;
|
|
free(self->pids.tasks);
|
|
}
|
|
|
|
+static enum callback_status
|
|
+no(struct process_stopping_handler *self)
|
|
+{
|
|
+ return CBS_STOP;
|
|
+}
|
|
+
|
|
+int
|
|
+process_install_stopping_handler(struct Process *proc, struct breakpoint *sbp,
|
|
+ void (*as)(struct process_stopping_handler *),
|
|
+ enum callback_status (*ks)
|
|
+ (struct process_stopping_handler *),
|
|
+ enum callback_status (*uw)
|
|
+ (struct process_stopping_handler *))
|
|
+{
|
|
+ debug(DEBUG_FUNCTION,
|
|
+ "process_install_stopping_handler: pid=%d", proc->pid);
|
|
+
|
|
+ struct process_stopping_handler *handler = calloc(sizeof(*handler), 1);
|
|
+ if (handler == NULL)
|
|
+ return -1;
|
|
+
|
|
+ if (as == NULL)
|
|
+ as = &linux_ptrace_disable_and_singlestep;
|
|
+ if (ks == NULL)
|
|
+ ks = &no;
|
|
+ if (uw == NULL)
|
|
+ uw = &no;
|
|
+
|
|
+ handler->super.on_event = process_stopping_on_event;
|
|
+ handler->super.destroy = process_stopping_destroy;
|
|
+ handler->task_enabling_breakpoint = proc;
|
|
+ handler->breakpoint_being_enabled = sbp;
|
|
+ handler->on_all_stopped = as;
|
|
+ handler->keep_stepping_p = ks;
|
|
+ handler->ugly_workaround_p = uw;
|
|
+
|
|
+ install_event_handler(proc->leader, &handler->super);
|
|
+
|
|
+ if (each_task(proc->leader, NULL, &send_sigstop,
|
|
+ &handler->pids) != NULL) {
|
|
+ destroy_event_handler(proc);
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /* And deliver the first fake event, in case all the
|
|
+ * conditions are already fulfilled. */
|
|
+ Event ev = {
|
|
+ .type = EVENT_NONE,
|
|
+ .proc = proc,
|
|
+ };
|
|
+ process_stopping_on_event(&handler->super, &ev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
void
|
|
continue_after_breakpoint(Process *proc, struct breakpoint *sbp)
|
|
{
|
|
+ debug(DEBUG_PROCESS,
|
|
+ "continue_after_breakpoint: pid=%d, addr=%p",
|
|
+ proc->pid, sbp->addr);
|
|
+
|
|
set_instruction_pointer(proc, sbp->addr);
|
|
+
|
|
if (sbp->enabled == 0) {
|
|
continue_process(proc->pid);
|
|
} else {
|
|
- debug(DEBUG_PROCESS,
|
|
- "continue_after_breakpoint: pid=%d, addr=%p",
|
|
- proc->pid, sbp->addr);
|
|
#if defined __sparc__ || defined __ia64___ || defined __mips__
|
|
/* we don't want to singlestep here */
|
|
continue_process(proc->pid);
|
|
#else
|
|
- struct process_stopping_handler * handler
|
|
- = calloc(sizeof(*handler), 1);
|
|
- if (handler == NULL) {
|
|
- perror("malloc breakpoint disable handler");
|
|
- fatal:
|
|
+ if (process_install_stopping_handler
|
|
+ (proc, sbp, NULL, NULL, NULL) < 0) {
|
|
+ perror("process_stopping_handler_create");
|
|
/* Carry on not bothering to re-enable. */
|
|
continue_process(proc->pid);
|
|
- return;
|
|
}
|
|
-
|
|
- handler->super.on_event = process_stopping_on_event;
|
|
- handler->super.destroy = process_stopping_destroy;
|
|
- handler->task_enabling_breakpoint = proc;
|
|
- handler->breakpoint_being_enabled = sbp;
|
|
- install_event_handler(proc->leader, &handler->super);
|
|
-
|
|
- if (each_task(proc->leader, &send_sigstop,
|
|
- &handler->pids) != NULL)
|
|
- goto fatal;
|
|
-
|
|
- /* And deliver the first fake event, in case all the
|
|
- * conditions are already fulfilled. */
|
|
- Event ev;
|
|
- ev.type = EVENT_NONE;
|
|
- ev.proc = proc;
|
|
- process_stopping_on_event(&handler->super, &ev);
|
|
#endif
|
|
}
|
|
}
|
|
@@ -872,18 +954,20 @@ continue_after_breakpoint(Process *proc, struct breakpoint *sbp)
|
|
*/
|
|
struct ltrace_exiting_handler
|
|
{
|
|
- Event_Handler super;
|
|
+ struct event_handler super;
|
|
struct pid_set pids;
|
|
};
|
|
|
|
static Event *
|
|
-ltrace_exiting_on_event(Event_Handler * super, Event * event)
|
|
+ltrace_exiting_on_event(struct event_handler *super, Event *event)
|
|
{
|
|
struct ltrace_exiting_handler * self = (void *)super;
|
|
Process * task = event->proc;
|
|
Process * leader = task->leader;
|
|
|
|
- debug(DEBUG_PROCESS, "pid %d; event type %d", task->pid, event->type);
|
|
+ debug(DEBUG_PROCESS,
|
|
+ "ltrace_exiting_on_event: pid %d; event type %d",
|
|
+ task->pid, event->type);
|
|
|
|
struct pid_task * task_info = get_task_info(&self->pids, task->pid);
|
|
handle_stopping_event(task_info, &event);
|
|
@@ -904,7 +988,7 @@ ltrace_exiting_on_event(Event_Handler * super, Event * event)
|
|
}
|
|
|
|
static void
|
|
-ltrace_exiting_destroy(Event_Handler * super)
|
|
+ltrace_exiting_destroy(struct event_handler *super)
|
|
{
|
|
struct ltrace_exiting_handler * self = (void *)super;
|
|
free(self->pids.tasks);
|
|
@@ -947,7 +1031,7 @@ ltrace_exiting_install_handler(Process * proc)
|
|
handler->super.destroy = ltrace_exiting_destroy;
|
|
install_event_handler(proc->leader, &handler->super);
|
|
|
|
- if (each_task(proc->leader, &send_sigstop,
|
|
+ if (each_task(proc->leader, NULL, &send_sigstop,
|
|
&handler->pids) != NULL)
|
|
goto fatal;
|
|
|
|
@@ -975,13 +1059,17 @@ ltrace_exiting_install_handler(Process * proc)
|
|
|
|
struct process_vfork_handler
|
|
{
|
|
- Event_Handler super;
|
|
+ struct event_handler super;
|
|
void * bp_addr;
|
|
};
|
|
|
|
static Event *
|
|
-process_vfork_on_event(Event_Handler * super, Event * event)
|
|
+process_vfork_on_event(struct event_handler *super, Event *event)
|
|
{
|
|
+ debug(DEBUG_PROCESS,
|
|
+ "process_vfork_on_event: pid %d; event type %d",
|
|
+ event->proc->pid, event->type);
|
|
+
|
|
struct process_vfork_handler * self = (void *)super;
|
|
struct breakpoint *sbp;
|
|
assert(self != NULL);
|
|
@@ -989,7 +1077,7 @@ process_vfork_on_event(Event_Handler * super, Event * event)
|
|
switch (event->type) {
|
|
case EVENT_BREAKPOINT:
|
|
/* Remember the vfork return breakpoint. */
|
|
- if (self->bp_addr == NULL)
|
|
+ if (self->bp_addr == 0)
|
|
self->bp_addr = event->e_un.brk_addr;
|
|
break;
|
|
|
|
@@ -998,13 +1086,15 @@ process_vfork_on_event(Event_Handler * super, Event * event)
|
|
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) {
|
|
+ if (self->bp_addr != 0) {
|
|
sbp = dict_find_entry(event->proc->leader->breakpoints,
|
|
self->bp_addr);
|
|
if (sbp != NULL)
|
|
- insert_breakpoint(event->proc->parent,
|
|
- self->bp_addr,
|
|
- sbp->libsym, 1);
|
|
+ assert(sbp->libsym == NULL);
|
|
+ /* We don't mind failing that, it's not a big
|
|
+ * deal to not display one extra vfork return. */
|
|
+ insert_breakpoint(event->proc->parent,
|
|
+ self->bp_addr, NULL);
|
|
}
|
|
|
|
continue_process(event->proc->parent->pid);
|
|
diff --git a/sysdeps/linux-gnu/trace.h b/sysdeps/linux-gnu/trace.h
|
|
new file mode 100644
|
|
index 0000000..0f40709
|
|
--- /dev/null
|
|
+++ b/sysdeps/linux-gnu/trace.h
|
|
@@ -0,0 +1,119 @@
|
|
+/*
|
|
+ * This file is part of ltrace.
|
|
+ * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
+ * License, or (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
+ * General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program; if not, write to the Free Software
|
|
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
+ * 02110-1301 USA
|
|
+ */
|
|
+
|
|
+#ifndef _LTRACE_LINUX_TRACE_H_
|
|
+#define _LTRACE_LINUX_TRACE_H_
|
|
+
|
|
+/* This publishes some Linux-specific data structures used for process
|
|
+ * handling. */
|
|
+
|
|
+/**
|
|
+ * This is used for bookkeeping related to PIDs that the event
|
|
+ * handlers work with.
|
|
+ */
|
|
+struct pid_task {
|
|
+ pid_t pid; /* This may be 0 for tasks that exited
|
|
+ * mid-handling. */
|
|
+ int sigstopped : 1;
|
|
+ int got_event : 1;
|
|
+ int delivered : 1;
|
|
+ int vforked : 1;
|
|
+ int sysret : 1;
|
|
+};
|
|
+
|
|
+struct pid_set {
|
|
+ struct pid_task *tasks;
|
|
+ size_t count;
|
|
+ size_t alloc;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Breakpoint re-enablement. When we hit a breakpoint, we must
|
|
+ * disable it, single-step, and re-enable it. That single-step can be
|
|
+ * done only by one task in a task group, while others are stopped,
|
|
+ * otherwise the processes would race for who sees the breakpoint
|
|
+ * disabled and who doesn't. The following is to keep track of it
|
|
+ * all.
|
|
+ */
|
|
+struct process_stopping_handler
|
|
+{
|
|
+ struct event_handler super;
|
|
+
|
|
+ /* The task that is doing the re-enablement. */
|
|
+ struct Process *task_enabling_breakpoint;
|
|
+
|
|
+ /* The pointer being re-enabled. */
|
|
+ struct breakpoint *breakpoint_being_enabled;
|
|
+
|
|
+ /* Artificial atomic skip breakpoint, if any needed. */
|
|
+ void *atomic_skip_bp_addrs[2];
|
|
+
|
|
+ /* When all tasks are stopped, this callback gets called. */
|
|
+ void (*on_all_stopped)(struct process_stopping_handler *);
|
|
+
|
|
+ /* When we get a singlestep event, this is called to decide
|
|
+ * whether to stop stepping, or whether to enable the
|
|
+ * brakpoint, sink remaining signals, and continue
|
|
+ * everyone. */
|
|
+ enum callback_status (*keep_stepping_p)
|
|
+ (struct process_stopping_handler *);
|
|
+
|
|
+ /* Whether we need to use ugly workaround to get around
|
|
+ * various problems with singlestepping. */
|
|
+ enum callback_status (*ugly_workaround_p)
|
|
+ (struct process_stopping_handler *);
|
|
+
|
|
+ enum {
|
|
+ /* We are waiting for everyone to land in t/T. */
|
|
+ psh_stopping = 0,
|
|
+
|
|
+ /* We are doing the PTRACE_SINGLESTEP. */
|
|
+ psh_singlestep,
|
|
+
|
|
+ /* We are waiting for all the SIGSTOPs to arrive so
|
|
+ * that we can sink them. */
|
|
+ psh_sinking,
|
|
+
|
|
+ /* This is for tracking the ugly workaround. */
|
|
+ psh_ugly_workaround,
|
|
+ } state;
|
|
+
|
|
+ int exiting;
|
|
+
|
|
+ struct pid_set pids;
|
|
+};
|
|
+
|
|
+/* Allocate a process stopping handler, initialize it and install it.
|
|
+ * Return 0 on success or a negative value on failure. Pass NULL for
|
|
+ * each callback to use a default instead. The default for
|
|
+ * ON_ALL_STOPPED is LINUX_PTRACE_DISABLE_AND_SINGLESTEP, the default
|
|
+ * for KEEP_STEPPING_P and UGLY_WORKAROUND_P is "no". */
|
|
+int process_install_stopping_handler
|
|
+ (struct Process *proc, struct breakpoint *sbp,
|
|
+ void (*on_all_stopped)(struct process_stopping_handler *),
|
|
+ enum callback_status (*keep_stepping_p)
|
|
+ (struct process_stopping_handler *),
|
|
+ enum callback_status (*ugly_workaround_p)
|
|
+ (struct process_stopping_handler *));
|
|
+
|
|
+void linux_ptrace_disable_and_singlestep(struct process_stopping_handler *self);
|
|
+void linux_ptrace_disable_and_continue(struct process_stopping_handler *self);
|
|
+
|
|
+#endif /* _LTRACE_LINUX_TRACE_H_ */
|
|
diff --git a/sysdeps/linux-gnu/x86_64/plt.c b/sysdeps/linux-gnu/x86_64/plt.c
|
|
index b53ff44..bb1b2b1 100644
|
|
--- a/sysdeps/linux-gnu/x86_64/plt.c
|
|
+++ b/sysdeps/linux-gnu/x86_64/plt.c
|
|
@@ -1,5 +1,7 @@
|
|
#include <gelf.h>
|
|
+#include "proc.h"
|
|
#include "common.h"
|
|
+#include "library.h"
|
|
|
|
GElf_Addr
|
|
arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) {
|
|
diff --git a/sysdeps/linux-gnu/x86_64/regs.c b/sysdeps/linux-gnu/x86_64/regs.c
|
|
index ed1f118..0ff3281 100644
|
|
--- a/sysdeps/linux-gnu/x86_64/regs.c
|
|
+++ b/sysdeps/linux-gnu/x86_64/regs.c
|
|
@@ -4,7 +4,7 @@
|
|
#include <sys/ptrace.h>
|
|
#include <sys/reg.h>
|
|
|
|
-#include "common.h"
|
|
+#include "proc.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
# define PTRACE_PEEKUSER PTRACE_PEEKUSR
|
|
diff --git a/sysdeps/linux-gnu/x86_64/trace.c b/sysdeps/linux-gnu/x86_64/trace.c
|
|
index d0299d9..0d3f693 100644
|
|
--- a/sysdeps/linux-gnu/x86_64/trace.c
|
|
+++ b/sysdeps/linux-gnu/x86_64/trace.c
|
|
@@ -12,6 +12,7 @@
|
|
|
|
#include "common.h"
|
|
#include "ptrace.h"
|
|
+#include "proc.h"
|
|
|
|
#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR))
|
|
# define PTRACE_PEEKUSER PTRACE_PEEKUSR
|
|
@@ -42,23 +43,35 @@ get_arch_dep(Process *proc) {
|
|
/* Returns 1 if syscall, 2 if sysret, 0 otherwise.
|
|
*/
|
|
int
|
|
-syscall_p(Process *proc, int status, int *sysnum) {
|
|
+syscall_p(struct Process *proc, int status, int *sysnum)
|
|
+{
|
|
if (WIFSTOPPED(status)
|
|
&& WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) {
|
|
+ struct callstack_element *elem = NULL;
|
|
+ if (proc->callstack_depth > 0)
|
|
+ elem = proc->callstack + proc->callstack_depth - 1;
|
|
+
|
|
long int ret = ptrace(PTRACE_PEEKUSER, proc->pid, 8 * ORIG_RAX, 0);
|
|
- if (ret == -1 && errno)
|
|
- return -1;
|
|
+ if (ret == -1) {
|
|
+ if (errno)
|
|
+ return -1;
|
|
+ /* Otherwise, ORIG_RAX == -1 means that the
|
|
+ * system call should not be restarted. In
|
|
+ * that case rely on what we have on
|
|
+ * stack. */
|
|
+ if (elem != NULL && elem->is_syscall)
|
|
+ ret = elem->c_un.syscall;
|
|
+ }
|
|
|
|
*sysnum = ret;
|
|
- if (proc->callstack_depth > 0 &&
|
|
- proc->callstack[proc->callstack_depth - 1].is_syscall &&
|
|
- proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) {
|
|
+ debug(DEBUG_FUNCTION, "sysnum=%ld %p %d\n", ret,
|
|
+ get_instruction_pointer(proc), errno);
|
|
+ if (elem != NULL && elem->is_syscall
|
|
+ && elem->c_un.syscall == *sysnum)
|
|
return 2;
|
|
- }
|
|
|
|
- if (*sysnum >= 0) {
|
|
+ if (*sysnum >= 0)
|
|
return 1;
|
|
- }
|
|
}
|
|
return 0;
|
|
}
|
|
diff --git a/sysdeps/sysdep.h b/sysdeps/sysdep.h
|
|
index 1d19c6f..96b3857 100644
|
|
--- a/sysdeps/sysdep.h
|
|
+++ b/sysdeps/sysdep.h
|
|
@@ -1 +1,31 @@
|
|
+#ifndef LTRACE_SYSDEP_H
|
|
+#define LTRACE_SYSDEP_H
|
|
+
|
|
#include <arch.h>
|
|
+
|
|
+#ifndef ARCH_HAVE_LTELF_DATA
|
|
+struct arch_ltelf_data {
|
|
+};
|
|
+#endif
|
|
+
|
|
+#ifndef ARCH_HAVE_BREAKPOINT_DATA
|
|
+struct arch_breakpoint_data {
|
|
+};
|
|
+#endif
|
|
+
|
|
+#ifndef ARCH_HAVE_LIBRARY_SYMBOL_DATA
|
|
+struct arch_library_symbol_data {
|
|
+};
|
|
+#endif
|
|
+
|
|
+#ifndef ARCH_HAVE_LIBRARY_DATA
|
|
+struct arch_library_data {
|
|
+};
|
|
+#endif
|
|
+
|
|
+#ifndef ARCH_HAVE_PROCESS_DATA
|
|
+struct arch_process_data {
|
|
+};
|
|
+#endif
|
|
+
|
|
+#endif /* LTRACE_SYSDEP_H */
|
|
diff --git a/testsuite/ltrace.main/filt.c b/testsuite/ltrace.main/filt.c
|
|
new file mode 100644
|
|
index 0000000..f31a30d
|
|
--- /dev/null
|
|
+++ b/testsuite/ltrace.main/filt.c
|
|
@@ -0,0 +1,8 @@
|
|
+void func1(void);
|
|
+
|
|
+int
|
|
+main(int argc, char *argv[])
|
|
+{
|
|
+ func1();
|
|
+ return 0;
|
|
+}
|
|
diff --git a/testsuite/ltrace.main/filt1.c b/testsuite/ltrace.main/filt1.c
|
|
new file mode 100644
|
|
index 0000000..a0eb906
|
|
--- /dev/null
|
|
+++ b/testsuite/ltrace.main/filt1.c
|
|
@@ -0,0 +1,7 @@
|
|
+void func2(void);
|
|
+
|
|
+void
|
|
+func1(void)
|
|
+{
|
|
+ func2();
|
|
+}
|
|
diff --git a/testsuite/ltrace.main/filt2.c b/testsuite/ltrace.main/filt2.c
|
|
new file mode 100644
|
|
index 0000000..24999a3
|
|
--- /dev/null
|
|
+++ b/testsuite/ltrace.main/filt2.c
|
|
@@ -0,0 +1,5 @@
|
|
+void
|
|
+func2(void)
|
|
+{
|
|
+ puts("func2");
|
|
+}
|
|
diff --git a/testsuite/ltrace.main/filters.exp b/testsuite/ltrace.main/filters.exp
|
|
new file mode 100644
|
|
index 0000000..1a9a8f7
|
|
--- /dev/null
|
|
+++ b/testsuite/ltrace.main/filters.exp
|
|
@@ -0,0 +1,79 @@
|
|
+# Copyright (C) 2012 Petr Machata, Red Hat Inc.
|
|
+
|
|
+set testfile "filt"
|
|
+set srcfile0 $srcdir/$subdir/$testfile.c
|
|
+set binfile0 $objdir/$subdir/$testfile
|
|
+set base1 "filt1"
|
|
+set srcfile1 $srcdir/$subdir/$base1.c
|
|
+set binfile1 $objdir/$subdir/lib$base1.so
|
|
+set base2 "filt2"
|
|
+set srcfile2 $srcdir/$subdir/$base2.c
|
|
+set binfile2 $objdir/$subdir/lib$base2.so
|
|
+
|
|
+if [get_compiler_info $binfile0] {
|
|
+ return -1
|
|
+}
|
|
+
|
|
+verbose "compiling source file now....."
|
|
+if { [ltrace_compile_shlib $srcfile2 $binfile2 debug ] != ""
|
|
+ || [ltrace_compile_shlib $srcfile1 $binfile1 debug ] != ""
|
|
+ || [ltrace_compile $srcfile0 $binfile0 executable [list debug shlib=$binfile1 shlib=$binfile2] ] != ""} {
|
|
+ send_user "Testcase compile failed, so all tests in this file will automatically fail.\n"
|
|
+}
|
|
+
|
|
+ltrace_options "-e*"
|
|
+set exec_output [ltrace_runtest $objdir/$subdir $binfile0]
|
|
+
|
|
+# 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 ${binfile0}.ltrace "filt->func1" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt1.so->func2" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt2.so->puts" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "func2 resumed" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "func1 resumed" 1
|
|
+
|
|
+# I simply can't figure out how to pass an empty string to
|
|
+# ltrace_options without that getting interpreted as {}, so pass
|
|
+# something harmless instead.
|
|
+ltrace_options "-b"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "^func1(.*)" 1
|
|
+
|
|
+ltrace_options "-e@MAIN"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "filt->func1(.*)" 1
|
|
+
|
|
+ltrace_options "-e@libfilt1.so"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt1.so->func2(.*)" 1
|
|
+
|
|
+ltrace_options "-e@libfilt2.so"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt2.so->puts(.*)" 1
|
|
+
|
|
+ltrace_options "-e@libfilt*"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt1.so->func2(" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt2.so->puts(.*)" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "func2 resumed" 1
|
|
+
|
|
+ltrace_options "-efunc*"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "filt->func1(" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "libfilt1.so->func2(.*)" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "func1 resumed" 1
|
|
+
|
|
+# Check that we handle breakpoint on both PLT entry and entry point
|
|
+ltrace_options "-efunc1" "-xfunc1"
|
|
+ltrace_runtest $objdir/$subdir $binfile0
|
|
+ltrace_verify_output ${binfile0}.ltrace "filt->func1(" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "func1@libfilt1.so(.*)" 1
|
|
+ltrace_verify_output ${binfile0}.ltrace "func1 resumed" 1
|
|
diff --git a/testsuite/ltrace.main/parameters.c b/testsuite/ltrace.main/parameters.c
|
|
index 154de84..fb46dfe 100644
|
|
--- a/testsuite/ltrace.main/parameters.c
|
|
+++ b/testsuite/ltrace.main/parameters.c
|
|
@@ -36,10 +36,16 @@ typedef enum {
|
|
void func_enum(color_t);
|
|
void func_typedef(color_t);
|
|
|
|
-void func_work (char *x);
|
|
-void func_call (char *x, char* y, void (*cb) (char *));
|
|
+void func_work(char *x);
|
|
+void func_call(char *x, char *y, void (*cb)(char *));
|
|
|
|
-int
|
|
+void
|
|
+call_func_work (char *x)
|
|
+{
|
|
+ func_work(x);
|
|
+}
|
|
+
|
|
+int
|
|
main ()
|
|
{
|
|
int x = 17;
|
|
@@ -124,7 +130,7 @@ main ()
|
|
{
|
|
char x[10] = {};
|
|
char y[10] = {};
|
|
- func_call (x, y, func_work);
|
|
+ func_call(x, y, call_func_work);
|
|
}
|
|
|
|
return 0;
|
|
diff --git a/testsuite/ltrace.minor/libdl-simple.exp b/testsuite/ltrace.minor/libdl-simple.exp
|
|
index 9957001..91af5bd 100644
|
|
--- a/testsuite/ltrace.minor/libdl-simple.exp
|
|
+++ b/testsuite/ltrace.minor/libdl-simple.exp
|
|
@@ -28,25 +28,5 @@ if [regexp {ELF from incompatible architecture} $exec_output] {
|
|
return
|
|
}
|
|
|
|
-# Verify the time for calling sleep.
|
|
-set fd [ open $objdir/$subdir/$binfile.ltrace r]
|
|
-set FOUND 0
|
|
-while { [gets $fd line] >= 0 } {
|
|
- # match the line with sleep and extract the spent time in sleep and sleep argument.
|
|
- if [ regexp {(test_libdl)\(} $line match tester ] then {
|
|
- verbose "test_libdl = $tester"
|
|
-
|
|
- if { $tester == "test_libdl" } then {
|
|
- pass "Successfully traced libdl loaded function."
|
|
- } else {
|
|
- fail "Failed to trace libdl loaded function."
|
|
- }
|
|
- set FOUND 1
|
|
- break
|
|
- }
|
|
-}
|
|
-close $fd
|
|
-
|
|
-if {$FOUND != 1} then {
|
|
- fail "Fail to trace libdl loaded function!"
|
|
-}
|
|
+set pattern "test_libdl@liblibdl-simple.so"
|
|
+ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1
|
|
diff -urp ltrace-0.6.0-orig/Makefile.in ltrace-0.6.0/Makefile.in
|
|
--- ltrace-0.6.0-orig/Makefile.in 2012-05-01 00:24:18.824433420 +0200
|
|
+++ ltrace-0.6.0/Makefile.in 2012-05-01 00:25:01.285738780 +0200
|
|
@@ -65,7 +65,7 @@ libltrace_la_DEPENDENCIES = $(am__DEPEND
|
|
am_libltrace_la_OBJECTS = breakpoints.lo debug.lo demangle.lo dict.lo \
|
|
display_args.lo ltrace-elf.lo execute_program.lo \
|
|
handle_event.lo libltrace.lo options.lo output.lo proc.lo \
|
|
- read_config_file.lo summary.lo
|
|
+ read_config_file.lo summary.lo library.lo filter.lo glob.lo
|
|
libltrace_la_OBJECTS = $(am_libltrace_la_OBJECTS)
|
|
am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" \
|
|
"$(DESTDIR)$(docdir)" "$(DESTDIR)$(sysconfdir)"
|
|
@@ -311,7 +314,10 @@ libltrace_la_SOURCES = \
|
|
output.c \
|
|
proc.c \
|
|
read_config_file.c \
|
|
- summary.c
|
|
+ summary.c \
|
|
+ library.c \
|
|
+ filter.c \
|
|
+ glob.c
|
|
|
|
libltrace_la_LIBADD = \
|
|
$(libelf_LIBS) \
|
|
@@ -338,7 +344,10 @@ noinst_HEADERS = \
|
|
ltrace.h \
|
|
options.h \
|
|
output.h \
|
|
- read_config_file.h
|
|
+ read_config_file.h \
|
|
+ library.h \
|
|
+ filter.h \
|
|
+ glob.h
|
|
|
|
dist_man1_MANS = \
|
|
ltrace.1
|
|
diff -urp ltrace-0.6.0-orig/sysdeps/linux-gnu/Makefile.in ltrace-0.6.0/sysdeps/linux-gnu/Makefile.in
|
|
--- ltrace-0.6.0-orig/sysdeps/linux-gnu/Makefile.in 2012-05-01 00:24:18.864434649 +0200
|
|
+++ ltrace-0.6.0/sysdeps/linux-gnu/Makefile.in 2012-05-01 00:24:59.835694202 +0200
|
|
@@ -260,7 +263,8 @@ ___libos_la_LIBADD = \
|
|
noinst_HEADERS = \
|
|
arch_syscallent.h \
|
|
signalent1.h \
|
|
- syscallent1.h
|
|
+ syscallent1.h \
|
|
+ trace.h
|
|
|
|
EXTRA_DIST = \
|
|
arch_mksyscallent \
|
|
diff --git a/ltrace-elf.c b/ltrace-elf.c
|
|
index a311c5f..b1af070 100644
|
|
--- a/ltrace-elf.c
|
|
+++ b/ltrace-elf.c
|
|
@@ -175,8 +175,8 @@ need_data(Elf_Data *data, GElf_Xword offset, GElf_Xword size)
|
|
{
|
|
assert(data != NULL);
|
|
if (data->d_size < size || offset > data->d_size - size) {
|
|
- debug(1, "Not enough data to read %zd-byte value"
|
|
- " at offset %zd.", size, offset);
|
|
+ debug(1, "Not enough data to read %"PRId64"-byte value"
|
|
+ " at offset %"PRId64".", size, offset);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c
|
|
index 3b6a25f..9717738 100644
|
|
--- a/sysdeps/linux-gnu/ppc/plt.c
|
|
+++ b/sysdeps/linux-gnu/ppc/plt.c
|
|
@@ -230,7 +230,9 @@ arch_translate_address_dyn(struct Process *proc,
|
|
error(0, errno, "dynamic .opd translation of %p", addr);
|
|
return -1;
|
|
}
|
|
- *ret = (target_address_t)value;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ *ret = (target_address_t)(uintptr_t)value;
|
|
return 0;
|
|
}
|
|
|
|
@@ -243,14 +245,17 @@ arch_translate_address(struct ltelf *lte,
|
|
target_address_t addr, target_address_t *ret)
|
|
{
|
|
if (lte->ehdr.e_machine == EM_PPC64) {
|
|
- GElf_Xword offset = (GElf_Addr)addr - lte->arch.opd_base;
|
|
+ /* XXX The double cast should be removed when
|
|
+ * target_address_t becomes integral type. */
|
|
+ GElf_Xword offset
|
|
+ = (GElf_Addr)(uintptr_t)addr - lte->arch.opd_base;
|
|
uint64_t value;
|
|
if (elf_read_u64(lte->arch.opd_data, offset, &value) < 0) {
|
|
error(0, 0, "static .opd translation of %p: %s", addr,
|
|
elf_errmsg(-1));
|
|
return -1;
|
|
}
|
|
- *ret = (target_address_t)(value + lte->bias);
|
|
+ *ret = (target_address_t)(uintptr_t)(value + lte->bias);
|
|
return 0;
|
|
}
|
|
|