[PATCH v7 1/1] Initial support for LuaJIT scripting

Victor Krapivensky krapivenskiy.va at phystech.edu
Thu Jul 6 12:17:06 UTC 2017


* Makefile.am: Build with LuaJIT if configured so.
(strace_SOURCES): Add defs_shared.h, ffi.h.
(luajit_lib.h): Auto-generate from luajit_lib.lua.
* configure.ac: Add new --with-luajit configure option.
* defs.h (TCB_AD_HOC_INJECT): new TCB flag.
(QUAL_HOOK_ENTRY, QUAL_HOOK_EXIT): new qual flags.
(struct tcb): If built with LuaJIT support, include currpers field even
if SUPPORTED_PERSONALITIES is 0.
If built with LuaJIT support, include new ad_hoc_inject_opts field.
Move definitions that need to be fed to LuaJIT's FFI to...
* defs_shared.h: ...new file.
* ffi.h: New file.
* luajit.h: Likewise.
* luajit_lib.lua: Likewise.
* qualify.c (syscall_classes): move syscall classes list to the global
scope, terminate it with a null entry.
(hook_entry_set, hook_exit_set): New sets (if built with LuaJIT
support).
(lookup_class): Use global, null entry-terminated list of syscall
classes.
(qual_flags): If built with LuaJIT support, return QUAL_HOOK_ENTRY and
QUAL_HOOK_EXIT flags.
(set_hook_qual): New function.
* strace.1 (LUAJIT SCRIPTING): New section.
* strace.c (alloctcb): update the condition of presence of currpers
field.
(droptcb): If built with LuaJIT support, free ad_hoc_inject_opts.
(init): New -l option (if built with LuaJIT support).
(main): run Lua script, if built with LuaJIT support and a script
was provided.
* syscall.c (errnoent_vec, nerrnoent_vec, signalent_vec,
nsignalent_vec, ioctlent_vec, nioctlent_vec): New global variables.
(tcb_inject_opts): introduce a second argument indicating whether tcp's
inject_vec should be copied from the global inject_vec if needed.
If built with LuaJIT support and TCB_AD_HOC_INJECT flag is set, return
tcp's ad_hoc_inject_opts.
(tamper_with_syscall_entering): Don't copy inject_vec here; instead,
pass true as a second argument to tcb_inject_opts.
(tamper_with_syscall_exiting): Pass false as a second argument to
tcb_inject_opts.
(syscall_ad_hoc_inject): New function.
(syscall_entering_trace): perform ad hoc injection even if the syscall
is not traced.
(syscall_exiting_decode): don't return 0 ("bail out") if exiting hook is
set up for this syscall, or if an ad hoc injection was performed.
Call tamper_with_syscall_exiting on success.
(syscall_exiting_trace): Don't call tamper_with_syscall_exiting, check
if the syscall is not traced again.
(syscall_exiting_finish): Clear TCB_AD_HOC_INJECT bit.
* sysent.h: Modify to support inclusion with FFI_CDEF.
---
 Makefile.am    |  17 +++
 configure.ac   |  36 +++++++
 defs.h         |  53 +++-------
 defs_shared.h  |  65 ++++++++++++
 ffi.h          |  19 ++++
 luajit.h       | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 luajit_lib.lua | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 qualify.c      |  84 +++++++++------
 strace.1       | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++
 strace.c       |  36 ++++++-
 syscall.c      | 117 ++++++++++++++++----
 sysent.h       |  24 ++++-
 12 files changed, 1293 insertions(+), 92 deletions(-)
 create mode 100644 defs_shared.h
 create mode 100644 ffi.h
 create mode 100644 luajit.h
 create mode 100644 luajit_lib.lua

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





More information about the Strace-devel mailing list