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