[PATCH v10 1/3] Initial support for LuaJIT scripting
Eugene Syromiatnikov
esyr at redhat.com
Mon Aug 7 08:12:47 UTC 2017
On Wed, Aug 02, 2017 at 12:50:54PM +0300, Victor Krapivensky wrote:
> * .gitignore: Add luajit_lib.h.
> * 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 1.
> 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.
> (make_number_set_universal): New function.
> (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, set_hook_qual_all): New functions.
> * strace.1 (LUA 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, personality_wordsize,
> personality_klongsize): New global variables.
> (personality_names): New global variable (if SUPPORTED_PERSONALITIES >
> 1).
> (update_personality): use personality_names for reporting personality
> name.
> (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.
> ---
> .gitignore | 1 +
> Makefile.am | 17 +++
> configure.ac | 36 ++++++
> defs.h | 61 ++++-----
> defs_shared.h | 66 ++++++++++
> ffi.h | 19 +++
> luajit.h | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> luajit_lib.lua | 368 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> qualify.c | 107 +++++++++++-----
> strace.1 | 394 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> strace.c | 36 +++++-
> syscall.c | 186 ++++++++++++++++++++-------
> sysent.h | 24 +++-
> 13 files changed, 1562 insertions(+), 121 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/.gitignore b/.gitignore
> index 0885bcb5..a5d0c361 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 5d365e24..b73dacca 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -106,6 +106,7 @@ strace_SOURCES = \
> copy_file_range.c \
> count.c \
> defs.h \
> + defs_shared.h \
> desc.c \
> dirent.c \
> dirent64.c \
> @@ -128,6 +129,7 @@ strace_SOURCES = \
> fetch_struct_stat.c \
> fetch_struct_stat64.c \
> fetch_struct_statfs.c \
> + ffi.h \
> file_handle.c \
> file_ioctl.c \
> flock.c \
> @@ -302,6 +304,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) \
> @@ -830,6 +838,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 \
> @@ -856,6 +865,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; \
> @@ -934,6 +946,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/configure.ac b/configure.ac
> index 0d407aff..b5c23015 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -734,6 +734,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 8aca8083..51fe8dda 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() */
Note that value 0x800 is now utilised in "[PATCH v6 9/9] Add stacktrace
filter action".
>
> #define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
>
> @@ -363,6 +329,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;
> @@ -686,6 +653,11 @@ 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);
> +extern void set_hook_qual_all(bool entry_hook, bool exit_hook);
> +#endif
>
> #define DECL_IOCTL(name) \
> extern int \
> @@ -968,6 +940,19 @@ 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];
> +
> +extern const int personality_wordsize[SUPPORTED_PERSONALITIES];
> +extern const int personality_klongsize[SUPPORTED_PERSONALITIES];
> +#if SUPPORTED_PERSONALITIES > 1
> +extern const char *const personality_names[];
> +#endif
> +
> #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..72ed2213
> --- /dev/null
> +++ b/defs_shared.h
> @@ -0,0 +1,66 @@
> +/*
> + * 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;
> +)
> +
> +/* Trace Control Block */
> +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..0b999400
> --- /dev/null
> +++ b/luajit.h
> @@ -0,0 +1,368 @@
> +/*
> + * 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;
> +
> +#define MAYBE_RESTART(res, sig) \
> + do { \
> + if ((res) >= 0 && ptrace_restart( \
> + PTRACE_SYSCALL, current_tcp, sig) < 0) { \
> + /* ptrace_restart emitted error message */ \
> + exit_code = 1; \
> + goto term; \
> + } \
> + } while (0)
> +
> + if (!first) {
> + if (!current_tcp)
> + return NULL;
> +
> + 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:
> + 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)
> +{
> + struct inject_opts *opts = current_tcp->ad_hoc_inject_opts;
> + if (!opts) {
> + opts = current_tcp->ad_hoc_inject_opts = xmalloc(sizeof(*opts));
> + opts->first = 1;
> + opts->step = 1;
> + }
> + if (!(current_tcp->flags & TCB_AD_HOC_INJECT)) {
> + opts->signo = 0;
> + opts->rval = INJECT_OPTS_RVAL_DEFAULT;
> + current_tcp->qual_flg |= QUAL_INJECT;
> + current_tcp->flags |= TCB_AD_HOC_INJECT;
> + }
Is my understanding correct that if something has to be injected
for this call, it will be overwritten? For example, if we have specific
errno set for injected calls and Lua upcalls strace.inject_signal, we will lose
our custom error number? This could be quite unexpected.
> +}
> +
> +static bool
> +func_inject_signo(int signo)
> +{
> + if (!current_tcp || exiting(current_tcp))
> + /* Too late! */
> + return false;
> + if (signo <= 0 || signo > SIGRTMAX)
> + return false;
> + prepare_ad_hoc_inject();
> + current_tcp->ad_hoc_inject_opts->signo = signo;
> + return true;
> +}
> +
> +static bool
> +func_inject_retval(int retval)
> +{
> + if (!current_tcp || exiting(current_tcp))
> + /* Too late! */
> + return false;
> + if (retval < -MAX_ERRNO_VALUE)
> + return false;
> + prepare_ad_hoc_inject();
> + current_tcp->ad_hoc_inject_opts->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(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 strace */
> + lua_newtable(L); /* L: ffi strace C */
> +
> + lua_getfield(L, 1, "cast"); /* L: ffi strace C cast */
> + lua_remove(L, 1); /* L: strace C cast */
> +
> +#define EXPOSE_FUNC(rettype, ptr, name, ...) \
> + do { \
> + rettype (*fptr_)(__VA_ARGS__) = ptr; \
> + lua_pushvalue(L, -1); /* L: strace C cast cast */ \
> + lua_pushstring(L, #rettype " (*)(" #__VA_ARGS__ ")"); \
> + /* L: strace C cast cast str */ \
> + lua_pushlightuserdata(L, * (void **) (&fptr_)); \
> + /* L: strace C cast cast str ptr */ \
> + assert_lua(lua_pcall(L, 2, 1, 0)); \
> + /* L: strace C cast value */ \
> + lua_setfield(L, -3, name); /* L: strace C cast */ \
> + } while (0)
> +
> + 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(struct tcb *, func_next_sc, "next_sc",
> + void);
> + 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);
> +
> +#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: strace C cast cast */ \
> + lua_pushstring(L, #type); \
> + /* L: strace C cast cast str */ \
> + lua_pushlightuserdata(L, (void *) ptr); \
> + /* L: strace C cast cast str ptr */ \
> + assert_lua(lua_pcall(L, 2, 1, 0)); \
> + /* L: strace C cast value */ \
> + lua_setfield(L, -3, name); /* L: strace C 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");
> +
> +#if SUPPORTED_PERSONALITIES == 1
> + static const char *const personality_names[] = {"default"};
> +#endif
> + EXPOSE(const char *const *, personality_names, "pers_names");
> + EXPOSE(const int *, personality_wordsize, "pers_wordsize");
> + EXPOSE(const int *, personality_klongsize, "pers_klongsize");
> +
> +#undef EXPOSE
> +
> + lua_pop(L, 1); /* L: strace C */
> + lua_setfield(L, -2, "C"); /* L: strace */
> +
> + lua_pushinteger(L, SUPPORTED_PERSONALITIES); /* L: strace int */
> + lua_setfield(L, -2, "npersonalities"); /* L: strace */
> +
> + lua_pushinteger(L, MAX_ARGS); /* L: strace int */
> + lua_setfield(L, -2, "max_args"); /* L: strace */
> +
> + lua_pushinteger(L, PATH_MAX); /* L: strace int */
> + lua_setfield(L, -2, "path_max"); /* L: strace */
> +
> + 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 */
> +
> + check_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..d1996409
> --- /dev/null
> +++ b/luajit_lib.lua
> @@ -0,0 +1,368 @@
> +-- 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
> +
> +local function register_hook(scno, pers, on_entry, on_exit, cb)
> + assert(not not strace.C.monitor(scno, pers, on_entry, on_exit))
> + 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
> +
> +-- Convert a cdata C string or a Lua string to a Lua string.
> +local function mkstring(s)
> + return type(s) == 'string' and s or ffi.string(s)
> +end
> +
> +local function parse_pers_spec(pers_spec)
> + return tonumber(pers_spec) or tonumber(pers_spec.currpers)
> +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
> + return false
> + 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
> + return true
> +end
> +for funcname, flagbit in pairs{
> + trace = priv.qual_trace,
> + abbrev = priv.qual_abbrev,
> + verbose = priv.qual_verbose,
> + raw = priv.qual_raw,
> +} do
> + strace[funcname] = function(tcp, ...)
> + return alter_trace_opt(flagbit, tcp, ...)
> + end
> +end
> +
> +function strace.ptr_to_kulong(ptr)
> + return ffi.cast('kernel_ulong_t', ffi.cast('unsigned long', ptr))
> +end
I'm not quite sure that this is correct in case of non-native
personalities.
> +
> +function strace.at_exit(f)
> + at_exit_cb = chain(at_exit_cb, f)
> +end
> +
> +function strace.get_err_name(err, pers_spec)
> + local pers = parse_pers_spec(pers_spec)
> + if err < 0 or err > strace.C.nerrnoent_vec[pers] then
> + return nil
> + end
> + local s = strace.C.errnoent_vec[pers][err]
> + return s ~= nil and ffi.string(s) or nil
> +end
> +
> +function strace.get_sc_name(scno, pers_spec)
> + local pers = parse_pers_spec(pers_spec)
> + 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 ~= nil and ffi.string(s) or nil
> +end
> +
> +function strace.get_ioctl_name(code, pers_spec)
> + local pers = parse_pers_spec(pers_spec)
> + -- 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.
Why not single call to (slightly modified) ioctl_lookup?
> + local lb, rb = ffi.cast('unsigned int', 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
> +
> +function strace.get_scno(scname, pers_spec)
> + local pers = parse_pers_spec(pers_spec)
> + local cstr = ffi.cast('const char *', scname)
> + for i = 0, tonumber(strace.C.nsysent_vec[pers]) - 1 do
> + local s = strace.C.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_spec)
Why not single call to sigstr_to_uint?
> + local pers = parse_pers_spec(pers_spec)
> + local cstr = ffi.cast('const char *', signame)
> + for i = 0, tonumber(strace.C.nsignalent_vec[pers]) - 1 do
> + local s = strace.C.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_spec)
Why not single call to find_errno_by_name?
> + local pers = parse_pers_spec(pers_spec)
> + local cstr = ffi.cast('const char *', errname)
> + for i = 0, tonumber(strace.C.nerrnoent_vec[pers]) - 1 do
> + local s = strace.C.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)
> + local signo = sig
> + if type(sig) == 'string' or type(sig) == 'cdata' then
> + signo = strace.get_signo(sig, tcp)
> + if not signo then
> + return false
> + end
> + end
> + return not not strace.C.inject_signo(signo)
> +end
> +
> +function strace.inject_error(tcp, err)
> + local errno = err
> + if type(err) == 'string' or type(err) == 'cdata' then
> + errno = strace.get_errno(err, tcp)
> + if not errno then
> + return false
> + end
> + end
> + return errno > 0 and not not strace.C.inject_retval(-errno)
> +end
> +
> +local ptr_size = ffi.sizeof('void *')
> +
> +function strace.read_obj(addr, ct, ...)
> + local obj = ffi.new(ct, ...)
> + local n = ffi.sizeof(obj)
> + -- work around FFI pointer semantics
> + if n == ptr_size then
> + -- it may be a pointer, and it is cheap to create another copy
> + local is_ok, ret = pcall(function()
> + local t = ffi.typeof(obj)
> + local arr = ffi.typeof('$ [1]', t)()
> + return strace.C.umove(addr, n, arr) == 0 and t(arr[0])
> + or nil
> + end)
> + if is_ok then
> + return ret
> + end
> + end
> + return strace.C.umove(addr, n, 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
> + return table.concat(t) .. s
> + end
> + end
> +end
> +
> +function strace.read_path(addr)
> + return strace.read_str(addr, strace.path_max, strace.path_max + 1)
> +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 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 cstr = ffi.cast('const char *', clsname)
> + local flag = nil
> + local ptr = strace.C.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
Why not single call to lookup_class?
> + if not flag then
> + return false
> + end
> + for p = 0, strace.npersonalities - 1 do
> + for i = 0, tonumber(strace.C.nsysent_vec[p]) - 1 do
> + if bit.band(strace.C.sysent_vec[p][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, when, cb, pers_spec)
> + local on_entry, on_exit = parse_when(when)
> + local pers = parse_pers_spec(pers_spec)
> + if type(scno) == 'table' then
> + return reduce_or(strace.hook_scno, scno, {on_entry, on_exit},
> + cb, pers)
> + end
> + register_hook(scno, pers, on_entry, on_exit, cb)
> +end
> +
> +function strace.path_match(set)
> + if type(set) ~= 'table' then
> + set = {set}
> + end
> + local nset = #set
> + return not not strace.C.path_match(
> + ffi.new('const char *[?]', nset, set), nset)
> +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.C.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..7462b1a7 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)
> @@ -86,6 +115,17 @@ is_number_in_set(const unsigned int number, const struct number_set *const set)
> && number_isset(number, set->vec)) ^ set->not;
> }
>
> +static void
> +make_number_set_universal(struct number_set *const set)
> +{
> + free(set->vec);
> + *set = (struct number_set) {
> + .vec = NULL,
> + .nslots = 0,
> + .not = true,
> + };
> +}
> +
> typedef int (*string_to_uint_func)(const char *);
>
> /*
> @@ -245,37 +285,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 +706,35 @@ 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]);
As far as I understand, add_number_to_set will set the bit in array
regardless of its "not" field, so, call to set_hook_qual after
set_hook_qual_all would disable hooks for some specific syscalls.
> +}
> +
> +void
> +set_hook_qual_all(bool entry_hook, bool exit_hook)
> +{
> + unsigned p;
> + for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> + if (entry_hook)
> + make_number_set_universal(&hook_entry_set[p]);
> + if (exit_hook)
> + make_number_set_universal(&hook_exit_set[p]);
> + }
> }
> +#endif
> diff --git a/strace.1 b/strace.1
> index af9704f5..c003d4f1 100644
> --- a/strace.1
> +++ b/strace.1
> @@ -751,6 +751,14 @@ 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 +774,392 @@ Print the help summary.
> .B \-V
> Print the version number of
> .BR strace .
> +.SH LUA 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 do one of the following two things:
> +.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 \fBstrace.C.next_sc\fR in a loop until it returns a null pointer (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.C.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
> +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
> +.SS 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: function
> +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
> +\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 \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.
The return code (status) is not described.
> +.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.monitor / strace.monitor_all .
> +.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
> +\fBstrace.C.next_sc()\fR: on syscall entry if \fIon_entry\fR is \fBtrue\fR,
> +and on syscall exit if \fIon_exit\fR is \fBtrue\fR.
> +See the note for \fBstrace.monitor\fR.
> +.TP
> +\fItcp\fR = \fBstrace.C.next_sc\fR()
> +C type:
> +.B struct tcb * (*)(void)
> +.IP
> +If this is not the first call to \fBstrace.C.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
> +trace control block.
> +.IP
> +If \fBstrace\fR needs to be terminated (e.g. last tracee has been terminated, or
> +\fBstrace\fR has been interrupted), returns a null pointer.
> +Once it returned a null pointer, all subsequent calls to it will also return it.
> +.TP
> +\fIstatus\fR = \fBstrace.C.inject_signo\fR(\fIsigno\fR)
> +C type:
> +.B bool (*)(int)
> +.IP
> +Deliver a signal with number \fIsigno\fR to the current tracee.
Ditto here.
> +.IP
> +Note that this must be done on syscall entry.
> +.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.
Ditto here.
> +.IP
> +Note that this must be done on syscall entry.
> +.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") \fIlen\fR bytes of data from the current tracee process at
> +address \fIaddr\fR to a local address \fIladdr\fR.
> +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 \fBstrace.C.umove\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.
"bytes"
> +.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).
> +.TP
> +\fIstatus\fR = \fBstrace.C.upoke\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR)
> +C type:
> +.B int (*)(kernel_ulong_t, size_t, const void *)
> +.IP
> +Copies ("pokes") \fIlen\fR bytes of data from the local address \fIladdr\fR to
> +the address \fIaddr\fR of the current tracee process' address space.
> +.IP
> +Returns 0 on success and \-1 on failure.
> +.TP
> +\fIstatus\fR = \fBstrace.C.path_match\fR(\fIset\fR, \fInset\fR)
> +C type:
> +.B bool (*)(const char **, size_t)
> +.IP
> +Returns \fBtrue\fR if the current syscall accesses one of the paths from a given
> +set of paths, and \fBfalse\fR otherwise.
> +.IP
> +Note: for string path arguments, the path is compared against the set; for file
> +descriptor arguments, the abolute 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 \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.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 \fIerrno\fR on personality \fIpers\fR.
> +.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 \fIsigno\fR on personality \fIpers\fR.
> +.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 \fIi\fR-th, ranked by
> +code, \fBstruct_ioctlent\fR for personality \fIpers\fR.
> +.TP
> +.B strace.C.nsysent_vec
> +.TP
> +.B strace.C.nerrnoent_vec
> +.TP
> +.B strace.C.nsignalent_vec
> +.TP
> +.B strace.C.nioctlent_vec
> +These are \fBstrace.npersonalities\fR-sized arrays containing sizes of subarrays
> +of
> +.BR strace.C.sysent_vec ", " strace.C.errnoent_vec ", " strace.C.signalent_vec ", and " strace.C.ioctlent_vec
> +correspondingly.
", respectively"?
> +.TP
> +.B strace.C.syscall_classes
> +Array of \fBstruct syscall_class\fR, with a terminating null entry (which has a
> +NULL \fBname\fR field).
> +.TP
> +.B strace.C.pers_names
> +A \fBstrace.npersonalities\fR-sized array of C strings with names for each
> +personality.
> +.TP
> +.B strace.C.pers_wordsize
> +.TP
> +.B strace.C.pers_klongsize
> +These are \fBstrace.npersonalities\fR-sized array with word and
> +\fI__kernel_long_t\fR sizes for each personality.
> +.SS strace module: functions
> +Glossary:
> +.IP \(bu 3
> +an \fIinteger\fR means either an integer Lua number or a cdata integer type;
> +.IP \(bu
> +a \fIboolean\fR means either a Lua boolean or a cdata \fBbool\fR;
> +.IP \(bu
> +a \fIstring\fR means either a Lua string or a cdata C string;
> +.IP \(bu
> +the \fIcurrent tracing control pointer\fR is either the return value of the last
> +invocation of \fBstrace.next_sc\fR (but not a null pointer), or the argument
> +passed to a now-running hook callback function.
Pretty strange wording. Maybe, something like "trace control block pointer
is a pointer to a (mostly opaque) structure containing descriptor of the current
tracee. This pointer is obtained from next_sc calls and provided to hook
callback functions. These pointers are not persistent across next_sc
and/or hook calls."
> +.PP
> +General conventions:
> +.IP \(bu 3
> +a \fItcp\fR argument is the \fIcurrent tracing control pointer\fR;
> +.IP \(bu
> +a \fIpers_spec\fR argument is either an \fIinteger\fR specifying personality
> +number, or the \fIcurrent tracing control pointer\fR from which a personality
> +number is copied;
> +.IP \(bu
> +an \fIaddr\fR argument is a cdata \fIkernel_ulong_t\fR.
> +.TP
> +\fIstatus\fR = \fBstrace.entering\fR(\fItcp\fR)
> +Returns \fBtrue\fR if this is a syscall entry, and \fBfalse\fR otherwise.
> +.TP
> +\fIstatus\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 is a \fIboolean\fR, defaults to \fBtrue\fR.
> +.TP
> +\fIaddr\fR = \fBstrace.ptr_to_kulong\fR(\fIptr\fR)
> +Converts a cdata pointer to a \fBkernel_ulong_t\fR.
"to a kernel_ulong t variable" or "to kernel_ulong_t type".
> +.TP
> +\fBstrace.at_exit\fR(\fIfunc\fR)
> +Registers a function \fIfunc\fR to be run when \fBstrace\fR needs to
> +be terminated.
"strace is going to terminate its execution"
> +.TP
> +\fIname\fR = \fBstrace.get_err_name\fR(\fIerrno\fR, \fIpers_spec\fR)
> +Returns error name (e.g. \fB"ENOENT"\fR) as Lua string by its error number
> +\fIerrno\fR for personality specified by \fIpers_spec\fR, or \fBnil\fR if
> +\fIerrno\fR is invalid.
> +.IP
> +\fIerrno\fR is an \fIinteger\fR.
> +.TP
> +\fIname\fR = \fBstrace.get_sc_name\fR(\fIscno\fR, \fIpers_spec\fR)
> +Returns syscall name as Lua string by its number for personality specified by
> +\fIpers_spec\fR, or \fBnil\fR if \fIscno\fR is invalid.
> +.IP
> +\fIscno\fR is an \fIinteger\fR.
> +.TP
> +\fIname\fR = \fBstrace.get_ioctl_name\fR(\fIreqcode\fR, \fIpers_spec\fR)
> +Returns ioctl symbol name (e.g. \fB"TIOCGWINSZ"\fR) as Lua string by its request
> +code \fIreqcode\fR for personality specified by \fIpers_spec\fR, or \fBnil\fR if
> +\fIreqcode\fR is invalid.
> +.IP
> +\fIreqcode\fR is an \fIinteger\fR.
> +.TP
> +\fIscno\fR = \fBstrace.get_scno\fR(\fIscname\fR, \fIpers_spec\fR)
> +Returns syscall number by its name for personality specified by \fIpers_spec\fR,
> +or \fBnil\fR if no such syscall was found.
> +.IP
> +\fIscname\fR is a \fIstring\fR.
> +.TP
> +\fIsigno\fR = \fBstrace.get_signo\fR(\fIsigname\fR, \fIpers_spec\fR)
> +Returns signal number by its name (e.g. \fB"SIGSEGV"\fR) for personality
> +specified by \fIpers_spec\fR, or \fBnil\fR if no such signal was found.
> +.IP
> +\fIsigname\fR is \fIstring\fR.
> +.TP
> +\fIerrno\fR = \fBstrace.get_errno\fR(\fIerrname\fR, \fIpers_spec\fR)
> +Returns error number by its name (e.g. \fB"ENOENT"\fR) for personality specified
> +by \fIpers_spec\fR, or \fBnil\fR if no such error was found.
> +.IP
> +\fIsigname\fR is a \fIstring\fR.
> +.TP
> +\fIstatus\fR = \fBstrace.inject_signal\fR(\fItcp\fR, \fIsig\fR)
> +Delivers a signal to the tracee.
> +\fIsig\fR is either signal number (an \fIinteger\fR) or name (a \fIstring\fR).
> +.IP
> +Returns \fBtrue\fR on success and \fBfalse\fR on failure.
> +.IP
> +Note that this must be done on syscall entry.
> +.TP
> +\fIstatus\fR = \fBstrace.inject_error\fR(\fItcp\fR, \fIerr\fR)
> +Injects an error into a current syscall invocation.
> +\fIerr\fR is either error number (an \fIinteger\fR) or error name (a
> +\fIstring\fR).
> +.IP
> +Returns \fBtrue\fR on success and \fBfalse\fR on failure.
> +.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 \fIct\fR from the current 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
> +VLA/VLS types require the \fInelem\fR argument (an \fIinteger\fR).
> +.IP
> +Returns an object on success and \fBnil\fR on failure.
> +.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 \fIaddr\fR using an
> +intermediate buffer of size \fIbufsz\fR and stopping at \fImaxsz\fR bytes.
> +.IP
> +\fImaxsz\fR and \fIbufsz\fR are \fIintegers\fR.
> +\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(\fIaddr\fR)
> +Reads a path C string from the current 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
> +\fIstatus\fR = \fBstrace.hook\fR(\fIscname\fR, \fIwhen\fR, \fIcb\fR)
> +.TP
> +\fIstatus\fR = \fBstrace.hook_class\fR(\fIclsname\fR, \fIwhen\fR, \fIcb\fR)
> +.TP
> +\fIstatus\fR = \fBstrace.hook_scno\fR(\fIscno\fR, \fIwhen\fR, \fIcb\fR, \fIpers_spec\fR)
> +These functions register a function \fIcb\fR 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 specified by
> +\fIpers_spec\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
> +(\fIwhen\fR is a Lua string).
> +.IP
> +A pointer to the trace control block is passed as the only argument to \fIcb\fR.
> +.IP
> +\fIscname\fR and \fIclsname\fR are either \fIstrings\fR or tables thereof.
> +\fIscno\fR is enther an \fIinteger\fR or a table thereof.
> +.IP
> +Return \fBtrue\fR on success and \fBfalse\fR on failure.
> +.TP
> +\fIstatus\fR = \fBstrace.path_match\fR(\fIset\fR)
> +Returns \fBtrue\fR if the current syscall accesses a given path, or one of the
> +paths from the given set of paths; and \fBfalse\fR otherwise (see the note for
> +\fBstrace.C.path_match\fR).
> +.IP
> +\fIset\fR is either a \fIstring\fR 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 \fBstruct tcb::u_arg\fR array (an integer Lua number).
> +.TP
> +.B strace.path_max
> +Value of \fBPATH_MAX\fR constant (an integer Lua number).
> .SH DIAGNOSTICS
> When
> .I command
> diff --git a/strace.c b/strace.c
> index c503c3d1..e89a90d2 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 f21a1467..786ad78d 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;
> @@ -258,29 +318,55 @@ const struct_sysent *const sysent_vec[SUPPORTED_PERSONALITIES] = {
> #endif
> };
>
> +const int personality_wordsize[SUPPORTED_PERSONALITIES] = {
> + PERSONALITY0_WORDSIZE,
> #if SUPPORTED_PERSONALITIES > 1
> + PERSONALITY1_WORDSIZE,
> +#endif
> +#if SUPPORTED_PERSONALITIES > 2
> + PERSONALITY2_WORDSIZE,
> +#endif
> +};
> +
> +const int personality_klongsize[SUPPORTED_PERSONALITIES] = {
> + PERSONALITY0_KLONGSIZE,
> +#if SUPPORTED_PERSONALITIES > 1
> + PERSONALITY1_KLONGSIZE,
> +#endif
> +#if SUPPORTED_PERSONALITIES > 2
> + PERSONALITY2_KLONGSIZE,
> +#endif
> +};
> +
> +#if SUPPORTED_PERSONALITIES > 1
> +
> +const char *const personality_names[] =
> +# if defined POWERPC64
> + {"64 bit", "32 bit"}
> +# elif defined X86_64
> + {"64 bit", "32 bit", "x32"}
> +# elif defined X32
> + {"x32", "32 bit"}
> +# elif defined AARCH64
> + {"64 bit", "32 bit"}
> +# elif defined TILE
> + {"64-bit", "32-bit"}
> +# else
> +# error Add personality names for your achitecture.
I think, for all/most non-multipers architectures it can be generated as
{ #__WORDSIZE " bit" }
or something like this.
> +# endif
> + ;
> +
> unsigned current_personality;
>
> # ifndef current_wordsize
> unsigned current_wordsize;
> -static const int personality_wordsize[SUPPORTED_PERSONALITIES] = {
> - PERSONALITY0_WORDSIZE,
> - PERSONALITY1_WORDSIZE,
> -# if SUPPORTED_PERSONALITIES > 2
> - PERSONALITY2_WORDSIZE,
> # endif
> -};
> +# ifndef current_klongsize
> +unsigned current_klongsize;
> # endif
>
> # ifndef current_klongsize
> unsigned current_klongsize;
> -static const int personality_klongsize[SUPPORTED_PERSONALITIES] = {
> - PERSONALITY0_KLONGSIZE,
> - PERSONALITY1_KLONGSIZE,
> -# if SUPPORTED_PERSONALITIES > 2
> - PERSONALITY2_KLONGSIZE,
> -# endif
> -};
> # endif
>
> void
> @@ -343,25 +429,10 @@ update_personality(struct tcb *tcp, unsigned int personality)
> return;
> tcp->currpers = personality;
>
> -# undef PERSONALITY_NAMES
> -# if defined POWERPC64
> -# define PERSONALITY_NAMES {"64 bit", "32 bit"}
> -# elif defined X86_64
> -# define PERSONALITY_NAMES {"64 bit", "32 bit", "x32"}
> -# elif defined X32
> -# define PERSONALITY_NAMES {"x32", "32 bit"}
> -# elif defined AARCH64
> -# define PERSONALITY_NAMES {"64 bit", "32 bit"}
> -# elif defined TILE
> -# define PERSONALITY_NAMES {"64-bit", "32-bit"}
> -# endif
> -# ifdef PERSONALITY_NAMES
> if (!qflag) {
> - static const char *const names[] = PERSONALITY_NAMES;
> error_msg("[ Process PID=%d runs in %s mode. ]",
> - tcp->pid, names[personality]);
> + tcp->pid, personality_names[personality]);
> }
> -# endif
> }
> #endif
>
> @@ -580,8 +651,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 +672,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 +695,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 +773,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 +801,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 +830,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 +875,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 +1105,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
So, what is (still) missing:
* NEWS entry
* Description, what personality is
* More examples, probably
More information about the Strace-devel
mailing list