[PATCH v11 1/3] Initial support for LuaJIT scripting

Eugene Syromiatnikov esyr at redhat.com
Wed Aug 23 19:47:22 UTC 2017


On Tue, Aug 15, 2017 at 01:09:23AM +0300, Victor Krapivensky wrote:
> * .gitignore: Add luajit_lib.h.
> * Makefile.am: Build with LuaJIT if configured so.
> (strace_SOURCES): Add defs_shared.h, ffi.h.
> (luajit_lib.h): Auto-generate from luajit_lib.lua.
> * basic_filters.c (make_number_set_universal, is_number_set_universal):
> New functions.
> (syscall_classes): Make global and terminate wit a null entry.
> (lookup_class): Use global syscall_classes.
> * configure.ac: Add new --with-luajit configure option.
> * defs.h (struct inject_opts): Move inject values to...
> (struct inject_values): ...new struct.
> (TCB_AD_HOC_INJECT, TCB_HOOK): New TCB flags.
> (QUAL_HOOK_ENTRY, QUAL_HOOK_EXIT): New qual flags.
> (RVAL_HOOKED): New return value flag.
> (struct tcb): If built with LuaJIT support, include currpers field even
> if SUPPORTED_PERSONALITIES is 1.
> If built with LuaJIT support, include new ad_hoc_inject_vals field.
> Move definitions that need to be fed to LuaJIT's FFI to...
> * defs_shared.h: ...new file.
> * ffi.h: New file.
> * luajit.h: Likewise.
> * luajit_lib.lua: Likewise.
> * filter_qualify.c: (hook_entry_set, hook_exit_set): New sets (if built
> with LuaJIT support).
> (parse_inject_token, qualify_inject_common): Update for new layout of
> struct inject_opts.
> (set_hook_qual, set_hook_qual_all): New functions (if built with LuaJIT
> support).
> (qual_flags): If built with LuaJIT support, return QUAL_HOOK_ENTRY and
> QUAL_HOOK_EXIT flags.
> * strace.1 (LUA SCRIPTING): New section.
> * strace.c (alloctcb): update the condition of presence of currpers
> field.
Btw, looks like there are other places that touch that field, wouldn't
it better to provide setter/getter for this field which would resolve to
null function in case no currpers field present?  Otherwise it's quite
difficult to maintain currpers-touching code and decide which macros
should be checked in each case.

> (init): New -l option (if built with LuaJIT support).
> (enum trace_event): New TE_SYSCALL_STOP_HOOK_EXIT entry.
> (enum hook_state): New enum.
> (trace_syscall): Introduce state argument.
> (dispatch_event): Introduce hooked argument, support
> TE_SYSCALL_STOP_HOOK_EXIT as first argument.
> (main): run Lua script, if built with LuaJIT support and a script
> was provided.
> * syscall.c (errnoent_vec, nerrnoent_vec, signalent_vec,
> nsignalent_vec, ioctlent_vec, nioctlent_vec, personality_wordsize,
> personality_klongsize): New global variables.
> (personality_names): New global variable (if SUPPORTED_PERSONALITIES >
> 1).
> (update_personality): use personality_names for reporting personality
> name.
> (tcb_inject_opts): Introduce step argument, change return type to struct
> inject_values, rename to tcb_inject_values.
> If built with LuaJIT support and TCB_AD_HOC_INJECT flag is set, blend
> the result with tcp's ad_hoc_inject_vals.
> (tamper_with_syscall_entering): Don't copy inject_vec here and do
> counter decrement logic here; pass true as a second argument to
> tcb_inject_values.
> (tamper_with_syscall_exiting): Pass false as a second argument to
> tcb_inject_values.
> (syscall_ad_hoc_inject): New function.
> (syscall_entering_trace): perform ad hoc injection even if the syscall
> is not traced.
> (syscall_exiting_decode): don't return 0 ("bail out") if exiting hook is
> set up for this syscall, or if an ad hoc injection was performed.
> Call tamper_with_syscall_exiting on success.
> (syscall_exiting_trace): Don't call tamper_with_syscall_exiting, check
> if the syscall is not traced again.
> (syscall_exiting_finish): Clear TCB_AD_HOC_INJECT bit.
> * sysent.h: Modify to support inclusion with FFI_CDEF.
> ---
>  .gitignore       |   1 +
>  Makefile.am      |  17 ++
>  basic_filters.c  |  84 ++++---
>  configure.ac     |  36 +++
>  defs.h           |  73 +++---
>  defs_shared.h    |  66 +++++
>  ffi.h            |  19 ++
>  filter_qualify.c |  57 ++++-
>  luajit.h         | 311 ++++++++++++++++++++++++
>  luajit_lib.lua   | 383 +++++++++++++++++++++++++++++
>  strace.1         | 724 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  strace.c         |  95 ++++++--
>  syscall.c        | 234 +++++++++++++-----
>  sysent.h         |  24 +-
>  14 files changed, 1961 insertions(+), 163 deletions(-)
>  create mode 100644 defs_shared.h
>  create mode 100644 ffi.h
>  create mode 100644 luajit.h
>  create mode 100644 luajit_lib.lua
> 
> diff --git a/.gitignore b/.gitignore
> index 0885bcb5..a5d0c361 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -32,6 +32,7 @@
>  /libmpers-m32.a
>  /libmpers-mx32.a
>  /libstrace.a
> +/luajit_lib.h
>  /m32_funcs.h
>  /m32_printer_decls.h
>  /m32_printer_defs.h
> diff --git a/Makefile.am b/Makefile.am
> index 5b2b93a0..280c176e 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -107,6 +107,7 @@ strace_SOURCES =	\
>  	copy_file_range.c \
>  	count.c		\
>  	defs.h		\
> +	defs_shared.h	\
>  	desc.c		\
>  	dirent.c	\
>  	dirent64.c	\
> @@ -131,6 +132,7 @@ strace_SOURCES =	\
>  	fetch_struct_stat.c \
>  	fetch_struct_stat64.c \
>  	fetch_struct_statfs.c \
> +	ffi.h		\
>  	file_handle.c	\
>  	file_ioctl.c	\
>  	filter_qualify.c \
> @@ -310,6 +312,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) \
> @@ -838,6 +846,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			\
> @@ -864,6 +873,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; \
> @@ -942,6 +954,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/basic_filters.c b/basic_filters.c
> index 1dbdeac8..6ea4dd00 100644
> --- a/basic_filters.c
> +++ b/basic_filters.c
> @@ -69,6 +69,30 @@ add_number_to_set(const unsigned int number, struct number_set *const set)
>  	number_setbit(number, set->vec);
>  }
>  
> +void
> +make_number_set_universal(struct number_set *const set)
> +{
> +	free(set->vec);
> +	*set = (struct number_set) {
> +		.vec = NULL,
> +		.nslots = 0,
> +		.not = true,
> +	};
> +}
> +
> +bool
> +is_number_set_universal(const struct number_set *const set)
> +{
> +	unsigned int i;
> +
> +	if (!set->not)
> +		return false;
> +	for (i = 0; i < set->nslots; ++i)
> +		if (set->vec[i])
> +			return false;
> +	return true;
> +}
> +
>  bool
>  is_number_in_set(const unsigned int number, const struct number_set *const set)
>  {
> @@ -139,40 +163,38 @@ qualify_syscall_regex(const char *s, struct number_set *set)
>  	return found;
>  }
>  
> +const struct syscall_class syscall_classes[] = {
> +	{ "desc",	TRACE_DESC	},
> +	{ "file",	TRACE_FILE	},
> +	{ "memory",	TRACE_MEMORY	},
> +	{ "process",	TRACE_PROCESS	},
> +	{ "signal",	TRACE_SIGNAL	},
> +	{ "ipc",	TRACE_IPC	},
> +	{ "network",	TRACE_NETWORK	},
> +	{ "%desc",	TRACE_DESC	},
> +	{ "%file",	TRACE_FILE	},
> +	{ "%memory",	TRACE_MEMORY	},
> +	{ "%process",	TRACE_PROCESS	},
> +	{ "%signal",	TRACE_SIGNAL	},
> +	{ "%ipc",	TRACE_IPC	},
> +	{ "%network",	TRACE_NETWORK	},
> +	{ "%stat",	TRACE_STAT	},
> +	{ "%lstat",	TRACE_LSTAT	},
> +	{ "%fstat",	TRACE_FSTAT	},
> +	{ "%%stat",	TRACE_STAT_LIKE	},
> +	{ "%statfs",	TRACE_STATFS	},
> +	{ "%fstatfs",	TRACE_FSTATFS	},
> +	{ "%%statfs",	TRACE_STATFS_LIKE	},
> +	{}
> +};
> +
>  static unsigned int
>  lookup_class(const char *s)
>  {
> -	static const struct {
> -		const char *name;
> -		unsigned int value;
> -	} syscall_class[] = {
> -		{ "desc",	TRACE_DESC	},
> -		{ "file",	TRACE_FILE	},
> -		{ "memory",	TRACE_MEMORY	},
> -		{ "process",	TRACE_PROCESS	},
> -		{ "signal",	TRACE_SIGNAL	},
> -		{ "ipc",	TRACE_IPC	},
> -		{ "network",	TRACE_NETWORK	},
> -		{ "%desc",	TRACE_DESC	},
> -		{ "%file",	TRACE_FILE	},
> -		{ "%memory",	TRACE_MEMORY	},
> -		{ "%process",	TRACE_PROCESS	},
> -		{ "%signal",	TRACE_SIGNAL	},
> -		{ "%ipc",	TRACE_IPC	},
> -		{ "%network",	TRACE_NETWORK	},
> -		{ "%stat",	TRACE_STAT	},
> -		{ "%lstat",	TRACE_LSTAT	},
> -		{ "%fstat",	TRACE_FSTAT	},
> -		{ "%%stat",	TRACE_STAT_LIKE	},
> -		{ "%statfs",	TRACE_STATFS	},
> -		{ "%fstatfs",	TRACE_FSTATFS	},
> -		{ "%%statfs",	TRACE_STATFS_LIKE	},
> -	};
> -
> -	unsigned int i;
> -	for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
> -		if (strcmp(s, syscall_class[i].name) == 0) {
> -			return syscall_class[i].value;
> +	const struct syscall_class *c;
> +	for (c = syscall_classes; c->name; ++c) {
> +		if (strcmp(s, c->name) == 0) {
> +			return c->value;
>  		}
>  	}
>  
> diff --git a/configure.ac b/configure.ac
> index 7a9abf8f..cbaa09b6 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -706,6 +706,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])]
> +                               )]
> +                        )]
> +)
BTW, tabs are used for indentation in configure.ac. There is some mess
in regards to libunwind-related code, but it's pretty consistent
otherwise.

> +
> +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 081fd4a8..df830cb2 100644
> --- a/defs.h
> +++ b/defs.h
> @@ -189,54 +189,21 @@ extern char *stpcpy(char *dst, const char *src);
>  # define PERSONALITY2_INCLUDE_FUNCS "empty.h"
>  #endif
>  
> -typedef struct ioctlent {
> -	const char *symbol;
> -	unsigned int code;
> -} struct_ioctlent;
> +struct inject_values {
> +	uint16_t signo;
> +	int rval;
> +};
>  
>  struct inject_opts {
>  	uint16_t first;
>  	uint16_t step;
> -	uint16_t signo;
> -	int rval;
> +	struct inject_values vals;
>  };
>  
>  #define MAX_ERRNO_VALUE			4095
> -#define INJECT_OPTS_RVAL_DEFAULT	(-(MAX_ERRNO_VALUE + 1))
> -
> -/* Trace Control Block */
> -struct tcb {
> -	int flags;		/* See below for TCB_ values */
> -	int pid;		/* If 0, this tcb is free */
> -	int qual_flg;		/* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW */
> -	unsigned long u_error;	/* Error code */
> -	kernel_ulong_t scno;	/* System call number */
> -	kernel_ulong_t u_arg[MAX_ARGS];	/* System call arguments */
> -	kernel_long_t u_rval;	/* Return value */
> -#if SUPPORTED_PERSONALITIES > 1
> -	unsigned int currpers;	/* Personality at the time of scno update */
> -#endif
> -	int sys_func_rval;	/* Syscall entry parser's return value */
> -	int curcol;		/* Output column for this process */
> -	FILE *outf;		/* Output file for this process */
> -	const char *auxstr;	/* Auxiliary info from syscall (see RVAL_STR) */
> -	void *_priv_data;	/* Private data for syscall decoding functions */
> -	void (*_free_priv_data)(void *); /* Callback for freeing priv_data */
> -	const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */
> -	const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" msg */
> -	struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
> -	struct timeval stime;	/* System time usage as of last process wait */
> -	struct timeval dtime;	/* Delta for system time usage */
> -	struct timeval etime;	/* Syscall entry time */
> +#define INJECT_VALS_RVAL_DEFAULT	(-(MAX_ERRNO_VALUE + 1))
>  
> -#ifdef USE_LIBUNWIND
> -	struct UPT_info *libunwind_ui;
> -	struct mmap_cache_t *mmap_cache;
> -	unsigned int mmap_cache_size;
> -	unsigned int mmap_cache_generation;
> -	struct queue_t *queue;
> -#endif
> -};
> +#include "defs_shared.h"
>  
>  /* TCB flags */
>  /* We have attached to this process, but did not see it stopping yet */
> @@ -260,6 +227,8 @@ struct tcb {
>  #define TCB_TAMPERED	0x40	/* A syscall has been tampered with */
>  #define TCB_HIDE_LOG	0x80	/* We should hide everything (until execve) */
>  #define TCB_SKIP_DETACH_ON_FIRST_EXEC	0x100	/* -b execve should skip detach on first execve */
> +#define TCB_AD_HOC_INJECT	0x200	/* an ad hoc injection was performed by Lua script */
> +#define TCB_HOOK	0x400	/* there is Lua hook for this syscall entry or exit */
>  
>  /* qualifier flags */
>  #define QUAL_TRACE	0x001	/* this system call should be traced */
> @@ -270,6 +239,8 @@ struct tcb {
>  #define QUAL_SIGNAL	0x100	/* report events with this signal */
>  #define QUAL_READ	0x200	/* dump data read from this file descriptor */
>  #define QUAL_WRITE	0x400	/* dump data written to this file descriptor */
> +#define QUAL_HOOK_ENTRY	0x800	/* return this syscall on entry from next_sc() */
> +#define QUAL_HOOK_EXIT	0x1000	/* return this syscall on exit from next_sc() */
>  
>  #define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
>  
> @@ -315,6 +286,7 @@ extern const struct xlat whence_codes[];
>  #define RVAL_NONE	040	/* Print nothing */
>  
>  #define RVAL_DECODED	0100	/* syscall decoding finished */
> +#define RVAL_HOOKED	0200	/* there is Lua hook for this syscall entry or exit */
>  
>  #define IOCTL_NUMBER_UNKNOWN 0
>  #define IOCTL_NUMBER_HANDLED 1
> @@ -360,6 +332,7 @@ typedef enum {
>  	CFLAG_ONLY_STATS,
>  	CFLAG_BOTH
>  } cflag_t;
> +extern const struct syscall_class syscall_classes[];
>  extern cflag_t cflag;
>  extern bool debug_flag;
>  extern bool Tflag;
> @@ -664,8 +637,15 @@ extern struct number_set write_set;
>  extern struct number_set signal_set;
>  
>  extern bool is_number_in_set(unsigned int number, const struct number_set *);
> +extern bool is_number_set_universal(const struct number_set *);
> +extern void make_number_set_universal(struct number_set *);
>  extern void qualify(const char *);
>  extern unsigned int qual_flags(const unsigned int);
> +#ifdef USE_LUAJIT
> +extern void set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook,
> +	bool exit_hook);
> +extern void set_hook_qual_all(bool entry_hook, bool exit_hook);
> +#endif
>  
>  #define DECL_IOCTL(name)						\
>  extern int								\
> @@ -958,6 +938,19 @@ extern const char *const errnoent0[];
>  extern const char *const signalent0[];
>  extern const struct_ioctlent ioctlent0[];
>  
> +extern const char *const *errnoent_vec[SUPPORTED_PERSONALITIES];
> +extern const char *const *signalent_vec[SUPPORTED_PERSONALITIES];
> +extern const struct_ioctlent *const ioctlent_vec[SUPPORTED_PERSONALITIES];
> +extern const unsigned int nerrnoent_vec[SUPPORTED_PERSONALITIES];
> +extern const unsigned int nsignalent_vec[SUPPORTED_PERSONALITIES];
> +extern const unsigned int nioctlent_vec[SUPPORTED_PERSONALITIES];
> +
> +extern const int personality_wordsize[SUPPORTED_PERSONALITIES];
> +extern const int personality_klongsize[SUPPORTED_PERSONALITIES];
> +#if SUPPORTED_PERSONALITIES > 1
> +extern const char *const personality_names[];
> +#endif
> +
>  #if SUPPORTED_PERSONALITIES > 1
>  extern const struct_sysent *sysent;
>  extern const char *const *errnoent;
> diff --git a/defs_shared.h b/defs_shared.h
> new file mode 100644
> index 00000000..1fb7a92d
> --- /dev/null
> +++ b/defs_shared.h
> @@ -0,0 +1,66 @@
> +/*
> + * Should only be included without FFI_CDEF from defs.h, so no include guards.
> + */
BTW, you can add something like

    #if !defined(FFI_CDEF) || !defined(STRACE_DEFS_SHARED_H)
    #ifndef FFI_CDEF
    # define STRACE_DEFS_SHARED_H
    #endif /* !FFI_CDEF */

    <...>

    #endif /* !FFI_CDEF || !STRACE_DEFS_SHARED_H */

to avoid commenting about absence of include guards.

> +
> +#include "ffi.h"
> +
> +FFI_CONTENT(
> +struct syscall_class {
> +	const char *name;
> +	unsigned int value;
> +};
> +)
> +
> +FFI_CONTENT(
> +typedef struct ioctlent {
> +	const char *symbol;
> +	unsigned int code;
> +} struct_ioctlent;
> +)
> +
> +/* Trace Control Block */
> +FFI_CONTENT(
> +struct tcb {
> +	int flags;		/* See below for TCB_ values */
> +	int pid;		/* If 0, this tcb is free */
> +	int qual_flg;		/* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW */
> +	unsigned long u_error;	/* Error code */
> +	kernel_ulong_t scno;	/* System call number */
> +	kernel_ulong_t u_arg[MAX_ARGS];	/* System call arguments */
> +	kernel_long_t u_rval;	/* Return value */
> +)
> +
> +#if defined(USE_LUAJIT) || SUPPORTED_PERSONALITIES > 1
> +FFI_CONTENT(
> +	unsigned int currpers;	/* Personality at the time of scno update */
> +)
> +#endif
> +
> +#ifndef FFI_CDEF
> +	int sys_func_rval;	/* Syscall entry parser's return value */
> +	int curcol;		/* Output column for this process */
> +	FILE *outf;		/* Output file for this process */
> +	const char *auxstr;	/* Auxiliary info from syscall (see RVAL_STR) */
> +	void *_priv_data;	/* Private data for syscall decoding functions */
> +	void (*_free_priv_data)(void *); /* Callback for freeing priv_data */
> +	const struct_sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */
> +	const struct_sysent *s_prev_ent; /* for "resuming interrupted SYSCALL" msg */
> +# ifdef USE_LUAJIT
> +	struct inject_values ad_hoc_inject_vals;
> +# endif
> +	struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
> +	struct timeval stime;	/* System time usage as of last process wait */
> +	struct timeval dtime;	/* Delta for system time usage */
> +	struct timeval etime;	/* Syscall entry time */
> +# ifdef USE_LIBUNWIND
> +	struct UPT_info *libunwind_ui;
> +	struct mmap_cache_t *mmap_cache;
> +	unsigned int mmap_cache_size;
> +	unsigned int mmap_cache_generation;
> +	struct queue_t *queue;
> +# endif
> +#endif /* !FFI_CDEF */
> +
> +FFI_CONTENT(
> +};
> +)
> diff --git a/ffi.h b/ffi.h
> new file mode 100644
> index 00000000..7b89e7a4
> --- /dev/null
> +++ b/ffi.h
> @@ -0,0 +1,19 @@
> +#ifndef STRACE_FFI_H
> +#define STRACE_FFI_H
> +
> +#include "macros.h"
> +
> +#define FFI_CONCAT(a, b) a ## b
> +#define FFI_CONCAT2(a, b) FFI_CONCAT(a, b)
> +
> +/*
> + * FFI_CONTENT expands to FFI_CONTENT_ (which strigifies its arguments) when
> + * FFI_CDEF is defined, and to FFI_CONTENT_FFI_CDEF (which simply expands to its
> + * arguments) when it is not.
> + */
> +#define FFI_CONTENT FFI_CONCAT2(FFI_CONTENT_, FFI_CDEF)
> +
> +#define FFI_CONTENT_(...)         STRINGIFY(__VA_ARGS__)
> +#define FFI_CONTENT_FFI_CDEF(...) __VA_ARGS__
> +
> +#endif /* !STRACE_FFI_H */
> diff --git a/filter_qualify.c b/filter_qualify.c
> index 4283e769..9409fa4a 100644
> --- a/filter_qualify.c
> +++ b/filter_qualify.c
> @@ -47,6 +47,10 @@ static struct number_set inject_set[SUPPORTED_PERSONALITIES];
>  static struct number_set raw_set[SUPPORTED_PERSONALITIES];
>  static struct number_set trace_set[SUPPORTED_PERSONALITIES];
>  static struct number_set verbose_set[SUPPORTED_PERSONALITIES];
> +#ifdef USE_LUAJIT
> +static struct number_set hook_entry_set[SUPPORTED_PERSONALITIES];
> +sgtatic struct number_set hook_exit_set[SUPPORTED_PERSONALITIES];
> +#endif
>  
>  static int
>  sigstr_to_uint(const char *s)
> @@ -127,28 +131,28 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts,
>  			fopts->step = 0;
>  		}
>  	} else if ((val = STR_STRIP_PREFIX(token, "error=")) != token) {
> -		if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
> +		if (fopts->vals.rval != INJECT_VALS_RVAL_DEFAULT)
>  			return false;
>  		intval = string_to_uint_upto(val, MAX_ERRNO_VALUE);
>  		if (intval < 0)
>  			intval = find_errno_by_name(val);
>  		if (intval < 1)
>  			return false;
> -		fopts->rval = -intval;
> +		fopts->vals.rval = -intval;
>  	} else if (!fault_tokens_only
>  		   && (val = STR_STRIP_PREFIX(token, "retval=")) != token) {
> -		if (fopts->rval != INJECT_OPTS_RVAL_DEFAULT)
> +		if (fopts->vals.rval != INJECT_VALS_RVAL_DEFAULT)
>  			return false;
>  		intval = string_to_uint(val);
>  		if (intval < 0)
>  			return false;
> -		fopts->rval = intval;
> +		fopts->vals.rval = intval;
>  	} else if (!fault_tokens_only
>  		   && (val = STR_STRIP_PREFIX(token, "signal=")) != token) {
>  		intval = sigstr_to_uint(val);
>  		if (intval < 1 || intval > NSIG_BYTES * 8)
>  			return false;
> -		fopts->signo = intval;
> +		fopts->vals.signo = intval;
>  	} else {
>  		return false;
>  	}
> @@ -232,8 +236,10 @@ qualify_inject_common(const char *const str,
>  	struct inject_opts opts = {
>  		.first = 1,
>  		.step = 1,
> -		.rval = INJECT_OPTS_RVAL_DEFAULT,
> -		.signo = 0
> +		.vals = {
> +			.rval = INJECT_VALS_RVAL_DEFAULT,
> +			.signo = 0,
> +		}
>  	};
>  	char *buf = NULL;
>  	char *name = parse_inject_expression(str, &buf, &opts, fault_tokens_only);
> @@ -242,10 +248,10 @@ qualify_inject_common(const char *const str,
>  	}
>  
>  	/* If neither of retval, error, or signal is specified, then ... */
> -	if (opts.rval == INJECT_OPTS_RVAL_DEFAULT && !opts.signo) {
> +	if (opts.vals.rval == INJECT_VALS_RVAL_DEFAULT && !opts.vals.signo) {
>  		if (fault_tokens_only) {
>  			/* in fault= syntax the default error code is ENOSYS. */
> -			opts.rval = -ENOSYS;
> +			opts.vals.rval = -ENOSYS;
>  		} else {
>  			/* in inject= syntax this is not allowed. */
>  			error_msg_and_die("invalid %s '%s'", description, str);
> @@ -343,6 +349,30 @@ qualify(const char *str)
>  	opt->qualify(str);
>  }
>  
> +#ifdef USE_LUAJIT
> +void
> +set_hook_qual(unsigned int scno, unsigned int pers, bool entry_hook,
> +	      bool exit_hook)
> +{
> +	if (entry_hook && !is_number_set_universal(&hook_entry_set[pers]))
> +		add_number_to_set(scno, &hook_entry_set[pers]);
> +	if (exit_hook && !is_number_set_universal(&hook_exit_set[pers]))
> +		add_number_to_set(scno, &hook_exit_set[pers]);
> +}
Well, instead of working the issue around, I'd prefer having
remove_number_from_set added along with some wrapper which choose
between {add,remove}_number_from_set based on not field. Or, probably,
some flag to add_number_to_set which clarifies the intention (whether
to take "not" flag into account or not).

> +
> +void
> +set_hook_qual_all(bool entry_hook, bool exit_hook)
> +{
> +	unsigned int p;
> +	for (p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
> +		if (entry_hook)
> +			make_number_set_universal(&hook_entry_set[p]);
> +		if (exit_hook)
> +			make_number_set_universal(&hook_exit_set[p]);
> +	}
> +}
> +#endif
> +
>  unsigned int
>  qual_flags(const unsigned int scno)
>  {
> @@ -355,5 +385,12 @@ qual_flags(const unsigned int scno)
>  		| (is_number_in_set(scno, &raw_set[current_personality])
>  		   ? QUAL_RAW : 0)
>  		| (is_number_in_set(scno, &inject_set[current_personality])
> -		   ? QUAL_INJECT : 0);
> +		   ? QUAL_INJECT : 0)
> +#ifdef USE_LUAJIT
> +		| (is_number_in_set(scno, &hook_entry_set[current_personality])
> +		   ? QUAL_HOOK_ENTRY : 0)
> +		| (is_number_in_set(scno, &hook_exit_set[current_personality])
> +		   ? QUAL_HOOK_EXIT : 0)
> +#endif
> +		;
>  }
> diff --git a/luajit.h b/luajit.h
> new file mode 100644
> index 00000000..db8a9474
> --- /dev/null
> +++ b/luajit.h
> @@ -0,0 +1,311 @@
> +/*
> + * Should only be included from strace.c, so no include guards.
> + */
Same here, regarding include guard.

> +
> +#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_vals.signo = 0;
> +		current_tcp->ad_hoc_inject_vals.rval = INJECT_VALS_RVAL_DEFAULT;
> +		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_vals.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_vals.rval = retval;
> +	return true;
> +}
> +
> +static int
> +func_umove(kernel_ulong_t addr, size_t len, void *laddr)
> +{
> +	return current_tcp ? umoven(current_tcp, addr, len, laddr) : -1;
> +}
> +
> +static int
> +func_umove_str(kernel_ulong_t addr, size_t len, char *laddr)
> +{
> +	return current_tcp ? umovestr(current_tcp, addr, len, laddr) : -1;
> +}
> +
> +static bool
> +func_path_match(const char **set, size_t nset)
> +{
> +	if (!current_tcp)
> +		return false;
> +	struct path_set s = {set, nset};
> +	return pathtrace_match_set(current_tcp, &s);
> +}
> +
> +static const char *
> +get_lua_msg(void)
> +{
> +	const char *msg = lua_tostring(L, -1);
> +	return msg ? msg : "(error object can't be converted to string)";
> +}
> +
> +static void
> +assert_lua_impl(int ret, const char *expr, const char *file, int line)
> +{
> +	if (ret == 0)
> +		return;
> +	error_msg_and_die("assert_lua(%s) failed at %s:%d: %s", expr, file,
> +		line, get_lua_msg());
> +}
> +
> +#define assert_lua(expr) assert_lua_impl(expr, #expr, __FILE__, __LINE__)
> +
> +static void
> +check_lua(int ret)
> +{
> +	if (ret == 0)
> +		return;
> +	error_msg_and_die("lua: %s", get_lua_msg());
> +}
> +
> +#ifdef LUA_FFILIBNAME
> +# define FFILIBNAME LUA_FFILIBNAME
> +#else
> +/* non-LuaJIT */
> +# define FFILIBNAME "ffi"
> +#endif
> +
> +#ifdef LUA_BITLIBNAME
> +# define BITLIBNAME LUA_BITLIBNAME
> +#else
> +/* Lua <= 5.1 (non-LuaJIT) */
> +# define BITLIBNAME "bit"
> +#endif
> +
> +static void
> +init_luajit(const char *scriptfile)
> +{
> +	if (L)
> +		/* already initialized? */
> +		error_msg_and_help("multiple -l arguments");
> +
> +	if (!(L = luaL_newstate()))
> +		error_msg_and_die("luaL_newstate failed (out of memory?)");
> +
> +	luaL_openlibs(L);
> +
> +	lua_getglobal(L, "require"); /* L: require */
> +	lua_pushstring(L, FFILIBNAME); /* L: require str */
> +	assert_lua(lua_pcall(L, 1, 1, 0)); /* L: ffi */
> +	lua_getfield(L, -1, "cdef"); /* L: ffi cdef */
> +	luaL_Buffer b;
> +	luaL_buffinit(L, &b); /* L: ffi cdef ? */
> +	{
> +		char buf[128];
> +		snprintf(buf, sizeof(buf),
> +			"typedef int%d_t kernel_long_t;"
> +			"typedef uint%d_t kernel_ulong_t;",
> +			(int) sizeof(kernel_long_t) * 8,
> +			(int) sizeof(kernel_ulong_t) * 8);
> +		luaL_addstring(&b, buf); /* L: ffi cdef ? */
> +	}
> +	const char *defs =
> +#define FFI_CDEF
> +#include "sysent.h"
> +#include "defs_shared.h"
> +#undef FFI_CDEF
> +	;
> +	luaL_addstring(&b, defs); /* L: ffi cdef ? */
> +	luaL_pushresult(&b); /* L: ffi cdef str */
> +	assert_lua(lua_pcall(L, 1, 0, 0)); /* L: ffi */
> +
> +	lua_newtable(L); /* L: ffi strace */
> +	lua_newtable(L); /* L: ffi strace C */
> +
> +	lua_getfield(L, 1, "cast"); /* L: ffi strace C cast */
> +	lua_remove(L, 1); /* L: strace C cast */
> +
> +#define EXPOSE_FUNC(rettype, ptr, name, ...)				\
> +	do {								\
> +		rettype (*fptr_)(__VA_ARGS__) = ptr;			\
> +		lua_pushvalue(L, -1); /* L: strace C cast cast */	\
> +		lua_pushstring(L, #rettype " (*)(" #__VA_ARGS__ ")");	\
> +		/* L: strace C cast cast str */				\
> +		lua_pushlightuserdata(L, * (void **) (&fptr_));		\
> +		/* L: strace C cast cast str ptr */			\
> +		assert_lua(lua_pcall(L, 2, 1, 0));			\
> +		/* L: strace C cast value */				\
> +		lua_setfield(L, -3, name); /* L: strace C cast */	\
> +	} while (0)
> +
> +	EXPOSE_FUNC(bool, func_monitor, "monitor",
> +		unsigned int, unsigned int, bool, bool);
> +	EXPOSE_FUNC(void, set_hook_qual_all, "monitor_all",
> +		bool, bool);
> +	EXPOSE_FUNC(struct tcb *, func_next_sc, "next_sc",
> +		void);
> +	EXPOSE_FUNC(bool, func_inject_signo, "inject_signo",
> +		int);
> +	EXPOSE_FUNC(bool, func_inject_retval, "inject_retval",
> +		int);
> +	EXPOSE_FUNC(int, func_umove, "umove",
> +		kernel_ulong_t, size_t, void *);
> +	EXPOSE_FUNC(int, func_umove_str, "umove_str",
> +		kernel_ulong_t, size_t, char *);
> +	EXPOSE_FUNC(bool, func_path_match, "path_match",
> +		const char **, size_t);
> +
> +#undef EXPOSE_FUNC
> +
> +#define EXPOSE(type, ptr, name)						\
> +	do {								\
> +		/* Get a compilation error/warning on type mismatch */	\
> +		type tmp_ = ptr;					\
> +		(void) tmp_;						\
> +		lua_pushvalue(L, -1); /* L: strace C cast cast */	\
> +		lua_pushstring(L, #type);				\
> +		/* L: strace C cast cast str */				\
> +		lua_pushlightuserdata(L, (void *) ptr);			\
> +		/* L: strace C cast cast str ptr */			\
> +		assert_lua(lua_pcall(L, 2, 1, 0));			\
> +		/* L: strace C cast value */				\
> +		lua_setfield(L, -3, name); /* L: strace C cast */	\
> +	} while (0)
> +
> +	EXPOSE(const struct_sysent *const *, sysent_vec, "sysent_vec");
> +	EXPOSE(const char *const **, errnoent_vec, "errnoent_vec");
> +	EXPOSE(const char *const **, signalent_vec, "signalent_vec");
> +	EXPOSE(const struct_ioctlent *const *, ioctlent_vec, "ioctlent_vec");
> +
> +	EXPOSE(const unsigned int *, nsyscall_vec, /*(!)*/ "nsysent_vec");
> +	EXPOSE(const unsigned int *, nerrnoent_vec, "nerrnoent_vec");
> +	EXPOSE(const unsigned int *, nsignalent_vec, "nsignalent_vec");
> +	EXPOSE(const unsigned int *, nioctlent_vec, "nioctlent_vec");
> +
> +	EXPOSE(const struct syscall_class *, syscall_classes,
> +		"syscall_classes");
> +
> +#if SUPPORTED_PERSONALITIES == 1
> +	static const char *const personality_names[] = {"default"};
> +#endif
> +	EXPOSE(const char *const *, personality_names, "pers_names");
> +	EXPOSE(const int *, personality_wordsize, "pers_wordsize");
> +	EXPOSE(const int *, personality_klongsize, "pers_klongsize");
> +
> +#undef EXPOSE
> +
> +	lua_pop(L, 1); /* L: strace C */
> +	lua_setfield(L, -2, "C"); /* L: strace */
> +
> +	lua_pushinteger(L, SUPPORTED_PERSONALITIES); /* L: strace int */
> +	lua_setfield(L, -2, "npersonalities"); /* L: strace */
> +
> +	lua_pushinteger(L, MAX_ARGS); /* L: strace int */
> +	lua_setfield(L, -2, "max_args"); /* L: strace */
> +
> +	lua_pushinteger(L, PATH_MAX); /* L: strace int */
> +	lua_setfield(L, -2, "path_max"); /* L: strace */
> +
> +	lua_setglobal(L, "strace"); /* L: - */
> +
> +	const char *code =
> +#include "luajit_lib.h"
> +	;
> +	assert_lua(luaL_loadstring(L, code)); /* L: chunk */
> +
> +	lua_newtable(L); /* L: chunk table */
> +
> +	lua_pushstring(L, FFILIBNAME); /* L: chunk table str */
> +	lua_setfield(L, -2, "ffilibname"); /* L: chunk table */
> +	lua_pushstring(L, BITLIBNAME); /* L: chunk table str */
> +	lua_setfield(L, -2, "bitlibname"); /* L: chunk table */
> +	lua_pushinteger(L, TCB_INSYSCALL); /* L: chunk table int */
> +	lua_setfield(L, -2, "tcb_insyscall"); /* L: chunk table */
> +	lua_pushinteger(L, QUAL_TRACE); /* L: chunk table int */
> +	lua_setfield(L, -2, "qual_trace"); /* L: chunk table */
> +	lua_pushinteger(L, QUAL_ABBREV); /* L: chunk table int */
> +	lua_setfield(L, -2, "qual_abbrev"); /* L: chunk table */
> +	lua_pushinteger(L, QUAL_VERBOSE); /* L: chunk table int */
> +	lua_setfield(L, -2, "qual_verbose"); /* L: chunk table */
> +	lua_pushinteger(L, QUAL_RAW); /* L: chunk table int */
> +	lua_setfield(L, -2, "qual_raw"); /* L: chunk table */
> +
> +	assert_lua(lua_pcall(L, 1, 1, 0)); /* L: func */
> +
> +	check_lua(luaL_loadfile(L, scriptfile)); /* L: func chunk */
> +}
> +
> +static void ATTRIBUTE_NORETURN
> +run_luajit(void)
> +{
> +	/* L: func chunk */
> +	check_lua(lua_pcall(L, 0, 0, 0)); /* L: func */
> +	check_lua(lua_pcall(L, 0, 0, 0)); /* L: - */
> +	terminate();
> +}
> +
> +#undef FFILIBNAME
> +#undef BITLIBNAME
> +#undef assert_lua
> +#undef L
> diff --git a/luajit_lib.lua b/luajit_lib.lua
> new file mode 100644
> index 00000000..512d786f
> --- /dev/null
> +++ b/luajit_lib.lua
> @@ -0,0 +1,383 @@
> +-- This "chunk" of code is loaded and run before the script is.
> +--
> +-- To quote https://www.lua.org/manual/5.1/manual.html#2.4.1,
> +-- "Lua handles a chunk as the body of an anonymous function with a variable
> +--  number of arguments (see §2.5.9). As such, chunks can define local
> +--  variables, receive arguments, and return values."
> +--
> +-- Thanks to Lua's support for closures, all the local variables defined here
> +-- will not leak to another chunks (i.e., the script), but all the functions
> +-- defined here can still access them.
> +--
> +-- strace calls this chunk with a single argument: a table with data that should
> +-- not be exposed to the script, but is needed for some API functions defined
> +-- here.
> +--
> +-- strace expects this chunk to return another function that will be run after
> +-- the script returns.
> +--
> +-- Arguments passed to this chunk are accessible through the "..." vararg
> +-- expression. The following line uses Lua's "adjust" assignment semantics to
> +-- assign the first argument to a local variable "priv".
> +local priv = ...
> +
> +local ffi = require(priv.ffilibname)
> +ffi.cdef[[
> +int strcmp(const char *, const char *);
> +]]
> +local bit = require(priv.bitlibname)
> +
> +local entry_cbs, exit_cbs, at_exit_cb = {}, {}, nil
> +for p = 0, strace.npersonalities - 1 do
> +	entry_cbs[p] = {}
> +	exit_cbs[p] = {}
> +end
> +
> +local nullptr = ffi.NULL
> +pcall(function()
> +	nullptr = ffi.C.NULL
> +end)
> +
> +local function chain(f, g)
> +	if not f then
> +		return g
> +	end
> +	return function(...)
> +		f(...)
> +		g(...)
> +	end
> +end
> +
> +local function register_hook(scno, pers, on_entry, on_exit, cb)
> +	assert(not not strace.C.monitor(scno, pers, on_entry, on_exit))
> +	scno, pers = tonumber(scno), tonumber(pers)
> +	if on_entry then
> +		entry_cbs[pers][scno] = chain(entry_cbs[pers][scno], cb)
> +	end
> +	if on_exit then
> +		exit_cbs[pers][scno] = chain(exit_cbs[pers][scno], cb)
> +	end
> +end
> +
> +-- Convert a cdata C string or a Lua string to a Lua string.
> +local function mkstring(s)
> +	return type(s) == 'string' and s or ffi.string(s)
> +end
> +
> +function strace.entering(tcp)
> +	return bit.band(tcp.flags, priv.tcb_insyscall) == 0
> +end
> +
> +function strace.exiting(tcp)
> +	return bit.band(tcp.flags, priv.tcb_insyscall) ~= 0
> +end
> +
> +local function alter_trace_opt(flagbit, tcp, ...)
> +	if strace.exiting(tcp) then
> +		return false
> +	end
> +	-- i.e., if ... is empty, or the first element of ... is true
> +	if select('#', ...) == 0 or select(1, ...) then
> +		tcp.qual_flg = bit.bor(tcp.qual_flg, flagbit)
> +	else
> +		tcp.qual_flg = bit.band(tcp.qual_flg, bit.bnot(flagbit))
> +	end
> +	return true
> +end
> +for funcname, flagbit in pairs{
> +	trace	= priv.qual_trace,
> +	abbrev	= priv.qual_abbrev,
> +	verbose	= priv.qual_verbose,
> +	raw	= priv.qual_raw,
> +} do
> +	strace[funcname] = function(tcp, ...)
> +		return alter_trace_opt(flagbit, tcp, ...)
> +	end
> +end
> +
> +function strace.ptr_to_kulong(ptr)
> +	return ffi.cast('kernel_ulong_t', ffi.cast('unsigned long', ptr))
> +end
> +
> +function strace.at_exit(f)
> +	at_exit_cb = chain(at_exit_cb, f)
> +end
> +
> +function strace.get_sc_name(scno, 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_sig_name(signo, 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)
> +	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
> +
> +function strace.get_ioctl_name(code, 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 = ffi.cast('unsigned int', 0), strace.C.nioctlent_vec[pers]
> +	if rb == 0 then
> +		return nil
> +	end
> +	local arr = strace.C.ioctlent_vec[pers]
> +	while rb - lb > 1 do
> +		local mid = lb + (rb - lb) / 2
> +		if arr[mid].code <= code then
> +			lb = mid
> +		else
> +			rb = mid
> +		end
> +	end
> +	return arr[lb].code == code and ffi.string(arr[lb].symbol) or nil
> +end
> +
> +function strace.get_ioctl_code(name, pers)
> +	local cstr = ffi.cast('const char *', name)
> +	local arr = strace.C.ioctlent_vec[pers]
> +	for i = 0, strace.C.nioctlent_vec[pers] 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)
> +	local cstr = ffi.cast('const char *', scname)
> +	for i = 0, tonumber(strace.C.nsysent_vec[pers]) - 1 do
> +		local s = strace.C.sysent_vec[pers][i].sys_name
> +		if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
> +			return i
> +		end
> +	end
> +	return nil
> +end
> +
> +function strace.get_signo(signame, pers)
> +	local cstr = ffi.cast('const char *', signame)
> +	for i = 0, tonumber(strace.C.nsignalent_vec[pers]) - 1 do
> +		local s = strace.C.signalent_vec[pers][i]
> +		if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
> +			return i
> +		end
> +	end
> +	return nil
> +end
> +
> +function strace.get_errno(errname, pers)
> +	local cstr = ffi.cast('const char *', errname)
> +	for i = 0, tonumber(strace.C.nerrnoent_vec[pers]) - 1 do
> +		local s = strace.C.errnoent_vec[pers][i]
> +		if s ~= nullptr and ffi.C.strcmp(s, cstr) == 0 then
> +			return i
> +		end
> +	end
> +	return nil
> +end
> +
> +function strace.inject_signal(tcp, sig)
> +	local signo = sig
> +	if type(sig) == 'string' or type(sig) == 'cdata' then
> +		signo = strace.get_signo(sig, tcp.currpers)
> +		if not signo then
> +			return false
> +		end
> +	end
> +	return not not strace.C.inject_signo(signo)
> +end
> +
> +function strace.inject_error(tcp, err)
> +	local errno = err
> +	if type(err) == 'string' or type(err) == 'cdata' then
> +		errno = strace.get_errno(err, tcp.currpers)
> +		if not errno then
> +			return false
> +		end
> +	end
> +	return errno > 0 and not not strace.C.inject_retval(-errno)
> +end
> +
> +local ptr_size = ffi.sizeof('void *')
> +
> +function strace.read_obj(addr, ct, ...)
> +	local obj = ffi.new(ct, ...)
> +	local n = ffi.sizeof(obj)
> +	-- work around FFI pointer semantics
> +	if n == ptr_size then
> +		-- It may be a pointer, and it is cheap to create another copy.
> +		-- FFI templating ('$ [1]') fails for variable-length arrays, so
> +		-- fall back to normal umove in case of error.
> +		local is_ok, ret = pcall(function()
> +			local t = ffi.typeof(obj)
> +			local arr = ffi.typeof('$ [1]', t)()
> +			return strace.C.umove(addr, n, arr) == 0 and t(arr[0])
> +				or nil
> +		end)
> +		if is_ok then
> +			return ret
> +		end
> +	end
> +	return strace.C.umove(addr, n, obj) == 0 and obj or nil
> +end
> +
> +function strace.read_str(addr, maxsz, bufsz)
> +	-- convert it to Lua number to prevent underflows
> +	maxsz = tonumber(maxsz or 4 * 1024 * 1024)
> +	bufsz = bufsz or 1024
> +	local t = {}
> +	local buf = ffi.new('char [?]', bufsz)
> +	while true do
> +		local r = strace.C.umove_str(addr, bufsz, buf)
> +		if r < 0 then
> +			return nil, 'readerr'
> +		elseif r == 0 then
> +			maxsz = maxsz - bufsz
> +			if maxsz < 0 then
> +				return nil, 'toolong'
> +			end
> +			t[#t + 1] = ffi.string(buf, bufsz)
> +			addr = addr + bufsz
> +		else
> +			local s = ffi.string(buf)
> +			if #s > maxsz then
> +				return nil, 'toolong'
> +			end
> +			return table.concat(t) .. s
> +		end
> +	end
> +end
> +
> +function strace.read_path(addr)
> +	return strace.read_str(addr, strace.path_max, strace.path_max + 1)
Please check out the commit v4.18-144-gd7a45f8.

> +end
> +
> +local function parse_when(when)
> +	if type(when) == 'table' then
> +		return unpack(when)
> +	elseif when == 'entering' then
> +		return true, false
> +	elseif when == 'exiting' then
> +		return false, true
> +	elseif when == 'both' then
> +		return true, true
> +	else
> +		error('unknown "when" value')
> +	end
> +end
> +
> +local function reduce_or(f, args, ...)
> +	local ret = false
> +	for _, arg in ipairs(args) do
> +		if f(arg, ...) then
> +			ret = true
> +		end
> +	end
> +	return ret
> +end
> +
> +function strace.hook(scname, when, cb)
> +	local on_entry, on_exit = parse_when(when)
> +	if type(scname) == 'table' then
> +		return reduce_or(strace.hook, scname, {on_entry, on_exit}, cb)
> +	end
> +	local found = false
> +	for p = 0, strace.npersonalities - 1 do
> +		local scno = strace.get_scno(scname, p)
> +		if scno then
> +			register_hook(scno, p, on_entry, on_exit, cb)
> +			found = true
> +		end
> +	end
> +	return found
> +end
> +
> +function strace.hook_class(clsname, when, cb)
> +	local on_entry, on_exit = parse_when(when)
> +	if type(clsname) == 'table' then
> +		return reduce_or(strace.hook_class, clsname,
> +			{on_entry, on_exit}, cb)
> +	end
> +	local cstr = ffi.cast('const char *', clsname)
> +	local flag = nil
> +	local ptr = strace.C.syscall_classes
> +	while ptr.name ~= nullptr do
> +		if ffi.C.strcmp(ptr.name, cstr) == 0 then
> +			flag = ptr.value
> +			break
> +		end
> +		ptr = ptr + 1
> +	end
> +	if not flag then
> +		return false
> +	end
> +	for p = 0, strace.npersonalities - 1 do
> +		for i = 0, tonumber(strace.C.nsysent_vec[p]) - 1 do
> +			if bit.band(strace.C.sysent_vec[p][i].sys_flags, flag)
> +			   ~= 0
> +			then
> +				register_hook(i, p, on_entry, on_exit, cb)
> +			end
> +		end
> +	end
> +	return true
> +end
> +
> +function strace.hook_scno(scno, pers, when, cb)
> +	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
> +	register_hook(scno, pers, on_entry, on_exit, cb)
> +end
> +
> +function strace.path_match(set)
> +	if type(set) ~= 'table' then
> +		set = {set}
> +	end
> +	local nset = #set
> +	return not not strace.C.path_match(
> +		ffi.new('const char *[?]', nset, set), nset)
> +end
> +
> +function print(...)
> +	local sep = ''
> +	for i = 1, select('#', ...) do
> +		io.stderr:write(sep .. tostring(select(i, ...)))
> +		sep = '\t'
> +	end
> +	io.stderr:write('\n')
> +end
> +
> +return function()
> +	while true do
> +		local tcp = strace.C.next_sc()
> +		if tcp == nullptr then
> +			break
> +		end
> +		local cb = (strace.entering(tcp) and entry_cbs or exit_cbs)
> +			[tonumber(tcp.currpers)][tonumber(tcp.scno)]
> +		if cb then
> +			cb(tcp)
> +		end
> +	end
> +	if at_exit_cb then
> +		at_exit_cb()
> +	end
> +end
> diff --git a/strace.1 b/strace.1
> index af9704f5..892d5c3d 100644
> --- a/strace.1
> +++ b/strace.1
> @@ -751,6 +751,14 @@ Unless this option is used setuid and setgid programs are executed
>  without effective privileges.
>  .SS Miscellaneous
>  .TP 12
> +.BI "\-l " filename
> +Load and run LuaJIT script from
> +.I filename
> +(experimental).
> +This option is available only if
> +.B strace
> +is built with LuaJIT scripting support.
> +.TP
>  .B \-d
>  Show some debugging output of
>  .B strace
> @@ -766,6 +774,722 @@ Print the help summary.
>  .B \-V
>  Print the version number of
>  .BR strace .
> +.SH LUA SCRIPTING
> +If built with LuaJIT support,
> +.B strace
> +can execute LuaJIT scripts.
> +A script file is passed to the
> +.B -l
> +option.
> +.PP
> +.B strace
> +provides the built-in module
> +.BR strace ,
"... to the Lua script 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:
> +.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.C.next_sc
> +in a loop until it returns a null pointer (or return earlier; in this case, the
> +installed hooks for the remaining syscalls are run).
Looks like this note duplicates the description after the list.

> +Note that
> +.B strace
> +performs tracing/tampering of a syscall on the next
> +.B strace.C.next_sc
> +call.
> +.IP \(bu
> +Install syscall and at-exit hooks with
> +.BR strace.hook ", " strace.hook_class ", " strace.hook_scno " and " strace.at_exit .
> +.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
> +FFI library (see
> +.IR http://luajit.org/ext_ffi.html )
You might want to use .URL macro from www.tmac package.

> +is an extension provided by LuaJIT;
> +.BR luaffi
> +(see
> +.IR https://github.com/jmckaskill/luaffi )
> +and
> +.BR luaffifb
> +(see
> +.IR https://github.com/facebook/luaffifb )
> +standalone implementations are also supported.
The implementation variant of the FFI library used looks like build-time choice
and I can't see any difference in the interface of these library versions,
is the library with which strace is built important for the strace users?
You may want to elaborate on this, however, in configure.ac or install.texi.

> +.PP
> +One may also want to look at
> +.B lcpp
> +(see
> +.IR https://github.com/m-schmoock/lcpp ),
> +a pure-Lua implementation of C preprocessor, which integrates nicely with
> +LuaJIT's FFI library and supports inclusion of header files and exposing all the
> +definitions and macros (!) to the FFI.
> +.TP
Why .TP and not .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: function
"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.monitor / strace.monitor_all .
"strace.C.monitor / strace.C.monitor_all"

> +.IP
> +Returns
> +.B true
> +on sucess 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.C.next_sc() :
Looks like parentheses are not used for function references elsewhere.

> +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.monitor .
"strace.C.monitor"

> +.TP
> +\fItcp\fR = \fBstrace.C.next_sc\fR()
> +C type:
> +.B struct tcb * (*)(void)
> +.IP
> +If this is not the first call to this function, performs tracing and tampering
> +of the previous syscall.
"previously returned"

> +.IP
> +Waits for the next monitored syscall to happen, and returns a pointer to its
> +trace control block.
> +.IP
> +If
> +.B strace
> +needs to be terminated (e.g. last tracee has been terminated, or
> +.B strace
> +has been interrupted), returns a null pointer.
> +Once it returned a null pointer, all subsequent calls to it will also return it.
> +.TP
> +\fIstatus\fR = \fBstrace.C.inject_signo\fR(\fIsigno\fR)
> +C type:
> +.B bool (*)(int)
> +.IP
> +Deliver a signal with number
> +.I signo
> +to the current tracee.
> +.IP
> +Note that this must be done on syscall entry.
> +.IP
> +Returns
> +.B true
> +on sucess 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 sucess 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 abolute path to the file behind the file descriptor is
> +compared against the set.
> +.SS strace module: C submodule: constants
> +.TP
> +.B strace.C.sysent_vec
> +Array of syscall tables for each of the supported personalities.
> +\fBstrace.C.sysent_vec\fR[\fIpers\fR][\fIscno\fR] is a
> +.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

> +.TP
> +.B strace.C.nerrnoent_vec
> +.TP
> +.B strace.C.nsignalent_vec
> +.TP
> +.B strace.C.nioctlent_vec
You can use .TQ here.

> +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_names
> +A
> +.BR strace.npersonalities \-sized
> +array of C strings with names for each personality.
> +.TP
> +.B strace.C.pers_wordsize

> +.TP
> +.B strace.C.pers_klongsize
And here.

> +These are
> +.BR strace.npersonalities \-sized
> +arrays with word and
> +.I __kernel_long_t
> +sizes for each personality.
> +.SS strace module: functions
> +Glossary:
> +.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;
> +.IP \(bu
> +the
> +.I current tracing control pointer
> +is a pointer to the current trace control block.
> +This pointer is obtained from
> +.B strace.C.next_sc
> +calls and provided to hook callback functions. These pointers are not persistent
> +across
> +.B strace.C.next_sc
> +and/or hook calls.
> +.PP
> +General conventions:
> +.IP \(bu 3
> +a
> +.I tcp
> +argument is the
> +.IR "current tracing control pointer" ;
> +.IP \(bu
> +a
> +.I pers
> +argument is an
> +.I integer
> +specifying personality number;
> +.IP \(bu
> +an
> +.I addr
> +argument is a cdata
> +.IR kernel_ulong_t .
> +.TP
You should provide indentation here explicitly or wrap previous .IP
calls in .RS/.RE, otherwise it inherits previously used indentation size
of 3.

> +\fIstatus\fR = \fBstrace.entering\fR(\fItcp\fR)
> +Returns
> +.B true
> +if this is a syscall entry, and
> +.B false
> +otherwise.
> +.TP
> +\fIstatus\fR = \fBstrace.exiting\fR(\fItcp\fR)
> +Returns
> +.B true
> +if this is a syscall exit, and
> +.B false
> +otherwise.
> +.TP
> +\fBstrace.trace\fR(\fItcp\fR[, \fIflag\fR])

> +.TP
> +\fBstrace.abbrev\fR(\fItcp\fR[, \fIflag\fR])
> +.TP
> +\fBstrace.verbose\fR(\fItcp\fR[, \fIflag\fR])
> +.TP
> +\fBstrace.raw\fR(\fItcp\fR[, \fIflag\fR])
And here.

> +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 signame
"errname", again.

> +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 = \fBstrace.inject_signal\fR(\fItcp\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
> +on failure.
> +.IP
> +Note that this must be done on syscall entry.
> +.TP
> +\fIstatus\fR = \fBstrace.inject_error\fR(\fItcp\fR, \fIerr\fR)
> +Injects an error into a current syscall invocation.
> +.I err
> +is either error number (an
> +.IR integer )
> +or error name (a
> +.IR string ).
> +.IP
> +Returns
> +.B true
> +on success and
> +.B false
> +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, \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)

> +.TP
> +\fIstatus\fR = \fBstrace.hook_class\fR(\fIclsname\fR, \fIwhen\fR, \fIcb\fR)
> +.TP
> +\fIstatus\fR = \fBstrace.hook_scno\fR(\fIscno\fR, \fIpers\fR, \fIwhen\fR, \fIcb\fR)
.TQ

> +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 .
> +.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 enther 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
> +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
>  .SH DIAGNOSTICS
>  When
>  .I command
> diff --git a/strace.c b/strace.c
> index ae93f923..cd45f14e 100644
> --- a/strace.c
> +++ b/strace.c
> @@ -45,6 +45,9 @@
>  # include <sys/prctl.h>
>  #endif
>  #include <asm/unistd.h>
> +#ifdef USE_LUAJIT
> +# include <lua.h>
> +#endif
>  
>  #include "scno.h"
>  #include "ptrace.h"
> @@ -169,6 +172,11 @@ static volatile sig_atomic_t interrupted;
>  static volatile int interrupted;
>  #endif
>  
> +#ifdef USE_LUAJIT
> +static lua_State *script_L = NULL;
> +static void init_luajit(const char *scriptfile);
> +#endif
> +
>  #ifndef HAVE_STRERROR
>  
>  #if !HAVE_DECL_SYS_ERRLIST
> @@ -227,6 +235,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\
> @@ -712,7 +725,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
>  
> @@ -1588,6 +1601,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) {
> @@ -1698,6 +1714,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");
> @@ -2215,6 +2236,9 @@ enum trace_event {
>  	 */
>  	TE_SYSCALL_STOP,
>  
> +	/* Syscall entry or exit, after hook. */
> +	TE_SYSCALL_STOP_HOOK_EXIT,
> +
>  	/*
>  	 * Tracee received signal with number WSTOPSIG(*pstatus); signal info
>  	 * is written to *si.  Restart the tracee (with that signal number
> @@ -2405,24 +2429,47 @@ next_event(int *pstatus, siginfo_t *si)
>  	}
>  }
>  
> +enum hook_state {
> +	HOOK_ENTER,
> +	HOOK_EXIT,
> +	HOOK_IGNORE,
> +};
> +
>  static int
> -trace_syscall(struct tcb *tcp, unsigned int *sig)
> +trace_syscall(struct tcb *tcp, unsigned int *sig, enum hook_state state)
>  {
>  	if (entering(tcp)) {
> -		int res = syscall_entering_decode(tcp);
> -		switch (res) {
> -		case 0:
> -			return 0;
> -		case 1:
> +		int res;
> +		switch (state) {
> +		case HOOK_ENTER:
> +		case HOOK_IGNORE:
> +			res = syscall_entering_decode(tcp);
> +			if (res == 0)
> +				return 0;
> +			if (res == 1)
> +				if (state == HOOK_ENTER &&
> +				    (tcp->qual_flg & QUAL_HOOK_ENTRY))
> +					return RVAL_HOOKED;
> +			/* Fall through */
> +		case HOOK_EXIT:
>  			res = syscall_entering_trace(tcp, sig);
>  		}
>  		syscall_entering_finish(tcp, res);
>  		return res;
>  	} else {
> -		struct timeval tv = {};
> -		int res = syscall_exiting_decode(tcp, &tv);
> -		if (res != 0) {
> -			res = syscall_exiting_trace(tcp, tv, res);
> +		static struct timeval tv;
> +		int res = 1;
> +		switch (state) {
> +		case HOOK_ENTER:
> +		case HOOK_IGNORE:
> +			res = syscall_exiting_decode(tcp, &tv);
> +			if (res == 1 && state == HOOK_ENTER &&
> +			    (tcp->qual_flg & QUAL_HOOK_EXIT))
> +				return RVAL_HOOKED;
> +			/* Fall through */
> +		case HOOK_EXIT:
> +			if (res != 0)
> +				res = syscall_exiting_trace(tcp, tv, res);
>  		}
>  		syscall_exiting_finish(tcp);
>  		return res;
> @@ -2431,10 +2478,11 @@ trace_syscall(struct tcb *tcp, unsigned int *sig)
>  
>  /* Returns true iff the main trace loop has to continue. */
>  static bool
> -dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
> +dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si, bool hooked)
>  {
>  	unsigned int restart_op = PTRACE_SYSCALL;
>  	unsigned int restart_sig = 0;
> +	int res;
>  
>  	switch (ret) {
>  	case TE_BREAK:
> @@ -2447,7 +2495,17 @@ dispatch_event(enum trace_event ret, int *pstatus, siginfo_t *si)
>  		break;
>  
>  	case TE_SYSCALL_STOP:
> -		if (trace_syscall(current_tcp, &restart_sig) < 0) {
> +	case TE_SYSCALL_STOP_HOOK_EXIT:
> +		res = trace_syscall(current_tcp, &restart_sig,
> +			hooked ? (ret == TE_SYSCALL_STOP ? HOOK_ENTER : HOOK_EXIT) :
> +			HOOK_IGNORE);
> +
> +		if (res == RVAL_HOOKED) {
> +			current_tcp->flags |= TCB_HOOK;
> +			return true;
> +		}
> +
> +		if (res < 0) {
>  			/*
>  			 * ptrace() failed in trace_syscall().
>  			 * Likely a result of process disappearing mid-flight.
> @@ -2583,6 +2641,10 @@ terminate(void)
>  	exit(exit_code);
>  }
>  
> +#ifdef USE_LUAJIT
> +# include "luajit.h"
> +#endif
> +
>  int
>  main(int argc, char *argv[])
>  {
> @@ -2590,9 +2652,14 @@ 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))
> +	while (dispatch_event(next_event(&status, &si), &status, &si, false))
>  		;
>  	terminate();
>  }
> diff --git a/syscall.c b/syscall.c
> index a0c649ea..546d0760 100644
> --- a/syscall.c
> +++ b/syscall.c
> @@ -159,6 +159,16 @@ enum {
>  #endif
>  };
>  
> +const char *const *errnoent_vec[SUPPORTED_PERSONALITIES] = {
> +	errnoent0,
> +#if SUPPORTED_PERSONALITIES > 1
> +	errnoent1,
> +# if SUPPORTED_PERSONALITIES > 2
> +	errnoent2,
> +# endif
> +#endif
> +};
> +
>  enum {
>  	nerrnos0 = ARRAY_SIZE(errnoent0)
>  #if SUPPORTED_PERSONALITIES > 1
> @@ -169,6 +179,16 @@ enum {
>  #endif
>  };
>  
> +const unsigned int nerrnoent_vec[] = {
> +	nerrnos0,
> +#if SUPPORTED_PERSONALITIES > 1
> +	nerrnos1,
> +# if SUPPORTED_PERSONALITIES > 2
> +	nerrnos2,
> +# endif
> +#endif
> +};
> +
>  enum {
>  	nsignals0 = ARRAY_SIZE(signalent0)
>  #if SUPPORTED_PERSONALITIES > 1
> @@ -179,6 +199,26 @@ enum {
>  #endif
>  };
>  
> +const char *const *signalent_vec[SUPPORTED_PERSONALITIES] = {
> +	signalent0,
> +#if SUPPORTED_PERSONALITIES > 1
> +	signalent1,
> +# if SUPPORTED_PERSONALITIES > 2
> +	signalent2,
> +# endif
> +#endif
> +};
> +
> +const unsigned int nsignalent_vec[] = {
> +	nsignals0,
> +#if SUPPORTED_PERSONALITIES > 1
> +	nsignals1,
> +# if SUPPORTED_PERSONALITIES > 2
> +	nsignals2,
> +# endif
> +#endif
> +};
> +
>  enum {
>  	nioctlents0 = ARRAY_SIZE(ioctlent0)
>  #if SUPPORTED_PERSONALITIES > 1
> @@ -189,6 +229,26 @@ enum {
>  #endif
>  };
>  
> +const unsigned int nioctlent_vec[] = {
> +	nioctlents0,
> +#if SUPPORTED_PERSONALITIES > 1
> +	nioctlents1,
> +# if SUPPORTED_PERSONALITIES > 2
> +	nioctlents2,
> +# endif
> +#endif
> +};
> +
> +const struct_ioctlent *const ioctlent_vec[SUPPORTED_PERSONALITIES] = {
> +	ioctlent0,
> +#if SUPPORTED_PERSONALITIES > 1
> +	ioctlent1,
> +# if SUPPORTED_PERSONALITIES > 2
> +	ioctlent2,
> +# endif
> +#endif
> +};
> +
>  #if SUPPORTED_PERSONALITIES > 1
>  const struct_sysent *sysent = sysent0;
>  const char *const *errnoent = errnoent0;
> @@ -221,29 +281,55 @@ const struct_sysent *const sysent_vec[SUPPORTED_PERSONALITIES] = {
>  #endif
>  };
>  
> +const int personality_wordsize[SUPPORTED_PERSONALITIES] = {
> +	PERSONALITY0_WORDSIZE,
> +#if SUPPORTED_PERSONALITIES > 1
> +	PERSONALITY1_WORDSIZE,
> +#endif
> +#if SUPPORTED_PERSONALITIES > 2
> +	PERSONALITY2_WORDSIZE,
> +#endif
> +};
> +
> +const int personality_klongsize[SUPPORTED_PERSONALITIES] = {
> +	PERSONALITY0_KLONGSIZE,
>  #if SUPPORTED_PERSONALITIES > 1
> +	PERSONALITY1_KLONGSIZE,
> +#endif
> +#if SUPPORTED_PERSONALITIES > 2
> +	PERSONALITY2_KLONGSIZE,
> +#endif
> +};
> +
> +#if SUPPORTED_PERSONALITIES > 1
> +
> +const char *const personality_names[] =
> +# if defined POWERPC64
> +	{"64 bit", "32 bit"}
> +# elif defined X86_64
> +	{"64 bit", "32 bit", "x32"}
> +# elif defined X32
> +	{"x32", "32 bit"}
> +# elif defined AARCH64
> +	{"64 bit", "32 bit"}
> +# elif defined TILE
> +	{"64-bit", "32-bit"}
> +# else
> +#  error Add personality names for your achitecture.
> +# endif
> +	;
> +
>  unsigned current_personality;
>  
>  # ifndef current_wordsize
>  unsigned current_wordsize;
> -static const int personality_wordsize[SUPPORTED_PERSONALITIES] = {
> -	PERSONALITY0_WORDSIZE,
> -	PERSONALITY1_WORDSIZE,
> -# if SUPPORTED_PERSONALITIES > 2
> -	PERSONALITY2_WORDSIZE,
>  # endif
> -};
> +# ifndef current_klongsize
> +unsigned current_klongsize;
>  # endif
>  
>  # ifndef current_klongsize
>  unsigned current_klongsize;
> -static const int personality_klongsize[SUPPORTED_PERSONALITIES] = {
> -	PERSONALITY0_KLONGSIZE,
> -	PERSONALITY1_KLONGSIZE,
> -#  if SUPPORTED_PERSONALITIES > 2
> -	PERSONALITY2_KLONGSIZE,
> -#  endif
> -};
>  # endif
>  
>  void
> @@ -306,25 +392,10 @@ update_personality(struct tcb *tcp, unsigned int personality)
>  		return;
>  	tcp->currpers = personality;
>  
> -# undef PERSONALITY_NAMES
> -# if defined POWERPC64
> -#  define PERSONALITY_NAMES {"64 bit", "32 bit"}
> -# elif defined X86_64
> -#  define PERSONALITY_NAMES {"64 bit", "32 bit", "x32"}
> -# elif defined X32
> -#  define PERSONALITY_NAMES {"x32", "32 bit"}
> -# elif defined AARCH64
> -#  define PERSONALITY_NAMES {"64 bit", "32 bit"}
> -# elif defined TILE
> -#  define PERSONALITY_NAMES {"64-bit", "32-bit"}
> -# endif
> -# ifdef PERSONALITY_NAMES
>  	if (!qflag) {
> -		static const char *const names[] = PERSONALITY_NAMES;
>  		error_msg("[ Process PID=%d runs in %s mode. ]",
> -			  tcp->pid, names[personality]);
> +			  tcp->pid, personality_names[personality]);
>  	}
> -# endif
>  }
>  #endif
>  
> @@ -542,40 +613,55 @@ static int arch_set_success(struct tcb *);
>  
>  struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
>  
> -static struct inject_opts *
> -tcb_inject_opts(struct tcb *tcp)
> +static struct inject_values
> +tcb_inject_values(struct tcb *tcp, bool step)
>  {
> -	return (scno_in_range(tcp->scno) && tcp->inject_vec[current_personality])
> -	       ? &tcp->inject_vec[current_personality][tcp->scno] : NULL;
> -}
> -
> -
> -static long
> -tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
> -{
> -	if (!tcp->inject_vec[current_personality]) {
> +	if (step && !tcp->inject_vec[current_personality] &&
> +	    inject_vec[current_personality]) {
>  		tcp->inject_vec[current_personality] =
>  			xcalloc(nsyscalls, sizeof(**inject_vec));
>  		memcpy(tcp->inject_vec[current_personality],
>  		       inject_vec[current_personality],
>  		       nsyscalls * sizeof(**inject_vec));
>  	}
> +	struct inject_opts *opts =
> +		scno_in_range(tcp->scno) && tcp->inject_vec[current_personality]
> +		? &tcp->inject_vec[current_personality][tcp->scno] : NULL;
> +	struct inject_values res = {
> +		.rval = INJECT_VALS_RVAL_DEFAULT,
> +		.signo = 0,
> +	};
> +	if (opts) {
> +		if (step) {
> +			if (opts->first != 0 && --opts->first == 0) {
> +				res = opts->vals;
> +				opts->first = opts->step;
> +			}
> +		} else {
> +			res = opts->vals;
> +		}
> +	}
> +#ifdef USE_LUAJIT
> +	if (tcp->flags & TCB_AD_HOC_INJECT) {
> +		struct inject_values ad_hoc_vals = tcp->ad_hoc_inject_vals;
> +		if (ad_hoc_vals.rval != INJECT_VALS_RVAL_DEFAULT)
> +			res.rval = ad_hoc_vals.rval;
> +		if (ad_hoc_vals.signo > 0)
> +			res.signo = ad_hoc_vals.signo;
> +	}
> +#endif
> +	return res;
> +}
>  
> -	struct inject_opts *opts = tcb_inject_opts(tcp);
> -
> -	if (!opts || opts->first == 0)
> -		return 0;
> -
> -	--opts->first;
> -
> -	if (opts->first != 0)
> -		return 0;
>  
> -	opts->first = opts->step;
> +static long
> +tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
> +{
> +	struct inject_values vals = tcb_inject_values(tcp, true);
>  
> -	if (opts->signo > 0)
> -		*signo = opts->signo;
> -	if (opts->rval != INJECT_OPTS_RVAL_DEFAULT && !arch_set_scno(tcp, -1))
> +	if (vals.signo > 0)
> +		*signo = vals.signo;
> +	if (vals.rval != INJECT_VALS_RVAL_DEFAULT && !arch_set_scno(tcp, -1))
>  		tcp->flags |= TCB_TAMPERED;
>  
>  	return 0;
> @@ -584,22 +670,22 @@ tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
>  static long
>  tamper_with_syscall_exiting(struct tcb *tcp)
>  {
> -	struct inject_opts *opts = tcb_inject_opts(tcp);
> +	struct inject_values vals = tcb_inject_values(tcp, false);
>  
> -	if (!opts)
> +	if (vals.rval == INJECT_VALS_RVAL_DEFAULT)
>  		return 0;
>  
> -	if (opts->rval >= 0) {
> +	if (vals.rval >= 0) {
>  		kernel_long_t u_rval = tcp->u_rval;
>  
> -		tcp->u_rval = opts->rval;
> +		tcp->u_rval = vals.rval;
>  		if (arch_set_success(tcp)) {
>  			tcp->u_rval = u_rval;
>  		} else {
>  			tcp->u_error = 0;
>  		}
>  	} else {
> -		unsigned long new_error = -opts->rval;
> +		unsigned long new_error = -vals.rval;
>  
>  		if (new_error != tcp->u_error && new_error <= MAX_ERRNO_VALUE) {
>  			unsigned long u_error = tcp->u_error;
> @@ -662,6 +748,12 @@ syscall_entering_decode(struct tcb *tcp)
>  	return 1;
>  }
>  
> +static bool
> +syscall_ad_hoc_injected(struct tcb *tcp)
> +{
> +	return (tcp->qual_flg & QUAL_INJECT) && (tcp->flags & TCB_AD_HOC_INJECT);
> +}
> +
>  int
>  syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
>  {
> @@ -684,13 +776,13 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
>  	 || (tracing_paths && !pathtrace_match(tcp))
>  	) {
>  		tcp->flags |= TCB_FILTERED;
> -		return 0;
> +		goto maybe_ad_hoc_tamper;
>  	}
>  
>  	tcp->flags &= ~TCB_FILTERED;
>  
>  	if (hide_log(tcp)) {
> -		return 0;
> +		goto maybe_ad_hoc_tamper;
>  	}
>  
>  	if (tcp->qual_flg & QUAL_INJECT)
> @@ -713,6 +805,11 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
>  		? printargs(tcp) : tcp->s_ent->sys_func(tcp);
>  	fflush(tcp->outf);
>  	return res;
> +
> +maybe_ad_hoc_tamper:
> +	if (syscall_ad_hoc_injected(tcp))
> +		tamper_with_syscall_entering(tcp, sig);
> +	return 0;
>  }
>  
>  void
> @@ -753,21 +850,28 @@ syscall_exiting_decode(struct tcb *tcp, struct timeval *ptv)
>  	}
>  #endif
>  
> -	if (filtered(tcp) || hide_log(tcp))
> +	if ((filtered(tcp) || hide_log(tcp))
> +	 && !(tcp->qual_flg & QUAL_HOOK_EXIT) && !syscall_ad_hoc_injected(tcp))
>  		return 0;
>  
>  	get_regs(tcp->pid);
>  #if SUPPORTED_PERSONALITIES > 1
>  	update_personality(tcp, tcp->currpers);
>  #endif
> -	return get_regs_error ? -1 : get_syscall_result(tcp);
> +	if (get_regs_error || get_syscall_result(tcp) == -1)
> +		return -1;
> +
> +	if (syserror(tcp) && syscall_tampered(tcp))
> +		tamper_with_syscall_exiting(tcp);
> +
> +	return 1;
>  }
>  
>  int
>  syscall_exiting_trace(struct tcb *tcp, struct timeval tv, int res)
>  {
> -	if (syserror(tcp) && syscall_tampered(tcp))
> -		tamper_with_syscall_exiting(tcp);
> +	if (filtered(tcp) || hide_log(tcp))
> +		return 0;
>  
>  	if (cflag) {
>  		count_syscall(tcp, &tv);
> @@ -976,7 +1080,7 @@ syscall_exiting_trace(struct tcb *tcp, struct timeval tv, int res)
>  void
>  syscall_exiting_finish(struct tcb *tcp)
>  {
> -	tcp->flags &= ~(TCB_INSYSCALL | TCB_TAMPERED);
> +	tcp->flags &= ~(TCB_INSYSCALL | TCB_TAMPERED | TCB_AD_HOC_INJECT);
>  	tcp->sys_func_rval = 0;
>  	free_tcb_priv_data(tcp);
>  }
> diff --GIT A/sysent.h b/sysent.h
> index 92de7468..15b83693 100644
> --- a/sysent.h
> +++ b/sysent.h
> @@ -1,13 +1,31 @@
> -#ifndef STRACE_SYSENT_H
> -#define STRACE_SYSENT_H
> +#if !defined(STRACE_SYSENT_H) || defined(FFI_CDEF)
> +#ifndef FFI_CDEF
> +# define STRACE_SYSENT_H
> +#endif
>  
> +#include "ffi.h"
> +
> +FFI_CONTENT(
>  typedef struct sysent {
>  	unsigned nargs;
>  	int	sys_flags;
> +)
> +/* We don't want to expose sen and sys_func to LuaJIT */
> +#ifdef FFI_CDEF
> +FFI_CONTENT(
> +	int priv1;
> +	void *priv2;
> +)
> +#else
> +FFI_CONTENT(
>  	int	sen;
>  	int	(*sys_func)();
> +)
> +#endif
> +FFI_CONTENT(
>  	const char *sys_name;
>  } struct_sysent;
> +)
>  
>  #define TRACE_FILE			00000001	/* Trace file-related syscalls. */
>  #define TRACE_IPC			00000002	/* Trace IPC-related syscalls. */
> @@ -29,4 +47,4 @@ typedef struct sysent {
>  #define TRACE_FSTAT			00400000	/* Trace *fstat{,at}{,64} syscalls. */
>  #define TRACE_STAT_LIKE			01000000	/* Trace *{,l,f}stat{,x,at}{,64} syscalls. */
>  
> -#endif /* !STRACE_SYSENT_H */
> +#endif /* !defined(STRACE_SYSENT_H) || defined(FFI_CDEF) */
> -- 
> 2.11.0

So, the script as simple as

	tcp = strace.C.next_sc()
	print(tcp.scno)

would crash strace process itself. What do you think about protecting
against those simplest ways to crash strace?




More information about the Strace-devel mailing list