* .gitignore: Add luajit_lib.h. * Makefile.am: Build with LuaJIT if configured so. (luajit_lib.h): Auto-generate from luajit_lib.lua. * NEWS: New entry. * configure.ac: Add new --with-luajit configure option. * defs_shared.h (struct tcb): If built with LuaJIT support, include currpers field even if SUPPORTED_PERSONALITIES is 1. If built with LuaJIT support, add new ad_hoc_inject_data field. * filter_qualify.c: (hook_entry_set, hook_exit_set): New sets (if built with LuaJIT support). * luajit.h: New file. * luajit_lib.h: Likewise. * strace.1.in (LUA SCRIPTING): New section. * strace.c (alloctcb): update the condition of presence of currpers field. (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 (tcb_inject_data): If built with LuaJIT support and, blend the result with tcp's ad_hoc_inject_data. --- .gitignore | 1 + Makefile.am | 15 ++ NEWS | 2 + configure.ac | 36 +++ defs.h | 6 + defs_shared.h | 5 +- filter_qualify.c | 44 ++++ luajit.h | 355 ++++++++++++++++++++++++++ luajit_lib.lua | 439 ++++++++++++++++++++++++++++++++ strace.1.in | 754 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ strace.c | 32 ++- syscall.c | 13 + 12 files changed, 1700 insertions(+), 2 deletions(-) create mode 100644 luajit.h create mode 100644 luajit_lib.lua
diff --git a/.gitignore b/.gitignore index 6b226fba..ae78c075 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ /libmpers-m32.a /libmpers-mx32.a /libstrace.a +/luajit_lib.h /m32_funcs.h /m32_printer_decls.h /m32_printer_defs.h diff --git a/Makefile.am b/Makefile.am index 0ab788b8..6ca7a20f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -334,6 +334,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) \ @@ -862,6 +868,7 @@ EXTRA_DIST = \ linux/xtensa/set_scno.c \ linux/xtensa/syscallent.h \ linux/xtensa/userent.h \ + luajit_lib.lua \ mpers.awk \ mpers.sh \ mpers_test.sh \ @@ -888,6 +895,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; \ @@ -967,6 +977,11 @@ CLEANFILES = $(ioctl_redefs_h) $(ioctlent_h) $(mpers_preproc_files) \ native_printer_decls.h native_printer_defs.h printers.h sen.h sys_func.h DISTCLEANFILES = gnu/stubs-32.h gnu/stubs-x32.h +if USE_LUAJIT +BUILT_SOURCES += luajit_lib.h +CLEANFILES += luajit_lib.h +endif + include scno.am $(strace_OBJECTS): scno.h diff --git a/NEWS b/NEWS index 2daf5bc3..efab6320 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ Noteworthy changes in release ?.?? (????-??-??) * Improvements * Updated lists of SOL_* and TCP_* constants. + * Added support for LuaJIT scripting (-l FILE option); see the LUA SCRIPTING + section of the manual page for documentation. Noteworthy changes in release 4.19 (2017-09-05) =============================================== diff --git a/configure.ac b/configure.ac index cb6571a3..2c166403 100644 --- a/configure.ac +++ b/configure.ac @@ -754,6 +754,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, or use use Lua library with provided name])], + [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 f4252fa5..8707edb2 100644 --- a/defs.h +++ b/defs.h @@ -628,6 +628,12 @@ extern void print_ifindex(unsigned int); 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); +extern void set_hook_qual_all(bool entry_hook, bool exit_hook); +#endif + #define DECL_IOCTL(name) \ extern int \ name ## _ioctl(struct tcb *, unsigned int request, kernel_ulong_t arg) \ diff --git a/defs_shared.h b/defs_shared.h index 1113fc0c..3c61d70a 100644 --- a/defs_shared.h +++ b/defs_shared.h @@ -30,7 +30,7 @@ struct tcb { kernel_long_t u_rval; /* Return value */ ) -#if SUPPORTED_PERSONALITIES > 1 +#if defined(USE_LUAJIT) && SUPPORTED_PERSONALITIES > 1 FFI_CONTENT( unsigned int currpers; /* Personality at the time of scno update */ ) @@ -46,6 +46,9 @@ FFI_CONTENT( 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]; +#ifdef USE_LUAJIT + struct inject_data ad_hoc_inject_data; +#endif 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 */ diff --git a/filter_qualify.c b/filter_qualify.c index 71766fd1..286f16bb 100644 --- a/filter_qualify.c +++ b/filter_qualify.c @@ -40,6 +40,10 @@ static struct number_set *inject_set; static struct number_set *raw_set; static struct number_set *trace_set; static struct number_set *verbose_set; +#ifdef USE_LUAJIT +static struct number_set *hook_entry_set; +static struct number_set *hook_exit_set; +#endif static int sigstr_to_uint(const char *s) @@ -357,6 +361,42 @@ qualify(const char *str) opt->qualify(str); } +#ifdef USE_LUAJIT +static void +alloc_hook_sets(void) +{ + if (!hook_entry_set) + hook_entry_set = alloc_number_set_array( + SUPPORTED_PERSONALITIES); + if (!hook_exit_set) + hook_exit_set = alloc_number_set_array( + SUPPORTED_PERSONALITIES); +} + +void +set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook, + bool exit_hook) +{ + alloc_hook_sets(); + if (entry_hook) + extend_set_array_with_number(scno, hook_entry_set, pers); + if (exit_hook) + extend_set_array_with_number(scno, hook_exit_set, pers); +} + +void +set_hook_qual_all(bool entry_hook, bool exit_hook) +{ + alloc_hook_sets(); + if (entry_hook) + make_number_set_array_universal(hook_entry_set, + SUPPORTED_PERSONALITIES); + if (exit_hook) + make_number_set_array_universal(hook_exit_set, + SUPPORTED_PERSONALITIES); +} +#endif + unsigned int qual_flags(const unsigned int scno) { @@ -368,6 +408,10 @@ qual_flags(const unsigned int scno) | QUALBIT(verbose_set, QUAL_VERBOSE) | QUALBIT(raw_set, QUAL_RAW) | QUALBIT(inject_set, QUAL_INJECT) +#ifdef USE_LUAJIT + | QUALBIT(hook_entry_set, QUAL_HOOK_ENTRY) + | QUALBIT(hook_exit_set, QUAL_HOOK_EXIT) +#endif ; #undef QUALBIT } diff --git a/luajit.h b/luajit.h new file mode 100644 index 00000000..a707446f --- /dev/null +++ b/luajit.h @@ -0,0 +1,355 @@ +/* + * 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 bool first = true; + if (!first) { + if (!current_tcp) + return NULL; + if (!dispatch_event(TE_SYSCALL_STOP_HOOK_EXIT, NULL, NULL, + true)) + goto term; + } + first = false; + + while (1) { + int status; + siginfo_t si; + enum trace_event ret = next_event(&status, &si); + if (!dispatch_event(ret, &status, &si, true)) + goto term; + if (ret == TE_SYSCALL_STOP && (current_tcp->flags & TCB_HOOK)) { + current_tcp->flags &= ~TCB_HOOK; + return current_tcp; + } + } + +term: + current_tcp = NULL; + return NULL; +} + +static bool +func_monitor(unsigned int scno, unsigned int pers, bool entry_hook, + bool exit_hook) +{ + if (pers >= SUPPORTED_PERSONALITIES) + return false; + set_hook_qual(scno, pers, entry_hook, exit_hook); + return true; +} + +static void +prepare_ad_hoc_inject(void) +{ + if (!(current_tcp->flags & TCB_AD_HOC_INJECT)) { + current_tcp->ad_hoc_inject_data.flags = 0; + current_tcp->qual_flg |= QUAL_INJECT; + current_tcp->flags |= TCB_AD_HOC_INJECT; + } +} + +static bool +func_inject_signo(int signo) +{ + if (!current_tcp || exiting(current_tcp)) + return false; + if (signo <= 0 || signo > SIGRTMAX) + return false; + prepare_ad_hoc_inject(); + current_tcp->ad_hoc_inject_data.flags |= INJECT_F_SIGNAL; + current_tcp->ad_hoc_inject_data.signo = signo; + return true; +} + +static bool +func_inject_retval(int retval) +{ + if (!current_tcp || exiting(current_tcp)) + return false; + if (retval < -MAX_ERRNO_VALUE) + return false; + prepare_ad_hoc_inject(); + current_tcp->ad_hoc_inject_data.flags |= INJECT_F_RETVAL; + current_tcp->ad_hoc_inject_data.rval = retval; + return true; +} + +static int +func_umove(kernel_ulong_t addr, size_t len, void *laddr) +{ + return current_tcp ? umoven(current_tcp, addr, len, laddr) : -1; +} + +static int +func_umove_str(kernel_ulong_t addr, size_t len, char *laddr) +{ + return current_tcp ? umovestr(current_tcp, addr, len, laddr) : -1; +} + +static bool +func_path_match(const char **set, size_t nset) +{ + if (!current_tcp) + return false; + struct path_set s = {set, nset}; + return pathtrace_match_set(current_tcp, &s); +} + +static const char * +get_lua_msg(int pos) +{ + const char *msg = lua_tostring(L, pos); + return msg ? msg : "(error object cannot 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(-1)); +} + +#define assert_lua(expr) assert_lua_impl(expr, #expr, __FILE__, __LINE__) + +static void +check_lua(int ret) +{ + switch (ret) { + case 0: + break; + case LUA_ERRERR: + error_msg_and_die("lua: error while running error handler: %s", + get_lua_msg(-1)); + break; + default: + error_msg_and_die("lua: %s", get_lua_msg(-1)); + } +} + +#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 + +#ifdef LUA_DBLIBNAME +# define DBLIBNAME LUA_DBLIBNAME +#else +/* ? */ +# define DBLIBNAME "debug" +#endif + +static int +traceback_and_die(lua_State *arg_L) +{ + /* L: error */ + const char *msg = get_lua_msg(1); + lua_getglobal(L, DBLIBNAME); /* L: error debug */ + lua_getfield(L, -1, "traceback"); /* L: error debug traceback */ + lua_pushstring(L, msg); /* L: error debug traceback msg */ + lua_pushinteger(L, 2); /* L: error debug traceback msg level */ + lua_call(L, 2, 1); /* L: error debug result */ + const char *traceback = lua_tostring(L, -1); + error_msg_and_die("lua: %s", traceback ? traceback : msg); +} + +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_getfield(L, 1, "cast"); /* L: ffi cast */ + lua_remove(L, 1); /* L: cast */ + + /* + * Assigns a FFI cdata function pointer of type rettype (*)(__VA_ARGS__) + * with value ptr, to the table on top of L's stack, under given name. + * Assumes the ffi.cast function is at the bottom of the stack. + */ +#define EXPOSE_FUNC(rettype, ptr, name, ...) \ + do { \ + rettype (*fptr_)(__VA_ARGS__) = ptr; \ + /* L: cast ? table */ \ + lua_pushvalue(L, 1); /* L: cast ? table cast */ \ + lua_pushstring(L, #rettype " (*)(" #__VA_ARGS__ ")"); \ + /* L: cast ? table cast str */ \ + lua_pushlightuserdata(L, * (void **) (&fptr_)); \ + /* L: cast ? table cast str ptr */ \ + assert_lua(lua_pcall(L, 2, 1, 0)); \ + /* L: cast ? table value */ \ + lua_setfield(L, -2, name); /* L: cast ? table */ \ + } while (0) + + /* + * Assigns a FFI cdata pointer of given type, to the table on top of L's + * stack, under given name. + * Assumes the ffi.cast function is at the bottom of the stack. + */ +#define EXPOSE(type, ptr, name) \ + do { \ + /* Get a compilation error/warning on type mismatch */ \ + type tmp_ = ptr; \ + (void) tmp_; \ + /* L: cast ? table */ \ + lua_pushvalue(L, 1); /* L: cast ? table cast */ \ + lua_pushstring(L, #type); \ + /* L: cast ? table cast str */ \ + lua_pushlightuserdata(L, (void *) ptr); \ + /* L: cast ? table cast str ptr */ \ + assert_lua(lua_pcall(L, 2, 1, 0)); \ + /* L: cast ? table cast value */ \ + lua_setfield(L, -2, name); /* L: cast ? table */ \ + } while (0) + + lua_newtable(L); /* L: cast strace */ + lua_newtable(L); /* L: cast strace C */ + + EXPOSE_FUNC(bool, func_monitor, "monitor", + unsigned int, unsigned int, bool, bool); + EXPOSE_FUNC(void, set_hook_qual_all, "monitor_all", + bool, bool); + EXPOSE_FUNC(bool, func_inject_signo, "inject_signo", + int); + EXPOSE_FUNC(bool, func_inject_retval, "inject_retval", + int); + EXPOSE_FUNC(int, func_umove, "umove", + kernel_ulong_t, size_t, void *); + EXPOSE_FUNC(int, func_umove_str, "umove_str", + kernel_ulong_t, size_t, char *); + EXPOSE_FUNC(bool, func_path_match, "path_match", + const char **, size_t); + + 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"); + +#if SUPPORTED_PERSONALITIES == 1 + static const char *const personality_names[] = {#__WORDSIZE " bit"}; +#endif + EXPOSE(const char *const *, personality_names, "pers_name"); + EXPOSE(const int *, personality_wordsize, "pers_wordsize"); + EXPOSE(const int *, personality_klongsize, "pers_klongsize"); + + lua_setfield(L, -2, "C"); /* L: cast strace */ + + lua_pushinteger(L, SUPPORTED_PERSONALITIES); /* L: cast strace int */ + lua_setfield(L, -2, "npersonalities"); /* L: cast strace */ + + lua_pushinteger(L, MAX_ARGS); /* L: cast strace int */ + lua_setfield(L, -2, "max_args"); /* L: cast strace */ + + lua_pushinteger(L, PATH_MAX); /* L: cast strace int */ + lua_setfield(L, -2, "path_max"); /* L: cast strace */ + + lua_setglobal(L, "strace"); /* L: cast */ + + const char *code = +#include "luajit_lib.h" + ; + assert_lua(luaL_loadstring(L, code)); /* L: cast chunk */ + + lua_newtable(L); /* L: cast chunk table */ + + lua_pushstring(L, FFILIBNAME); /* L: cast chunk table str */ + lua_setfield(L, -2, "ffilibname"); /* L: cast chunk table */ + lua_pushstring(L, BITLIBNAME); /* L: cast chunk table str */ + lua_setfield(L, -2, "bitlibname"); /* L: cast chunk table */ + lua_pushinteger(L, TCB_INSYSCALL); /* L: cast chunk table int */ + lua_setfield(L, -2, "tcb_insyscall"); /* L: cast chunk table */ + lua_pushinteger(L, QUAL_TRACE); /* L: cast chunk table int */ + lua_setfield(L, -2, "qual_trace"); /* L: cast chunk table */ + lua_pushinteger(L, QUAL_ABBREV); /* L: cast chunk table int */ + lua_setfield(L, -2, "qual_abbrev"); /* L: cast chunk table */ + lua_pushinteger(L, QUAL_VERBOSE); /* L: cast chunk table int */ + lua_setfield(L, -2, "qual_verbose"); /* L: cast chunk table */ + lua_pushinteger(L, QUAL_RAW); /* L: cast chunk table int */ + lua_setfield(L, -2, "qual_raw"); /* L: cast chunk table */ + + EXPOSE_FUNC(struct tcb *, func_next_sc, "next_sc", + void); /* L: cast chunk table */ + + lua_pushcfunction(L, traceback_and_die); + /* L: cast chunk table traceback_and_die */ + lua_replace(L, 1); /* L: traceback_and_die chunk table */ + + assert_lua(lua_pcall(L, 1, 1, 1)); /* L: traceback_and_die func */ + + check_lua(luaL_loadfile(L, scriptfile)); + /* L: traceback_and_die func chunk */ +#undef EXPOSE_FUNC +#undef EXPOSE +} + +static void ATTRIBUTE_NORETURN +run_luajit(void) +{ + /* L: traceback_and_die func chunk */ + check_lua(lua_pcall(L, 0, 0, 1)); /* L: traceback_and_die func */ + check_lua(lua_pcall(L, 0, 0, 1)); /* L: traceback_and_die */ + 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..188e818b --- /dev/null +++ b/luajit_lib.lua @@ -0,0 +1,439 @@ +-- 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 at_exit_cb +local entry_cbs, exit_cbs = {}, {} +for p = 0, strace.npersonalities - 1 do + entry_cbs[p] = {} + exit_cbs[p] = {} +end + +local tcp + +local nullptr = ffi.NULL +pcall(function() + nullptr = ffi.C.NULL +end) + +local function chain(f, g) + if f == nil then + return g + end + return function(...) + f(...) + g(...) + end +end + +local function register_hook(scno, pers, on_entry, on_exit, cb) + assert(not not strace.C.monitor(scno, pers, on_entry, on_exit), + 'unexpected strace.C.monitor failure') + scno, pers = tonumber(scno), tonumber(pers) + 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 priv_next_sc = priv.next_sc + +function strace.next_sc() + local ptr = priv_next_sc() + tcp = ptr ~= nullptr and ptr or nil + return tcp +end + +function strace.entering() + return bit.band(tcp.flags, priv.tcb_insyscall) == 0 +end + +function strace.exiting() + return bit.band(tcp.flags, priv.tcb_insyscall) ~= 0 +end + +local function alter_trace_opt(flagbit, ...) + assert(strace.entering(), + 'altering tracing options must be done on syscall entry') + -- 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(...) alter_trace_opt(priv.qual_trace, ...) end +function strace.abbrev(...) alter_trace_opt(priv.qual_abbrev, ...) end +function strace.verbose(...) alter_trace_opt(priv.qual_verbose, ...) end +function strace.raw(...) alter_trace_opt(priv.qual_raw, ...) end + +local ulong_t = ffi.typeof('unsigned long') +local kulong_t = ffi.typeof('kernel_ulong_t') + +function strace.ptr_to_kulong(ptr) + return ffi.cast(kulong_t, ffi.cast(ulong_t, ptr)) +end + +function strace.at_exit(f) + at_exit_cb = chain(at_exit_cb, f) +end + +local function validate_pers(pers) + assert(pers >= 0 and pers < strace.npersonalities, + 'invalid personality number') +end + +function strace.get_sc_name(scno, pers) + validate_pers(pers) + if scno < 0 or scno >= strace.C.nsysent_vec[pers] then + return nil + end + local s = strace.C.sysent_vec[pers][scno].sys_name + return s ~= nullptr and ffi.string(s) or nil +end + +function strace.get_sc_flags(scno, pers) + validate_pers(pers) + if scno < 0 or scno >= strace.C.nsysent_vec[pers] then + return nil + end + local entry = strace.C.sysent_vec[pers][scno] + return entry.sys_name ~= nullptr and entry.sys_flags or nil +end + +function strace.get_sig_name(signo, pers) + validate_pers(pers) + if signo < 0 or signo >= strace.C.nsignalent_vec[pers] then + return nil + end + local s = strace.C.signalent_vec[pers][signo] + return s ~= nullptr and ffi.string(s) or nil +end + +function strace.get_err_name(err, pers) + validate_pers(pers) + if err < 0 or err > strace.C.nerrnoent_vec[pers] then + return nil + end + local s = strace.C.errnoent_vec[pers][err] + return s ~= nullptr and ffi.string(s) or nil +end + +local uint_t = ffi.typeof('unsigned int') + +function strace.get_ioctl_name(code, pers) + validate_pers(pers) + -- 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 = uint_t(0), strace.C.nioctlent_vec[pers] + if rb == 0 then + return nil + end + local arr = strace.C.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 + +local const_cstr_t = ffi.typeof('const char *') + +local function validate_cstr_initializer(s) + -- ffi.istype() ignores constness, so the following accepts a cdata + -- (non-const) char * as well. + assert(type(s) == 'string' or ffi.istype(const_cstr_t, s), + 'expected either a Lua string or a cdata C string') +end + +function strace.get_class_flagbit(clsname) + validate_cstr_initializer(clsname) + local cstr = const_cstr_t(clsname) + local ptr = strace.C.syscall_classes + while ptr.name ~= nullptr do + if ffi.C.strcmp(ptr.name, cstr) == 0 then + return ptr.value + end + ptr = ptr + 1 + end + return nil +end + +function strace.get_ioctl_code(name, pers) + validate_cstr_initializer(name) + validate_pers(pers) + local cstr = const_cstr_t(name) + local arr = strace.C.ioctlent_vec[pers] + for i = 0, tonumber(strace.C.nioctlent_vec[pers]) - 1 do + if ffi.C.strcmp(arr[i].symbol, cstr) == 0 then + return arr[i].code + end + end + return nil +end + +function strace.get_scno(scname, pers) + validate_cstr_initializer(scname) + validate_pers(pers) + local cstr = const_cstr_t(scname) + local arr = strace.C.sysent_vec[pers] + for i = 0, tonumber(strace.C.nsysent_vec[pers]) - 1 do + local s = arr[i].sys_name + if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then + return i + end + end + return nil +end + +function strace.get_signo(signame, pers) + validate_cstr_initializer(signame) + validate_pers(pers) + local cstr = const_cstr_t(signame) + local arr = strace.C.signalent_vec[pers] + for i = 0, tonumber(strace.C.nsignalent_vec[pers]) - 1 do + local s = arr[i] + if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then + return i + end + end + return nil +end + +function strace.get_errno(errname, pers) + validate_cstr_initializer(errname) + validate_pers(pers) + local cstr = const_cstr_t(errname) + local arr = strace.C.errnoent_vec[pers] + for i = 0, tonumber(strace.C.nerrnoent_vec[pers]) - 1 do + local s = arr[i] + if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then + return i + end + end + return nil +end + +function strace.inject_signal(sig) + local signo = tonumber(sig) + if signo == nil then + signo = strace.get_signo(sig, tcp.currpers) + if signo == nil then + return false, 'notfound' + end + end + if not strace.C.inject_signo(signo) then + return false, 'injecterr' + end + return true +end + +function strace.inject_error(err) + local errno = tonumber(err) + if errno == nil then + errno = strace.get_errno(err, tcp.currpers) + if errno == nil then + return false, 'notfound' + end + end + if errno <= 0 or not strace.C.inject_retval(-errno) then + return false, 'injecterr' + end + return true +end + +function strace.inject_retval(val) + if not strace.C.inject_retval(val) then + return false, 'injecterr' + end + return true +end + +local function make_cdata_ptr(obj) + local is_ok, ret = pcall( + ffi.typeof('$ *', ffi.typeof(obj)), + obj) + return is_ok and ret or obj +end + +function strace.read_obj(addr, ct, ...) + local obj = ffi.new(ct, ...) + return strace.C.umove(addr, ffi.sizeof(obj), make_cdata_ptr(obj)) == 0 + and obj or nil +end + +function strace.read_str(addr, maxsz, bufsz) + -- convert it to Lua number to prevent underflows + maxsz = tonumber(maxsz) or 4 * 1024 * 1024 + bufsz = bufsz or 1024 + local t = {} + local buf = ffi.new('char [?]', bufsz) + while true do + local r = strace.C.umove_str(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 + t[#t + 1] = s + return table.concat(t) + end + end +end + +function strace.read_path(addr) + return strace.read_str(addr, strace.path_max - 1, strace.path_max) +end + +local function parse_when(when) + if type(when) == 'table' then + return unpack(when) + elseif 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 + +local function reduce_or(f, args, ...) + local ret = false + for _, arg in ipairs(args) do + if f(arg, ...) then + ret = true + end + end + return ret +end + +function strace.hook(scname, when, cb) + local on_entry, on_exit = parse_when(when) + if type(scname) == 'table' then + return reduce_or(strace.hook, scname, {on_entry, on_exit}, cb) + end + local found = false + for p = 0, strace.npersonalities - 1 do + local scno = strace.get_scno(scname, p) + if scno ~= nil then + register_hook(scno, p, on_entry, on_exit, cb) + found = true + end + end + return found +end + +function strace.hook_class(clsname, when, cb) + local on_entry, on_exit = parse_when(when) + if type(clsname) == 'table' then + return reduce_or(strace.hook_class, clsname, + {on_entry, on_exit}, cb) + end + local flag = strace.get_class_flagbit(clsname) + if flag == nil then + return false + end + for p = 0, strace.npersonalities - 1 do + local arr = strace.C.sysent_vec[p] + for i = 0, tonumber(strace.C.nsysent_vec[p]) - 1 do + if bit.band(arr[i].sys_flags, flag) ~= 0 then + register_hook(i, p, on_entry, on_exit, cb) + end + end + end + return true +end + +function strace.hook_scno(scno, pers, when, cb) + validate_pers(pers) + local on_entry, on_exit = parse_when(when) + if type(scno) == 'table' then + return reduce_or(strace.hook_scno, scno, pers, + {on_entry, on_exit}, cb) + end + if scno < 0 then + return false + end + register_hook(scno, pers, on_entry, on_exit, cb) + return true +end + +function strace.path_match(set) + if type(set) ~= 'table' then + set = {set} + end + for _, elem in pairs(set) do + validate_cstr_initializer(elem) + end + local nset = #set + return not not strace.C.path_match( + ffi.new('const char *[?]', nset, set), nset) +end + +function print(...) + local file = io.stderr + local sep = '' + for i = 1, select('#', ...) do + file:write(sep .. tostring(select(i, ...))) + sep = '\t' + end + file:write('\n') +end + +return function() + local next_sc = strace.next_sc + local entering = strace.entering + while next_sc() ~= nil do + local cb = (entering() and entry_cbs or exit_cbs) + [tonumber(tcp.currpers)][tonumber(tcp.scno)] + if cb ~= nil then + cb(tcp) + end + end + if at_exit_cb ~= nil then + at_exit_cb() + end +end diff --git a/strace.1.in b/strace.1.in index 61293cfa..5ab650dd 100644 --- a/strace.1.in +++ b/strace.1.in @@ -53,6 +53,10 @@ . el \ . BR "\\$1" .. +.de URL +\\$2 \(laURL: \\$1 \(ra\\$3 +.. +.if \n[.g] .mso www.tmac .TH STRACE 1 "@MANPAGE_DATE@" "strace @VERSION@" .SH NAME strace \- trace system calls and signals @@ -796,6 +800,756 @@ Print the help summary. .B \-V Print the version number of .BR strace . +.SH LUA SCRIPTING +If built with Lua support, +.B strace +can execute Lua scripts. +A script file is passed to the +.B -l +option. +.PP +.B strace +provides the built-in module +.B strace +to the Lua execution environment, which contains various functions and +constants. +.PP +Before any tracing takes place, the script is run. +At this stage, it can do one of the following two things: +.RS +.IP \(bu 3 +Implement its own tracing loop by selecting syscalls it wants to be notified +about with +.BR strace.C.monitor / strace.C.monitor_all +and calling +.B strace.next_sc +in a loop until it returns +.B nil +(or return earlier; in this case, the installed hooks for the remaining syscalls +are run). +.IP \(bu +Install syscall and at-exit hooks with +.BR strace.hook ", " strace.hook_class ", " strace.hook_scno " and " strace.at_exit . +.RE +.PP +Then, +.B strace +enters its own tracing loop, and all the installed hooks are run. +.SS Personalities +A personality is an execution domain with its own sets of syscalls, signals and +errors. +A +.I personality number +is strace-specific 0-based index in the architecture-dependent list of +personalities. +.SS FFI +.URL http://luajit.org/ext_ffi.html "FFI library" +is an extension provided by LuaJIT; +.URL https://github.com/jmckaskill/luaffi luaffi +and +.URL https://github.com/facebook/luaffifb luaffifb +standalone implementations are also supported, but only for Lua 5.2. +.PP +One may want to look at +.URL https://github.com/m-schmoock/lcpp lcpp , +a pure-Lua implementation of C preprocessor, which integrates with LuaJIT's FFI +library and supports inclusion of header files and exposing all the definitions +and macros to the FFI. +As for now, lcpp does not support system include directories, and treats all +includes as local. +.URL https://github.com/umegaya/ffiex ffiex , +a library based on it, on the other hand, does support them, and thus can be +used to preprocess system headers. +.PP +.B strace +provides the following FFI definitions: +.CW +typedef /* implementation-defined signed integer type */ kernel_long_t; +typedef /* implementation-defined unsigned integer type */ 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 submodule: functions +Note: be careful with boxed boolean values and use +.B not not +.I boxed_bool +when in doubt. +In particular, an +.B assert +on a boxed boolean will never raise an error. +.TP +\fIstatus\fR = \fBstrace.C.monitor\fR(\fIscno\fR, \fIpers\fR, \fIon_entry\fR, \fIon_exit\fR) +C type: +.B bool (*)(unsigned int, unsigned int, bool, bool) +.IP +Marks the syscall with number +.I scno +on personality +.I pers +as to be returned from +.BR strace.next_sc . +If +.I on_entry +is +.BR true , +it is marked as to be returned on syscall entry, and if +.I on_exit +is +.BR true , +it is marked as to be returned on syscall exit. +.IP +Note that this "marking" is a one-way process, and specifying +.B false +as any of the flags does not undo any previous calls to +.BR strace.C.monitor / strace.C.monitor_all . +.IP +Returns +.B true +on success and +.B false +on failure. +.TP +\fBstrace.C.monitor_all\fR(\fIon_entry\fR, \fIon_exit\fR) +C type: +.B void (*)(bool, bool) +.IP +Marks all syscalls on all personalities as to be returned from +.BR strace.next_sc : +on syscall entry if +.I on_entry +is +.BR true , +and on syscall exit if +.I on_exit +is +.BR true . +See the note for +.BR strace.C.monitor . +.TP +\fIstatus\fR = \fBstrace.C.inject_signo\fR(\fIsigno\fR) +C type: +.B bool (*)(int) +.IP +Delivers a signal with number +.I signo +to the current tracee. +.IP +Note that this must be done on syscall entry. +.IP +Returns +.B true +on success and +.B false +on failure. +.TP +\fIstatus\fR = \fBstrace.C.inject_retval\fR(\fIval\fR) +C type: +.B bool (*)(int) +.IP +Injects a return value to the current syscall invocation. +.IP +Note that this must be done on syscall entry. +.IP +Returns +.B true +on success and +.B false +on failure. +.TP +\fIstatus\fR = \fBstrace.C.umove\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR) +C type: +.B int (*)(kernel_ulong_t, size_t, void *) +.IP +Copies ("moves") +.I len +bytes of data from the current tracee process at address +.I addr +to a local address +.IR laddr . +.IP +Returns 0 on success and \-1 on failure. +.TP +\fIstatus\fR = \fBstrace.C.umove_str\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR) +C type: +.B int (*)(kernel_ulong_t, size_t, char *) +.IP +Like +.BR strace.C.umove , +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 +.I len +bytes 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 +.IR laddr[len-1] ). +.TP +\fIstatus\fR = \fBstrace.C.path_match\fR(\fIset\fR, \fInset\fR) +C type: +.B bool (*)(const char **, size_t) +.IP +Returns +.B true +if the current syscall accesses one of the paths from a given set of paths, and +.B false +otherwise. +.IP +Note: for string path arguments, the path is compared against the set; for file +descriptor arguments, the absolute path to the file behind the file descriptor +is compared against the set. +.SS strace module: C submodule: constants +.TP +.B strace.C.sysent_vec +Array of syscall tables for each of the supported personalities. +\fBstrace.C.sysent_vec\fR[\fIpers\fR][\fIscno\fR] is a +.B struct_sysent +for syscall number +.I scno +on personality +.IR pers . +May contain null entries (which have a NULL +.B sys_name +field). +.TP +.B strace.C.errnoent_vec +Array of error name tables for each of the supported personalities. +\fBstrace.C.errnoent_vec\fR[\fIpers\fR][\fIerrno\fR] is either a null pointer or +a C string with the name of error +.I errno +on personality +.IR pers . +.TP +.B strace.C.signalent_vec +Array of signal name tables for each of the supported personalities. +\fBstrace.C.signalent_vec\fR[\fIpers\fR][\fIsigno\fR] is either a null pointer +or a C string with the name of signal +.I signo +on personality +.IR pers . +.TP +.B strace.C.ioctlent_vec +Arrays of sorted known ioctl symbols, sorted by code, for each of the supported +personalities. +\fBstrace.C.ioctlent_vec\fR[\fIpers\fR][\fIi\fR] is the +.IR i \-th, +ranked by code, +.B struct_ioctlent +for personality +.IR pers . +.TP +.B strace.C.nsysent_vec +.TQ +.B strace.C.nerrnoent_vec +.TQ +.B strace.C.nsignalent_vec +.TQ +.B strace.C.nioctlent_vec +These are +.BR strace.npersonalities \-sized +arrays containing sizes of subarrays of +.BR strace.C.sysent_vec , +.BR strace.C.errnoent_vec , +.BR strace.C.signalent_vec " and" +.BR strace.C.ioctlent_vec , +respectively. +.TP +.B strace.C.syscall_classes +Array of +.BR "struct syscall_class" , +with a terminating null entry (which has a NULL +.B name +field). +.TP +.B strace.C.pers_name +A +.BR strace.npersonalities \-sized +array of C strings with names for each personality. +.TP +.B strace.C.pers_wordsize +.TQ +.B strace.C.pers_klongsize +These are +.BR strace.npersonalities \-sized +arrays with word and +.I __kernel_long_t +sizes for each personality. +.SS strace module: functions +Glossary: +.RS +.IP \(bu 3 +an +.I integer +means either an integer Lua number or a cdata integer type; +.IP \(bu +a +.I boolean +means either a Lua boolean or a cdata +.BR bool ; +.IP \(bu +a +.I string +means either a Lua string or a cdata C string. +.RE +.PP +General conventions: +.RS +.IP \(bu 3 +a +.I pers +argument is an +.I integer +specifying personality number; +.IP \(bu +an +.I addr +argument is a cdata +.IR kernel_ulong_t . +.RE +.TP +\fItcp\fR = \fBstrace.next_sc\fR() +If this is not the first call to this function, performs tracing and tampering +of the previously returned syscall. +.IP +Waits for the next monitored syscall to happen, and returns a pointer to its +trace control block. +This pointer is not persistent across different calls to this function and/or +hook calls. +.IP +If +.B strace +needs to be terminated (e.g. last tracee has been terminated, or +.B strace +has been interrupted), returns +.BR nil . +Once it returned +.BR nil , +all subsequent calls to it will also return +.BR nil . +.TP +\fIstatus\fR = \fBstrace.entering\fR() +Returns +.B true +if this is a syscall entry, and +.B false +otherwise. +.TP +\fIstatus\fR = \fBstrace.exiting\fR() +Returns +.B true +if this is a syscall exit, and +.B false +otherwise. +.TP +\fBstrace.trace\fR([\fIflag\fR]) +.TQ +\fBstrace.abbrev\fR([\fIflag\fR]) +.TQ +\fBstrace.verbose\fR([\fIflag\fR]) +.TQ +\fBstrace.raw\fR([\fIflag\fR]) +These functions alter corresponding trace options. +.I flag +is a +.IR boolean , +defaults to +.BR true . +.TP +\fIaddr\fR = \fBstrace.ptr_to_kulong\fR(\fIptr\fR) +Converts a cdata pointer to a +.B kernel_ulong_t +variable. +.TP +\fBstrace.at_exit\fR(\fIfunc\fR) +Registers a function +.I func +to be run when +.B strace +is going to terminate its execution. +.IP +If multiple exit callback functions are registered, they are run in the order +they were registered. +.TP +\fIname\fR = \fBstrace.get_sc_name\fR(\fIscno\fR, \fIpers\fR) +Returns syscall name as Lua string by its number for personality +.IR pers , +or +.B nil +if +.I scno +is invalid. +.IP +.I scno +is an +.IR integer . +.TP +\fIname\fR = \fBstrace.get_sig_name\fR(\fIsigno\fR, \fIpers\fR) +Returns signal name (.e.g \fB"SIGSEGV"\fR) as Lua string by its number +.I signo +for personality +.IR pers , +or +.B nil +if +.I signo +is invalid. +.TP +\fIname\fR = \fBstrace.get_err_name\fR(\fIerrno\fR, \fIpers\fR) +Returns error name (e.g. \fB"ENOENT"\fR) as Lua string by its error number +.I errno +for personality +.IR pers , +or +.B nil +if +.I errno +is invalid. +.IP +.I errno +is an +.IR integer . +.TP +\fIname\fR = \fBstrace.get_ioctl_name\fR(\fIreqcode\fR, \fIpers\fR) +Returns ioctl symbol name (e.g. \fB"TIOCGWINSZ"\fR) as Lua string by its request +code +.I reqcode +for personality +.IR pers , +or +.B nil +if +.I reqcode +is invalid. +.IP +.I reqcode +is an +.IR integer . +.TP +\fIscno\fR = \fBstrace.get_scno\fR(\fIscname\fR, \fIpers\fR) +Returns syscall number by its name for personality +.IR pers , +or +.B nil +if no such syscall was found. +.IP +.I scname +is a +.IR string . +.TP +\fIsigno\fR = \fBstrace.get_signo\fR(\fIsigname\fR, \fIpers\fR) +Returns signal number by its name (e.g. \fB"SIGSEGV"\fR) for personality +.IR pers , +or +.B nil +if no such signal was found. +.IP +.I signame +is +.IR string . +.TP +\fIerrno\fR = \fBstrace.get_errno\fR(\fIerrname\fR, \fIpers\fR) +Returns error number by its name (e.g. \fB"ENOENT"\fR) for personality +.IR pers , +or +.B nil +if no such error was found. +.IP +.I errname +is a +.IR string . +.TP +\fIcode\fR = \fBstrace.get_ioctl_code\fR(\fIname\fR, \fIpers\fR) +Returns ioctl request code by its symbol name (e.g. \fB"TIOCGWINSZ"\fR) for +personality +.IR pers , +or +.B nil +if no such ioctl request was found. +.IP +.I name +is a +.IR string . +.TP +\fIstatus\fR, [\fIerr_msg\fR] = \fBstrace.inject_signal\fR(\fIsig\fR) +Delivers a signal to the tracee. +.I sig +is either signal number (an +.IR integer ) +or name (a +.IR string ). +.IP +Returns +.B true +on success; \fBfalse, "notfound"\fR if +.I sig +is a +.I string +and no signal with such name was found; and \fBfalse, "injecterr"\fR if the +signal cannot be injected for some other reason. +.IP +Note that this must be done on syscall entry. +.TP +\fIstatus\fR, [\fIerr_msg\fR] = \fBstrace.inject_retval\fR(\fIval\fR) +Injects a return value to the current syscall invocation. +.I val +is an +.IR integer . +.IP +Returns +.B true +on success and \fBfalse, "injecterr"\fR on failure. +.IP +Note that this must be done on syscall entry. +.TP +\fIstatus\fR, [\fIerr_msg\fR] = \fBstrace.inject_error\fR(\fIerr\fR) +Injects an error into a current syscall invocation. +.I err +is either error number (an +.IR integer ) +or error name (a +.IR string ). +.IP +Returns +.B true +on success; \fBfalse, "notfound"\fR if +.I err +is a +.I string +and no error with such name was found; and \fBfalse, "injecterr"\fR if the +error cannot be injected for some other reason. +.IP +Note that this must be done on syscall entry. +.TP +\fIobj\fR = \fBstrace.read_obj\fR(\fIaddr\fR, \fIct\fR[, \fInelem\fR]) +Reads an object of type +.I ct +from the current tracee process at address +.IR addr . +.I ct +is either a +.I cdecl +(a Lua string), a +.I cdata +serving as a template type, or a +.I ctype +(special kind of +.I cdata +returned by +.BR ffi.typeof ). +.IP +VLA/VLS types require the +.I nelem +argument (an +.IR integer ). +.IP +Returns an object on success and +.B nil +on failure. +.IP +Note that type sizes and structure paddings may differ from FFI's ones if +tracee's personality differs from +.BR strace 's +one. +.TP +\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_str\fR(\fIaddr\fR[, \fImaxsz\fR[, \fIbufsz\fR]]) +Reads a C string from the current tracee process at address +.I addr +using an intermediate buffer of size +.I bufsz +and stopping at +.I maxsz +bytes. +.IP +.I maxsz +and +.I bufsz +are +.IR integers . +.I maxsz +defaults to 4 Mb, +.I bufsz +to 1 Kb. +.IP +Returns a Lua string on success, \fBnil, "readerr"\fR on read error, and +\fBnil, "toolong"\fR if the +.I maxsz +limit was exceeded. +.TP +\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_path\fR(\fIaddr\fR) +Reads a path C string from the current tracee process at address +.IR addr . +.IP +Returns a Lua string on success, \fBnil, "readerr"\fR on read error, and +\fBnil, "toolong"\fR if the +.B PATH_MAX +limit was exceeded. +.TP +\fIstatus\fR = \fBstrace.hook\fR(\fIscname\fR, \fIwhen\fR, \fIcb\fR) +.TQ +\fIstatus\fR = \fBstrace.hook_class\fR(\fIclsname\fR, \fIwhen\fR, \fIcb\fR) +.TQ +\fIstatus\fR = \fBstrace.hook_scno\fR(\fIscno\fR, \fIpers\fR, \fIwhen\fR, \fIcb\fR) +These functions register a function +.I cb +to be run when a syscall with the given name (or with a name from a given set +thereof), belonging to a class with the given name (or with a name from a given +set thereof), or with the given number (or with a number from a given set +thereof) on personality +.IR pers , +happens. +.IP +.I when +is either a two-element array (that is, a table with keys 1 and 2) with boolean +values, or a Lua string. +.I cb +is registered to be run: +.RS +.IP \(bu 3 +on syscall entry if +.I when +is either +.B {true, false} +or \fB"entering"\fR; +.IP \(bu +on syscall exit if +.I when +is either +.B {false, true} +or \fB"exiting"\fR; +.IP \(bu +both on syscall entry and exit if +.I when +is either +.B {true, true} +or \fB"both"\fR; +.IP \(bu +at no time if +.I when +is +.B "{false, false}" +(in this case, a call is effectively ignored). +.RE +.IP +A pointer to the trace control block is passed as the only argument to +.IR cb . +This pointer is not persistent across +.B strace.next_sc +and/or another hook calls. +.IP +If multiple callback functions are registered for the same event (syscall entry +or exit), they are run in the order they were registered. +.IP +.IR scname " and " clsname +are either +.I strings +or tables thereof. +.I scno +is either an +.I integer +or a table thereof. +.IP +Return +.B true +on success and +.B false +on failure. +.TP +\fIstatus\fR = \fBstrace.path_match\fR(\fIset\fR) +Returns +.B true +if the current syscall accesses a given path, or one of the paths from the given +set of paths; and +.B false +otherwise (see the note for +.BR strace.C.path_match ). +.IP +.I set +is either a +.I string +or a table thereof. +.SS strace module: constants +.TP +.B strace.npersonalities +Number of supported personalities (an integer Lua number). +.TP +.B strace.max_args +Size of +.B struct tcb::u_arg +array (an integer Lua number). +.TP +.B strace.path_max +Value of +.B PATH_MAX +constant (an integer Lua number). +.SS Examples +The following script counts the number of processes (including threads) spawned +by the tracee. +Note that you would probably want to launch +.B strace +with +.B -f +option, so that children also be traced. +.CW +n = 0 +assert(strace.hook({'clone', 'fork', 'vfork'}, 'exiting', function(tcp) + if tcp.u_rval ~= -1 then + n = n + 1 + end +end)) +strace.at_exit(function() print('Processes spawned:', n) end) +.CE +The following script uses the ffiex library (see the discussion in the +.B FFI +section) and injects the +.B EPERM +error to each +.B fcntl +(and +.BR fcntl64 ) +syscall invocation with +.B F_SETPIPE_SZ +command. +.CW +ffiex = require 'ffiex' +ffiex.cdef[[ +#define _GNU_SOURCE +#include <fcntl.h> +]] +f_setpipe_sz = assert(tonumber(ffiex.defs.F_SETPIPE_SZ)) +assert(strace.hook({'fcntl', 'fcntl64'}, 'entering', function(tcp) + if tcp.u_arg[1] == f_setpipe_sz then + assert(strace.inject_error(tcp, 'EPERM')) + end +end)) +.CE .SH DIAGNOSTICS When .I command diff --git a/strace.c b/strace.c index 7b8a0e24..470b2768 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 "number_set.h" #include "scno.h" @@ -170,6 +173,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 @@ -228,6 +236,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\ @@ -713,7 +726,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 @@ -1589,6 +1602,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) { @@ -1699,6 +1715,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"); @@ -2621,6 +2642,10 @@ terminate(void) exit(exit_code); } +#ifdef USE_LUAJIT +# include "luajit.h" +#endif + int main(int argc, char *argv[]) { @@ -2628,6 +2653,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, false)) diff --git a/syscall.c b/syscall.c index abc3f80c..41ee5520 100644 --- a/syscall.c +++ b/syscall.c @@ -640,6 +640,19 @@ tcb_inject_data(struct tcb *tcp, bool step) res = opts->data; } } +#ifdef USE_LUAJIT + if (tcp->flags & TCB_AD_HOC_INJECT) { + struct inject_data ad_hoc_data = tcp->ad_hoc_inject_data; + if (ad_hoc_data.flags & INJECT_F_SIGNAL) { + res.flags |= INJECT_F_SIGNAL; + res.signo = ad_hoc_data.signo; + } + if (ad_hoc_data.flags & INJECT_F_RETVAL) { + res.flags |= INJECT_F_RETVAL; + res.rval = ad_hoc_data.rval; + } + } +#endif return res; } -- 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