~asterism/domme

26a9e7d70b7eab480f455079bccd83aa2f00feed — Phantom 1 year, 6 months ago 402a205 v1.0.1
Significant bug fixes. Made domme.asm easier to follow.

Replaced message 0x2f.
Removed some redundant instructions.
Changed stack frame index register from %r8 to %r10.
(None of these changes affect execution.)

Fleshed out comments.
Moved global symbols to top of file for use as hyperlinks.
Make now generates listing file domme.lst from domme.asm.

Added missing translation of kernel oldact into libc form.
Added simple address checking for act and oldact.
Fixed bug that corrupted backtraces if DOMME_init registered SIG_DFL and crashed.
Added more tests.

Removed register aliases.
Most of the GPRs used have some special significance to this or that instruction,
so it's important to be able to keep track of which ones are being used for what.
I now understand why nobody else does this.
5 files changed, 344 insertions(+), 194 deletions(-)

M .gitignore
M Makefile
M domme-test
M domme-test-driver.c
M domme.asm
M .gitignore => .gitignore +9 -0
@@ 1,5 1,6 @@
# Generated by make.
domme.o
domme.lst
libdomme.a
domme-test-driver



@@ 11,5 12,13 @@ core
TAGS
tags

# For distribution on the homepage.
domme.tar.gz
domme/

# Miscellaneous.
*~

# Private.
NOTES
TODO

M Makefile => Makefile +3 -2
@@ 22,13 22,14 @@ all: libdomme.a domme-test-driver
libdomme.a: domme.o
	ar -r libdomme.a domme.o

# Let's see if '--noexecstack' shuts off that one compiler warning...
domme.o: domme.asm
	$(AS) $(ASFLAGS) -o domme.o domme.asm
	$(AS) $(ASFLAGS) -o domme.o -als=domme.lst --noexecstack domme.asm

# If we don't use "-L.",
# the test program might use an outdated version of the library,
# if the most recently compiled version hasn't been installed.
domme-test-driver: domme.h libdomme.a
domme-test-driver: domme.h libdomme.a domme-test-driver.c
	$(CC) $(CFLAGS) -o domme-test-driver domme-test-driver.c -L. -ldomme

check: domme-test-driver

M domme-test => domme-test +18 -6
@@ 15,18 15,30 @@
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

./domme-test-driver
./domme-test-driver # this does most of the work
ret=$?
if [ $ret != 139 ]
if [ $ret != 139 ] # 139 = a crash with SIGSEGV--which is exactly what we want
then
    echo 'Test failed. (Call "echo $?" for details.)' >&2
    if [ $ret == 0  ]
    then
	exit 63
	exit 10 # higher than any exit normal code in domme-test-driver
    else
	exit $ret
	exit $ret # domme-test-driver exit code
    fi
else
    echo "I meant to do that. Test passed." >&2
    exit 0
    echo "I meant to do that." >&2
    echo "Inspecting backtrace for evidence of domme..." >&2
    # If domme actually returned at the end of domme-test-driver,
    # you shouldn't be able to see it on the backtrace.
    # But you should be able to see the handler it piggybacked on.
    echo bt | gdb ./domme-test-driver ./core -batch | grep "Hi GDB!" >&2
    if [ $? == 0 ]
    then
	echo "CORE=OK"
	echo "Test passed." >&2
    else
	echo "CORE=ERROR"
	exit 10
    fi
fi

M domme-test-driver.c => domme-test-driver.c +81 -29
@@ 18,17 18,22 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>
#include <sysexits.h>
#include <sys/ucontext.h>
/* Remember,
 * we want to test a header that may not have been installed yet. */
#include "domme.h"

enum test_phase { VERSION, MSGTBL, XLAT_A, HANDLER } phase;
extern const char *program_invocation_short_name;

/* Where are we in the test? */
enum test_phase { BUFFER = 1, ACT, OLDACT, VERSION, MSGTBL, XLAT_A, HANDLER } phase;

/* If you think you know why I chose this number, email me. */
const int CRASH_ARG = 8426;	/* hint: 8 Apr 1926 */
const int CRASH_ARG = 8426;	// hint: 8 Apr 1926

/* Index variable for loops in main,
   made global so it can be accessed from the segfault handler below. */


@@ 36,30 41,36 @@ unsigned int i = 0;

void
handler(int sig, siginfo_t *info, void *ucontext) {
	/* ucontext has to be declared as void,
	/* ucontext has to be declared as a pointer to void,
	 * but also needs to be dereferenced later. */
	ucontext_t *context = ucontext;

	/* We enter this function at most twice. */
	static int re_entry = 0;
	static bool re_entry = false;
	
	switch (phase) {
	case ACT:
		printf("ACT=ERROR\n");
		exit(phase);
	case OLDACT:
		printf("OLDACT=ERROR\n");
		exit(phase);
	case VERSION:
		/* We got here from the middle of a printf call,
		 * but we should assume printf won't output anything
		 * until its format string has been fully processed.
		 * So we have to reprint the version line from the beginning. */
		printf("VERSION=ERROR\n");
		exit(1);
		exit(phase);
	case MSGTBL:
		printf("MSGTBL=ERROR:%d\n", i);
		exit(2);
		exit(phase);
	case XLAT_A:
		printf("XLAT_A=ERROR:%d\n", i);
		exit(3);
		exit(phase);
	case HANDLER:
		if (!re_entry) {
			re_entry = 1;
			re_entry = true;
			fprintf(stderr, "Attempting re-entry to the handler...\n");
			return;
		}


@@ 74,18 85,17 @@ handler(int sig, siginfo_t *info, void *ucontext) {
		      && info
		      && info->si_signo == SIGSEGV
		      && info->si_code  == SEGV_MAPERR
		      && info->si_addr  == CRASH	/* this should be where we crashed */
		      && info->si_addr  == CRASH	// this should be where we crashed
		      && context
		      /* The most important test. Also the scariest. */
		      && context->uc_mcontext.gregs[REG_RDI] == CRASH_ARG)) {
			printf("HANDLER=ERROR\n");
			exit(4);
			exit(phase);
		}
		
		printf("HANDLER=OK\n");

		/* Finally, it's time to see what happens if we segfault
		 * _without_ giving domme a handler.
		/* Finally, try using domme with default behavior.
		 * Because we're already in a signal handler,
		 * we have to manually unblock SIGSEGV,
		 * which was blocked by the kernel on our arrival. */


@@ 96,29 106,74 @@ handler(int sig, siginfo_t *info, void *ucontext) {
		sigprocmask(SIG_UNBLOCK, &set, NULL);
		
		/* If this program exits normally,
		 * call an exorcist. */
		 * your computer is haunted. */
		struct sigaction sa = { 0 };
		DOMME_init(&sa, 0, sizeof(sigset_t));
		CRASH(0);
		*(char **)NULL = "Hi GDB!";
		__attribute__ ((fallthrough));
	default:		// You should never see this.
		fprintf(stderr,
			"%s: Internal error in test program (check exit code)\n",
			program_invocation_short_name);
		exit(phase);	// phase == bad if unknown, even worse if HANDLER
	}
}

/* This should never get called. */
void
wrong_handler () {
	fprintf(stderr, "Bad news--rt_sigaction writes oldact before reading act.\n");
	exit(HANDLER+1);
}

int
main (void) {
	/* To test how OLDACT is saved,
	 * we need something to be registered before our real handler. */
	struct sigaction fake_sa = { 0 };
	fake_sa.sa_flags = SA_SIGINFO;
	fake_sa.sa_sigaction = wrong_handler;
	sigaction(SIGSEGV, &fake_sa, NULL);
	
	struct sigaction sa = { 0 };
	sa.sa_flags = SA_SIGINFO;
	sa.sa_sigaction = handler;
	/* Uncomment the line below to make domme point at itself;
	 * DOMME_init should catch this. */
	/* sa.sa_sigaction = (void *)domme; */
	DOMME_init(&sa, 0, sizeof(sigset_t));
	// sa.sa_sigaction = (void *)domme;
	DOMME_init(&sa, &sa, sizeof(sigset_t));

	/* An error code higher than 63 denotes an error in the test program itself. */
	if (fprintf(stderr, "Now testing libdomme.\n") < 0
	    /* An error in that last write might not have been caught
	     * if its file was block-buffered. Better make sure. */
	    || fflush(stderr) == EOF)
		exit(EX_IOERR); 	/* oh come on */
		exit(EX_IOERR); 	// oh come on

	if (printf("OUTPUT=OK\n") < 0 || fflush(stdout) == EOF)
		exit(EX_IOERR);

	/* Before we can compare SA and FAKE_SA,
	 * we have to undo whatever libc might've done to FAKE_SA
	 * between buffering it and calling rt_sigaction. */
	phase = BUFFER;
	sa.sa_flags = sa.sa_flags &~ 0x04000000; // (SA_RESTORER)
	sa.sa_restorer = NULL;
	if (memcmp(&sa, &fake_sa, sizeof(sa))) {
		printf("BUFFER=ERROR\n");
		exit(phase);
	}
	printf("BUFFER=OK\n");

	/* Does DOMME_init catch bad addresses
	 * on behalf of rt_sigaction?
	 * If not, one of the calls below will segault. */
	phase = ACT;
	DOMME_init((void *)-1L, (void *)-1L, 0);
	printf("ACT=OK\n");
	phase = OLDACT;
	DOMME_init(0, (void *)-1L, 0);
	printf("OLDACT=OK\n");

	/* Let's see if the globals in domme.h have been properly defined.
	 * Be aware that this tests only whether they point to valid memory.


@@ 128,13 183,11 @@ main (void) {
	/* Print version number.
	 * Also, verify stdout is legit, as with stderr earlier. */
	phase = VERSION;
	if (printf("VERSION=%d.%d.%d.%d\n",
		    DOMME_version[0],
		    DOMME_version[1],
		    DOMME_version[2],
		    DOMME_version[3]) < 0
		|| fflush(stdout) == EOF)
		exit(EX_IOERR);
	printf("VERSION=%d.%d.%d.%d\n",
	       DOMME_version[0],
	       DOMME_version[1],
	       DOMME_version[2],
	       DOMME_version[3]);

	/* Try printing the script. */
	phase = MSGTBL;


@@ 150,15 203,14 @@ main (void) {
			DOMME_xlat_a[i], (i & 15) == 15 ? "\n" : "");
	printf("XLAT_A=OK\n");

	/* So far, so good. Trigger a segfault and see what happens. */
	fprintf(stderr, "\nTriggering segfault with handler...\n");
	phase = HANDLER;

	/* We give our crash a (thematically appropriate) numerical argument
	/* So far, so good. Trigger a segfault and see what happens.
	 * We give our crash a (thematically appropriate) numerical argument
	 * so that we can later check whether it appears in the right place
	 * in the saved context passed to the handler.
	 * Because this was the first (and only) argument,
	 * it should be placed in %rdi, according to the SysV AMD64 ABI. */
	fprintf(stderr, "\nTriggering segfault with handler...\n");
	phase = HANDLER;
	CRASH(CRASH_ARG);

	/* Can't happen.

M domme.asm => domme.asm +233 -157
@@ 14,7 14,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

(Note: Timestamps in comments take the form HHMM.DD.MM.YYYY>.)
(Note: Timestamps in comments take the form <HHMM.DD.MM.YYYY>.)

To start: The symbols below were pulled, almost verbatim, from a much
bigger file named "defs.asm", which is on my computer. It includes the


@@ 25,156 25,233 @@ complete. There used to be a line ".include \"defs.asm\"" around this
point, but I'd rather distribute this as a one-file program. especially
since the file has gotten large enough that _not_ including it has a
significant effect on the size of the resulting binary. */
	.set	stderr, 2

	## We might as well use %r11 for temporary storage,
	## since it gets clobbered by the kernel anyway.
	.set	tmp, %r11

	## System call arguments.
	.set	syscode, %rax
	.set	sysarg1, %rdi
	.set	sysarg2, %rsi
	.set	sysarg3, %rdx
	.set	sysarg4, %r10
	.set	sysret1, %rax

	## Libc function call arguments.
	.set	libarg1, %rdi
	.set	libarg2, %rsi
	.set	libarg3, %rdx
	.set	libarg4, %rcx
	.set	libret,  %rax	# may use later

	## System calls.
	.set	SYS_write,	  1
	.set	SYS_rt_sigaction, 13
	.set	SYS_rt_sigreturn, 15
	.set	SYS_exit,	  60
	.set	SYS_time,	  201
	.set	SYS_write,	  1	#\
	.set	SYS_rt_sigaction, 13	# \
	.set	SYS_rt_sigreturn, 15	#  from <asm/unistd_64.h>
	.set	SYS_exit,	  60	# /
	.set	SYS_time,	  201	#/

	## Flags for 'rt_sigaction'.
	## Symbols for rt_sigaction.
	.set	RT_SA_RESTORER, 0x04000000
	.set	SIGSEGV, 11	# from "man 7 signal"
	.set	SIG_ERR, -1 	#\
	.set	SIG_DFL,  0	# from "/usr/src/linux/include/uapi/asm-generic/signal-defs.h"
	.set	SIG_IGN, +1	#/

	## Error codes.
	.set	EFAULT, 14	# Bad address
	
	## Fields for the kernel's sigaction struct.
	## This is different from the POSIX sigaction struct
	## used by a typical C library, where sa_mask
	## would be the second field instead of the last,
	## so DOMME_init has to translate between forms.
	## (See "man 2 sigaction" for your standard library;
	## compare with "include/linux/signal_types.h"
	## in your linux sources.)
	## Some fields for the libc sigaction are also defined here.
	.struct	0
sa_handler:
sa_sigaction:
libc_sa_handler:
	.struct sa_handler+8
sa_flags:
libc_sa_mask:			# variable-size
	.struct sa_flags+8
sa_restorer:
	.struct sa_restorer+8
sa_mask:
sa_mask:			# known size
	.struct	sa_mask+8
sa_size:

	## Signal values, ripped straight from *Man 7 signal*.
	.set	SIGSEGV, 11

	## "Fake signal functions",
	## as described in "signum-generic.h".
	.set	SIG_ERR, -1
	.set	SIG_DFL,  0
	.set	SIG_IGN, +1

	.globl	DOMME_init
	.globl	domme
	## Index of global symbols.
	## To users of etags, ctags, etc.: jump from here.
	.globl	DOMME_init		# call this first
	.globl	domme			# call this for trouble
	.globl	DOMME_msgtbl		# string table with embedded sizes
	.globl	DOMME_xlat_a		# array of indices manipulated with XLAT
	.globl	DOMME_version		# version number
	.globl	DOMME_VERSION_SIZE	#\
	.globl	DOMME_MSGTBL_SIZE	# number of elements in each array
	.globl	DOMME_XLAT_A_SIZE	#/

	.text

	## DOMME_init -- Register SIGSEGV handler with rt_sigaction.
	## On entry:
	## %rdi =:: rt_sigaction:act.
	## %rsi =:: rt_sigaction:oldact.
	## %rdx =:: sigsetsize.
	## 	sizeof(sigset_t) in caller's libc implementation.
	## 
	## On exit:
	## %rax =:: return value of rt_sigaction.
	## 
	## Notes:
	## %r10 =:: stack frame index register (see below).
	## %r8  =:: value of act->sa_sigaction.
	## rt_sigaction:sig is implied to be SIGSEGV (11).
	## act->sa_sigaction is substituted for domme and saved;
	## it will be jumped directly to after domme runs.
	## 
	## DOMME_init buffers act and oldact and passes the buffers to rt_sigaction;
	## therefore it works even if act is read-only.
	## Furthermore, DOMME_init always reads act in before writing oldact out;
	## therefore _act and oldact may point to the same structure_.
	## 
	## See also: man 2 sigaction; man 2 sigprocmask.
DOMME_init:
	## Create stack frame.
	## Because the stack frame is variable-length,
	## and because the fields of its sigaction struct
	## must appear in ascending addresses,
	## we need to set up an index register to look into it.
	push	%rbp
	lea	(%rsp), %rbp
	mov	%rdx, %r8
	neg	%r8
	sub	$sa_mask, %r8
	add	%r8, %rsp

	## Save clobbered values we'll need later.
	mov	$8, sysarg4
	push	%rsi		# libarg2
	push	%rdi		# libarg1
	## The size of libc sa_mask varies by implementation.
	## Because this makes the stack frame variable-length,
	## we can't use hardcoded offsets to index into it
	## and must set up an index register instead.
	## We could use hardcoded offsets if the sigaction's fields--
	## along with the elements of sa_mask--
	## were stored in descending addresses,
	## since we'd only have to point to the beginning of sa_mask.	
	## ...But actually no we couldn't,
	## because even then the total size of the sigaction could
	## (and on the author's system, does) exceed the size of the red zone.
	push	%rbp		# save old base pointer
	lea	(%rsp), %rbp	# establish new base pointer
	mov	%rdx, %r10	#\
	neg	%r10		# %r10 := -(%rdx+<offset of sa_mask>)
	sub	$sa_mask, %r10	# <offset of FIELD>(%rsp,%r10) =:: kact.FIELD
	add	%r10, %rsp	#/

	## rt_sigaction returns -EFAULT if either act or oldact
	## lie outside the process's address space.
	## Since we pass neither to rt_sigaction,
	## we have to check them both ourselves.
	push	%rdx			#\
	push	%rsi			#_not popped until after rt_sigaction
	mov	$-1, %rax		#\
	shl	$47, %rax		#_mask for illegal address bits
	mov	$2, %rcx		# check next 2 stack entries
2:	mov	-16(%rsp,%rcx,8), %r11	#\
	test	%r11, %rax		# are we out of bounds?
	jnz	efault			#/
	sub	%r10, %r11		#\ %r11 -(-sigsetsize)
	test	%r11, %rax		# are we headed out of bounds?
	jz	1f			#/
efault:	mov	$-EFAULT, %rax
	jmp	0f
1:	loop	2b

	## Copy caller's sigaction onto the stack.
	## But first, save the caller's handler.
	## We can't place it in target until the last minute.
	mov	sa_handler(%rdi), %r9
	mov	libc_sa_handler(%rdi), %r8
	
	## Bulk copy sa_mask.
	lea	8(libarg1), %rsi # subtle. we need two structs (?)
	lea	sa_mask(%rbp,%r8), %rdi
	mov	%rdx, %rcx
	lea	libc_sa_mask(%rdi), %rsi
	lea	sa_mask(%rbp,%r10), %rdi
	mov	8(%rsp), %rcx		# saved sigsetsize
	cld
	rep movsb
	
	## Finally sa_flags and sa_restorer.
	## %rsi is already where it needs to be.
	mov	(%rsi), %r11
	mov	%r11, sa_flags(%rbp,%r8)
	mov	8(%rsi), %r11
	mov	%r11, sa_restorer(%rbp,%r8)

	## Restore old values in new, syscall-appropriate registers.
	pop	sysarg2
	pop	sysarg3
	## Next, sa_flags and sa_restorer.
	## Assertion: %rsi = &(act->sa_flags).
	lea	sa_flags(%rbp,%r10), %rdi
	movsq
	movsq

	## Don't change anything
	## if sa_sigaction is SIG_IGN or SIG_ERR,
	## since even to print a message
	## would dishonor the meanings of those values.
	## We assert %rcx = 0 after the previous rep.
	mov	$1, tmp
	cmpq	$SIG_IGN, %r9
	cmove	tmp, %rcx
	cmpq	$SIG_ERR, %r9 # is this needed?
	cmove	tmp, %rcx
	cmp	domme(%rip), %r9
	cmpq	$SIG_IGN, %r8
	je	2f
	cmp	domme(%rip), %r8
	je	9f
	jrcxz	1f # SIG_DFL
	jmp	2f

	## The big switch. Set up the signal handler.
1:	lea	domme(%rip), tmp
	mov	tmp, sa_handler(%rbp,%r8)
	lea	(%rbp,%r8), sysarg2
	
	## Now for 'rt_sigaction' to install the handler.
2:	lea	trampl(%rip), tmp
	mov	tmp, sa_restorer(%rbp,%r8)
	mov	$RT_SA_RESTORER, tmp
	or	tmp, sa_flags(%rbp,%r8)
	mov	$SYS_rt_sigaction, syscode
	mov	$SIGSEGV, sysarg1
	## Watch out: If the caller provided an oldact,
	## we still have to translate the incoming sigaction
	## into libc representation before shipping it back.
	## So we save the caller-provided pointer
	## and replace it with our own.
	## As it happens, we use the same buffer for our kernel act
	## and kernel oldact. This makes indexing easier,
	## and we no longer need the former after calling rt_sigaction anyway.
	## This means DOMME_init always provides an oldact to rt_sigaction.
1:	lea	domme(%rip), %r11 		#\
	mov	%r11, sa_handler(%rbp,%r10)	#_THE BIG SWITCH
2:	lea	(%rbp,%r10), %rsi
	mov	%rsi, %rdx		     	# copy act to oldact
	lea	trampl(%rip), %r11 		#\
	mov	%r11, sa_restorer(%rbp,%r10)	# \
	mov	$RT_SA_RESTORER, %r11		#  set up signal trampoline
	or	%r11, sa_flags(%rbp,%r10)	#_/
	mov	$SYS_rt_sigaction, %rax
	mov	$SIGSEGV, %rdi
	push	%r10
	mov	$8, %r10
	syscall
	pop	%r10

	## Save target only if the syscall succeeds.
	test	%rax, %rax
	js	0f
	mov	%r9, target(%rip)
	test	%r8, %r8		# SIG_DFL has to be handled specially
	jnz	1f
	lea	dmytgt(%rip), %r8	# jump to the dummy target, not to NULL!
1:	mov	%r8, target(%rip)

	## Now to see if the caller provided an oldact and, if so, copy it.
	pop	%rdi		# %rsi = kernel act = kernel oldact
	test	%rdi, %rdi
	jz	0f
	
	## This is almost the same as copying in earlier.
	cld			# the syscall may have set this flag
	movsq			# %rsi and %rdi already point to their respective sa_handlers
	lea	sa_mask(%rbp,%r10), %rsi
	pop	%rcx		# saved sigsetsize
	rep movsb
	lea	sa_flags(%rbp,%r10), %rsi
	movsq
	movsq

	## Restore stack frame and return;
	## The return value will be in %rax.
	## Restore stack frame and return.
0:	lea	(%rbp), %rsp
	pop	%rbp
	ret

	## Exit program if target = domme.
9:	mov	$SYS_write, syscode
	mov	$stderr, sysarg1
	lea	errmsg(%rip), sysarg2
	mov	$errsiz, sysarg3
9:	mov	$SYS_write, %rax
	mov	$2, %rdi
	lea	errmsg(%rip), %rsi
	mov	$errsiz, %rdx
	syscall
	mov	$SYS_exit, %rax	# 33 is well above ordinary exit codes,
	mov	$33, %rdi	# but doesn't collide with <sysexits.h>.
	syscall			# other exit codes from domme would also be 32+something.
	
	mov	$SYS_exit, syscode
	mov	$69, sysarg1
	syscall
	
	## TODO: Should this get its own stack frame?
	## domme -- SIGSEGV handler for masochists.
	## On entry:
	## %rsi =:: signal number (usually SIGSEGV).
	## %rdi =:: pointer to signal information block.
	## %rdx =:: pointer to copy of context at time signal was raised.
	## 
	## On exit:
	## All other registers besides %rax are as they were on entry.
	##  Userspace call:
	##  No return value is (yet) specified.
	##  (At present %rax := trampl, in case you were wondering.)
	## 
	##  Kernel call:
	##  domme does not return--it jumps to (target) on completion
	##  with %rax := 0.
	## 
	## Notes:
	## domme manipulates the stack.
	## If your SIGSEGV handler is expected to handle stack overflow.
	## an alternate stack must be registered with rt_sigaltstack.
	## (But this would be true regardless.)
domme:
	## A lot of register info is passed to this routine,
	## which needs to be forwarded unchanged to the user handler,


@@ 193,24 270,23 @@ domme:
	pushq	%rdx		# syscall arg
	pushq	%rcx		# clobbered by kernel
	pushq	%rbx		# DOMME_xlat_a

	## Let's make a note now of where %rdi was pushed--
	## we'll need to look it back up later.
	.set	savrdi, 32
	.set	savrdi, 32	# we'll need this for later
	
	## Now, to figure out which message to print.
	## Now to figure out which message to print.
	## We start by generating a random number.
	## We need to test SEEDED to see if SEED has already been initialized,
	## because SEED could legitimately take on any value.
	mov	seed(%rip), %rax
	mov	seeded(%rip), sysarg1	# we initialized the seed, right?
	test	sysarg1, sysarg1 	# ...right?
	mov	seeded(%rip), %rdi	# we initialized the seed, right?
	test	%rdi, %rdi 		# ...right?
	jnz	1f			# if so, continue
	
	## If not, ask for the time and seed from that.
	mov	$SYS_time, %rax	# sysarg1 is assumed to be 0
	mov	$SYS_time, %rax	# %rdi is assumed to be 0
	syscall
	inc	sysarg1		# set %rdi to $1
	mov	sysarg1, seeded(%rip)
	inc	%rdi			# set %rdi to $1 for spinlock section
	mov	%rdi, seeded(%rip)
	
	## Either way, the seed will now be in %rax,
	## and %rdi will now be 1.
	## From here we can do the generating.


@@ 231,59 307,62 @@ domme:
	jnz	2b

	## We're cleared to store, as we should;
	## "domme" may be called again later.
	## domme may be called again later.
	mov	%rdx, seed(%rip)
	xchg	%rax, slock(%rip) # swap out the relic
	xchg	%rdx, %rax	# now seed is back in %rax.
	xchg	%rax, slock(%rip)	# seed is safe to use starting here
	xchg	%rdx, %rax		# now seed is back in %rax.

	## Load message.
	## There's a maximum of 64 messages,
	## which is more than we currently use.
	## Someday I might forget about reserving the flags
	## and bump this up to 256, but we're nowhere near that yet,
	## and I don't expect to be for some time.
	lea	DOMME_xlat_a(%rip), %rbx 	# load translation array
	xlatb					# index of message is now in %rax.
	and	$0x3f, %rax			# mask off the extra bits...
	shl	$4, %rax			# ...because this might shift into the next byte.
	lea	DOMME_msgtbl(%rip), %rdx
	add	%rdx, %rax
	mov	(%rax), sysarg2 	# %rsi now contains the message to write
	mov	msgsiz(%rax), sysarg3
	mov	(%rax), %rsi 	# %rsi now contains the message to write
	mov	msgsiz(%rax), %rdx

	## Finally, print it.
	mov	$SYS_write, syscode
	mov	$stderr, sysarg1
	mov	$SYS_write, %rax
	mov	$2, %rdi
	syscall
	
	## If the write failed, don't bother adding a newline...
	test	sysret1, sysret1
	test	%rax, %rax
	js	1f

	## ...Otherwise, go ahead.
	lea	newln(%rip), sysarg2
	mov	$1, sysarg3
	mov	$SYS_write, syscode # don't forget %rax was overwritten!
	lea	newln(%rip), %rsi
	mov	$1, %rdx
	mov	$SYS_write, %rax # don't forget %rax was overwritten!
	syscall

	## Let's recover the signal we were originally handed.
	## If our signal isn't SIGSEGV, nothing more needs to be done.
	## (This is assumed to have been a userspace call.)
1:	mov	savrdi(%rsp), sysarg1	# sysarg1 == libarg1 == %rsi
	cmp	$SIGSEGV, sysarg1
	## If our signal isn't SIGSEGV, nothing more needs to be done
	## (because this is assumed to have been a userspace call).
1:	mov	savrdi(%rsp), %rdi
	cmp	$SIGSEGV, %rdi
	jne	1f

	## If it is, then we have something to handle.
	## Check target; if not the dummy target,
	## register special behavior and return.
	## Check target; if it's the dummy target,
	## register default behavior and return.
	lea	dmytgt(%rip), %rax
	cmp	target(%rip), %rax
	jne	1f

	## Not the dummy target!
	## Call rt_sigaction with special behavior.
	## $SIGSEGV is already in %rsi at this point.
	mov	$SYS_rt_sigaction, syscode
	lea	fallbk(%rip), sysarg2
	mov	$0, sysarg3
	mov	$8, sysarg4
	## It's the dummy target.
	## Call rt_sigaction with default behavior.
	## $SIGSEGV is already in %rdi at this point.
	mov	$SYS_rt_sigaction, %rax
	lea	fallbk(%rip), %rsi
	mov	$0, %rdx
	mov	$8, %r10
	syscall

	## Restore the original registers,


@@ 301,13 380,13 @@ domme:
	## Last question:
	## Who called us, the kernel or the user?
	## One way to check is to see if our return address
	## is the signal trampoline (see "trampl" below),
	## is the signal trampoline (see trampl below),
	## which is nigh-invisible to user programs.
	## You could abuse this heuristic
	## by using rt_sigaction's OLDACT parameter
	## to obtain the restorer previously installed by "DOMME_init",
	## to obtain the restorer previously installed by DOMME_init,
	## then pushing that restorer onto the stack
	## and jumping directly to "domme"...
	## and jumping directly to domme...
	## ...but you can't do any of that from a high-level language,
	## so it's unlikely to happen in a situation
	## where anything's at stake.


@@ 334,12 413,12 @@ domme:
	popfq			   # pop the flags (this has to be the last step)
dmytgt:	ret			   # and we're off! da svidania!
	## The dummy target above is stored in TARGET by default,
	## since "domme" has to jump _somewhere_
	## since domme has to jump _somewhere_
	## when called from kernelspace.

	## Signal trampoline
	## (pun intended).
trampl:	mov $SYS_rt_sigreturn, syscode
trampl:	mov $SYS_rt_sigreturn, %rax
	syscall

/* And now, the text.


@@ 526,7 605,11 @@ msg046:	.ascii	"Inasmuch as your computer is an extension of your body,\n"
	.ascii	"\n"
	.asciz	"This stain on your screen isn't a sin--it's a sacrament!"
	.set	siz046, .-msg046-1
msg047:	.asciz	"Hello? Are you there? Did you do this?"
	## It was a toss-up between "about to" and "gonna".
	## There was a better sense of immanence with the former,
	## but it just had to be two syllables. I think this works.
	## (Also: I don't think anyone's going to get the reference.)
msg047:	.asciz	"Assume the position, babe--I'm 'bout to realign your spine."
	.set	siz047, .-msg047-1
	## I may use these slots one day. But not today.
	## In the meantime, you can (see DOMME_NMSGS below).


@@ 575,7 658,7 @@ msg063:	.asciz	"Unused message #15."
msgptr:	.struct msgptr+8
msgsiz:	.struct msgsiz+8

	## Turns out, this table has to go in the data segment,
	## Turns out, this table has to go in the data section,
	## so these could be replaced at runtime with custom entries.
	## It's not a bug, it's _extensible_.
	## In all seriousness, though, if you do this,


@@ 651,8 734,6 @@ DOMME_msgtbl:
	.dc.a	msg062, siz062
	.dc.a	msg063, siz063
	
	.globl	DOMME_msgtbl

	## Number of available messages,
	## including reserved messages.
	.set	MSGTBL_SIZE, (.-DOMME_msgtbl)/16


@@ 660,9 741,7 @@ DOMME_msgtbl:
	## The likelihood of getting any particular message
	## is weighted using this table,
	## which will be adjusted over time.
	## TODO: Consider letting the caller pass a custom table in %rcx?
	## That would be not-great, since the pointer could be uninitialized, null,
	## or otherwise kooky.
	## You can memcpy your own data here, if you like.
DOMME_xlat_a:	
	.dc.b	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15
	.dc.b	0x0a,0x0a,0x0a,0x0a,0x0a,0x26,0x0a,0x0a,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c


@@ 681,10 760,11 @@ DOMME_xlat_a:
	.dc.b	0x09,0x2e,0x08,0x08,0x08,0x08,0x16,0x16,0x16,0x16,0x0d,0x0d,0x0d,0x0d,0x22,0x19
	.dc.b	0x06,0x06,0x0e,0x2d,0x0e,0x0e,0x17,0x17,0x17,0x17,0x1d,0x1d,0x1d,0x1d,0x1c,0x1c

	## The high bits of bytes of 'DOMME_xlat_a' are masked off before being used as indices,
	## At the moment,
	## the high bits of bytes of 'DOMME_xlat_a' are masked off before being used as indices,
	## so you can modify this table arbitrarily. That said, I may use the high bits for
	## something else later, so using them may have unexpected effects in later versions.
	.globl	DOMME_xlat_a
	## (Or not.)
	.set	XLAT_A_SIZE, .-DOMME_xlat_a

	.section .rodata


@@ 733,20 813,19 @@ The current version is 1.0.0.0. */
	.data
	.align	16
DOMME_version:
	.dc.l	1,0,0,0
	.globl	DOMME_version
	.dc.l	1,0,1,0
	.set	VERSION_SIZE, (.-DOMME_version)/4

	## An empty sigaction struct, to be used as a fallback handler
	## when target = 0.
	## when target = dmytgt.
fallbk:	.dc.a	0
	.dc.l	RT_SA_RESTORER
	.skip	4
	.dc.a	RT_SA_RESTORER
	.dc.a	trampl
	.skip	8

	## This should only be written by DOMME_init,
	## which would ideally only be called once, on startup.
	## which would normally be called on startup.
	## (Though it can be written more than once.)
target:	.dc.a	dmytgt		# target handler to jump to.

	.bss


@@ 760,10 839,7 @@ slock:	.dc.a	0		# spinlock for 'seed'.
	## Sizes of the tables assembled earlier.
DOMME_VERSION_SIZE:
	.dc.a	VERSION_SIZE
	.globl	DOMME_VERSION_SIZE
DOMME_MSGTBL_SIZE:
	.dc.a	MSGTBL_SIZE
	.globl	DOMME_MSGTBL_SIZE
DOMME_XLAT_A_SIZE:
	.dc.a	XLAT_A_SIZE
	.globl	DOMME_XLAT_A_SIZE