[PATCH v15 2/4] Add initial support for Lua scripting

Victor Krapivensky krapivenskiy.va at phystech.edu
Wed Sep 20 16:42:47 UTC 2017


* .gitignore: Add luajit_lib.h.
* Makefile.am: Build with LuaJIT if configured so.
(luajit_lib.h): Auto-generate from luajit_lib.lua.
* NEWS: New entry.
* configure.ac: Add new --with-luajit configure option.
* defs_shared.h (struct tcb): If built with LuaJIT support, include
currpers field even if SUPPORTED_PERSONALITIES is 1.
If built with LuaJIT support, add new ad_hoc_inject_data field.
* filter_qualify.c: (hook_entry_set, hook_exit_set): New sets (if built
with LuaJIT support).
* luajit.h: New file.
* luajit_lib.h: Likewise.
* strace.1.in (LUA SCRIPTING): New section.
* strace.c (alloctcb): update the condition of presence of currpers
field.
(init): New -l option (if built with LuaJIT support).
(main): run Lua script, if built with LuaJIT support and a script
was provided.
* syscall.c (tcb_inject_data): If built with LuaJIT support and, blend
the result with tcp's ad_hoc_inject_data.
---
 .gitignore       |   1 +
 Makefile.am      |  15 +
 NEWS             |   2 +
 configure.ac     |  36 +++
 defs.h           |   6 +
 defs_shared.h    |   5 +-
 filter_qualify.c |  44 +++
 luajit.h         | 355 +++++++++++++++++++++++
 luajit_lib.lua   | 464 +++++++++++++++++++++++++++++
 strace.1.in      | 871 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 strace.c         |  32 +-
 syscall.c        |  13 +
 12 files changed, 1842 insertions(+), 2 deletions(-)
 create mode 100644 luajit.h
 create mode 100644 luajit_lib.lua

diff --git a/.gitignore b/.gitignore
index 6b226fba..ae78c075 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@
 /libmpers-m32.a
 /libmpers-mx32.a
 /libstrace.a
+/luajit_lib.h
 /m32_funcs.h
 /m32_printer_decls.h
 /m32_printer_defs.h
diff --git a/Makefile.am b/Makefile.am
index 0ab788b8..6ca7a20f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -334,6 +334,12 @@ strace_LDFLAGS += $(libunwind_LDFLAGS)
 strace_LDADD += $(libunwind_LIBS)
 endif
 
+if USE_LUAJIT
+strace_SOURCES += luajit.h
+strace_CPPFLAGS += $(LUAJIT_CFLAGS)
+strace_LDADD += $(LUAJIT_LIBS)
+endif
+
 @CODE_COVERAGE_RULES@
 CODE_COVERAGE_BRANCH_COVERAGE = 1
 CODE_COVERAGE_GENHTML_OPTIONS = $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) \
@@ -862,6 +868,7 @@ EXTRA_DIST =				\
 	linux/xtensa/set_scno.c		\
 	linux/xtensa/syscallent.h	\
 	linux/xtensa/userent.h		\
+	luajit_lib.lua			\
 	mpers.awk			\
 	mpers.sh			\
 	mpers_test.sh			\
@@ -888,6 +895,9 @@ $(srcdir)/.version:
 strace_SOURCES_c = \
 	$(filter %.c,$(strace_SOURCES)) $(filter %.c,$(libstrace_a_SOURCES))
 
+luajit_lib.h: luajit_lib.lua
+	sed 's/["\\]/\\\0/g;s/.*/"\0\\n"/' $< > $@
+
 sys_func.h: $(patsubst %,$(srcdir)/%,$(strace_SOURCES_c))
 	for f in $^; do \
 		sed -n 's/^SYS_FUNC(.*/extern &;/p' $$f; \
@@ -967,6 +977,11 @@ CLEANFILES    = $(ioctl_redefs_h) $(ioctlent_h) $(mpers_preproc_files) \
 		native_printer_decls.h native_printer_defs.h printers.h sen.h sys_func.h
 DISTCLEANFILES = gnu/stubs-32.h gnu/stubs-x32.h
 
+if USE_LUAJIT
+BUILT_SOURCES += luajit_lib.h
+CLEANFILES += luajit_lib.h
+endif
+
 include scno.am
 
 $(strace_OBJECTS): scno.h
diff --git a/NEWS b/NEWS
index 2daf5bc3..efab6320 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ Noteworthy changes in release ?.?? (????-??-??)
 
 * Improvements
   * Updated lists of SOL_* and TCP_* constants.
+  * Added support for LuaJIT scripting (-l FILE option); see the LUA SCRIPTING
+    section of the manual page for documentation.
 
 Noteworthy changes in release 4.19 (2017-09-05)
 ===============================================
diff --git a/configure.ac b/configure.ac
index cb6571a3..2c166403 100644
--- a/configure.ac
+++ b/configure.ac
@@ -754,6 +754,42 @@ AC_SUBST(dl_LIBS)
 
 AC_PATH_PROG([PERL], [perl])
 
+dnl LuaJIT scripting support
+use_luajit=no
+force_luajit=no
+luajit_lib=luajit
+LUAJIT_LIBS=
+LUAJIT_CFLAGS=
+AC_ARG_WITH([luajit],
+	    [AS_HELP_STRING([--with-luajit],
+			    [build with LuaJIT scripting support, or use use Lua library with provided name])],
+			    [case "${withval}" in
+			     yes)   force_luajit=yes ;;
+			     check) ;;
+			     *)     force_luajit=yes; luajit_lib="${withval}" ;;
+		     esac],
+		     [:]
+		     )
+AS_IF([test "x$luajit_lib" != xno],
+      [PKG_CHECK_MODULES([LUAJIT],
+			 [$luajit_lib],
+			 [use_luajit=yes],
+			 [AS_IF([test "x$force_luajit" = xyes],
+				[AC_MSG_ERROR([cannot find luajit library: $luajit_lib])]
+				)]
+			 )]
+      )
+
+dnl enable LuaJIT
+AC_MSG_CHECKING([whether to enable Lua scripting])
+if test "x$use_luajit" = xyes; then
+	AC_DEFINE([USE_LUAJIT], 1, [Enable Lua scripting support])
+	AC_SUBST(LUAJIT_LIBS)
+	AC_SUBST(LUAJIT_CFLAGS)
+fi
+AM_CONDITIONAL([USE_LUAJIT], [test "x$use_luajit" = xyes])
+AC_MSG_RESULT([$use_luajit])
+
 dnl stack trace with libunwind
 libunwind_CPPFLAGS=
 libunwind_LDFLAGS=
diff --git a/defs.h b/defs.h
index b6828748..ec14cd84 100644
--- a/defs.h
+++ b/defs.h
@@ -628,6 +628,12 @@ extern void print_ifindex(unsigned int);
 extern void qualify(const char *);
 extern unsigned int qual_flags(const unsigned int);
 
+#ifdef USE_LUAJIT
+extern void set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook,
+	bool exit_hook);
+extern void set_hook_qual_all(bool entry_hook, bool exit_hook);
+#endif
+
 #define DECL_IOCTL(name)						\
 extern int								\
 name ## _ioctl(struct tcb *, unsigned int request, kernel_ulong_t arg)	\
diff --git a/defs_shared.h b/defs_shared.h
index 1113fc0c..eb68f6cf 100644
--- a/defs_shared.h
+++ b/defs_shared.h
@@ -30,7 +30,7 @@ struct tcb {
 	kernel_long_t u_rval;	/* Return value */
 )
 
-#if SUPPORTED_PERSONALITIES > 1
+#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1
 FFI_CONTENT(
 	unsigned int currpers;	/* Personality at the time of scno update */
 )
@@ -46,6 +46,9 @@ FFI_CONTENT(
 	const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */
 	const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" msg */
 	struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
+#ifdef USE_LUAJIT
+	struct inject_data ad_hoc_inject_data;
+#endif
 	struct timeval stime;	/* System time usage as of last process wait */
 	struct timeval dtime;	/* Delta for system time usage */
 	struct timeval etime;	/* Syscall entry time */
diff --git a/filter_qualify.c b/filter_qualify.c
index 71766fd1..286f16bb 100644
--- a/filter_qualify.c
+++ b/filter_qualify.c
@@ -40,6 +40,10 @@ static struct number_set *inject_set;
 static struct number_set *raw_set;
 static struct number_set *trace_set;
 static struct number_set *verbose_set;
+#ifdef USE_LUAJIT
+static struct number_set *hook_entry_set;
+static struct number_set *hook_exit_set;
+#endif
 
 static int
 sigstr_to_uint(const char *s)
@@ -357,6 +361,42 @@ qualify(const char *str)
 	opt->qualify(str);
 }
 
+#ifdef USE_LUAJIT
+static void
+alloc_hook_sets(void)
+{
+	if (!hook_entry_set)
+		hook_entry_set = alloc_number_set_array(
+			SUPPORTED_PERSONALITIES);
+	if (!hook_exit_set)
+		hook_exit_set = alloc_number_set_array(
+			SUPPORTED_PERSONALITIES);
+}
+
+void
+set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook,
+	      bool exit_hook)
+{
+	alloc_hook_sets();
+	if (entry_hook)
+		extend_set_array_with_number(scno, hook_entry_set, pers);
+	if (exit_hook)
+		extend_set_array_with_number(scno, hook_exit_set, pers);
+}
+
+void
+set_hook_qual_all(bool entry_hook, bool exit_hook)
+{
+	alloc_hook_sets();
+	if (entry_hook)
+		make_number_set_array_universal(hook_entry_set,
+			SUPPORTED_PERSONALITIES);
+	if (exit_hook)
+		make_number_set_array_universal(hook_exit_set,
+			SUPPORTED_PERSONALITIES);
+}
+#endif
+
 unsigned int
 qual_flags(const unsigned int scno)
 {
@@ -368,6 +408,10 @@ qual_flags(const unsigned int scno)
 		| QUALBIT(verbose_set, QUAL_VERBOSE)
 		| QUALBIT(raw_set, QUAL_RAW)
 		| QUALBIT(inject_set, QUAL_INJECT)
+#ifdef USE_LUAJIT
+		| QUALBIT(hook_entry_set, QUAL_HOOK_ENTRY)
+		| QUALBIT(hook_exit_set, QUAL_HOOK_EXIT)
+#endif
 		;
 #undef QUALBIT
 }
diff --git a/luajit.h b/luajit.h
new file mode 100644
index 00000000..a707446f
--- /dev/null
+++ b/luajit.h
@@ -0,0 +1,355 @@
+/*
+ * Should only be included from strace.c, so no include guards.
+ */
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#define L script_L
+
+static struct tcb *
+func_next_sc(void)
+{
+	static bool first = true;
+	if (!first) {
+		if (!current_tcp)
+			return NULL;
+		if (!dispatch_event(TE_SYSCALL_STOP_HOOK_EXIT, NULL, NULL,
+		    true))
+			goto term;
+	}
+	first = false;
+
+	while (1) {
+		int status;
+		siginfo_t si;
+		enum trace_event ret = next_event(&status, &si);
+		if (!dispatch_event(ret, &status, &si, true))
+			goto term;
+		if (ret == TE_SYSCALL_STOP && (current_tcp->flags & TCB_HOOK)) {
+			current_tcp->flags &= ~TCB_HOOK;
+			return current_tcp;
+		}
+	}
+
+term:
+	current_tcp = NULL;
+	return NULL;
+}
+
+static bool
+func_monitor(unsigned int scno, unsigned int pers, bool entry_hook,
+	     bool exit_hook)
+{
+	if (pers >= SUPPORTED_PERSONALITIES)
+		return false;
+	set_hook_qual(scno, pers, entry_hook, exit_hook);
+	return true;
+}
+
+static void
+prepare_ad_hoc_inject(void)
+{
+	if (!(current_tcp->flags & TCB_AD_HOC_INJECT)) {
+		current_tcp->ad_hoc_inject_data.flags = 0;
+		current_tcp->qual_flg |= QUAL_INJECT;
+		current_tcp->flags |= TCB_AD_HOC_INJECT;
+	}
+}
+
+static bool
+func_inject_signo(int signo)
+{
+	if (!current_tcp || exiting(current_tcp))
+		return false;
+	if (signo <= 0 || signo > SIGRTMAX)
+		return false;
+	prepare_ad_hoc_inject();
+	current_tcp->ad_hoc_inject_data.flags |= INJECT_F_SIGNAL;
+	current_tcp->ad_hoc_inject_data.signo = signo;
+	return true;
+}
+
+static bool
+func_inject_retval(int retval)
+{
+	if (!current_tcp || exiting(current_tcp))
+		return false;
+	if (retval < -MAX_ERRNO_VALUE)
+		return false;
+	prepare_ad_hoc_inject();
+	current_tcp->ad_hoc_inject_data.flags |= INJECT_F_RETVAL;
+	current_tcp->ad_hoc_inject_data.rval = retval;
+	return true;
+}
+
+static int
+func_umove(kernel_ulong_t addr, size_t len, void *laddr)
+{
+	return current_tcp ? umoven(current_tcp, addr, len, laddr) : -1;
+}
+
+static int
+func_umove_str(kernel_ulong_t addr, size_t len, char *laddr)
+{
+	return current_tcp ? umovestr(current_tcp, addr, len, laddr) : -1;
+}
+
+static bool
+func_path_match(const char **set, size_t nset)
+{
+	if (!current_tcp)
+		return false;
+	struct path_set s = {set, nset};
+	return pathtrace_match_set(current_tcp, &s);
+}
+
+static const char *
+get_lua_msg(int pos)
+{
+	const char *msg = lua_tostring(L, pos);
+	return msg ? msg : "(error object cannot be converted to string)";
+}
+
+static void
+assert_lua_impl(int ret, const char *expr, const char *file, int line)
+{
+	if (ret == 0)
+		return;
+	error_msg_and_die("assert_lua(%s) failed at %s:%d: %s", expr, file,
+		line, get_lua_msg(-1));
+}
+
+#define assert_lua(expr) assert_lua_impl(expr, #expr, __FILE__, __LINE__)
+
+static void
+check_lua(int ret)
+{
+	switch (ret) {
+	case 0:
+		break;
+	case LUA_ERRERR:
+		error_msg_and_die("lua: error while running error handler: %s",
+			get_lua_msg(-1));
+		break;
+	default:
+		error_msg_and_die("lua: %s", get_lua_msg(-1));
+	}
+}
+
+#ifdef LUA_FFILIBNAME
+# define FFILIBNAME LUA_FFILIBNAME
+#else
+/* non-LuaJIT */
+# define FFILIBNAME "ffi"
+#endif
+
+#ifdef LUA_BITLIBNAME
+# define BITLIBNAME LUA_BITLIBNAME
+#else
+/* Lua <= 5.1 (non-LuaJIT) */
+# define BITLIBNAME "bit"
+#endif
+
+#ifdef LUA_DBLIBNAME
+# define DBLIBNAME LUA_DBLIBNAME
+#else
+/* ? */
+# define DBLIBNAME "debug"
+#endif
+
+static int
+traceback_and_die(lua_State *arg_L)
+{
+	/* L: error */
+	const char *msg = get_lua_msg(1);
+	lua_getglobal(L, DBLIBNAME); /* L: error debug */
+	lua_getfield(L, -1, "traceback"); /* L: error debug traceback */
+	lua_pushstring(L, msg); /* L: error debug traceback msg */
+	lua_pushinteger(L, 2); /* L: error debug traceback msg level */
+	lua_call(L, 2, 1); /* L: error debug result */
+	const char *traceback = lua_tostring(L, -1);
+	error_msg_and_die("lua: %s", traceback ? traceback : msg);
+}
+
+static void
+init_luajit(const char *scriptfile)
+{
+	if (L)
+		/* already initialized? */
+		error_msg_and_help("multiple -l arguments");
+
+	if (!(L = luaL_newstate()))
+		error_msg_and_die("luaL_newstate failed (out of memory?)");
+
+	luaL_openlibs(L);
+
+	lua_getglobal(L, "require"); /* L: require */
+	lua_pushstring(L, FFILIBNAME); /* L: require str */
+	assert_lua(lua_pcall(L, 1, 1, 0)); /* L: ffi */
+	lua_getfield(L, -1, "cdef"); /* L: ffi cdef */
+	luaL_Buffer b;
+	luaL_buffinit(L, &b); /* L: ffi cdef ? */
+	{
+		char buf[128];
+		snprintf(buf, sizeof(buf),
+			"typedef int%d_t kernel_long_t;"
+			"typedef uint%d_t kernel_ulong_t;",
+			(int) sizeof(kernel_long_t) * 8,
+			(int) sizeof(kernel_ulong_t) * 8);
+		luaL_addstring(&b, buf); /* L: ffi cdef ? */
+	}
+	const char *defs =
+#define FFI_CDEF
+#include "sysent.h"
+#include "defs_shared.h"
+#undef FFI_CDEF
+	;
+	luaL_addstring(&b, defs); /* L: ffi cdef ? */
+	luaL_pushresult(&b); /* L: ffi cdef str */
+	assert_lua(lua_pcall(L, 1, 0, 0)); /* L: ffi */
+
+	lua_getfield(L, 1, "cast"); /* L: ffi cast */
+	lua_remove(L, 1); /* L: cast */
+
+	/*
+	 * Assigns a FFI cdata function pointer of type rettype (*)(__VA_ARGS__)
+	 * with value ptr, to the table on top of L's stack, under given name.
+	 * Assumes the ffi.cast function is at the bottom of the stack.
+	 */
+#define EXPOSE_FUNC(rettype, ptr, name, ...)				\
+	do {								\
+		rettype (*fptr_)(__VA_ARGS__) = ptr;			\
+		/* L: cast ? table */					\
+		lua_pushvalue(L, 1); /* L: cast ? table cast */		\
+		lua_pushstring(L, #rettype " (*)(" #__VA_ARGS__ ")");	\
+		/* L: cast ? table cast str */				\
+		lua_pushlightuserdata(L, * (void **) (&fptr_));		\
+		/* L: cast ? table cast str ptr */			\
+		assert_lua(lua_pcall(L, 2, 1, 0));			\
+		/* L: cast ? table value */				\
+		lua_setfield(L, -2, name); /* L: cast ? table */	\
+	} while (0)
+
+	/*
+	 * Assigns a FFI cdata pointer of given type, to the table on top of L's
+	 * stack, under given name.
+	 * Assumes the ffi.cast function is at the bottom of the stack.
+	 */
+#define EXPOSE(type, ptr, name)						\
+	do {								\
+		/* Get a compilation error/warning on type mismatch */	\
+		type tmp_ = ptr;					\
+		(void) tmp_;						\
+		/* L: cast ? table */					\
+		lua_pushvalue(L, 1); /* L: cast ? table cast */		\
+		lua_pushstring(L, #type);				\
+		/* L: cast ? table cast str */				\
+		lua_pushlightuserdata(L, (void *) ptr);			\
+		/* L: cast ? table cast str ptr */			\
+		assert_lua(lua_pcall(L, 2, 1, 0));			\
+		/* L: cast ? table cast value */			\
+		lua_setfield(L, -2, name); /* L: cast ? table */	\
+	} while (0)
+
+	lua_newtable(L); /* L: cast strace */
+	lua_newtable(L); /* L: cast strace C */
+
+	EXPOSE_FUNC(bool, func_monitor, "monitor",
+		unsigned int, unsigned int, bool, bool);
+	EXPOSE_FUNC(void, set_hook_qual_all, "monitor_all",
+		bool, bool);
+	EXPOSE_FUNC(bool, func_inject_signo, "inject_signo",
+		int);
+	EXPOSE_FUNC(bool, func_inject_retval, "inject_retval",
+		int);
+	EXPOSE_FUNC(int, func_umove, "umove",
+		kernel_ulong_t, size_t, void *);
+	EXPOSE_FUNC(int, func_umove_str, "umove_str",
+		kernel_ulong_t, size_t, char *);
+	EXPOSE_FUNC(bool, func_path_match, "path_match",
+		const char **, size_t);
+
+	EXPOSE(const struct_sysent *const *, sysent_vec, "sysent_vec");
+	EXPOSE(const char *const **, errnoent_vec, "errnoent_vec");
+	EXPOSE(const char *const **, signalent_vec, "signalent_vec");
+	EXPOSE(const struct_ioctlent *const *, ioctlent_vec, "ioctlent_vec");
+
+	EXPOSE(const unsigned int *, nsyscall_vec, /*(!)*/ "nsysent_vec");
+	EXPOSE(const unsigned int *, nerrnoent_vec, "nerrnoent_vec");
+	EXPOSE(const unsigned int *, nsignalent_vec, "nsignalent_vec");
+	EXPOSE(const unsigned int *, nioctlent_vec, "nioctlent_vec");
+
+	EXPOSE(const struct syscall_class *, syscall_classes,
+		"syscall_classes");
+
+#if SUPPORTED_PERSONALITIES == 1
+	static const char *const personality_names[] = {#__WORDSIZE " bit"};
+#endif
+	EXPOSE(const char *const *, personality_names, "pers_name");
+	EXPOSE(const int *, personality_wordsize, "pers_wordsize");
+	EXPOSE(const int *, personality_klongsize, "pers_klongsize");
+
+	lua_setfield(L, -2, "C"); /* L: cast strace */
+
+	lua_pushinteger(L, SUPPORTED_PERSONALITIES); /* L: cast strace int */
+	lua_setfield(L, -2, "npersonalities"); /* L: cast strace */
+
+	lua_pushinteger(L, MAX_ARGS); /* L: cast strace int */
+	lua_setfield(L, -2, "max_args"); /* L: cast strace */
+
+	lua_pushinteger(L, PATH_MAX); /* L: cast strace int */
+	lua_setfield(L, -2, "path_max"); /* L: cast strace */
+
+	lua_setglobal(L, "strace"); /* L: cast */
+
+	const char *code =
+#include "luajit_lib.h"
+	;
+	assert_lua(luaL_loadstring(L, code)); /* L: cast chunk */
+
+	lua_newtable(L); /* L: cast chunk table */
+
+	lua_pushstring(L, FFILIBNAME); /* L: cast chunk table str */
+	lua_setfield(L, -2, "ffilibname"); /* L: cast chunk table */
+	lua_pushstring(L, BITLIBNAME); /* L: cast chunk table str */
+	lua_setfield(L, -2, "bitlibname"); /* L: cast chunk table */
+	lua_pushinteger(L, TCB_INSYSCALL); /* L: cast chunk table int */
+	lua_setfield(L, -2, "tcb_insyscall"); /* L: cast chunk table */
+	lua_pushinteger(L, QUAL_TRACE); /* L: cast chunk table int */
+	lua_setfield(L, -2, "qual_trace"); /* L: cast chunk table */
+	lua_pushinteger(L, QUAL_ABBREV); /* L: cast chunk table int */
+	lua_setfield(L, -2, "qual_abbrev"); /* L: cast chunk table */
+	lua_pushinteger(L, QUAL_VERBOSE); /* L: cast chunk table int */
+	lua_setfield(L, -2, "qual_verbose"); /* L: cast chunk table */
+	lua_pushinteger(L, QUAL_RAW); /* L: cast chunk table int */
+	lua_setfield(L, -2, "qual_raw"); /* L: cast chunk table */
+
+	EXPOSE_FUNC(struct tcb *, func_next_sc, "next_sc",
+		void); /* L: cast chunk table */
+
+	lua_pushcfunction(L, traceback_and_die);
+	/* L: cast chunk table traceback_and_die */
+	lua_replace(L, 1); /* L: traceback_and_die chunk table */
+
+	assert_lua(lua_pcall(L, 1, 1, 1)); /* L: traceback_and_die func */
+
+	check_lua(luaL_loadfile(L, scriptfile));
+	/* L: traceback_and_die func chunk */
+#undef EXPOSE_FUNC
+#undef EXPOSE
+}
+
+static void ATTRIBUTE_NORETURN
+run_luajit(void)
+{
+	/* L: traceback_and_die func chunk */
+	check_lua(lua_pcall(L, 0, 0, 1)); /* L: traceback_and_die func */
+	check_lua(lua_pcall(L, 0, 0, 1)); /* L: traceback_and_die */
+	terminate();
+}
+
+#undef FFILIBNAME
+#undef BITLIBNAME
+#undef assert_lua
+#undef L
diff --git a/luajit_lib.lua b/luajit_lib.lua
new file mode 100644
index 00000000..e3710627
--- /dev/null
+++ b/luajit_lib.lua
@@ -0,0 +1,464 @@
+-- This "chunk" of code is loaded and run before the script is.
+--
+-- To quote https://www.lua.org/manual/5.1/manual.html#2.4.1,
+-- "Lua handles a chunk as the body of an anonymous function with a variable
+--  number of arguments (see §2.5.9). As such, chunks can define local
+--  variables, receive arguments, and return values."
+--
+-- Thanks to Lua's support for closures, all the local variables defined here
+-- will not leak to another chunks (i.e., the script), but all the functions
+-- defined here can still access them.
+--
+-- strace calls this chunk with a single argument: a table with data that should
+-- not be exposed to the script, but is needed for some API functions defined
+-- here.
+--
+-- strace expects this chunk to return another function that will be run after
+-- the script returns.
+--
+-- Arguments passed to this chunk are accessible through the "..." vararg
+-- expression. The following line uses Lua's "adjust" assignment semantics to
+-- assign the first argument to a local variable "priv".
+local priv = ...
+
+local ffi = require(priv.ffilibname)
+ffi.cdef[[
+int strcmp(const char *, const char *);
+]]
+local bit = require(priv.bitlibname)
+
+local at_exit_cb
+local entry_cbs, exit_cbs = {}, {}
+for p = 0, strace.npersonalities - 1 do
+	entry_cbs[p] = {}
+	exit_cbs[p] = {}
+end
+
+local tcp
+
+local nullptr = ffi.NULL
+pcall(function()
+	nullptr = ffi.C.NULL
+end)
+
+local function chain(f, g)
+	if f == nil then
+		return g
+	end
+	return function(...)
+		f(...)
+		g(...)
+	end
+end
+
+local function register_hook(scno, pers, on_entry, on_exit, cb)
+	assert(not not strace.C.monitor(scno, pers, on_entry, on_exit),
+		'unexpected strace.C.monitor failure')
+	scno, pers = tonumber(scno), tonumber(pers)
+	if on_entry then
+		entry_cbs[pers][scno] = chain(entry_cbs[pers][scno], cb)
+	end
+	if on_exit then
+		exit_cbs[pers][scno] = chain(exit_cbs[pers][scno], cb)
+	end
+end
+
+local priv_next_sc = priv.next_sc
+
+function strace.next_sc()
+	local ptr = priv_next_sc()
+	tcp = ptr ~= nullptr and ptr or nil
+	return tcp
+end
+
+function strace.entering()
+	return bit.band(tcp.flags, priv.tcb_insyscall) == 0
+end
+
+function strace.exiting()
+	return bit.band(tcp.flags, priv.tcb_insyscall) ~= 0
+end
+
+local function alter_trace_opt(flagbit, ...)
+	assert(strace.entering(),
+		'altering tracing options must be done on syscall entry')
+	-- i.e., if ... is empty, or the first element of ... is true
+	if select('#', ...) == 0 or select(1, ...) then
+		tcp.qual_flg = bit.bor(tcp.qual_flg, flagbit)
+	else
+		tcp.qual_flg = bit.band(tcp.qual_flg, bit.bnot(flagbit))
+	end
+end
+function strace.trace(...)	alter_trace_opt(priv.qual_trace, ...)	end
+function strace.abbrev(...)	alter_trace_opt(priv.qual_abbrev, ...)	end
+function strace.verbose(...)	alter_trace_opt(priv.qual_verbose, ...)	end
+function strace.raw(...)	alter_trace_opt(priv.qual_raw, ...)	end
+
+local ulong_t = ffi.typeof('unsigned long')
+local kulong_t = ffi.typeof('kernel_ulong_t')
+
+function strace.ptr_to_kulong(ptr)
+	return ffi.cast(kulong_t, ffi.cast(ulong_t, ptr))
+end
+
+function strace.at_exit(f)
+	at_exit_cb = chain(at_exit_cb, f)
+end
+
+local function validate_pers(pers)
+	assert(pers >= 0 and pers < strace.npersonalities,
+		'invalid personality number')
+end
+
+function strace.get_sc_name(scno, pers)
+	validate_pers(pers)
+	if scno < 0 or scno >= strace.C.nsysent_vec[pers] then
+		return nil
+	end
+	local s = strace.C.sysent_vec[pers][scno].sys_name
+	return s ~= nullptr and ffi.string(s) or nil
+end
+
+function strace.get_sc_flags(scno, pers)
+	validate_pers(pers)
+	if scno < 0 or scno >= strace.C.nsysent_vec[pers] then
+		return nil
+	end
+	local entry = strace.C.sysent_vec[pers][scno]
+	return entry.sys_name ~= nullptr and entry.sys_flags or nil
+end
+
+function strace.get_sig_name(signo, pers)
+	validate_pers(pers)
+	if signo < 0 or signo >= strace.C.nsignalent_vec[pers] then
+		return nil
+	end
+	local s = strace.C.signalent_vec[pers][signo]
+	return s ~= nullptr and ffi.string(s) or nil
+end
+
+function strace.get_err_name(err, pers)
+	validate_pers(pers)
+	if err < 0 or err > strace.C.nerrnoent_vec[pers] then
+		return nil
+	end
+	local s = strace.C.errnoent_vec[pers][err]
+	return s ~= nullptr and ffi.string(s) or nil
+end
+
+local uint_t = ffi.typeof('unsigned int')
+
+function strace.get_ioctl_name(code, pers)
+	validate_pers(pers)
+	-- we could have provided a definition for stdlib's bsearch() and used
+	-- it, but LuaJIT's FFI manual says generated callbacks are a limited
+	-- resource and also slow. So implement binary search ourselves.
+	local lb, rb = uint_t(0), strace.C.nioctlent_vec[pers]
+	if rb == 0 then
+		return nil
+	end
+	local arr = strace.C.ioctlent_vec[pers]
+	while rb - lb > 1 do
+		local mid = lb + (rb - lb) / 2
+		if arr[mid].code <= code then
+			lb = mid
+		else
+			rb = mid
+		end
+	end
+	return arr[lb].code == code and ffi.string(arr[lb].symbol) or nil
+end
+
+local const_cstr_t = ffi.typeof('const char *')
+
+local function validate_cstr_initializer(s)
+	-- ffi.istype() ignores constness, so the following accepts a cdata
+	-- (non-const) char * as well.
+	assert(type(s) == 'string' or ffi.istype(const_cstr_t, s),
+		'expected either a Lua string or a cdata C string')
+end
+
+function strace.get_class_flagbit(clsname)
+	validate_cstr_initializer(clsname)
+	local cstr = const_cstr_t(clsname)
+	local ptr = strace.C.syscall_classes
+	while ptr.name ~= nullptr do
+		if ffi.C.strcmp(ptr.name, cstr) == 0 then
+			return ptr.value
+		end
+		ptr = ptr + 1
+	end
+	return nil
+end
+
+function strace.get_ioctl_code(name, pers)
+	validate_cstr_initializer(name)
+	validate_pers(pers)
+	local cstr = const_cstr_t(name)
+	local arr = strace.C.ioctlent_vec[pers]
+	for i = 0, tonumber(strace.C.nioctlent_vec[pers]) - 1 do
+		if ffi.C.strcmp(arr[i].symbol, cstr) == 0 then
+			return arr[i].code
+		end
+	end
+	return nil
+end
+
+function strace.get_scno(scname, pers)
+	validate_cstr_initializer(scname)
+	validate_pers(pers)
+	local cstr = const_cstr_t(scname)
+	local arr = strace.C.sysent_vec[pers]
+	for i = 0, tonumber(strace.C.nsysent_vec[pers]) - 1 do
+		local s = arr[i].sys_name
+		if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
+			return i
+		end
+	end
+	return nil
+end
+
+function strace.get_signo(signame, pers)
+	validate_cstr_initializer(signame)
+	validate_pers(pers)
+	local cstr = const_cstr_t(signame)
+	local arr = strace.C.signalent_vec[pers]
+	for i = 0, tonumber(strace.C.nsignalent_vec[pers]) - 1 do
+		local s = arr[i]
+		if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
+			return i
+		end
+	end
+	return nil
+end
+
+function strace.get_errno(errname, pers)
+	validate_cstr_initializer(errname)
+	validate_pers(pers)
+	local cstr = const_cstr_t(errname)
+	local arr = strace.C.errnoent_vec[pers]
+	for i = 0, tonumber(strace.C.nerrnoent_vec[pers]) - 1 do
+		local s = arr[i]
+		if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
+			return i
+		end
+	end
+	return nil
+end
+
+function strace.inject_signal(sig)
+	local signo = tonumber(sig)
+	if signo == nil then
+		signo = strace.get_signo(sig, tcp.currpers)
+		if signo == nil then
+			return false, 'signal not found'
+		end
+	end
+	if not strace.C.inject_signo(signo) then
+		return false, 'cannot inject signal'
+	end
+	return true
+end
+
+function strace.inject_error(err)
+	local errno = tonumber(err)
+	if errno == nil then
+		errno = strace.get_errno(err, tcp.currpers)
+		if errno == nil then
+			return false, 'error not found'
+		end
+	end
+	if errno <= 0 or not strace.C.inject_retval(-errno) then
+		return false, 'cannot inject error'
+	end
+	return true
+end
+
+function strace.inject_retval(val)
+	if not strace.C.inject_retval(val) then
+		return false, 'cannot inject return value'
+	end
+	return true
+end
+
+local ptr_size = ffi.sizeof(const_cstr_t)
+
+-- Calls f(addr, ffi.sizeof(obj), P), where P is a pointer to obj (or to its
+-- copy; see below), and f is a cdata C function that takes a (possibly const)
+-- void * as the last argument.
+-- Returns the result of that call.
+-- This function may decide to make a copy of obj, and, in this case, returns
+-- that copy as an additional return value.
+local function call_ufunc_on(f, addr, obj)
+	local n = ffi.sizeof(obj)
+	if n ~= ptr_size then
+		-- either a structure/union, an array, a scalar, or a VLA
+		local is_ok, ret = pcall(f, addr, n, obj)
+		if is_ok then
+			-- not a scalar
+			return ret
+		end
+	end
+	-- either a structure/union, an array, a scalar, a VLA, or a pointer
+	local obj_t = ffi.typeof(obj)
+	-- FFI templating fails for VLAs
+	local is_ok, arr_t = pcall(ffi.typeof, '$ [1]', obj_t)
+	if is_ok then
+		-- not a VLA
+		local arr = arr_t(obj)
+		local ret = f(addr, n, arr)
+		return ret, obj_t(arr[0])
+	end
+	-- a VLA
+	return f(addr, n, obj)
+end
+
+function strace.read_obj(addr, ct, ...)
+	local obj = ffi.new(ct, ...)
+	local status, copy = call_ufunc_on(strace.C.umove, addr, obj)
+	return status == 0 and (copy or 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, 'read error'
+		elseif r == 0 then
+			maxsz = maxsz - bufsz
+			if maxsz < 0 then
+				return nil, 'string is too long'
+			end
+			t[#t + 1] = ffi.string(buf, bufsz)
+			addr = addr + bufsz
+		else
+			local s = ffi.string(buf)
+			if #s > maxsz then
+				return nil, 'string is too long'
+			end
+			t[#t + 1] = s
+			return table.concat(t)
+		end
+	end
+end
+
+function strace.read_path(addr)
+	return strace.read_str(addr, strace.path_max - 1, strace.path_max)
+end
+
+local function parse_when(when)
+	if type(when) == 'table' then
+		return unpack(when)
+	elseif when == 'entering' then
+		return true, false
+	elseif when == 'exiting' then
+		return false, true
+	elseif when == 'both' then
+		return true, true
+	else
+		error('unknown "when" value')
+	end
+end
+
+local function reduce_or(f, args, ...)
+	local ret = false
+	for _, arg in ipairs(args) do
+		if f(arg, ...) then
+			ret = true
+		end
+	end
+	return ret
+end
+
+function strace.hook(scname, when, cb)
+	local on_entry, on_exit = parse_when(when)
+	if type(scname) == 'table' then
+		return reduce_or(strace.hook, scname, {on_entry, on_exit}, cb)
+	end
+	local found = false
+	for p = 0, strace.npersonalities - 1 do
+		local scno = strace.get_scno(scname, p)
+		if scno ~= nil then
+			register_hook(scno, p, on_entry, on_exit, cb)
+			found = true
+		end
+	end
+	return found
+end
+
+function strace.hook_class(clsname, when, cb)
+	local on_entry, on_exit = parse_when(when)
+	if type(clsname) == 'table' then
+		return reduce_or(strace.hook_class, clsname,
+			{on_entry, on_exit}, cb)
+	end
+	local flag = strace.get_class_flagbit(clsname)
+	if flag == nil then
+		return false
+	end
+	for p = 0, strace.npersonalities - 1 do
+		local arr = strace.C.sysent_vec[p]
+		for i = 0, tonumber(strace.C.nsysent_vec[p]) - 1 do
+			if bit.band(arr[i].sys_flags, flag) ~= 0 then
+				register_hook(i, p, on_entry, on_exit, cb)
+			end
+		end
+	end
+	return true
+end
+
+function strace.hook_scno(scno, pers, when, cb)
+	validate_pers(pers)
+	local on_entry, on_exit = parse_when(when)
+	if type(scno) == 'table' then
+		return reduce_or(strace.hook_scno, scno, pers,
+			{on_entry, on_exit}, cb)
+	end
+	if scno < 0 then
+		return false
+	end
+	register_hook(scno, pers, on_entry, on_exit, cb)
+	return true
+end
+
+function strace.path_match(set)
+	if type(set) ~= 'table' then
+		set = {set}
+	end
+	for _, elem in pairs(set) do
+		validate_cstr_initializer(elem)
+	end
+	local nset = #set
+	return not not strace.C.path_match(
+		ffi.new('const char *[?]', nset, set), nset)
+end
+
+function print(...)
+	local file = io.stderr
+	local sep = ''
+	for i = 1, select('#', ...) do
+		file:write(sep .. tostring(select(i, ...)))
+		sep = '\t'
+	end
+	file:write('\n')
+end
+
+return function()
+	local next_sc = strace.next_sc
+	local entering = strace.entering
+	while next_sc() ~= nil do
+		local cb = (entering() and entry_cbs or exit_cbs)
+			[tonumber(tcp.currpers)][tonumber(tcp.scno)]
+		if cb ~= nil then
+			cb(tcp)
+		end
+	end
+	if at_exit_cb ~= nil then
+		at_exit_cb()
+	end
+end
diff --git a/strace.1.in b/strace.1.in
index 61293cfa..a48f3cf2 100644
--- a/strace.1.in
+++ b/strace.1.in
@@ -53,6 +53,10 @@
 .  el \
 .    BR "\\$1"
 ..
+.de URL
+\\$2 \(laURL: \\$1 \(ra\\$3
+..
+.if \n[.g] .mso www.tmac
 .TH STRACE 1 "@MANPAGE_DATE@" "strace @VERSION@"
 .SH NAME
 strace \- trace system calls and signals
@@ -796,6 +800,873 @@ Print the help summary.
 .B \-V
 Print the version number of
 .BR strace .
+.SH LUA SCRIPTING
+If built with Lua support,
+.B strace
+can execute Lua scripts.
+A script file is passed to the
+.B -l
+option.
+.PP
+.B strace
+provides the built-in module
+.B strace
+to the Lua execution environment, which contains various functions and
+constants.
+.PP
+Before any tracing takes place, the script is run.
+At this stage, it can do one of the following two things:
+.RS
+.IP \(bu 3
+Implement its own tracing loop by selecting syscalls it wants to be notified
+about with
+.BR strace.C.monitor / strace.C.monitor_all
+and calling
+.B strace.next_sc
+in a loop until it returns
+.B nil
+(or return earlier; in this case, the installed hooks for the remaining syscalls
+are run).
+.IP \(bu
+Install syscall and at-exit hooks with
+.BR strace.hook ", " strace.hook_class ", " strace.hook_scno " and " strace.at_exit .
+.RE
+.PP
+Then,
+.B strace
+enters its own tracing loop, and all the installed hooks are run.
+.SS Personalities
+A personality is an execution domain with its own sets of syscalls, signals and
+errors.
+A
+.I personality number
+is strace-specific 0-based index in the architecture-dependent list of
+personalities.
+.SS FFI
+.URL http://luajit.org/ext_ffi.html "FFI library"
+is an extension provided by LuaJIT;
+.URL https://github.com/jmckaskill/luaffi luaffi
+and
+.URL https://github.com/facebook/luaffifb luaffifb
+standalone implementations are also supported, but only for Lua 5.2.
+.PP
+.B strace
+provides the following FFI definitions:
+.CW
+typedef /* implementation-defined signed integer type   */ kernel_long_t;
+typedef /* implementation-defined unsigned integer type */ kernel_ulong_t;
+
+typedef struct sysent {
+    unsigned nargs;          /* Number of arguments */
+    int sys_flags;           /* Flags.  Currently, only meaningful in the
+                              * context of struct syscall_class::value field:
+                              * a syscall belongs to a class iff
+                              * (class.value & syscall.sys_flags) != 0. */
+    const char *sys_name;    /* Name */
+} struct_sysent;
+
+struct syscall_class {
+    const char *name;        /* Name */
+    unsigned int value;      /* Flag bit, see the comment on struct
+                              * sysent::sys_flags field. */
+};
+
+typedef struct ioctlent {
+    const char *symbol;
+    unsigned int code;
+} struct_ioctlent;
+
+/* Trace control block */
+struct tcb {
+    int pid;                              /* Tracee's PID */
+    unsigned long u_error;                /* Error code */
+    kernel_ulong_t scno;                  /* System call number */
+    kernel_ulong_t u_arg[/* MAX_ARGS */]; /* System call arguments */
+    kernel_ulong_t u_rval;                /* Return value */
+    unsigned int currpers;                /* Current personality */
+};
+.CE
+.SS strace module: C submodule: functions
+Note: be careful with boxed boolean values and use
+.B not not
+.I boxed_bool
+when in doubt.
+In particular, an
+.B assert
+on a boxed boolean will never raise an error.
+.TP
+\fIstatus\fR = \fBstrace.C.monitor\fR(\fIscno\fR, \fIpers\fR, \fIon_entry\fR, \fIon_exit\fR)
+C type:
+.B bool (*)(unsigned int, unsigned int, bool, bool)
+.IP
+Marks the syscall with number
+.I scno
+on personality
+.I pers
+as to be returned from
+.BR strace.next_sc .
+If
+.I on_entry
+is
+.BR true ,
+it is marked as to be returned on syscall entry, and if
+.I on_exit
+is
+.BR true ,
+it is marked as to be returned on syscall exit.
+.IP
+Note that this "marking" is a one-way process, and specifying
+.B false
+as any of the flags does not undo any previous calls to
+.BR strace.C.monitor / strace.C.monitor_all .
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fBstrace.C.monitor_all\fR(\fIon_entry\fR, \fIon_exit\fR)
+C type:
+.B void (*)(bool, bool)
+.IP
+Marks all syscalls on all personalities as to be returned from
+.BR strace.next_sc :
+on syscall entry if
+.I on_entry
+is
+.BR true ,
+and on syscall exit if
+.I on_exit
+is
+.BR true .
+See the note for
+.BR strace.C.monitor .
+.TP
+\fIstatus\fR = \fBstrace.C.inject_signo\fR(\fIsigno\fR)
+C type:
+.B bool (*)(int)
+.IP
+Delivers a signal with number
+.I signo
+to the current tracee.
+.IP
+Note that this must be done on syscall entry.
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fIstatus\fR = \fBstrace.C.inject_retval\fR(\fIval\fR)
+C type:
+.B bool (*)(int)
+.IP
+Injects a return value to the current syscall invocation.
+.IP
+Note that this must be done on syscall entry.
+.IP
+Returns
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fIstatus\fR = \fBstrace.C.umove\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR)
+C type:
+.B int (*)(kernel_ulong_t, size_t, void *)
+.IP
+Copies ("moves")
+.I len
+bytes of data from the current tracee process at address
+.I addr
+to a local address
+.IR laddr .
+.IP
+Returns 0 on success and \-1 on failure.
+.TP
+\fIstatus\fR = \fBstrace.C.umove_str\fR(\fIaddr\fR, \fIlen\fR, \fIladdr\fR)
+C type:
+.B int (*)(kernel_ulong_t, size_t, char *)
+.IP
+Like
+.BR strace.C.umove ,
+but makes the additional effort of looking for a terminating zero byte.
+Returns a negative value on failure, a positive value if a NUL was seen, and 0
+if
+.I len
+bytes were read but no NUL seen.
+.IP
+Note: there is no guarantee it won't overwrite some bytes in \fIladdr\fR after
+terminating NUL (but, of course, it never writes past
+.IR laddr[len-1] ).
+.TP
+\fIstatus\fR = \fBstrace.C.path_match\fR(\fIset\fR, \fInset\fR)
+C type:
+.B bool (*)(const char **, size_t)
+.IP
+Returns
+.B true
+if the current syscall accesses one of the paths from a given set of paths, and
+.B false
+otherwise.
+.IP
+Note: for string path arguments, the path is compared against the set; for file
+descriptor arguments, the absolute path to the file behind the file descriptor
+is compared against the set.
+.SS strace module: C submodule: constants
+.TP
+.B strace.C.sysent_vec
+Array of syscall tables for each of the supported personalities.
+\fBstrace.C.sysent_vec\fR[\fIpers\fR][\fIscno\fR] is a
+.B struct_sysent
+for syscall number
+.I scno
+on personality
+.IR pers .
+May contain null entries (which have a NULL
+.B sys_name
+field).
+.TP
+.B strace.C.errnoent_vec
+Array of error name tables for each of the supported personalities.
+\fBstrace.C.errnoent_vec\fR[\fIpers\fR][\fIerrno\fR] is either a null pointer or
+a C string with the name of error
+.I errno
+on personality
+.IR pers .
+.TP
+.B strace.C.signalent_vec
+Array of signal name tables for each of the supported personalities.
+\fBstrace.C.signalent_vec\fR[\fIpers\fR][\fIsigno\fR] is either a null pointer
+or a C string with the name of signal
+.I signo
+on personality
+.IR pers .
+.TP
+.B strace.C.ioctlent_vec
+Arrays of sorted known ioctl symbols, sorted by code, for each of the supported
+personalities.
+\fBstrace.C.ioctlent_vec\fR[\fIpers\fR][\fIi\fR] is the
+.IR i \-th,
+ranked by code,
+.B struct_ioctlent
+for personality
+.IR pers .
+.TP
+.B strace.C.nsysent_vec
+.TQ
+.B strace.C.nerrnoent_vec
+.TQ
+.B strace.C.nsignalent_vec
+.TQ
+.B strace.C.nioctlent_vec
+These are
+.BR strace.npersonalities \-sized
+arrays containing sizes of subarrays of
+.BR strace.C.sysent_vec ,
+.BR strace.C.errnoent_vec ,
+.BR strace.C.signalent_vec " and"
+.BR strace.C.ioctlent_vec ,
+respectively.
+.TP
+.B strace.C.syscall_classes
+Array of
+.BR "struct syscall_class" ,
+with a terminating null entry (which has a NULL
+.B name
+field).
+.TP
+.B strace.C.pers_name
+A
+.BR strace.npersonalities \-sized
+array of C strings with names for each personality.
+.TP
+.B strace.C.pers_wordsize
+.TQ
+.B strace.C.pers_klongsize
+These are
+.BR strace.npersonalities \-sized
+arrays with word and
+.I __kernel_long_t
+sizes for each personality.
+.SS strace module: functions
+Glossary:
+.RS
+.IP \(bu 3
+an
+.I integer
+means either an integer Lua number or a cdata integer type;
+.IP \(bu
+a
+.I boolean
+means either a Lua boolean or a cdata
+.BR bool ;
+.IP \(bu
+a
+.I string
+means either a Lua string or a cdata C string.
+.RE
+.PP
+General conventions:
+.RS
+.IP \(bu 3
+a
+.I pers
+argument is an
+.I integer
+specifying personality number;
+.IP \(bu
+an
+.I addr
+argument is a cdata
+.IR kernel_ulong_t .
+.RE
+.TP
+\fItcp\fR = \fBstrace.next_sc\fR()
+If this is not the first call to this function, performs tracing and tampering
+of the previously returned syscall.
+.IP
+Waits for the next monitored syscall to happen, and returns a pointer to its
+trace control block.
+This pointer is not persistent across different calls to this function and/or
+hook calls.
+.IP
+If
+.B strace
+needs to be terminated (e.g. last tracee has been terminated, or
+.B strace
+has been interrupted), returns
+.BR nil .
+Once it returned
+.BR nil ,
+all subsequent calls to it will also return
+.BR nil .
+.TP
+\fIstatus\fR = \fBstrace.entering\fR()
+Returns
+.B true
+if this is a syscall entry, and
+.B false
+otherwise.
+.TP
+\fIstatus\fR = \fBstrace.exiting\fR()
+Returns
+.B true
+if this is a syscall exit, and
+.B false
+otherwise.
+.TP
+\fBstrace.trace\fR([\fIflag\fR])
+.TQ
+\fBstrace.abbrev\fR([\fIflag\fR])
+.TQ
+\fBstrace.verbose\fR([\fIflag\fR])
+.TQ
+\fBstrace.raw\fR([\fIflag\fR])
+These functions alter corresponding trace options.
+.I flag
+is a
+.IR boolean ,
+defaults to
+.BR true .
+.TP
+\fIaddr\fR = \fBstrace.ptr_to_kulong\fR(\fIptr\fR)
+Converts a cdata pointer to a
+.B kernel_ulong_t
+variable.
+.TP
+\fBstrace.at_exit\fR(\fIfunc\fR)
+Registers a function
+.I func
+to be run when
+.B strace
+is going to terminate its execution.
+.IP
+If multiple exit callback functions are registered, they are run in the order
+they were registered.
+.TP
+\fIname\fR = \fBstrace.get_sc_name\fR(\fIscno\fR, \fIpers\fR)
+Returns syscall name as Lua string by its number for personality
+.IR pers ,
+or
+.B nil
+if
+.I scno
+is invalid.
+.IP
+.I scno
+is an
+.IR integer .
+.TP
+\fIname\fR = \fBstrace.get_sig_name\fR(\fIsigno\fR, \fIpers\fR)
+Returns signal name (.e.g \fB"SIGSEGV"\fR) as Lua string by its number
+.I signo
+for personality
+.IR pers ,
+or
+.B nil
+if
+.I signo
+is invalid.
+.TP
+\fIname\fR = \fBstrace.get_err_name\fR(\fIerrno\fR, \fIpers\fR)
+Returns error name (e.g. \fB"ENOENT"\fR) as Lua string by its error number
+.I errno
+for personality
+.IR pers ,
+or
+.B nil
+if
+.I errno
+is invalid.
+.IP
+.I errno
+is an
+.IR integer .
+.TP
+\fIname\fR = \fBstrace.get_ioctl_name\fR(\fIreqcode\fR, \fIpers\fR)
+Returns ioctl symbol name (e.g. \fB"TIOCGWINSZ"\fR) as Lua string by its request
+code
+.I reqcode
+for personality
+.IR pers ,
+or
+.B nil
+if
+.I reqcode
+is invalid.
+.IP
+.I reqcode
+is an
+.IR integer .
+.TP
+\fIscno\fR = \fBstrace.get_scno\fR(\fIscname\fR, \fIpers\fR)
+Returns syscall number by its name for personality
+.IR pers ,
+or
+.B nil
+if no such syscall was found.
+.IP
+.I scname
+is a
+.IR string .
+.TP
+\fIsigno\fR = \fBstrace.get_signo\fR(\fIsigname\fR, \fIpers\fR)
+Returns signal number by its name (e.g. \fB"SIGSEGV"\fR) for personality
+.IR pers ,
+or
+.B nil
+if no such signal was found.
+.IP
+.I signame
+is
+.IR string .
+.TP
+\fIerrno\fR = \fBstrace.get_errno\fR(\fIerrname\fR, \fIpers\fR)
+Returns error number by its name (e.g. \fB"ENOENT"\fR) for personality
+.IR pers ,
+or
+.B nil
+if no such error was found.
+.IP
+.I errname
+is a
+.IR string .
+.TP
+\fIcode\fR = \fBstrace.get_ioctl_code\fR(\fIname\fR, \fIpers\fR)
+Returns ioctl request code by its symbol name (e.g. \fB"TIOCGWINSZ"\fR) for
+personality
+.IR pers ,
+or
+.B nil
+if no such ioctl request was found.
+.IP
+.I name
+is a
+.IR string .
+.TP
+\fIflagbit\fR = \fBstrace.get_class_flagbit\fR(\fIclsname\fR)
+Returns the
+.I value
+field of the
+.I struct syscall_class
+structure
+(see its definition in the
+.B FFI
+section above) corresponding to the syscall class with name
+.IR clsname ,
+or
+.B nil
+if such syscall class is invalid.
+.I clsname
+is a
+.IR string .
+.TP
+\fIflags\fR = \fBstrace.get_sc_flags\fR(\fIscno\fR, \fIpers\fR)
+Returns the
+.I sys_flags
+field of the
+.I struct sysent
+structure
+(see its definition in the
+.B FFI
+section above)
+corresponding to the syscall with number
+.I scno
+on personality
+.IR pers ,
+or
+.B nil
+if no such syscall exists.
+.I scno
+is an
+.IR integer .
+.TP
+\fIstatus\fR[, \fIerr_msg\fR] = \fBstrace.inject_signal\fR(\fIsig\fR)
+Delivers a signal to the tracee.
+.I sig
+is either signal number (an
+.IR integer )
+or name (a
+.IR string ).
+.IP
+Returns
+.B true
+on success and
+.B false
+and an error message string on failure.
+.IP
+Note that this must be done on syscall entry.
+.TP
+\fIstatus\fR[, \fIerr_msg\fR] = \fBstrace.inject_retval\fR(\fIval\fR)
+Injects a return value to the current syscall invocation.
+.I val
+is an
+.IR integer .
+.IP
+Returns
+.B true
+on success and
+.B false
+and an error message string on failure.
+.IP
+Note that this must be done on syscall entry.
+.TP
+\fIstatus\fR[, \fIerr_msg\fR] = \fBstrace.inject_error\fR(\fIerr\fR)
+Injects an error into a current syscall invocation.
+.I err
+is either error number (an
+.IR integer )
+or error name (a
+.IR string ).
+.IP
+Returns
+.B true
+on success and
+.B false
+and an error message string 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
+.I ct
+from the current tracee process at address
+.IR addr .
+.I ct
+is either a
+.I cdecl
+(a Lua string), a
+.I cdata
+serving as a template type, or a
+.I ctype
+(special kind of
+.I cdata
+returned by
+.BR ffi.typeof ).
+.IP
+VLA/VLS types require the
+.I nelem
+argument (an
+.IR integer ).
+.IP
+Returns an object on success and
+.B nil
+on failure.
+.IP
+Note that type sizes and structure paddings may differ from FFI's ones if
+tracee's personality differs from
+.BR strace 's
+one.
+.TP
+\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_str\fR(\fIaddr\fR[, \fImaxsz\fR[, \fIbufsz\fR]])
+Reads a C string from the current tracee process at address
+.I addr
+using an intermediate buffer of size
+.I bufsz
+and stopping at
+.I maxsz
+bytes.
+.IP
+.I maxsz
+and
+.I bufsz
+are
+.IR integers .
+.I maxsz
+defaults to 4 Mb,
+.I bufsz
+to 1 Kb.
+.IP
+Returns a Lua string on success and
+.B nil
+and an error message string on failure.
+.TP
+\fIstr\fR[, \fIerr_msg\fR] = \fBstrace.read_path\fR(\fIaddr\fR)
+Reads a path C string from the current tracee process at address
+.IR addr ,
+stopping at
+.B PATH_MAX
+bytes.
+.IP
+Returns a Lua string on success and
+.B nil
+and an error message string on failure.
+.TP
+\fIstatus\fR = \fBstrace.hook\fR(\fIscname\fR, \fIwhen\fR, \fIcb\fR)
+.TQ
+\fIstatus\fR = \fBstrace.hook_class\fR(\fIclsname\fR, \fIwhen\fR, \fIcb\fR)
+.TQ
+\fIstatus\fR = \fBstrace.hook_scno\fR(\fIscno\fR, \fIpers\fR, \fIwhen\fR, \fIcb\fR)
+These functions register a function
+.I cb
+to be run when a syscall with the given name (or with a name from a given set
+thereof), belonging to a class with the given name (or with a name from a given
+set thereof), or with the given number (or with a number from a given set
+thereof) on personality
+.IR pers ,
+happens.
+.IP
+.I when
+is either a two-element array (that is, a table with keys 1 and 2) with boolean
+values, or a Lua string.
+.I cb
+is registered to be run:
+.RS
+.IP \(bu 3
+on syscall entry if
+.I when
+is either
+.B {true, false}
+or \fB"entering"\fR;
+.IP \(bu
+on syscall exit if
+.I when
+is either
+.B {false, true}
+or \fB"exiting"\fR;
+.IP \(bu
+both on syscall entry and exit if
+.I when
+is either
+.B {true, true}
+or \fB"both"\fR;
+.IP \(bu
+at no time if
+.I when
+is
+.B "{false, false}"
+(in this case, a call is effectively ignored).
+.RE
+.IP
+A pointer to the trace control block is passed as the only argument to
+.IR cb .
+This pointer is not persistent across
+.B strace.next_sc
+and/or another hook calls.
+.IP
+If multiple callback functions are registered for the same event (syscall entry
+or exit), they are run in the order they were registered.
+.IP
+.IR scname " and " clsname
+are either
+.I strings
+or tables thereof.
+.I scno
+is either an
+.I integer
+or a table thereof.
+.IP
+Return
+.B true
+on success and
+.B false
+on failure.
+.TP
+\fIstatus\fR = \fBstrace.path_match\fR(\fIset\fR)
+Returns
+.B true
+if the current syscall accesses a given path, or one of the paths from the given
+set of paths; and
+.B false
+otherwise (see the note for
+.BR strace.C.path_match ).
+.IP
+.I set
+is either a
+.I string
+or a table thereof.
+.SS strace module: constants
+.TP
+.B strace.npersonalities
+Number of supported personalities (an integer Lua number).
+.TP
+.B strace.max_args
+Size of
+.B struct tcb::u_arg
+array (an integer Lua number).
+.TP
+.B strace.path_max
+Value of
+.B PATH_MAX
+constant (an integer Lua number).
+.SS Examples
+The following script counts the number of processes (including threads) spawned
+by the tracee.
+Note that you would probably want to launch
+.B strace
+with
+.B -f
+option, so that children also be traced.
+.CW
+n = 0
+assert(strace.hook({'clone', 'fork', 'vfork'}, 'exiting', function(tcp)
+    if tcp.u_rval ~= -1 then
+        n = n + 1
+    end
+end))
+strace.at_exit(function() print('Processes spawned:', n) end)
+.CE
+If you have a C preprocessor installed, you can invoke it to pre-process local
+and/or system headers.
+However, using it this way, the only type of macros you can access is integer
+constant macros (possibly expanding to a constant arithmetic expression: the FFI
+library is able to evaluate them).
+To do so, declare an enumeration member or a static constant of an appropriate
+type with value being the result of expansion of the macro (see the following
+example).
+.PP
+The following script injects the
+.B EPERM
+error to each
+.B fcntl
+(and
+.BR fcntl64 )
+syscall invocation with
+.B F_SETPIPE_SZ
+command.
+.CW
+ffi = require 'ffi'
+f = assert(io.popen([[cpp - <<EOF | grep -v '^#'
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+enum { f_setpipe_sz = F_SETPIPE_SZ };
+
+EOF]], 'r'))
+ffi.cdef(f:read('*a'))
+f:close()
+
+assert(strace.hook({'fcntl', 'fcntl64'}, 'entering', function(tcp)
+    if tcp.u_arg[1] == ffi.C.f_setpipe_sz then
+        assert(strace.inject_error('EPERM'))
+    end
+end))
+.CE
+The following script reads the definition of a structure from a system header
+file, and modifies one of its fields so that
+.BR uname (1)
+always prints out
+.IR Windows .
+.CW
+ffi = require 'ffi'
+f = assert(io.popen([[cpp - <<EOF | grep -v '^#'
+
+#include <sys/utsname.h>
+
+EOF]], 'r'))
+ffi.cdef(f:read('*a'))
+f:close()
+
+assert(strace.hook('uname', 'exiting', function(tcp)
+    if tcp.u_rval == -1 then
+        return
+    end
+
+    local u = assert(strace.read_obj(tcp.u_arg[0], 'struct utsname'))
+
+    local s = 'Windows'
+    assert(ffi.sizeof(u.sysname) >= #s + 1)
+    ffi.copy(u.sysname, s)
+
+    assert(strace.write_obj(tcp.u_arg[0], u))
+end))
+.CE
+Note that this does not work if the name of the structure or one of its field is
+defined to something else.
+In the case of the first, however, something like
+.CW
+typedef struct utsname struct_utsname;
+.CE
+should work.
+.PP
+To access other types of macros (namely, floating-point and strings constants,
+and function macros), the
+.URL https://github.com/umegaya/ffiex "ffiex library"
+can be used.
+The following example gathers statistics on exit codes of child processes that
+have been waited for by the tracee with one of the appropriate syscalls with
+non-NULL
+.I status
+argument.
+.CW
+ffiex = require 'ffiex'
+ffiex.cdef[[
+#include <sys/wait.h>
+]]
+
+function is_truthy(x)
+    return x and x ~= 0
+end
+
+stats = {}
+
+assert(strace.hook({'waitpid', 'wait4', 'osf_wait4'}, 'exiting', function(tcp)
+    if tcp.u_rval == -1 or tcp.u_rval == 0 or tcp.u_arg[1] == 0 then
+        return
+    end
+    local status = tonumber(assert(strace.read_obj(tcp.u_arg[1], 'int')))
+    if is_truthy(ffiex.defs.WIFEXITED(status)) then
+        local c = ffiex.defs.WEXITSTATUS(status)
+        stats[c] = (stats[c] or 0) + 1
+    end
+end))
+
+strace.at_exit(function()
+    print('Exit codes:')
+    for k, v in pairs(stats) do
+        print(k .. ':', v)
+    end
+end)
+.CE
+Note that this does not work if the result of the expansion of the macro
+contains something that can not be evaluated by
+.BR ffiex ,
+e.g. a call to a
+.I static inline
+function defined in the header.
 .SH DIAGNOSTICS
 When
 .I command
diff --git a/strace.c b/strace.c
index 7b8a0e24..470b2768 100644
--- a/strace.c
+++ b/strace.c
@@ -45,6 +45,9 @@
 # include <sys/prctl.h>
 #endif
 #include <asm/unistd.h>
+#ifdef USE_LUAJIT
+# include <lua.h>
+#endif
 
 #include "number_set.h"
 #include "scno.h"
@@ -170,6 +173,11 @@ static volatile sig_atomic_t interrupted;
 static volatile int interrupted;
 #endif
 
+#ifdef USE_LUAJIT
+static lua_State *script_L = NULL;
+static void init_luajit(const char *scriptfile);
+#endif
+
 #ifndef HAVE_STRERROR
 
 #if !HAVE_DECL_SYS_ERRLIST
@@ -228,6 +236,11 @@ Output format:\n\
   -k             obtain stack trace between each syscall (experimental)\n\
 "
 #endif
+#ifdef USE_LUAJIT
+"\
+  -l file        run a Lua script from FILE\n\
+"
+#endif
 "\
   -o file        send trace output to FILE instead of stderr\n\
   -q             suppress messages about attaching, detaching, etc.\n\
@@ -713,7 +726,7 @@ alloctcb(int pid)
 		if (!tcp->pid) {
 			memset(tcp, 0, sizeof(*tcp));
 			tcp->pid = pid;
-#if SUPPORTED_PERSONALITIES > 1
+#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1
 			tcp->currpers = current_personality;
 #endif
 
@@ -1589,6 +1602,9 @@ init(int argc, char *argv[])
 #ifdef USE_LIBUNWIND
 		"k"
 #endif
+#ifdef USE_LUAJIT
+		"l:"
+#endif
 		"D"
 		"a:e:o:O:p:s:S:u:E:P:I:")) != EOF) {
 		switch (c) {
@@ -1699,6 +1715,11 @@ init(int argc, char *argv[])
 			stack_trace_enabled = true;
 			break;
 #endif
+#ifdef USE_LUAJIT
+		case 'l':
+			init_luajit(optarg);
+			break;
+#endif
 		case 'E':
 			if (putenv(optarg) < 0)
 				perror_msg_and_die("putenv");
@@ -2621,6 +2642,10 @@ terminate(void)
 	exit(exit_code);
 }
 
+#ifdef USE_LUAJIT
+# include "luajit.h"
+#endif
+
 int
 main(int argc, char *argv[])
 {
@@ -2628,6 +2653,11 @@ main(int argc, char *argv[])
 
 	exit_code = !nprocs;
 
+#ifdef USE_LUAJIT
+	if (script_L)
+		run_luajit();
+#endif
+
 	int status;
 	siginfo_t si;
 	while (dispatch_event(next_event(&status, &si), &status, &si, false))
diff --git a/syscall.c b/syscall.c
index 363d65c3..4ef88963 100644
--- a/syscall.c
+++ b/syscall.c
@@ -636,6 +636,19 @@ tcb_inject_data(struct tcb *tcp, bool step)
 			res = opts->data;
 		}
 	}
+#ifdef USE_LUAJIT
+	if (tcp->flags & TCB_AD_HOC_INJECT) {
+		struct inject_data ad_hoc_data = tcp->ad_hoc_inject_data;
+		if (ad_hoc_data.flags & INJECT_F_SIGNAL) {
+			res.flags |= INJECT_F_SIGNAL;
+			res.signo = ad_hoc_data.signo;
+		}
+		if (ad_hoc_data.flags & INJECT_F_RETVAL) {
+			res.flags |= INJECT_F_RETVAL;
+			res.rval = ad_hoc_data.rval;
+		}
+	}
+#endif
 	return res;
 }
 
-- 
2.11.0





More information about the Strace-devel mailing list