summaryrefslogtreecommitdiffstats
path: root/runtime/regs.c
diff options
context:
space:
mode:
authorJim Keniston <jkenisto@us.ibm.com>2008-05-01 16:02:29 -0700
committerJim Keniston <jkenisto@us.ibm.com>2008-05-01 16:02:29 -0700
commit5edbdffdc71747c402b781b2720228406eda7666 (patch)
treef2d22d6afb53564ce4017933e802af85104ece86 /runtime/regs.c
parent65da5355aa93d507d8ed8ca97ce7bed234af638a (diff)
downloadsystemtap-steved-5edbdffdc71747c402b781b2720228406eda7666.tar.gz
systemtap-steved-5edbdffdc71747c402b781b2720228406eda7666.tar.xz
systemtap-steved-5edbdffdc71747c402b781b2720228406eda7666.zip
Added support for register(), u_register(), arg(), and u_arg() functions.
Still missing: arg64(), _stp_copy_from_user stack, .linkage clause, tests, docs.
Diffstat (limited to 'runtime/regs.c')
-rw-r--r--runtime/regs.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/runtime/regs.c b/runtime/regs.c
index 5e08e376..743a230d 100644
--- a/runtime/regs.c
+++ b/runtime/regs.c
@@ -383,5 +383,414 @@ void _stp_print_regs(struct pt_regs * regs)
#endif
+/*
+ * (Theoretically) arch-independent scheme for binary lookup of register
+ * values (from pt_regs) by register name. A register may be called by
+ * more than one name.
+ */
+struct _stp_register_desc {
+ const char *name;
+ unsigned short size; // in bytes
+ unsigned short offset; // in bytes, from start of pt_regs
+};
+
+struct _stp_register_table {
+ struct _stp_register_desc *registers;
+ unsigned nr_registers;
+ unsigned nr_slots; // capacity
+};
+
+static void _stp_populate_register_table(void);
+
+/*
+ * If the named register is in the list, return its slot number and *found=1.
+ * Else *found=0 and return the slot number where the name should be inserted.
+ */
+static int _stp_lookup_register(const char *name,
+ struct _stp_register_table *table, int *found)
+{
+ unsigned begin, mid, end;
+
+ *found = 0;
+ end = table->nr_registers;
+ if (end == 0)
+ return 0;
+ begin = 0;
+ mid = -1;
+ for (;;) {
+ int cmp;
+ int prev_mid = mid;
+ mid = (begin + end) / 2;
+ if (mid == prev_mid)
+ break;
+ cmp = strcmp(name, table->registers[mid].name);
+ if (cmp == 0) {
+ *found = 1;
+ return mid;
+ } else if (cmp < 0)
+ end = mid;
+ else
+ begin = mid;
+ }
+ if (begin == 0 && strcmp(name, table->registers[0].name) < 0)
+ return 0;
+ return begin + 1;
+}
+
+/*
+ * If found, return 1 and the size and/or offset in the pt_regs array.
+ * Else return 0.
+ */
+static int _stp_find_register(const char *name,
+ struct _stp_register_table *table, size_t *size, size_t *offset)
+{
+ int slot, found;
+ if (table->nr_registers == 0)
+ /*
+ * Should we do this at the beginning of time to avoid
+ * the possibility of spending too long in a handler?
+ */
+ _stp_populate_register_table();
+ slot = _stp_lookup_register(name, table, &found);
+ if (found) {
+ if (size)
+ *size = table->registers[slot].size;
+ if (offset)
+ *offset = table->registers[slot].offset;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Add name to the register-lookup table. Note that the name pointer
+ * is merely copied, not strdup-ed.
+ */
+void _stp_add_register(const char *name, struct _stp_register_table *table,
+ size_t size, size_t offset)
+{
+ int idx, found;
+ struct _stp_register_desc *slot;
+
+ idx = _stp_lookup_register(name, table, &found);
+ if (found)
+ _stp_error("stap runtime internal error: "
+ "register name %s used twice\n", name);
+ if (table->nr_registers >= table->nr_slots)
+ _stp_error("stap runtime internal error: "
+ "register table overflow\n");
+ slot = &table->registers[idx];
+
+ // Move the slots later in the array out of the way.
+ if (idx < table->nr_registers)
+ memmove(slot+1, slot,
+ sizeof(*slot) * (table->nr_registers - idx));
+ table->nr_registers++;
+ slot->name = name;
+ slot->size = size;
+ slot->offset = offset;
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+/*
+ * This register set is used for i386 kernel and apps, and for 32-bit apps
+ * running on x86_64. For the latter case, this allows the user to use
+ * things like reg("eax") as well as the standard x86_64 pt_regs names.
+ */
+
+/*
+ * x86_64 and i386 are especially ugly because the pt_reg member names
+ * changed as part of the x86 merge. We allow (and use, as needed)
+ * either the pre-merge name or the post-merge name.
+ */
+
+// I count 32 different names, but add a fudge factor.
+static struct _stp_register_desc i386_registers[32+8];
+static struct _stp_register_table i386_register_table = {
+ .registers = i386_registers,
+ .nr_slots = ARRAY_SIZE(i386_registers)
+};
+
+/*
+ * sizeof(long) is indeed what we want here, for both i386 and x86_64.
+ * Unlike function args, x86_64 pt_regs is the same even if the int3
+ * was in an -m32 app.
+ */
+#define ADD_PT_REG(name, member) \
+ _stp_add_register(name, &i386_register_table, \
+ sizeof(long), offsetof(struct pt_regs, member))
+#define ADD2NAMES(nm1, nm2, member) \
+ do { \
+ ADD_PT_REG(nm1, member); \
+ ADD_PT_REG(nm2, member); \
+ } while (0)
+
+#ifdef STAPCONF_X86_UNIREGS
+
+/* Map "ax" and "eax" to regs->ax, and "cs" and "xcs" to regs->cs */
+#define ADD_EREG(nm) ADD2NAMES(#nm, "e" #nm, nm)
+#define ADD_XREG(nm) ADD2NAMES(#nm, "x" #nm, nm)
+#define ADD_FLAGS_REG() ADD_EREG(flags)
+#define EREG(nm, regs) ((regs)->nm)
+#define RREG(nm, regs) ((regs)->nm)
+
+#else /* ! STAPCONF_X86_UNIREGS */
+
+#ifdef __i386__
+#define ADD_EREG(nm) ADD2NAMES(#nm, "e" #nm, e##nm)
+#define ADD_XREG(nm) ADD2NAMES(#nm, "x" #nm, x##nm)
+#define ADD_FLAGS_REG() ADD_EREG(flags)
+#define EREG(nm, regs) ((regs)->e##nm)
+#else /* __x86_64__ */
+/*
+ * Map "eax" to regs->rax and "xcs" to regs->cs. Other mappings are
+ * handled in x86_64_register_table.
+ */
+#define ADD_EREG(nm) ADD_PT_REG("e" #nm, r##nm)
+#define ADD_XREG(nm) ADD_PT_REG("x" #nm, nm)
+#define ADD_FLAGS_REG() ADD2NAMES("flags", "eflags", eflags)
+#define EREG(nm, regs) ((regs)->r##nm)
+#define RREG(nm, regs) ((regs)->r##nm)
+#endif /* __x86_64__ */
+
+#endif /* ! STAPCONF_X86_UNIREGS */
+
+static void _stp_populate_i386_register_table(void)
+{
+ /*
+ * The order here is the same as in i386 struct pt_regs.
+ * It's a different order from x86_64 pt_regs; but that doesn't
+ * matter -- even when compiling for x86_64 -- because the
+ * offsets are determined by offsetof(), not the calling order.
+ */
+ ADD_EREG(bx);
+ ADD_EREG(cx);
+ ADD_EREG(dx);
+ ADD_EREG(si);
+ ADD_EREG(di);
+ ADD_EREG(bp);
+ ADD_EREG(ax);
+#ifdef __i386__
+ ADD_XREG(ds);
+ ADD_XREG(es);
+ ADD_XREG(fs);
+ /* gs not saved */
+#endif
+#ifdef STAPCONF_X86_UNIREGS
+ ADD2NAMES("orig_ax", "orig_eax", orig_ax);
+#else
+#ifdef __i386__
+ ADD2NAMES("orig_ax", "orig_eax", orig_eax);
+#else /* __x86_64__ */
+ ADD2NAMES("orig_ax", "orig_eax", orig_rax);
+#endif
+#endif /* STAPCONF_X86_UNIREGS */
+ ADD_EREG(ip);
+ ADD_XREG(cs);
+ ADD_FLAGS_REG();
+ ADD_EREG(sp);
+ ADD_XREG(ss);
+}
+
+static long
+_stp_get_reg32_by_name(const char *name, struct pt_regs *regs)
+{
+ size_t offset = 0;
+ long value; // works for i386 or x86_64
+ BUG_ON(!name);
+ if (!regs)
+ _stp_error("Register values not available in this context.\n");
+#ifdef __i386__
+ if (!user_mode(regs)) {
+ /* esp and ss aren't saved on trap from kernel mode. */
+ if (!strcmp(name,"esp") || !strcmp(name, "sp"))
+ return (long) &EREG(sp, regs);
+ if (!strcmp(name,"xss") || !strcmp(name, "ss")) {
+ /*
+ * Assume ss register hasn't changed since we took
+ * the trap.
+ */
+ unsigned short ss;
+ asm volatile("movw %%ss, %0" : : "m" (ss));
+ return ss;
+ }
+ }
+#endif
+ if (!_stp_find_register(name, &i386_register_table, NULL, &offset))
+ _stp_error("Unknown register name: %s\n", name);
+ (void) memcpy(&value, ((char*)regs) + offset, sizeof(value));
+ return value;
+}
+
+#endif /* __i386__ || __x86_64__ */
+
+#ifdef __i386__
+static void _stp_populate_register_table(void)
+{
+ _stp_populate_i386_register_table();
+}
+#endif /* __i386__ */
+
+#ifdef __x86_64__
+// I count 32 different names (not the same 32 as i386), but add a fudge factor.
+static struct _stp_register_desc x86_64_registers[32+8];
+static struct _stp_register_table x86_64_register_table = {
+ .registers = x86_64_registers,
+ .nr_slots = ARRAY_SIZE(x86_64_registers)
+};
+
+/* NB: Redefining ADD_PT_REG here. ADD2NAMES and such change accordingly. */
+#undef ADD_PT_REG
+#define ADD_PT_REG(name, member) \
+ _stp_add_register(name, &x86_64_register_table, \
+ sizeof(unsigned long), offsetof(struct pt_regs, member))
+
+#define ADD_NREG(nm) ADD_PT_REG(#nm, nm)
+
+#ifdef STAPCONF_X86_UNIREGS
+#define ADD_RREG(nm) ADD2NAMES(#nm, "r" #nm, nm)
+#else
+#define ADD_RREG(nm) ADD2NAMES(#nm, "r" #nm, r##nm)
+#endif
+
+static void _stp_populate_register_table(void)
+{
+ /* Same order as in struct pt_regs */
+ ADD_NREG(r15);
+ ADD_NREG(r14);
+ ADD_NREG(r13);
+ ADD_NREG(r12);
+ ADD_RREG(bp);
+ ADD_RREG(bx);
+ ADD_NREG(r11);
+ ADD_NREG(r10);
+ ADD_NREG(r9);
+ ADD_NREG(r8);
+ ADD_RREG(ax);
+ ADD_RREG(cx);
+ ADD_RREG(dx);
+ ADD_RREG(si);
+ ADD_RREG(di);
+#ifdef STAPCONF_X86_UNIREGS
+ ADD2NAMES("orig_ax", "orig_rax", orig_ax);
+#else
+ ADD2NAMES("orig_ax", "orig_rax", orig_rax);
+#endif
+ ADD_RREG(ip);
+ ADD_NREG(cs);
+ ADD_FLAGS_REG();
+ ADD_RREG(sp);
+ ADD_NREG(ss);
+
+ _stp_populate_i386_register_table();
+}
+
+static int _stp_probing_32bit_app(struct pt_regs *regs)
+{
+ return (user_mode(regs) && test_tsk_thread_flag(current, TIF_IA32));
+}
+
+static unsigned long
+_stp_get_reg64_by_name(const char *name, struct pt_regs *regs)
+{
+ size_t offset = 0;
+ unsigned long value;
+ BUG_ON(!name);
+ if (!regs)
+ _stp_error("Register values not available in this context.\n");
+ if (_stp_find_register(name, &x86_64_register_table, NULL, &offset)) {
+ (void) memcpy(&value, ((char*)regs) + offset, sizeof(value));
+ return value;
+ }
+ if (_stp_probing_32bit_app(regs))
+ return _stp_get_reg32_by_name(name, regs);
+ _stp_error("Unknown register name: %s\n", name);
+ /* NOTREACHED */
+ return 0;
+}
+#endif /* __x86_64__ */
+
+/* Function arguments */
+
+#if defined(__i386__) || defined(__x86_64__)
+static long _stp_get_sp(struct pt_regs *regs)
+{
+#ifdef __i386__
+ if (!user_mode(regs))
+ return (long) &EREG(sp, regs);
+#endif
+ return EREG(sp, regs);
+}
+
+/*
+ * Use this for i386 kernel and apps, and for 32-bit apps running on x86_64.
+ * Does arch-specific work for fetching function arg #argnum (1 = first arg).
+ * nr_regargs is the number of arguments that reside in registers (e.g.,
+ * 3 for fastcall functions).
+ * Returns:
+ * 0 if the arg resides in a register. *val contains its value.
+ * 1 if the arg resides on the kernel stack. *val contains its address.
+ * 2 if the arg resides on the user stack. *val contains its address.
+ * -1 if the arg number is invalid.
+ * We assume that the regs pointer is valid.
+ */
+static int _stp_get_arg32_by_number(int n, int nr_regargs,
+ struct pt_regs *regs, long *val)
+{
+ if (nr_regargs < 0)
+ return -1;
+ if (n > nr_regargs) {
+ /*
+ * The typical case: arg n is on the stack.
+ * stack[0] = return address
+ */
+ int stack_index = n - nr_regargs;
+ int32_t *stack = (int32_t*) _stp_get_sp(regs);
+ *val = (long) &stack[stack_index];
+ return (user_mode(regs) ? 2 : 1);
+ } else {
+ switch (n) {
+ case 1: *val = EREG(ax, regs); break;
+ case 2: *val = EREG(dx, regs); break;
+ case 3: *val = EREG(cx, regs); break;
+ default:
+ /* gcc rejects regparm values > 3. */
+ return -1;
+ }
+ return 0;
+ }
+}
+#endif /* __i386__ || __x86_64__ */
+
+#ifdef __x86_64__
+/* See _stp_get_arg32_by_number(). */
+static int _stp_get_arg64_by_number(int n, int nr_regargs,
+ struct pt_regs *regs, unsigned long *val)
+{
+ if (nr_regargs < 0)
+ return -1;
+ if (n > nr_regargs) {
+ /* arg n is on the stack. stack[0] = return address */
+ int stack_index = n - nr_regargs;
+ unsigned long *stack = (unsigned long*) _stp_get_sp(regs);
+ *val = (unsigned long) &stack[stack_index];
+ return (user_mode(regs) ? 2 : 1);
+ } else {
+ switch (n) {
+ case 1: *val = RREG(di, regs); break;
+ case 2: *val = RREG(si, regs); break;
+ case 3: *val = RREG(dx, regs); break;
+ case 4: *val = RREG(cx, regs); break;
+ case 5: *val = regs->r8; break;
+ case 6: *val = regs->r9; break;
+ default:
+ /* gcc rejects regparm values > 6. */
+ return -1;
+ }
+ return 0;
+ }
+}
+#endif /* __x86_64__ */
+
/** @} */
#endif /* _REGS_C_ */