* Makefile.am: Build with LuaJIT if configured so. (strace_SOURCES): Add defs_shared.h, ffi.h. (luajit_lib.h): Auto-generate from luajit_lib.lua. * configure.ac: Add new --with-luajit configure option. * defs.h (TCB_AD_HOC_INJECT): new TCB flag. (QUAL_HOOK_ENTRY, QUAL_HOOK_EXIT): new qual flags. (struct tcb): If built with LuaJIT support, include currpers field even if SUPPORTED_PERSONALITIES is 0. If built with LuaJIT support, include new ad_hoc_inject_opts field. Move definitions that need to be fed to LuaJIT's FFI to... * defs_shared.h: ...new file. * ffi.h: New file. * luajit.h: Likewise. * luajit_lib.lua: Likewise. * qualify.c (syscall_classes): move syscall classes list to the global scope, terminate it with a null entry. (hook_entry_set, hook_exit_set): New sets (if built with LuaJIT support). (lookup_class): Use global, null entry-terminated list of syscall classes. (qual_flags): If built with LuaJIT support, return QUAL_HOOK_ENTRY and QUAL_HOOK_EXIT flags. (set_hook_qual): New function. * strace.1 (LUAJIT SCRIPTING): New section. * strace.c (alloctcb): update the condition of presence of currpers field. (droptcb): If built with LuaJIT support, free ad_hoc_inject_opts. (init): New -l option (if built with LuaJIT support). (main): run Lua script, if built with LuaJIT support and a script was provided. * syscall.c (errnoent_vec, nerrnoent_vec, signalent_vec, nsignalent_vec, ioctlent_vec, nioctlent_vec): New global variables. (tcb_inject_opts): introduce a second argument indicating whether tcp's inject_vec should be copied from the global inject_vec if needed. If built with LuaJIT support and TCB_AD_HOC_INJECT flag is set, return tcp's ad_hoc_inject_opts. (tamper_with_syscall_entering): Don't copy inject_vec here; instead, pass true as a second argument to tcb_inject_opts. (tamper_with_syscall_exiting): Pass false as a second argument to tcb_inject_opts. (syscall_ad_hoc_inject): New function. (syscall_entering_trace): perform ad hoc injection even if the syscall is not traced. (syscall_exiting_decode): don't return 0 ("bail out") if exiting hook is set up for this syscall, or if an ad hoc injection was performed. Call tamper_with_syscall_exiting on success. (syscall_exiting_trace): Don't call tamper_with_syscall_exiting, check if the syscall is not traced again. (syscall_exiting_finish): Clear TCB_AD_HOC_INJECT bit. * sysent.h: Modify to support inclusion with FFI_CDEF. --- Makefile.am | 15 ++- configure.ac | 36 +++++++ defs.h | 53 +++------- defs_shared.h | 65 ++++++++++++ ffi.h | 19 ++++ luajit.h | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ luajit_lib.lua | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ qualify.c | 84 +++++++++------ strace.1 | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++ strace.c | 36 ++++++- syscall.c | 117 ++++++++++++++++---- sysent.h | 24 ++++- 12 files changed, 1289 insertions(+), 94 deletions(-) create mode 100644 defs_shared.h create mode 100644 ffi.h create mode 100644 luajit.h create mode 100644 luajit_lib.lua
diff --git a/Makefile.am b/Makefile.am index cb650215..a193063d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,6 +100,7 @@ strace_SOURCES = \ copy_file_range.c \ count.c \ defs.h \ + defs_shared.h \ desc.c \ dirent.c \ dirent64.c \ @@ -122,6 +123,7 @@ strace_SOURCES = \ fetch_struct_stat.c \ fetch_struct_stat64.c \ fetch_struct_statfs.c \ + ffi.h \ file_handle.c \ file_ioctl.c \ fs_x_ioctl.c \ @@ -284,6 +286,12 @@ strace_LDFLAGS += $(libunwind_LDFLAGS) strace_LDADD += $(libunwind_LIBS) endif +if USE_LUAJIT +strace_SOURCES += luajit.h +strace_CPPFLAGS += $(LUAJIT_CFLAGS) +strace_LDADD += $(LUAJIT_LIBS) +endif + @CODE_COVERAGE_RULES@ CODE_COVERAGE_BRANCH_COVERAGE = 1 CODE_COVERAGE_GENHTML_OPTIONS = $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) \ @@ -838,6 +846,9 @@ $(srcdir)/.version: strace_SOURCES_c = \ $(filter %.c,$(strace_SOURCES)) $(filter %.c,$(libstrace_a_SOURCES)) +luajit_lib.h: luajit_lib.lua + sed 's/["\\]/\\\0/g;s/.*/"\0\\n"/' $< > $@ + sys_func.h: $(patsubst %,$(srcdir)/%,$(strace_SOURCES_c)) for f in $^; do \ sed -n 's/^SYS_FUNC(.*/extern &;/p' $$f; \ @@ -910,10 +921,10 @@ ioctls_all%.h: $(srcdir)/$(OS)/$(ARCH)/ioctls_inc%.h $(srcdir)/$(OS)/$(ARCH)/ioc cat $^ > $@ BUILT_SOURCES = $(ioctl_redefs_h) $(ioctlent_h) \ - native_printer_decls.h native_printer_defs.h printers.h sen.h sys_func.h .version + luajit_lib.h native_printer_decls.h native_printer_defs.h printers.h sen.h sys_func.h .version CLEANFILES = $(ioctl_redefs_h) $(ioctlent_h) $(mpers_preproc_files) \ ioctl_iocdef.h ioctl_iocdef.i \ - native_printer_decls.h native_printer_defs.h printers.h sen.h sys_func.h + luajit_lib.h native_printer_decls.h native_printer_defs.h printers.h sen.h sys_func.h DISTCLEANFILES = gnu/stubs-32.h gnu/stubs-x32.h include scno.am diff --git a/configure.ac b/configure.ac index eb7b0e78..53ecd332 100644 --- a/configure.ac +++ b/configure.ac @@ -727,6 +727,42 @@ AC_SUBST(dl_LIBS) AC_PATH_PROG([PERL], [perl]) +dnl LuaJIT scripting support +use_luajit=no +force_luajit=no +luajit_lib=luajit +LUAJIT_LIBS= +LUAJIT_CFLAGS= +AC_ARG_WITH([luajit], + [AS_HELP_STRING([--with-luajit], + [build with LuaJIT scripting support])], + [case "${withval}" in + yes) force_luajit=yes ;; + check) ;; + *) force_luajit=yes; luajit_lib="${withval}" ;; + esac], + [:] +) +AS_IF([test "x$luajit_lib" != xno], + [PKG_CHECK_MODULES([LUAJIT], + [$luajit_lib], + [use_luajit=yes], + [AS_IF([test "x$force_luajit" = xyes], + [AC_MSG_ERROR([cannot find luajit library: $luajit_lib])] + )] + )] +) + +dnl enable LuaJIT +AC_MSG_CHECKING([whether to enable Lua scripting]) +if test "x$use_luajit" = xyes; then + AC_DEFINE([USE_LUAJIT], 1, [Enable Lua scripting support]) + AC_SUBST(LUAJIT_LIBS) + AC_SUBST(LUAJIT_CFLAGS) +fi +AM_CONDITIONAL([USE_LUAJIT], [test "x$use_luajit" = xyes]) +AC_MSG_RESULT([$use_luajit]) + dnl stack trace with libunwind libunwind_CPPFLAGS= libunwind_LDFLAGS= diff --git a/defs.h b/defs.h index b3a315f3..52d2f137 100644 --- a/defs.h +++ b/defs.h @@ -192,11 +192,6 @@ extern char *stpcpy(char *dst, const char *src); # define PERSONALITY2_INCLUDE_FUNCS "empty.h" #endif -typedef struct ioctlent { - const char *symbol; - unsigned int code; -} struct_ioctlent; - struct inject_opts { uint16_t first; uint16_t step; @@ -207,39 +202,7 @@ struct inject_opts { #define MAX_ERRNO_VALUE 4095 #define INJECT_OPTS_RVAL_DEFAULT (-(MAX_ERRNO_VALUE + 1)) -/* Trace Control Block */ -struct tcb { - int flags; /* See below for TCB_ values */ - int pid; /* If 0, this tcb is free */ - int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW */ - unsigned long u_error; /* Error code */ - kernel_ulong_t scno; /* System call number */ - kernel_ulong_t u_arg[MAX_ARGS]; /* System call arguments */ - kernel_long_t u_rval; /* Return value */ -#if SUPPORTED_PERSONALITIES > 1 - unsigned int currpers; /* Personality at the time of scno update */ -#endif - int sys_func_rval; /* Syscall entry parser's return value */ - int curcol; /* Output column for this process */ - FILE *outf; /* Output file for this process */ - const char *auxstr; /* Auxiliary info from syscall (see RVAL_STR) */ - void *_priv_data; /* Private data for syscall decoding functions */ - void (*_free_priv_data)(void *); /* Callback for freeing priv_data */ - const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */ - const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" msg */ - struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES]; - struct timeval stime; /* System time usage as of last process wait */ - struct timeval dtime; /* Delta for system time usage */ - struct timeval etime; /* Syscall entry time */ - -#ifdef USE_LIBUNWIND - struct UPT_info *libunwind_ui; - struct mmap_cache_t *mmap_cache; - unsigned int mmap_cache_size; - unsigned int mmap_cache_generation; - struct queue_t *queue; -#endif -}; +#include "defs_shared.h" /* TCB flags */ /* We have attached to this process, but did not see it stopping yet */ @@ -263,6 +226,7 @@ struct tcb { #define TCB_TAMPERED 0x40 /* A syscall has been tampered with */ #define TCB_HIDE_LOG 0x80 /* We should hide everything (until execve) */ #define TCB_SKIP_DETACH_ON_FIRST_EXEC 0x100 /* -b execve should skip detach on first execve */ +#define TCB_AD_HOC_INJECT 0x200 /* an ad hoc injection was performed by Lua script */ /* qualifier flags */ #define QUAL_TRACE 0x001 /* this system call should be traced */ @@ -273,6 +237,8 @@ struct tcb { #define QUAL_SIGNAL 0x100 /* report events with this signal */ #define QUAL_READ 0x200 /* dump data read from this file descriptor */ #define QUAL_WRITE 0x400 /* dump data written to this file descriptor */ +#define QUAL_HOOK_ENTRY 0x800 /* return this syscall on entry from next_sc() */ +#define QUAL_HOOK_EXIT 0x1000 /* return this syscall on exit from next_sc() */ #define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE) @@ -361,6 +327,7 @@ typedef enum { CFLAG_ONLY_STATS, CFLAG_BOTH } cflag_t; +extern const struct syscall_class syscall_classes[]; extern cflag_t cflag; extern bool debug_flag; extern bool Tflag; @@ -670,6 +637,9 @@ extern struct number_set signal_set; extern bool is_number_in_set(unsigned int number, const struct number_set *); extern void qualify(const char *); extern unsigned int qual_flags(const unsigned int); +#ifdef USE_LUAJIT +extern void set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook, bool exit_hook); +#endif #define DECL_IOCTL(name) \ extern int \ @@ -950,6 +920,13 @@ extern const char *const errnoent0[]; extern const char *const signalent0[]; extern const struct_ioctlent ioctlent0[]; +extern const char *const *errnoent_vec[SUPPORTED_PERSONALITIES]; +extern const char *const *signalent_vec[SUPPORTED_PERSONALITIES]; +extern const struct_ioctlent *const ioctlent_vec[SUPPORTED_PERSONALITIES]; +extern const unsigned int nerrnoent_vec[SUPPORTED_PERSONALITIES]; +extern const unsigned int nsignalent_vec[SUPPORTED_PERSONALITIES]; +extern const unsigned int nioctlent_vec[SUPPORTED_PERSONALITIES]; + #if SUPPORTED_PERSONALITIES > 1 extern const struct_sysent *sysent; extern const char *const *errnoent; diff --git a/defs_shared.h b/defs_shared.h new file mode 100644 index 00000000..9609749b --- /dev/null +++ b/defs_shared.h @@ -0,0 +1,65 @@ +/* + * Should only be included without FFI_CDEF from defs.h, so no include guards. + */ + +#include "ffi.h" + +FFI_CONTENT( +struct syscall_class { + const char *name; + unsigned int value; +}; +) + +FFI_CONTENT( +typedef struct ioctlent { + const char *symbol; + unsigned int code; +} struct_ioctlent; +) + +FFI_CONTENT( +struct tcb { + int flags; /* See below for TCB_ values */ + int pid; /* If 0, this tcb is free */ + int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW */ + unsigned long u_error; /* Error code */ + kernel_ulong_t scno; /* System call number */ + kernel_ulong_t u_arg[MAX_ARGS]; /* System call arguments */ + kernel_long_t u_rval; /* Return value */ +) + +#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1 +FFI_CONTENT( + unsigned int currpers; /* Personality at the time of scno update */ +) +#endif + +#ifndef FFI_CDEF + int sys_func_rval; /* Syscall entry parser's return value */ + int curcol; /* Output column for this process */ + FILE *outf; /* Output file for this process */ + const char *auxstr; /* Auxiliary info from syscall (see RVAL_STR) */ + void *_priv_data; /* Private data for syscall decoding functions */ + void (*_free_priv_data)(void *); /* Callback for freeing priv_data */ + const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */ + const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" msg */ +# ifdef USE_LUAJIT + struct inject_opts *ad_hoc_inject_opts; +# endif + struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES]; + struct timeval stime; /* System time usage as of last process wait */ + struct timeval dtime; /* Delta for system time usage */ + struct timeval etime; /* Syscall entry time */ +# ifdef USE_LIBUNWIND + struct UPT_info *libunwind_ui; + struct mmap_cache_t *mmap_cache; + unsigned int mmap_cache_size; + unsigned int mmap_cache_generation; + struct queue_t *queue; +# endif +#endif /* !FFI_CDEF */ + +FFI_CONTENT( +}; +) diff --git a/ffi.h b/ffi.h new file mode 100644 index 00000000..7b89e7a4 --- /dev/null +++ b/ffi.h @@ -0,0 +1,19 @@ +#ifndef STRACE_FFI_H +#define STRACE_FFI_H + +#include "macros.h" + +#define FFI_CONCAT(a, b) a ## b +#define FFI_CONCAT2(a, b) FFI_CONCAT(a, b) + +/* + * FFI_CONTENT expands to FFI_CONTENT_ (which strigifies its arguments) when + * FFI_CDEF is defined, and to FFI_CONTENT_FFI_CDEF (which simply expands to its + * arguments) when it is not. + */ +#define FFI_CONTENT FFI_CONCAT2(FFI_CONTENT_, FFI_CDEF) + +#define FFI_CONTENT_(...) STRINGIFY(__VA_ARGS__) +#define FFI_CONTENT_FFI_CDEF(...) __VA_ARGS__ + +#endif /* !STRACE_FFI_H */ diff --git a/luajit.h b/luajit.h new file mode 100644 index 00000000..c1177b05 --- /dev/null +++ b/luajit.h @@ -0,0 +1,328 @@ +/* + * Should only be included from strace.c, so no include guards. + */ + +#include <lualib.h> +#include <lauxlib.h> + +#define L script_L + +static struct tcb * +func_next_sc(void) +{ + static struct timeval tv = {}; + static bool first = true; + static bool done = false; + + if (done) + return NULL; + +#define MAYBE_RESTART(res, sig) \ + do { \ + if ((res) >= 0 && ptrace_restart(PTRACE_SYSCALL, current_tcp, sig) < 0) { \ + /* Note: ptrace_restart emitted error message */ \ + exit_code = 1; \ + goto term; \ + } \ + } while (0) + + if (!first) { + unsigned int sig = 0; + int res; + if (entering(current_tcp)) { + res = syscall_entering_trace(current_tcp, &sig); + syscall_entering_finish(current_tcp, res); + } else { + res = syscall_exiting_trace(current_tcp, tv, 1); + syscall_exiting_finish(current_tcp); + } + MAYBE_RESTART(res, sig); + } + first = false; + + while (1) { + int status; + siginfo_t si; + enum trace_event ret = next_event(&status, &si); + if (ret == TE_SYSCALL_STOP) { + unsigned int sig = 0; + int res; + if (entering(current_tcp)) { + res = syscall_entering_decode(current_tcp); + switch (res) { + case 0: + break; + case 1: + if (current_tcp->qual_flg & QUAL_HOOK_ENTRY) + return current_tcp; + res = syscall_entering_trace(current_tcp, &sig); + /* fall through */ + default: + syscall_entering_finish(current_tcp, res); + } + } else { + res = syscall_exiting_decode(current_tcp, &tv); + switch (res) { + case 0: + break; + case 1: + if (current_tcp->qual_flg & QUAL_HOOK_EXIT) + return current_tcp; + /* fall through */ + default: + res = syscall_exiting_trace(current_tcp, tv, res); + } + syscall_exiting_finish(current_tcp); + } + MAYBE_RESTART(res, sig); + } else { + if (!dispatch_event(ret, &status, &si)) { + goto term; + } + } + } +#undef MAYBE_RESTART +term: + done = true; + return NULL; +} + +static bool +func_monitor(unsigned int scno, unsigned int pers, bool entry_hook, bool exit_hook) +{ + if (pers >= SUPPORTED_PERSONALITIES || scno >= nsyscall_vec[pers]) + return false; + set_hook_qual(scno, pers, entry_hook, exit_hook); + return true; +} + +static void +prepare_ad_hoc_inject(struct tcb *tcp) +{ + if (!tcp->ad_hoc_inject_opts) { + tcp->ad_hoc_inject_opts = xmalloc(sizeof(*tcp->ad_hoc_inject_opts)); + tcp->ad_hoc_inject_opts->first = 1; + tcp->ad_hoc_inject_opts->step = 1; + } + if (!(tcp->flags & TCB_AD_HOC_INJECT)) { + tcp->ad_hoc_inject_opts->signo = 0; + tcp->ad_hoc_inject_opts->rval = INJECT_OPTS_RVAL_DEFAULT; + tcp->qual_flg |= QUAL_INJECT; + tcp->flags |= TCB_AD_HOC_INJECT; + } +} + +static bool +func_inject_signo(struct tcb *tcp, int signo) +{ + if (exiting(tcp)) + /* Too late! */ + return false; + if (signo <= 0 || signo > SIGRTMAX) + return false; + prepare_ad_hoc_inject(tcp); + tcp->ad_hoc_inject_opts->signo = signo; + return true; +} + +static bool +func_inject_retval(struct tcb *tcp, int retval) +{ + if (exiting(tcp)) + /* Too late! */ + return false; + if (retval < -MAX_ERRNO_VALUE) + return false; + prepare_ad_hoc_inject(tcp); + tcp->ad_hoc_inject_opts->rval = retval; + return true; +} + +static const char * +get_lua_msg(void) +{ + const char *msg = lua_tostring(L, -1); + return msg ? msg : "(error object can't be converted to string)"; +} + +static void +assert_lua_impl(int ret, const char *expr, const char *file, int line) +{ + if (ret == 0) + return; + error_msg_and_die("assert_lua(%s) failed at %s:%d: %s", expr, file, + line, get_lua_msg()); +} + +#define assert_lua(expr) assert_lua_impl(expr, #expr, __FILE__, __LINE__) + +static void +check_lua(int ret) +{ + if (ret == 0) + return; + error_msg_and_die("lua: %s", get_lua_msg()); +} + +#ifdef LUA_FFILIBNAME +# define FFILIBNAME LUA_FFILIBNAME +#else +/* non-LuaJIT */ +# define FFILIBNAME "ffi" +#endif + +#ifdef LUA_BITLIBNAME +# define BITLIBNAME LUA_BITLIBNAME +#else +/* Lua <=5.1 (non-LuaJIT) */ +# define BITLIBNAME "bit" +#endif + +static void +init_luajit(const char *scriptfile) +{ + if (L) + /* already initialized? */ + error_msg_and_help("multiple -l arguments"); + + if (!(L = luaL_newstate())) + error_msg_and_die("luaL_newstate failed (out of memory?)"); + + luaL_openlibs(L); + + lua_getglobal(L, "require"); /* L: require */ + lua_pushstring(L, FFILIBNAME); /* L: require str */ + assert_lua(lua_pcall(L, 1, 1, 0)); /* L: ffi */ + lua_getfield(L, -1, "cdef"); /* L: ffi cdef */ + luaL_Buffer b; + luaL_buffinit(L, &b); /* L: ffi cdef ? */ + { + char buf[128]; + snprintf(buf, sizeof(buf), + "typedef int%d_t kernel_long_t;" + "typedef uint%d_t kernel_ulong_t;", + (int) sizeof(kernel_long_t) * 8, + (int) sizeof(kernel_ulong_t) * 8); + luaL_addstring(&b, buf); /* L: ffi cdef ? */ + } + const char *defs = +#define FFI_CDEF +#include "sysent.h" +#include "defs_shared.h" +#undef FFI_CDEF + ; + luaL_addstring(&b, defs); /* L: ffi cdef ? */ + luaL_pushresult(&b); /* L: ffi cdef str */ + assert_lua(lua_pcall(L, 1, 0, 0)); /* L: ffi */ + + lua_newtable(L); /* L: ffi table */ + + lua_getfield(L, -2, "cast"); /* L: ffi table cast */ + lua_remove(L, -3); /* L: table cast */ + +#define EXPOSE_FUNC(rettype, ptr, name, ...) \ + do { \ + rettype (*fptr_)(__VA_ARGS__) = ptr; \ + lua_pushvalue(L, -1); /* L: table cast cast */ \ + lua_pushstring(L, #rettype " (*)(" #__VA_ARGS__ ")"); \ + /* L: table cast cast str */ \ + lua_pushlightuserdata(L, * (void **) (&fptr_)); \ + /* L: table cast cast str ptr */ \ + assert_lua(lua_pcall(L, 2, 1, 0)); /* L: table cast value */ \ + lua_setfield(L, -3, name); /* L: table cast */ \ + } while (0) + + EXPOSE_FUNC(struct tcb *, func_next_sc, "next_sc", + void); + EXPOSE_FUNC(bool, func_monitor, "monitor", + unsigned int, unsigned int, bool, bool); + EXPOSE_FUNC(bool, func_inject_signo, "inject_signo", + struct tcb *, int); + EXPOSE_FUNC(bool, func_inject_retval, "inject_retval", + struct tcb *, int); + EXPOSE_FUNC(int, umoven, "umoven", + struct tcb *, kernel_ulong_t, unsigned int, void *); + EXPOSE_FUNC(int, umovestr, "umovestr", + struct tcb *, kernel_ulong_t, unsigned int, char *); + +#undef EXPOSE_FUNC + +#define EXPOSE(type, ptr, name) \ + do { \ + /* Get a compilation error/warning on type mismatch */ \ + type tmp_ = ptr; \ + (void) tmp_; \ + lua_pushvalue(L, -1); /* L: table cast cast */ \ + lua_pushstring(L, #type); /* L: table cast cast str */ \ + lua_pushlightuserdata(L, (void *) ptr); /* L: table cast cast str ptr */ \ + assert_lua(lua_pcall(L, 2, 1, 0)); /* L: table cast value */ \ + lua_setfield(L, -3, name); /* L: table cast */ \ + } while (0) + + EXPOSE(const struct_sysent *const *, sysent_vec, "sysent_vec"); + EXPOSE(const char *const **, errnoent_vec, "errnoent_vec"); + EXPOSE(const char *const **, signalent_vec, "signalent_vec"); + EXPOSE(const struct_ioctlent *const *, ioctlent_vec, "ioctlent_vec"); + + EXPOSE(const unsigned int *, nsyscall_vec, /*(!)*/ "nsysent_vec"); + EXPOSE(const unsigned int *, nerrnoent_vec, "nerrnoent_vec"); + EXPOSE(const unsigned int *, nsignalent_vec, "nsignalent_vec"); + EXPOSE(const unsigned int *, nioctlent_vec, "nioctlent_vec"); + + EXPOSE(const struct syscall_class *, syscall_classes, "syscall_classes"); + +#undef EXPOSE + + lua_pop(L, 1); /* L: table */ + + lua_pushinteger(L, SUPPORTED_PERSONALITIES); /* L: table int */ + lua_setfield(L, -2, "npersonalities"); /* L: table */ + + lua_pushinteger(L, MAX_ARGS); /* L: table int */ + lua_setfield(L, -2, "max_args"); /* L: table */ + + lua_pushinteger(L, PATH_MAX); /* L: table int */ + lua_setfield(L, -2, "path_max"); /* L: table */ + + lua_setglobal(L, "strace"); /* L: - */ + + const char *code = +#include "luajit_lib.h" + ; + assert_lua(luaL_loadstring(L, code)); /* L: chunk */ + + lua_newtable(L); /* L: chunk table */ + + lua_pushstring(L, FFILIBNAME); /* L: chunk table str */ + lua_setfield(L, -2, "ffilibname"); /* L: chunk table */ + lua_pushstring(L, BITLIBNAME); /* L: chunk table str */ + lua_setfield(L, -2, "bitlibname"); /* L: chunk table */ + lua_pushinteger(L, TCB_INSYSCALL); /* L: chunk table int */ + lua_setfield(L, -2, "tcb_insyscall"); /* L: chunk table */ + lua_pushinteger(L, QUAL_TRACE); /* L: chunk table int */ + lua_setfield(L, -2, "qual_trace"); /* L: chunk table */ + lua_pushinteger(L, QUAL_ABBREV); /* L: chunk table int */ + lua_setfield(L, -2, "qual_abbrev"); /* L: chunk table */ + lua_pushinteger(L, QUAL_VERBOSE); /* L: chunk table int */ + lua_setfield(L, -2, "qual_verbose"); /* L: chunk table */ + lua_pushinteger(L, QUAL_RAW); /* L: chunk table int */ + lua_setfield(L, -2, "qual_raw"); /* L: chunk table */ + + assert_lua(lua_pcall(L, 1, 1, 0)); /* L: func */ + + assert_lua(luaL_loadfile(L, scriptfile)); /* L: func chunk */ +} + +static void ATTRIBUTE_NORETURN +run_luajit(void) +{ + /* L: func chunk */ + check_lua(lua_pcall(L, 0, 0, 0)); /* L: func */ + check_lua(lua_pcall(L, 0, 0, 0)); /* L: - */ + terminate(); +} + +#undef FFILIBNAME +#undef BITLIBNAME +#undef assert_lua +#undef L diff --git a/luajit_lib.lua b/luajit_lib.lua new file mode 100644 index 00000000..7bf5218d --- /dev/null +++ b/luajit_lib.lua @@ -0,0 +1,316 @@ +-- This "chunk" of code is loaded and run before the script is. +-- +-- To quote https://www.lua.org/manual/5.1/manual.html#2.4.1, +-- "Lua handles a chunk as the body of an anonymous function with a variable +-- number of arguments (see ยง2.5.9). As such, chunks can define local +-- variables, receive arguments, and return values." +-- +-- Thanks to Lua's support for closures, all the local variables defined here +-- will not leak to another chunks (i.e., the script), but all the functions +-- defined here can still access them. +-- +-- strace calls this chunk with a single argument: a table with data that should +-- not be exposed to the script, but is needed for some API functions defined +-- here. +-- +-- strace expects this chunk to return another function that will be run after +-- the script returns. +-- +-- Arguments passed to this chunk are accessible through the "..." vararg +-- expression. The following line uses Lua's "adjust" assignment semantics to +-- assign the first argument to a local variable "priv". +local priv = ... + +local ffi = require(priv.ffilibname) +ffi.cdef[[ +int strcmp(const char *, const char *); +]] +local bit = require(priv.bitlibname) + +local entry_cbs, exit_cbs, at_exit_cb = {}, {}, nil +for p = 0, strace.npersonalities - 1 do + entry_cbs[p] = {} + exit_cbs[p] = {} +end + +local function chain(f, g) + if not f then + return g + end + return function(...) + f(...) + g(...) + end +end + +function strace.entering(tcp) + return bit.band(tcp.flags, priv.tcb_insyscall) == 0 +end + +function strace.exiting(tcp) + return bit.band(tcp.flags, priv.tcb_insyscall) ~= 0 +end + +local function alter_trace_opt(flagbit, tcp, ...) + if strace.exiting(tcp) then + error('altering tracing options must be done on syscall entry') + end + -- i.e., if ... is empty, or the first element of ... is true + if select('#', ...) == 0 or select(1, ...) then + tcp.qual_flg = bit.bor(tcp.qual_flg, flagbit) + else + tcp.qual_flg = bit.band(tcp.qual_flg, bit.bnot(flagbit)) + end +end +function strace.trace (tcp, ...) alter_trace_opt(priv.qual_trace, tcp, ...) end +function strace.abbrev (tcp, ...) alter_trace_opt(priv.qual_abbrev, tcp, ...) end +function strace.verbose(tcp, ...) alter_trace_opt(priv.qual_verbose, tcp, ...) end +function strace.raw (tcp, ...) alter_trace_opt(priv.qual_raw, tcp, ...) end + +function strace.ptr_to_kulong(ptr) + return ffi.cast('kernel_ulong_t', ffi.cast('unsigned long', ptr)) +end + +function strace.at_exit(f) + at_exit_cb = chain(at_exit_cb, f) +end + +function strace.get_err_name(err, pers) + pers = pers or 0 + if err < 0 or err > strace.nerrnoent_vec[pers] then + return nil + end + local s = strace.errnoent_vec[pers][err] + return s ~= nil and ffi.string(s) or nil +end + +function strace.get_sc_name(scno, pers) + pers = pers or 0 + if scno < 0 or scno >= strace.nsysent_vec[pers] then + return nil + end + local s = strace.sysent_vec[pers][scno].sys_name + return s ~= nil and ffi.string(s) or nil +end + +function strace.get_ioctl_name(code, pers) + pers = pers or 0 + -- we could have provided a definition for stdlib's bsearch() and used + -- it, but LuaJIT's FFI manual says generated callbacks are a limited + -- resource and also slow. So implement binary search ourselves. + local lb, rb = ffi.cast('unsigned int', 0), strace.nioctlent_vec[pers] + if rb == 0 then + return nil + end + local arr = strace.ioctlent_vec[pers] + while rb - lb > 1 do + local mid = lb + (rb - lb) / 2 + if arr[mid].code <= code then + lb = mid + else + rb = mid + end + end + return arr[lb].code == code and ffi.string(arr[lb].symbol) or nil +end + +function strace.get_scno(scname, pers) + pers = pers or 0 + local cstr = ffi.cast('const char *', scname) + for i = 0, tonumber(strace.nsysent_vec[pers]) - 1 do + local s = strace.sysent_vec[pers][i].sys_name + if s ~= nil and ffi.C.strcmp(s, cstr) == 0 then + return i + end + end + return nil +end + +function strace.get_signo(signame, pers) + pers = pers or 0 + local cstr = fii.cast('const char *', signame) + for i = 0, tonumber(strace.nsignalent_vec[pers]) - 1 do + local s = strace.signalent_vec[pers][i] + if s ~= nil and ffi.C.strcmp(s, cstr) == 0 then + return i + end + end + return nil +end + +function strace.get_errno(errname, pers) + pers = pers or 0 + local cstr = fii.cast('const char *', errname) + for i = 0, tonumber(strace.nerrnoent_vec[pers]) - 1 do + local s = strace.errnoent_vec[pers][i] + if s ~= nil and ffi.C.strcmp(s, cstr) == 0 then + return i + end + end + return nil +end + +function strace.inject_signal(tcp, sig) + if type(sig) == 'string' then + sig = strace.get_signo(sig, tcp.currpers) + if not sig then + error('signal not found') + end + end + if not strace.inject_signo(tcp, sig) then + error('cannot inject signal') + end +end + +function strace.inject_error(tcp, err) + if type(err) == 'string' then + err = strace.get_errno(err, tcp.currpers) + if not err then + error('error not found') + end + end + if err <= 0 then + error('err must be positive') + end + if not strace.inject_retval(tcp, -err) then + error('cannot inject error') + end +end + +function strace.read_obj(tcp, addr, ct, ...) + local obj = ffi.new(ct, ...) + return strace.umoven(tcp, addr, ffi.sizeof(obj), obj) == 0 and obj or nil +end + +function strace.read_str(tcp, addr, maxsz, bufsz) + maxsz = maxsz or 4 * 1024 * 1024 + bufsz = bufsz or 1024 + local t = {} + local buf = ffi.new('char[?]', bufsz) + while true do + local r = strace.umovestr(tcp, addr, bufsz, buf) + if r < 0 then + return nil, 'readerr' + elseif r == 0 then + maxsz = maxsz - bufsz + if maxsz < 0 then + return nil, 'toolong' + end + t[#t + 1] = ffi.string(buf, bufsz) + addr = addr + bufsz + else + local s = ffi.string(buf) + if #s > maxsz then + return nil, 'toolong' + end + return table.concat(t) .. s + end + end +end + +function strace.read_path(tcp, addr) + return strace.read_str(tcp, addr, strace.path_max, strace.path_max) +end + +local function register_hook(scno, pers, on_entry, on_exit, cb) + assert(not not strace.monitor(scno, pers, on_entry, on_exit)) + pers = tonumber(pers) + scno = tonumber(scno) + if on_entry then + entry_cbs[pers][scno] = chain(entry_cbs[pers][scno], cb) + end + if on_exit then + exit_cbs[pers][scno] = chain(exit_cbs[pers][scno], cb) + end +end + +local function parse_when(when) + if when == 'entering' then + return true, false + elseif when == 'exiting' then + return false, true + elseif when == 'both' then + return true, true + else + error('unknown "when" value') + end +end + +function strace.monitor_all() + for p = 0, strace.npersonalities - 1 do + for i = 0, tonumber(strace.nsysent_vec[p]) - 1 do + strace.monitor(i, p, true, true) + end + end +end + +function strace.hook(scname, when, cb) + local on_entry, on_exit = parse_when(when) + local found = false + for p = 0, strace.npersonalities - 1 do + local scno = strace.get_scno(scname, p) + if scno then + register_hook(scno, p, on_entry, on_exit, cb) + found = true + end + end + if not found then + error('syscall not found') + end +end + +function strace.hook_class(clsname, when, cb) + local cstr = ffi.cast('const char *', clsname) + local flag = nil + local ptr = strace.syscall_classes + while ptr.name ~= nil do + if ffi.C.strcmp(ptr.name, cstr) == 0 then + flag = ptr.value + break + end + ptr = ptr + 1 + end + if not flag then + error('syscall class not found') + end + local on_entry, on_exit = parse_when(when) + for p = 0, strace.npersonalities - 1 do + for i = 0, tonumber(strace.nsysent_vec[p]) - 1 do + if bit.band(strace.sysent_vec[p][i].sys_flags, flag) ~= 0 then + register_hook(i, p, on_entry, on_exit, cb) + end + end + end +end + +function strace.hook_scno(scno, when, cb, pers) + pers = pers or 0 + local on_entry, on_exit = parse_when(when) + reigster_hook(scno, pers, on_entry, on_exit, cb) +end + +function print(...) + local sep = '' + for i = 1, select('#', ...) do + io.stderr:write(sep .. tostring(select(i, ...))) + sep = '\t' + end + io.stderr:write('\n') +end + +return function() + while true do + local tcp = strace.next_sc() + if tcp == nil then + break + end + local cb = (strace.entering(tcp) and entry_cbs or exit_cbs) + [tonumber(tcp.currpers)][tonumber(tcp.scno)] + if cb then + cb(tcp) + end + end + if at_exit_cb then + at_exit_cb() + end +end diff --git a/qualify.c b/qualify.c index 3df4805a..15f8d40e 100644 --- a/qualify.c +++ b/qualify.c @@ -30,6 +30,31 @@ #include "nsig.h" #include <regex.h> +const struct syscall_class syscall_classes[] = { + { "desc", TRACE_DESC }, + { "file", TRACE_FILE }, + { "memory", TRACE_MEMORY }, + { "process", TRACE_PROCESS }, + { "signal", TRACE_SIGNAL }, + { "ipc", TRACE_IPC }, + { "network", TRACE_NETWORK }, + { "%desc", TRACE_DESC }, + { "%file", TRACE_FILE }, + { "%memory", TRACE_MEMORY }, + { "%process", TRACE_PROCESS }, + { "%signal", TRACE_SIGNAL }, + { "%ipc", TRACE_IPC }, + { "%network", TRACE_NETWORK }, + { "%stat", TRACE_STAT }, + { "%lstat", TRACE_LSTAT }, + { "%fstat", TRACE_FSTAT }, + { "%%stat", TRACE_STAT_LIKE }, + { "%statfs", TRACE_STATFS }, + { "%fstatfs", TRACE_FSTATFS }, + { "%%statfs", TRACE_STATFS_LIKE }, + {} +}; + typedef unsigned int number_slot_t; #define BITS_PER_SLOT (sizeof(number_slot_t) * 8) @@ -48,6 +73,10 @@ static struct number_set inject_set[SUPPORTED_PERSONALITIES]; static struct number_set raw_set[SUPPORTED_PERSONALITIES]; static struct number_set trace_set[SUPPORTED_PERSONALITIES]; static struct number_set verbose_set[SUPPORTED_PERSONALITIES]; +#ifdef USE_LUAJIT +static struct number_set hook_entry_set[SUPPORTED_PERSONALITIES]; +static struct number_set hook_exit_set[SUPPORTED_PERSONALITIES]; +#endif static void number_setbit(const unsigned int i, number_slot_t *const vec) @@ -245,37 +274,10 @@ qualify_syscall_regex(const char *s, struct number_set *set) static unsigned int lookup_class(const char *s) { - static const struct { - const char *name; - unsigned int value; - } syscall_class[] = { - { "desc", TRACE_DESC }, - { "file", TRACE_FILE }, - { "memory", TRACE_MEMORY }, - { "process", TRACE_PROCESS }, - { "signal", TRACE_SIGNAL }, - { "ipc", TRACE_IPC }, - { "network", TRACE_NETWORK }, - { "%desc", TRACE_DESC }, - { "%file", TRACE_FILE }, - { "%memory", TRACE_MEMORY }, - { "%process", TRACE_PROCESS }, - { "%signal", TRACE_SIGNAL }, - { "%ipc", TRACE_IPC }, - { "%network", TRACE_NETWORK }, - { "%stat", TRACE_STAT }, - { "%lstat", TRACE_LSTAT }, - { "%fstat", TRACE_FSTAT }, - { "%%stat", TRACE_STAT_LIKE }, - { "%statfs", TRACE_STATFS }, - { "%fstatfs", TRACE_FSTATFS }, - { "%%statfs", TRACE_STATFS_LIKE }, - }; - - unsigned int i; - for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) { - if (strcmp(s, syscall_class[i].name) == 0) { - return syscall_class[i].value; + const struct syscall_class *c; + for (c = syscall_classes; c->name; ++c) { + if (strcmp(s, c->name) == 0) { + return c->value; } } @@ -693,5 +695,23 @@ qual_flags(const unsigned int scno) | (is_number_in_set(scno, &raw_set[current_personality]) ? QUAL_RAW : 0) | (is_number_in_set(scno, &inject_set[current_personality]) - ? QUAL_INJECT : 0); + ? QUAL_INJECT : 0) +#ifdef USE_LUAJIT + | (is_number_in_set(scno, &hook_entry_set[current_personality]) + ? QUAL_HOOK_ENTRY : 0) + | (is_number_in_set(scno, &hook_exit_set[current_personality]) + ? QUAL_HOOK_EXIT : 0) +#endif + ; +} + +#ifdef USE_LUAJIT +void +set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook, bool exit_hook) +{ + if (entry_hook) + add_number_to_set(scno, &hook_entry_set[pers]); + if (exit_hook) + add_number_to_set(scno, &hook_exit_set[pers]); } +#endif diff --git a/strace.1 b/strace.1 index 869da08c..07dd3662 100644 --- a/strace.1 +++ b/strace.1 @@ -751,6 +751,13 @@ Unless this option is used setuid and setgid programs are executed without effective privileges. .SS Miscellaneous .TP 12 +.BI "\-l " filename +Load and run LuaJIT script from +.I filename +(experimental). This option is available only if +.B strace +is built with LuaJIT scripting support. +.TP .B \-d Show some debugging output of .B strace @@ -766,6 +773,289 @@ Print the help summary. .B \-V Print the version number of .BR strace . +.SH LUAJIT SCRIPTING +If built with LuaJIT support, \fBstrace\fR can execute LuaJIT scripts. +A script file is passed to the \fB\-l\fR option. +.PP +\fBstrace\fR provides the built-in module \fBstrace\fR, which contains various +functions and constants. +.PP +Before any tracing takes place, the script is run. At this stage, it can either: +.IP \[bu] 2 +implement its own tracing loop by selecting syscalls it wants to be notified +about with \fBstrace.monitor\fR/\fBstrace.monitor_all\fR and calling +\fBstrace.next_sc\fR in a loop until it returns \fBNULL\fR (or return earlier; +in this case, the installed hooks for the remaining syscalls are run). +Note that \fBstrace\fR performs tracing/tampering of a syscall on the next +\fBstrace.next_sc\fR call; +.IP \[bu] +install syscall and at-exit hooks with \fBstrace.hook\fR, +\fBstrace.hook_class\fR, \fBstrace.hook_scno\fR and \fBstrace.at_exit\fR. +.PP +Then, \fBstrace\fR enters its own tracing loop, and all the installed hooks are +run. +.SS Example +The following script counts the number of processes (including threads) spawned +by the tracee. +Note that you would probably want to launch \fBstrace\fR with \fB\-f\fR option, +so that children also be traced. +.CW +n = 0 +function incr_if_ok(tcp) + if tcp.u_rval ~= -1 then + n = n + 1 + end +end +strace.hook('clone', 'exiting', incr_if_ok) +strace.hook('fork', 'exiting', incr_if_ok) +strace.hook('vfork', 'exiting', incr_if_ok) +strace.at_exit(function() print('Processes spawned:', n) end) +.CE +.SS FFI definitions +.CW +typedef /*unspecified*/ kernel_long_t; +typedef /*unspecified*/ kernel_ulong_t; + +typedef struct sysent { + unsigned nargs; /* Number of arguments */ + int sys_flags; /* Flags. Currently, only meaningful in the + * context of struct syscall_class::value field: + * a syscall belongs to a class iff + * (class.value & syscall.sys_flags) != 0. */ + const char *sys_name; /* Name */ +} struct_sysent; + +struct syscall_class { + const char *name; /* Name */ + unsigned int value; /* Flag bit, see the comment on struct + * sysent::sys_flags field. */ +}; + +typedef struct ioctlent { + const char *symbol; + unsigned int code; +} struct_ioctlent; + +/* Trace control block */ +struct tcb { + int pid; /* Tracee's PID */ + unsigned long u_error; /* Error code */ + kernel_ulong_t scno; /* System call number */ + kernel_ulong_t u_arg[/*MAX_ARGS*/]; /* System call arguments */ + kernel_ulong_t u_rval; /* Return value */ + unsigned int currpers; /* Current personality */ +}; +.CE +.SS strace module: C function pointers +Note: be careful with boxed boolean values and use \fBnot not \fIboxed_bool\fR +when in doubt. +In particular, an \fBassert\fR on a boxed boolean will never raise an error. +.TP +\fIis_ok\fR = \fBstrace.monitor\fR(\fIscno\fR, \fIpers\fR, \fIon_entry\fR, \fIon_exit\fR) +Marks the syscall with number \fIscno\fR on personality \fIpers\fR as to be +returned from \fBstrace.next_sc\fR. +If \fIon_entry\fR is \fBtrue\fR, it is marked as to be returned on syscall +entry, and if \fIon_exit\fR is \fBtrue\fR, it is marked as to be returned on +syscall exit. +.IP +Note that this "marking" is a one-way process, and specifying \fBfalse\fR as any +of the flags does not undo any previous calls to \fBstrace.monitor\fR. +.TP +\fItcp\fR = \fBstrace.next_sc\fR() +If this is not the first call to \fBstrace.next_sc\fR, performs tracing and +tampering of the previous syscall. +.IP +Waits for the next monitored syscall to happen, and returns a pointer to its +tracing control block. +.IP +If \fBstrace\fR needs to be terminated (e.g. last tracee has terminated, or +\fBstrace\fR has been interrupted), returns \fBNULL\fR. Once it returned +\fBNULL\fR, all subsequent calls to it will also return \fBNULL\fR. +.TP +\fIis_ok\fR = \fBstrace.inject_signo\fR(\fItcp\fR, \fIsigno\fR) +Deliver a signal with number \fIsigno\fR to the tracee. +.IP +Note that this must be done on syscall entry. +.TP +\fIis_ok\fR = \fBstrace.inject_retval\fR(\fItcp\fR, \fIval\fR) +Injects a return value to the current syscall invocation. +.IP +Note that this must be done on syscall entry. +.TP +\fIret\fR = \fBstrace.umoven\fR(\fItcp\fR, \fIaddr\fR, \fIlen\fR, \fIladdr\fR) +Copies ("moves") \fIlen\fR bytes of data from the tracee process at address +\fIaddr\fR to a local address \fIladdr\fR. +Returns 0 on success and \-1 on failure. +.TP +\fIret\fR = \fBstrace.umovestr\fR(\fItcp\fR, \fIaddr\fR, \fIlen\fR, \fIladdr\fR) +Like \fBstrace.umoven\fR, but makes the additional effort of looking for a +terminating zero byte. +Returns a negative value on failure, a positive value if a NUL was seen, and 0 +if \fIlen\fR byes were read but no NUL seen. +.IP +Note: there is no guarantee it won't overwrite some bytes in \fIladdr\fR after +terminating NUL (but, of course, it never writes past \fIladdr[len-1]\fR). +.SS strace module: proper functions +Note: \fIpers\fR always defaults to 0. +.TP +\fIis_entering\fR = \fBstrace.entering\fR(\fItcp\fR) +Returns \fBtrue\fR if this is a syscall entry, and \fBfalse\fR otherwise. +.TP +\fIis_exiting\fR = \fBstrace.exiting\fR(\fItcp\fR) +Returns \fBtrue\fR if this is a syscall exit, and \fBfalse\fR otherwise. +.TP +\fBstrace.trace\fR(\fItcp\fR[, \fIflag\fR]) +.TP +\fBstrace.abbrev\fR(\fItcp\fR[, \fIflag\fR]) +.TP +\fBstrace.verbose\fR(\fItcp\fR[, \fIflag\fR]) +.TP +\fBstrace.raw\fR(\fItcp\fR[, \fIflag\fR]) +These functions alter corresponding trace options. +\fIflag\fR defaults to true. +.TP +\fIaddr\fR = \fBstrace.ptr_to_kulong\fR(\fIptr\fR) +Converts a pointer to a \fBkernel_ulong_t\fR. +.TP +\fBstrace.at_exit\fR(\fIfunc\fR) +Registers a function \fIfunc\fR to be run when \fBstrace\fR needs to +be terminated. +.TP +\fIname\fR = \fBstrace.get_err_name\fR(\fIerr\fR[, \fIpers\fR]) +Returns error name (e.g. \fB"ENOENT"\fR) as Lua string by its errno number +\fIerr\fR for personality \fIpers\fR, or \fBnil\fR if \fIerr\fR is invalid. +.TP +\fIname\fR = \fBstrace.get_sc_name\fR(\fIscno\fR[, \fIpers\fR]) +Returns syscall name as Lua string by its number for personality \fIpers\fR, or +\fBnil\fR if \fIscno\fR is invalid. +.TP +\fIname\fR = \fBstrace.get_ioctl_name\fR(\fIcode\fR[, \fIpers\fR]) +Returns ioctl symbol name (.e.g \fB"TIOCGWINSZ"\fR) as Lua string by its request +code for personality \fIpers\fR, or \fBnil\fR if \fIcode\fR is invalid. +.TP +\fIscno\fR = \fBstrace.get_scno\fR(\fIscname\fR[, \fIpers\fR]) +Returns syscall number by its name for personality \fIpers\fR, or \fBnil\fR if +no such syscall was found. +.TP +\fIsigno\fR = \fBstrace.get_signo\fR(\fIsigname\fR[, \fIpers\fR]) +Returns signal number by its name (e.g. \fB"SIGSEGV"\fR) for personality +\fIpers\fR, or \fBnil\fR if no such signal was found. +.TP +\fIerrno\fR = \fBstrace.get_errno\fR(\fIerrname\fR[, \fIpers\fR]) +Returns errno number by its name (e.g. \fB"ENOENT"\fR) for personality +\fIpers\fR, or \fBnil\fR if no such error was found. +.TP +\fBstrace.inject_signal\fI(\fItcp\fR, \fIsig\fR) +Delivers a signal to the tracee. +\fIsig\fR is either signal number or name. +.IP +Note that this must be done on syscall entry. +.IP +Raises an error on failure. +.TP +\fBstrace.inject_error\fR(\fItcp\fR, \fIerr\fR) +Injects an error into a current syscall invocation. +\fIerr\fR is either errno number or error name. +.IP +Note that this must be done on syscall entry. +.IP +Raises an error on failure. +.TP +\fIobj\fR = \fBstrace.read_obj\fR(\fItcp\fR, \fIaddr\fR, \fIct\fR[, \fInelem\fR]) +Reads an object of type \fIct\fR from the tracee process at address \fIaddr\fR. +\fIct\fR is either a \fIcdecl\fR (a Lua string), a \fIcdata\fR serving as a +template type, or a \fIctype\fR (special kind of \fIcdata\fR returned by +\fBffi.typeof\fR). +.IP +Due to the FFI semantics quirks, \fIct\fR must not be a pointer. +If you want to read a pointer of type \fIT\fR, pass \fIT *[1]\fR as \fIct\fR. +.IP +VLA/VLS types require the \fInelem\fR argument. +.IP +Returns an object on success and \fBnil\fR on failure. +.TP +\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_str\fR(\fItcp\fR, \fIaddr\fR[, \fImaxsz\fR[, \fIbufsz\fR]]) +Reads a C string from the tracee process at address \fIaddr\fR using an +intermediate buffer of size \fIbufsz\fR and stopping at \fImaxsz\fR bytes. +.IP +\fImaxsz\fR defaults to 4 Mb, \fIbufsz\fR to 1 Kb. +.IP +Returns a Lua string on success, \fBnil, "readerr"\fR on read error, and +\fBnil, "toolong"\fR if the \fImaxsz\fR limit was exceeded. +.TP +\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_path\fR(\fItcp\fR, \fIaddr\fR) +Reads a path C string the tracee process at address \fIaddr\fR. +.IP +Returns a Lua string on success, \fBnil, "readerr"\fR on read error, and +\fBnil, "toolong"\fR if the \fBPATH_MAX\fR limit was exceeded. +.TP +\fBstrace.monitor_all\fR() +Marks all syscalls on all personalities as to be returned from +\fBstrace.next_sc\fR both on entry and on exit. +.TP +\fBstrace.hook\fR(\fIscname\fR, \fIwhen\fR, \fIcb\fR) +.TP +\fBstrace.hook_class\fR(\fIclsname\fR, \fIwhen\fR, \fIcb\fR) +.TP +\fBstrace.hook_scno\fR(\fIscno\fR, \fIwhen\fR, \fIcb\fR[, \fIpers\fR]) +These functions register a function \fIcb\fR to be run when a syscall with the +given name, belonging to a class with the given name, or with the given number +on personality \fIpers\fR, happens. +.IP +It will be run on syscall entry if \fIwhen\fR is \fB"entering"\fR, syscall exit +if \fIwhen\fR is \fB"exiting"\fR, or both if \fIwhen\fR is \fB"both"\fR. +.IP +A pointer to the trace control block is passed as the only argument to \fIcb\fR. +.IP +Raise an error on failure. +.SS strace module: constants +.TP +.B strace.npersonalities +Number of supported personalities. +.TP +.B strace.max_args +Size of \fBstruct tcb::u_arg\fR array. +.TP +.B strace.path_max +Value of \fBPATH_MAX\fR constant. May occasionally be useful. +.TP +.B strace.sysent_vec +Array of syscall tables for each of the supported personalities. +\fBstrace.sysent_vec\fR[\fIpers\fR][\fIscno\fR] is a \fBstruct_sysent\fR for +syscall number \fIscno\fR on personality \fIpers\fR. +May contain null entries (which have a NULL \fBsys_name\fR field). +.TP +.B strace.errnoent_vec +Array of error name tables for each of the supported personalities. +\fBstrace.errnoent_vec\fR[\fIpers\fR][\fIerrno\fR] is either a null pointer or a +C string with the name of error \fIerrno\fR on personality \fIpers\fR. +.TP +.B strace.signalent_vec +Array of signal name tables for each of the supported personalities. +\fBstrace.signalent_vec\fR[\fIpers\fR][\fIsigno\fR] is either a null pointer or +a C string with the name of signal \fIsigno\fR on personality \fIpers\fR. +.TP +.B strace.ioctlent_vec +Arrays of sorted known ioctl symbols, sorted by code, for each of the supported +personalities. +\fBstrace.ioctlent_vec\fR[\fIpers\fR][\fIi\fR] is the \fIi\fR-th, ranked by +code, \fBstruct_ioctlent\fR for personality \fIpers\fR. +.TP +.B strace.nsysent_vec +.TP +.B strace.nerrnoent_vec +.TP +.B strace.nsignalent_vec +.TP +.B strace.nioctlent_vec +These are \fBstrace.npersonalities\fR-sized arrays containing sizes of subarrays +of +.BR strace.sysent_vec ", " strace.errnoent_vec ", " strace.signalent_vec ", and " strace.ioctlent_vec +correspondingly. +.TP +.B strace.syscall_classes +Array of \fBstruct syscall_class\fR, with a terminating null entry (which has a +NULL \fBname\fR field). .SH DIAGNOSTICS When .I command diff --git a/strace.c b/strace.c index 955a1c9f..1ec38112 100644 --- a/strace.c +++ b/strace.c @@ -45,6 +45,9 @@ # include <sys/prctl.h> #endif #include <asm/unistd.h> +#ifdef USE_LUAJIT +# include <lua.h> +#endif #include "scno.h" #include "ptrace.h" @@ -169,6 +172,11 @@ static volatile sig_atomic_t interrupted; static volatile int interrupted; #endif +#ifdef USE_LUAJIT +static lua_State *script_L = NULL; +static void init_luajit(const char *scriptfile); +#endif + #ifndef HAVE_STRERROR #if !HAVE_DECL_SYS_ERRLIST @@ -219,6 +227,11 @@ Output format:\n\ -k obtain stack trace between each syscall (experimental)\n\ " #endif +#ifdef USE_LUAJIT +"\ + -l file run a Lua script from FILE\n\ +" +#endif "\ -o file send trace output to FILE instead of stderr\n\ -q suppress messages about attaching, detaching, etc.\n\ @@ -772,7 +785,7 @@ alloctcb(int pid) if (!tcp->pid) { memset(tcp, 0, sizeof(*tcp)); tcp->pid = pid; -#if SUPPORTED_PERSONALITIES > 1 +#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif @@ -828,6 +841,10 @@ droptcb(struct tcb *tcp) if (tcp->pid == 0) return; +#ifdef USE_LUAJIT + free(tcp->ad_hoc_inject_opts); +#endif + int p; for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) free(tcp->inject_vec[p]); @@ -1648,6 +1665,9 @@ init(int argc, char *argv[]) #ifdef USE_LIBUNWIND "k" #endif +#ifdef USE_LUAJIT + "l:" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { @@ -1758,6 +1778,11 @@ init(int argc, char *argv[]) stack_trace_enabled = true; break; #endif +#ifdef USE_LUAJIT + case 'l': + init_luajit(optarg); + break; +#endif case 'E': if (putenv(optarg) < 0) perror_msg_and_die("putenv"); @@ -2639,6 +2664,10 @@ terminate(void) exit(exit_code); } +#ifdef USE_LUAJIT +# include "luajit.h" +#endif + int main(int argc, char *argv[]) { @@ -2646,6 +2675,11 @@ main(int argc, char *argv[]) exit_code = !nprocs; +#ifdef USE_LUAJIT + if (script_L) + run_luajit(); +#endif + int status; siginfo_t si; while (dispatch_event(next_event(&status, &si), &status, &si)) diff --git a/syscall.c b/syscall.c index 02626c73..955ed2f4 100644 --- a/syscall.c +++ b/syscall.c @@ -196,6 +196,16 @@ enum { #endif }; +const char *const *errnoent_vec[SUPPORTED_PERSONALITIES] = { + errnoent0, +#if SUPPORTED_PERSONALITIES > 1 + errnoent1, +# if SUPPORTED_PERSONALITIES > 2 + errnoent2, +# endif +#endif +}; + enum { nerrnos0 = ARRAY_SIZE(errnoent0) #if SUPPORTED_PERSONALITIES > 1 @@ -206,6 +216,16 @@ enum { #endif }; +const unsigned int nerrnoent_vec[] = { + nerrnos0, +#if SUPPORTED_PERSONALITIES > 1 + nerrnos1, +# if SUPPORTED_PERSONALITIES > 2 + nerrnos2, +# endif +#endif +}; + enum { nsignals0 = ARRAY_SIZE(signalent0) #if SUPPORTED_PERSONALITIES > 1 @@ -216,6 +236,26 @@ enum { #endif }; +const char *const *signalent_vec[SUPPORTED_PERSONALITIES] = { + signalent0, +#if SUPPORTED_PERSONALITIES > 1 + signalent1, +# if SUPPORTED_PERSONALITIES > 2 + signalent2, +# endif +#endif +}; + +const unsigned int nsignalent_vec[] = { + nsignals0, +#if SUPPORTED_PERSONALITIES > 1 + nsignals1, +# if SUPPORTED_PERSONALITIES > 2 + nsignals2, +# endif +#endif +}; + enum { nioctlents0 = ARRAY_SIZE(ioctlent0) #if SUPPORTED_PERSONALITIES > 1 @@ -226,6 +266,26 @@ enum { #endif }; +const unsigned int nioctlent_vec[] = { + nioctlents0, +#if SUPPORTED_PERSONALITIES > 1 + nioctlents1, +# if SUPPORTED_PERSONALITIES > 2 + nioctlents2, +# endif +#endif +}; + +const struct_ioctlent *const ioctlent_vec[SUPPORTED_PERSONALITIES] = { + ioctlent0, +#if SUPPORTED_PERSONALITIES > 1 + ioctlent1, +# if SUPPORTED_PERSONALITIES > 2 + ioctlent2, +# endif +#endif +}; + #if SUPPORTED_PERSONALITIES > 1 const struct_sysent *sysent = sysent0; const char *const *errnoent = errnoent0; @@ -580,8 +640,19 @@ static int arch_set_success(struct tcb *); struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES]; static struct inject_opts * -tcb_inject_opts(struct tcb *tcp) +tcb_inject_opts(struct tcb *tcp, bool copy_if_needed) { +#ifdef USE_LUAJIT + if (tcp->flags & TCB_AD_HOC_INJECT) + return tcp->ad_hoc_inject_opts; +#endif + if (copy_if_needed && !tcp->inject_vec[current_personality]) { + tcp->inject_vec[current_personality] = + xcalloc(nsyscalls, sizeof(**inject_vec)); + memcpy(tcp->inject_vec[current_personality], + inject_vec[current_personality], + nsyscalls * sizeof(**inject_vec)); + } return (scno_in_range(tcp->scno) && tcp->inject_vec[current_personality]) ? &tcp->inject_vec[current_personality][tcp->scno] : NULL; } @@ -590,15 +661,7 @@ tcb_inject_opts(struct tcb *tcp) static long tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo) { - if (!tcp->inject_vec[current_personality]) { - tcp->inject_vec[current_personality] = - xcalloc(nsyscalls, sizeof(**inject_vec)); - memcpy(tcp->inject_vec[current_personality], - inject_vec[current_personality], - nsyscalls * sizeof(**inject_vec)); - } - - struct inject_opts *opts = tcb_inject_opts(tcp); + struct inject_opts *opts = tcb_inject_opts(tcp, true); if (!opts || opts->first == 0) return 0; @@ -621,7 +684,7 @@ tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo) static long tamper_with_syscall_exiting(struct tcb *tcp) { - struct inject_opts *opts = tcb_inject_opts(tcp); + struct inject_opts *opts = tcb_inject_opts(tcp, false); if (!opts) return 0; @@ -699,6 +762,12 @@ syscall_entering_decode(struct tcb *tcp) return 1; } +static bool +syscall_ad_hoc_injected(struct tcb *tcp) +{ + return (tcp->qual_flg & QUAL_INJECT) && (tcp->flags & TCB_AD_HOC_INJECT); +} + int syscall_entering_trace(struct tcb *tcp, unsigned int *sig) { @@ -721,13 +790,13 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig) || (tracing_paths && !pathtrace_match(tcp)) ) { tcp->flags |= TCB_FILTERED; - return 0; + goto maybe_ad_hoc_tamper; } tcp->flags &= ~TCB_FILTERED; if (hide_log(tcp)) { - return 0; + goto maybe_ad_hoc_tamper; } if (tcp->qual_flg & QUAL_INJECT) @@ -750,6 +819,11 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig) ? printargs(tcp) : tcp->s_ent->sys_func(tcp); fflush(tcp->outf); return res; + +maybe_ad_hoc_tamper: + if (syscall_ad_hoc_injected(tcp)) + tamper_with_syscall_entering(tcp, sig); + return 0; } void @@ -790,21 +864,28 @@ syscall_exiting_decode(struct tcb *tcp, struct timeval *ptv) } #endif - if (filtered(tcp) || hide_log(tcp)) + if ((filtered(tcp) || hide_log(tcp)) + && !(tcp->qual_flg & QUAL_HOOK_EXIT) && !syscall_ad_hoc_injected(tcp)) return 0; get_regs(tcp->pid); #if SUPPORTED_PERSONALITIES > 1 update_personality(tcp, tcp->currpers); #endif - return get_regs_error ? -1 : get_syscall_result(tcp); + if (get_regs_error || get_syscall_result(tcp) == -1) + return -1; + + if (syserror(tcp) && syscall_tampered(tcp)) + tamper_with_syscall_exiting(tcp); + + return 1; } int syscall_exiting_trace(struct tcb *tcp, struct timeval tv, int res) { - if (syserror(tcp) && syscall_tampered(tcp)) - tamper_with_syscall_exiting(tcp); + if (filtered(tcp) || hide_log(tcp)) + return 0; if (cflag) { count_syscall(tcp, &tv); @@ -1013,7 +1094,7 @@ syscall_exiting_trace(struct tcb *tcp, struct timeval tv, int res) void syscall_exiting_finish(struct tcb *tcp) { - tcp->flags &= ~(TCB_INSYSCALL | TCB_TAMPERED); + tcp->flags &= ~(TCB_INSYSCALL | TCB_TAMPERED | TCB_AD_HOC_INJECT); tcp->sys_func_rval = 0; free_tcb_priv_data(tcp); } diff --git a/sysent.h b/sysent.h index 92de7468..15b83693 100644 --- a/sysent.h +++ b/sysent.h @@ -1,13 +1,31 @@ -#ifndef STRACE_SYSENT_H -#define STRACE_SYSENT_H +#if !defined(STRACE_SYSENT_H) || defined(FFI_CDEF) +#ifndef FFI_CDEF +# define STRACE_SYSENT_H +#endif +#include "ffi.h" + +FFI_CONTENT( typedef struct sysent { unsigned nargs; int sys_flags; +) +/* We don't want to expose sen and sys_func to LuaJIT */ +#ifdef FFI_CDEF +FFI_CONTENT( + int priv1; + void *priv2; +) +#else +FFI_CONTENT( int sen; int (*sys_func)(); +) +#endif +FFI_CONTENT( const char *sys_name; } struct_sysent; +) #define TRACE_FILE 00000001 /* Trace file-related syscalls. */ #define TRACE_IPC 00000002 /* Trace IPC-related syscalls. */ @@ -29,4 +47,4 @@ typedef struct sysent { #define TRACE_FSTAT 00400000 /* Trace *fstat{,at}{,64} syscalls. */ #define TRACE_STAT_LIKE 01000000 /* Trace *{,l,f}stat{,x,at}{,64} syscalls. */ -#endif /* !STRACE_SYSENT_H */ +#endif /* !defined(STRACE_SYSENT_H) || defined(FFI_CDEF) */ -- 2.11.0 ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot _______________________________________________ Strace-devel mailing list Strace-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/strace-devel