From 9addf322a677eef2ee0efeca8bc41a9bda58de94 Mon Sep 17 00:00:00 2001 From: Jim Keniston Date: Mon, 5 May 2008 17:03:49 -0700 Subject: [PATCH] Replaced [u_]arg() with [u]int_arg(), [u]long_arg(), {s|u}32_arg(), {s|u}64_arg(), etc. Added asmlinkage(), fastcall(), regparm(). Dealt with some surprises -- e.g., rax is ZERO-extended eax. Seems to work well with -m32 and -m64 user apps, and with a (small) dwarfless subset of syscall.stp. --- runtime/regs.c | 75 +++++++++++++++--- tapset/i686/registers.stp | 104 ++++++++++++++++++++----- tapset/x86_64/registers.stp | 148 ++++++++++++++++++++++++++++-------- tapsets.cxx | 1 + translate.cxx | 1 + 5 files changed, 268 insertions(+), 61 deletions(-) diff --git a/runtime/regs.c b/runtime/regs.c index 743a230d0..2daeaa3c9 100644 --- a/runtime/regs.c +++ b/runtime/regs.c @@ -400,6 +400,7 @@ struct _stp_register_table { unsigned nr_slots; // capacity }; +static DEFINE_SPINLOCK(_stp_register_table_lock); static void _stp_populate_register_table(void); /* @@ -445,12 +446,17 @@ 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) + if (unlikely(table->nr_registers == 0)) { + unsigned long flags; /* * Should we do this at the beginning of time to avoid * the possibility of spending too long in a handler? */ - _stp_populate_register_table(); + spin_lock_irqsave(&_stp_register_table_lock, flags); + if (table->nr_registers == 0) + _stp_populate_register_table(); + spin_unlock_irqrestore(&_stp_register_table_lock, flags); + } slot = _stp_lookup_register(name, table, &found); if (found) { if (size) @@ -549,6 +555,7 @@ static struct _stp_register_table i386_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) +/* Note: After a store to %eax, %rax holds the ZERO-extended %eax. */ #define EREG(nm, regs) ((regs)->r##nm) #define RREG(nm, regs) ((regs)->r##nm) #endif /* __x86_64__ */ @@ -592,6 +599,10 @@ static void _stp_populate_i386_register_table(void) ADD_XREG(ss); } +/* + * For x86_64, this gets a copy of the saved 64-bit register (e.g., regs->rax). + * After a store to %eax, %rax holds the ZERO-extended %eax. + */ static long _stp_get_reg32_by_name(const char *name, struct pt_regs *regs) { @@ -687,31 +698,50 @@ static void _stp_populate_register_table(void) static int _stp_probing_32bit_app(struct pt_regs *regs) { + if (!regs) + return 0; return (user_mode(regs) && test_tsk_thread_flag(current, TIF_IA32)); } +/* Ensure that the upper 32 bits of val are a sign-extension of the lower 32. */ +static long _stp_sign_extend32(long val) +{ + int32_t *val_ptr32 = (int32_t*) &val; + return *val_ptr32; +} + +/* + * Get the value of the 64-bit register with the specified name. "rax", + * "ax", and "eax" all get you regs->[r]ax. Sets *reg32=1 if the name + * designates a 32-bit register (e.g., "eax"), 0 otherwise. + */ static unsigned long -_stp_get_reg64_by_name(const char *name, struct pt_regs *regs) +_stp_get_reg64_by_name(const char *name, struct pt_regs *regs, int *reg32) { size_t offset = 0; unsigned long value; BUG_ON(!name); - if (!regs) + if (!regs) { _stp_error("Register values not available in this context.\n"); + return 0; + } if (_stp_find_register(name, &x86_64_register_table, NULL, &offset)) { + if (reg32) + *reg32 = 0; (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; + if (reg32) + *reg32 = 1; + return _stp_get_reg32_by_name(name, regs); } #endif /* __x86_64__ */ /* Function arguments */ +#define _STP_REGPARM 0x8000 +#define _STP_REGPARM_MASK ((_STP_REGPARM) - 1) + #if defined(__i386__) || defined(__x86_64__) static long _stp_get_sp(struct pt_regs *regs) { @@ -762,6 +792,21 @@ static int _stp_get_arg32_by_number(int n, int nr_regargs, } #endif /* __i386__ || __x86_64__ */ +#ifdef __i386__ +static int _stp_get_regparm(int regparm, struct pt_regs *regs) +{ + if (regparm == 0) { + /* Default */ + if (user_mode(regs)) + return 0; + else + // Kernel is built with -mregparm=3. + return 3; + } else + return (regparm & _STP_REGPARM_MASK); +} +#endif + #ifdef __x86_64__ /* See _stp_get_arg32_by_number(). */ static int _stp_get_arg64_by_number(int n, int nr_regargs, @@ -790,6 +835,18 @@ static int _stp_get_arg64_by_number(int n, int nr_regargs, return 0; } } + +static int _stp_get_regparm(int regparm, struct pt_regs *regs) +{ + if (regparm == 0) { + /* Default */ + if (_stp_probing_32bit_app(regs)) + return 0; + else + return 6; + } else + return (regparm & _STP_REGPARM_MASK); +} #endif /* __x86_64__ */ /** @} */ diff --git a/tapset/i686/registers.stp b/tapset/i686/registers.stp index 2afbbb9b9..85aa7a7f0 100644 --- a/tapset/i686/registers.stp +++ b/tapset/i686/registers.stp @@ -1,10 +1,7 @@ /* Return the named register value as a signed value. */ function register:long (name:string) %{ /* pure */ - if (CONTEXT->regs) - THIS->__retvalue = (int64_t) + THIS->__retvalue = (int64_t) _stp_get_reg32_by_name(THIS->name, CONTEXT->regs); - else - THIS->__retvalue = 0; %} /* @@ -16,10 +13,9 @@ function u_register:long (name:string) { } /* Return the value of function arg #argnum (1=first arg) as a signed value. */ -function arg:long (argnum:long) %{ +function _stp_arg:long (argnum:long) %{ long val; - int n; - int result; + int n, nr_regargs, result; THIS->__retvalue = 0; if (!CONTEXT->regs) { @@ -31,24 +27,30 @@ function arg:long (argnum:long) %{ if (THIS->argnum < 1) goto bad_argnum; n = (int) THIS->argnum; - // TODO: Get nr_regargs from .linkage clause. - result = _stp_get_arg32_by_number(n, 0, CONTEXT->regs, &val); + nr_regargs = _stp_get_regparm(CONTEXT->regparm, CONTEXT->regs); + result = _stp_get_arg32_by_number(n, nr_regargs, CONTEXT->regs, &val); switch (result) { case 0: + /* Arg is in register. */ THIS->__retvalue = (int64_t) val; break; case 1: + /* Arg is on kernel stack. */ THIS->__retvalue = kread((long *) val); break; case 2: - /* - * Is copy_from_user satisfactory, since uprobe - * handlers can block? - */ - snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer), - "arg() not yet implemented for user functions"); - CONTEXT->last_error = CONTEXT->error_buffer; + { + /* Arg is on user stack. */ + const char __user *vaddr = (const char __user*) val; + if (_stp_copy_from_user((char*)&val, vaddr, sizeof(val)) != 0) { + /* Stack page not resident. */ + _stp_warn("cannot access arg(%d) " + "at user stack address %p\n", n, vaddr); + THIS->__retvalue = 0; + } else + THIS->__retvalue = (int64_t) val; break; + } default: goto bad_argnum; } @@ -69,7 +71,71 @@ deref_fault: /* branched to from deref() */ } %} -/* Return the value of function arg #argnum as an unsigned value. */ -function u_arg:long (argnum:long) { - return arg(argnum) & 0xffffffff; +/* Return the value of function arg #argnum as a signed int. */ +function int_arg:long (argnum:long) { + return _stp_arg(argnum) +} + +/* Return the value of function arg #argnum as an unsigned int. */ +function uint_arg:long (argnum:long) { + return _stp_arg(argnum) & 0xffffffff; +} + +function long_arg:long (argnum:long) { + return int_arg(argnum) +} + +function ulong_arg:long (argnum:long) { + return uint_arg(argnum) +} + +function longlong_arg:long (argnum:long) { + /* + * TODO: If argnum == nr_regarg, gcc puts the whole 64-bit arg + * on the stack. + */ + lowbits = uint_arg(argnum) + highbits = uint_arg(argnum+1) + return ((highbits << 32) | lowbits) +} + +function ulonglong_arg:long (argnum:long) { + return longlong_arg(argnum) +} + +function pointer_arg:long (argnum:long) { + return ulong_arg(argnum) +} + +function s32_arg:long (argnum:long) { + return int_arg(argnum) +} + +function u32_arg:long (argnum:long) { + return uint_arg(argnum) +} + +function s64_arg:long (argnum:long) { + return longlong_arg(argnum) +} + +function u64_arg:long (argnum:long) { + return ulonglong_arg(argnum) } + +function asmlinkage() %{ + CONTEXT->regparm = _STP_REGPARM | 0; +%} + +function fastcall() %{ + CONTEXT->regparm = _STP_REGPARM | 3; +%} + +function regparm(n) %{ + if (THIS->n < 0 || THIS->n > 3) { + snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer), + "For i386, regparm value must be in the range 0-3."); + CONTEXT->last_error = CONTEXT->error_buffer; + } else + CONTEXT->regparm = _STP_REGPARM | (int) n; +%} diff --git a/tapset/x86_64/registers.stp b/tapset/x86_64/registers.stp index 375be396e..45acddd15 100644 --- a/tapset/x86_64/registers.stp +++ b/tapset/x86_64/registers.stp @@ -1,22 +1,27 @@ /* Return the named register value as a signed value. */ function register:long (name:string) %{ /* pure */ - if (CONTEXT->regs) - THIS->__retvalue = (int64_t) - _stp_get_reg64_by_name(THIS->name, CONTEXT->regs); - else - THIS->__retvalue = 0; + int reg32 = 0; + THIS->__retvalue = (int64_t) _stp_get_reg64_by_name(THIS->name, + CONTEXT->regs, ®32); + if (reg32) + THIS->__retvalue = _stp_sign_extend32(THIS->__retvalue); %} /* Return the named register value as an unsigned value. */ -function u_register:long (name:string) { - return register(name) -} +function u_register:long (name:string) %{ + THIS->__retvalue = (int64_t) _stp_get_reg64_by_name(THIS->name, + CONTEXT->regs, NULL); +%} -/* Return the value of function arg #argnum (1=first arg) as a signed value. */ -function arg:long (argnum:long) %{ +/* + * Return the value of function arg #argnum (1=first arg). + * If truncate=1, mask off the top 32 bits. + * If sign_extend=1 and (truncate=1 or the probepoint we've hit is in a + * 32-bit app), sign-extend the 32-bit value. + */ +function _stp_arg:long (argnum:long, sign_extend:long, truncate:long) %{ long val; - int result; - int n; + int result, n, nr_regargs; size_t argsz = sizeof(long); THIS->__retvalue = 0; @@ -29,33 +34,46 @@ function arg:long (argnum:long) %{ if (THIS->argnum < 1) goto bad_argnum; n = (int) THIS->argnum; + nr_regargs = _stp_get_regparm(CONTEXT->regparm, CONTEXT->regs); if (_stp_probing_32bit_app(CONTEXT->regs)) { - // TODO: Get nr_regargs from .linkage clause. argsz = sizeof(int); - result = _stp_get_arg32_by_number(n, 0, CONTEXT->regs, &val); - } else { - // TODO: Get nr_regargs from .linkage clause. - result = _stp_get_arg64_by_number(n, 6, CONTEXT->regs, &val); - } + result = _stp_get_arg32_by_number(n, nr_regargs, CONTEXT->regs, + &val); + } else + result = _stp_get_arg64_by_number(n, nr_regargs, CONTEXT->regs, + &val); switch (result) { case 0: - THIS->__retvalue = (int64_t) val; + /* Arg is in register. */ break; case 1: - THIS->__retvalue = kread((long *) val); + /* Arg is on kernel stack. */ + val = kread((long *) val); break; case 2: - /* - * Is copy_from_user satisfactory, since uprobe - * handlers can block? - */ - snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer), - "arg() not yet implemented for user functions"); - CONTEXT->last_error = CONTEXT->error_buffer; + { + /* Arg is on user stack. */ + const char __user *vaddr = (const char __user*) val; + if (_stp_copy_from_user((char*)&val, vaddr, argsz) != 0) { + /* Stack page not resident. */ + _stp_warn("cannot access arg(%d) " + "at user stack address %p\n", n, vaddr); + THIS->__retvalue = 0; + return; + } break; + } default: goto bad_argnum; } + if (THIS->truncate || argsz == sizeof(int)) { + if (THIS->sign_extend) + THIS->__retvalue = (int64_t) _stp_sign_extend32(val); + else + /* High bits may be garbage. */ + THIS->__retvalue = (int64_t) (val & 0xffffffff); + } else + THIS->__retvalue = (int64_t) val; return; bad_argnum: @@ -77,10 +95,74 @@ function probing_32bit_app() %{ THIS->__retvalue = _stp_probing_32bit_app(CONTEXT->regs); %} -/* Return the value of function arg #argnum as an unsigned value. */ -function u_arg:long (argnum:long) { - if (probing_32bit_app()) - return arg(argnum) & 0xffffffff - else - return arg(argnum) +/* Return the value of function arg #argnum (1=first arg) as a signed int. */ +function int_arg:long (argnum:long) { + return _stp_arg(argnum, 1, 1) +} + +/* Return the value of function arg #argnum (1=first arg) as an unsigned int. */ +function uint_arg:long (argnum:long) { + return _stp_arg(argnum, 0, 1) +} + +function long_arg:long (argnum:long) { + return _stp_arg(argnum, 1, 0) +} + +function ulong_arg:long (argnum:long) { + return _stp_arg(argnum, 0, 0) +} + +function longlong_arg:long (argnum:long) { + if (probing_32bit_app()) { + lowbits = _stp_arg(argnum, 0, 1) + highbits = _stp_arg(argnum+1, 0, 1) + return ((highbits << 32) | lowbits) + } else + return _stp_arg(argnum, 0, 0) +} + +function ulonglong_arg:long (argnum:long) { + return longlong_arg(argnum) +} + +function pointer_arg:long (argnum:long) { + return _stp_arg(argnum, 0, 0) } + +function s32_arg:long (argnum:long) { + return int_arg(argnum) +} + +function u32_arg:long (argnum:long) { + return uint_arg(argnum) +} + +function s64_arg:long (argnum:long) { + return longlong_arg(argnum) +} + +function u64_arg:long (argnum:long) { + return ulonglong_arg(argnum) +} + +function asmlinkage() { +} + +function fastcall() { +} + +function regparm(n) %{ + if (_stp_probing_32bit_app(CONTEXT->regs) && + (THIS->n < 0 || THIS->n > 3)) { + snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer), + "For -m32 programs, " + "regparm value must be in the range 0-3."); + CONTEXT->last_error = CONTEXT->error_buffer; + } else if (THIS->n < 0 || THIS->n > 6) { + snprintf(CONTEXT->error_buffer, sizeof(CONTEXT->error_buffer), + "For x86_64, regparm value must be in the range 0-6."); + CONTEXT->last_error = CONTEXT->error_buffer; + } else + CONTEXT->regparm = _STP_REGPARM | (int) n; +%} diff --git a/tapsets.cxx b/tapsets.cxx index af7e55909..47bb20e24 100644 --- a/tapsets.cxx +++ b/tapsets.cxx @@ -209,6 +209,7 @@ common_probe_entryfn_prologue (translator_output* o, string statestr, o->newline() << "c->unwaddr = 0;"; // reset unwound address cache o->newline() << "c->pi = 0;"; + o->newline() << "c->regparm = 0;"; o->newline() << "c->probe_point = 0;"; if (! interruptible) o->newline() << "c->actionremaining = MAXACTION;"; diff --git a/translate.cxx b/translate.cxx index c9ec094a0..6aef37f00 100644 --- a/translate.cxx +++ b/translate.cxx @@ -877,6 +877,7 @@ c_unparser::emit_common_header () o->newline() << "unsigned long *unwaddr;"; // unwaddr is caching unwound address in each probe handler on ia64. o->newline() << "struct kretprobe_instance *pi;"; + o->newline() << "int regparm;"; o->newline() << "va_list *mark_va_list;"; o->newline() << "void *data;"; o->newline() << "#ifdef STP_TIMING"; -- 2.43.5