~asterism/domme

25e3b990075eb0e52787aac6912c22670ee9a15d — Phantom 3 months ago 26a9e7d mistress
You can now initialize libdomme in one line. Some minor bug fixes.

Included a test of libdomme's ability to detect direct pointers to
$domme itself. The address check done by $DOMME_init can now catch any
unmapped address, not just illegal ones. Program symbols are prefixed
with '$' in documentation, for visibility. The README has been updated
to reflect these facts. "make clean" no longer removes "domme.lst",
but does remove the file "core" generated by the test program.
6 files changed, 223 insertions(+), 158 deletions(-)

M Makefile
M README
M domme-test
M domme-test-driver.c
M domme.asm
M domme.h
M Makefile => Makefile +2 -2
@@ 1,5 1,5 @@
# Makefile.
# Copyright (C) 2022, 2023 phantom (phantom@syslbnth.com)
# Copyright (C) 2022-2024 phantom (phantom@syslbnth.com)
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by


@@ 40,4 40,4 @@ install:
	cp domme.h /usr/include

clean:
	rm libdomme.a domme.o domme-test-driver
	rm libdomme.a domme.o domme-test-driver core

M README => README +33 -21
@@ 1,8 1,8 @@
Copyright (C) 2022, 2023 phantom (phantom@syslbnth.com)
Copyright (C) 2022-2024 phantom (phantom@syslbnth.com)
(See end of file for copying information.)

	Within this directory is a file, "domme.asm", that contains a
short routine, "domme", that attaches itself to a user-provided
short routine, $domme, that attaches itself to a user-provided
SIGSEGV signal handler. When a segfault (short for "segmentation
fault", an illegal memory access) occurs, the routine will print a
randomly-selected message and then jump directly to the start of the


@@ 10,11 10,11 @@ handler; conceptually, it is like a preamble prepended to the start of
the handler. On arrival at the handler, any arguments passed by the
kernel will appear unmodified in their expected registers; if the
default behavior for SIGSEGV on UNIX-like systems--crash and core
dump--is registered via "domme", "domme" will not appear on the call
stack in a subsequent backtrace. In both these ways "domme" attempts
dump--is registered via $domme, $domme will not appear on the call
stack in a subsequent backtrace. In both these ways $domme attempts
to appear as though it had never executed, and so avoid getting in the
way of error correction and debugging, respectively. Furthermore, for
this reason, "domme" must assure the preservation, while executing, of
this reason, $domme must assure the preservation, while executing, of
certain registers set by the kernel on entering userspace, which are
inaccessible from the glibc ABI but may, in principle, be accessed by
the handler, as the handler could have been written in any language--


@@ 22,28 22,40 @@ for instance, Assembly. On top of that, a handler originally written
to be run without interception, may have been written to expect the
address of the signal trampoline to be at the top of the stack, and
for kernel-provided data to be at a known offset from the top of the
stack. To accomplish this, "domme" has to be able jump directly to
stack. To accomplish this, $domme has to be able jump directly to
the start of the handler, rather than making a subroutine call and
consequently pushing a new return address onto the stack. The result
of all of this is that "domme" is itself written in Assembly. Like
of all of this is that $domme is itself written in Assembly. Like
most assembly programs, it is highly playform-specific; it was written
for an x86-64 CPU interfacing with the Linux kernel.

	Before "domme" is run, it may be initialized by calling
"DOMME_init" and passing it the address of a signal handler; if no
such initialization is detected when a segfault occurs, "domme" will
register the default behavior for the handler and return. The program
will then re-attempt the illegal access and properly fall to pieces
(and this is why "domme" will not appear in the resulting
backtrace). "DOMME_init" accepts two more arguments: an pointer to a
buffer to store the old SIGSEGV handler in, if any (this is optional;
NULL pointers are ignored,) and a non-optional 'sigsetsize', the size of
the sigset_t type used by your standard library or programming
environment (in C, this is "sizeof(sigset_t)",) as opposed to the
sigset_t type used internally by the kernel (which domme converts your
handler into). You can also call "domme" during normal execution; it will
	Before $domme is run, it may be initialized by calling
$DOMME_init and passing it the address of a sigaction, the primary
signal-handling data structure on UNIX-like systems; if this address
is 0, $DOMME_init allocates its own sigaction with default settings and
passes it directly to the kernel, removing the need to declare your
own. If no such initialization is detected when a segfault occurs,
$domme will register the default behavior for the signal and return.
The program will then re-attempt the illegal access and properly fall
to pieces (and this is why $domme will not appear in the resulting
backtrace). You can also call $domme during normal execution; it will
print a randomly selected message and return.

	$DOMME_init accepts two more arguments: the address of a
buffer to store a program's previous SIGSEGV sigaction in, if any
(this is optional; null pointers are ignored,) and a non-optional
$sigsetsize, the size of the sigset_t datatype used by your standard
library or programming environment, as opposed to the sigset_t datatype
used internally by the kernel (which $domme converts your sigset_t
into). In C, you would typically write

	DOMME_init(NULL, NULL, sizeof(sigset_t));

at or near the beginning of $main (ideally before you have a chance to
segfault!) and that would be the end of it. This returns 0 if
successful, or -$EFAULT if either of the addresses passed is neither
zero nor mapped to anything.

	This file, "domme.asm", is position-independent. You can
assemble it, turn it into a static library with "ar" (with the name
"libdomme.a", for example,) then link it into any program you like


@@ 74,7 86,7 @@ in this directory.

	...and now that all of this has been established, let's talk
about what we're really looking at here. segfaults are probably the most
common error in C code ("domme" was originally drafted in C, before the
common error in C code ($domme was originally drafted in C, before the
technical requirements detailed above forced me to switch to assembly)
and can be painful to pick apart. often you'll patch one bad access only
to run into another as a result of the patch. chains of function

M domme-test => domme-test +26 -16
@@ 1,6 1,6 @@
#!/bin/bash
# domme-test -- test program entry point
# Copyright (C) 2023 phantom (phantom@syslbnth.com)
# Copyright (C) 2023, 2024 phantom (phantom@syslbnth.com)
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by


@@ 26,19 26,29 @@ then
    else
	exit $ret # domme-test-driver exit code
    fi
else
    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
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=ERROR"
    exit 8
fi
echo "CORE=OK"

# Last is to test the short-circuit easter egg.
echo -e "\nFinal test: What if we point \$domme at itself?"
./domme-test-driver --short-circuit >&2
if [ $? != 33 ]
then
    echo "SHORT_CIRCUIT=ERROR"
    exit 9
fi
echo "SHORT_CIRCUIT=OK"

echo -e "\nAll tests passed." >&2

M domme-test-driver.c => domme-test-driver.c +34 -30
@@ 1,5 1,5 @@
/* domme-test-driver -- some sanity checks for domme
Copyright (C) 2023 phantom (phantom@syslbnth.com)
Copyright (C) 2023, 2024 phantom (phantom@syslbnth.com)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


@@ 22,6 22,7 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>. */
#include <signal.h>
#include <stdbool.h>
#include <sysexits.h>
#include <err.h>
#include <sys/ucontext.h>
/* Remember,
 * we want to test a header that may not have been installed yet. */


@@ 35,13 36,13 @@ enum test_phase { BUFFER = 1, ACT, OLDACT, VERSION, MSGTBL, XLAT_A, HANDLER } ph
/* If you think you know why I chose this number, email me. */
const int CRASH_ARG = 8426;	// hint: 8 Apr 1926

/* Index variable for loops in main,
/* Index variable for loops in $main,
   made global so it can be accessed from the segfault handler below. */
unsigned int i = 0;

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



@@ 56,8 57,8 @@ handler(int sig, siginfo_t *info, void *ucontext) {
		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
		/* 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");


@@ 95,7 96,7 @@ handler(int sig, siginfo_t *info, void *ucontext) {
		
		printf("HANDLER=OK\n");

		/* Finally, try using domme with default behavior.
		/* 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. */


@@ 107,8 108,7 @@ handler(int sig, siginfo_t *info, void *ucontext) {
		
		/* If this program exits normally,
		 * your computer is haunted. */
		struct sigaction sa = { 0 };
		DOMME_init(&sa, 0, sizeof(sigset_t));
		DOMME_init(NULL, NULL, sizeof(sigset_t)); // test NULL behavior
		*(char **)NULL = "Hi GDB!";
		__attribute__ ((fallthrough));
	default:		// You should never see this.


@@ 122,13 122,13 @@ handler(int sig, siginfo_t *info, void *ucontext) {
/* This should never get called. */
void
wrong_handler () {
	fprintf(stderr, "Bad news--rt_sigaction writes oldact before reading act.\n");
	fprintf(stderr, "Bad news--$rt_sigaction writes $oldact before reading $act.\n");
	exit(HANDLER+1);
}

int
main (void) {
	/* To test how OLDACT is saved,
main (int argc, char *argv[]) {
	/* 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;


@@ 138,9 138,13 @@ main (void) {
	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;
	if (argc > 1) {
		if (!strcmp(argv[1], "--short-circuit"))
			sa.sa_sigaction = (void *)domme;	// $DOMME_init should catch this
		else	// there's at least one unknown argument
			errx(EX_USAGE, "Unknown argument detected");
	}
	
	DOMME_init(&sa, &sa, sizeof(sigset_t));

	/* An error code higher than 63 denotes an error in the test program itself. */


@@ 153,9 157,9 @@ main (void) {
	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. */
	/* 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;


@@ 165,23 169,23 @@ main (void) {
	}
	printf("BUFFER=OK\n");

	/* Does DOMME_init catch bad addresses
	 * on behalf of rt_sigaction?
	/* 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);
	DOMME_init(0, (void *)-1L, 0); /* NULL should be fine, though */
	printf("OLDACT=OK\n");

	/* Let's see if the globals in domme.h have been properly defined.
	/* 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.
	 * If they do, errors in the printed output are rare,
	 * but you should still keep an eye out for them. */
	
	/* Print version number.
	 * Also, verify stdout is legit, as with stderr earlier. */
	 * Also, verify $stdout is legit, as with $stderr earlier. */
	phase = VERSION;
	printf("VERSION=%d.%d.%d.%d\n",
	       DOMME_version[0],


@@ 215,13 219,13 @@ main (void) {

	/* Can't happen.
	 *
	 * The handler should drop us right back at the call to CRASH
	 * The handler should drop us right back at the call to $CRASH
	 * after exiting once, and we should never reach this point--
	 * that is, execution should resume _at_ the offending instruction,
	 * not _after_ it.
	 * domme relies on this behavior to run,
	 * so if by some terrible miracle it doesn't work,
	 * you can't use libdomme on your system. */
	fprintf(stderr, "Handler re-entry failed.\n");
	abort();
	 * That is, per POSIX signal handling, execution should resume
	 * _at_ the offending instruction, not _after_ it.
	 * If you somehow you get here anyway,
	 * you might want to save any open files now,
	 * because something is very wrong with your system,
	 * and you are going to find out what very soon. */
	errx(EX_OSERR, "Handler re-entry failed");
}

M domme.asm => domme.asm +127 -88
@@ 1,5 1,5 @@
/* domme - your computer is mean to you if you segfault
Copyright (C) 2022, 2023 phantom (phantom@syslbnth.com)
Copyright (C) 2022-2024 phantom (phantom@syslbnth.com)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


@@ 14,8 14,6 @@ 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>.)

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
names of system calls, arguments, data structure fields, flags, error


@@ 24,16 22,22 @@ from the ground up. I may put it online sometime, but it's far from
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. */
significant effect on the size of the resulting binary.
	
Note: Timestamps in comments take the form <HHMM.DD.MM.YYYY>.
Program symbols and data structure fields are prefixed with '$' to
distinguish them in comments (and in any other documentation that came
with this file). */

	## System calls.
	.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	#/
	.set	SYS_rt_sigreturn, 15	#  \
	.set	SYS_mincore,	  27	#   from <asm/unistd_64.h>
	.set	SYS_exit,	  60	#  /
	.set	SYS_time,	  201	#_/

	## Symbols for rt_sigaction.
	## Symbols for $rt_sigaction.
	.set	RT_SA_RESTORER, 0x04000000
	.set	SIGSEGV, 11	# from "man 7 signal"
	.set	SIG_ERR, -1 	#\


@@ 45,9 49,9 @@ significant effect on the size of the resulting binary. */
	
	## Fields for the kernel's sigaction struct.
	## This is different from the POSIX sigaction struct
	## used by a typical C library, where sa_mask
	## 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.
	## 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.)


@@ 91,25 95,29 @@ sa_size:
	## 
	## 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;
	## %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_.
	## $DOMME_init buffers $act and $oldact and passes the buffers to
	## $rt_sigaction; therefore it works even if $act is read-only.
	## Furthermore, if $act is null, $DOMME_init passes an internal
	## buffer directly to $rt_sigaction, reducing setup of the
	## library to a one-liner in most cases.
	## Finally, $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.
	## The size of libc sa_mask varies by implementation.
	## 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--
	## 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,


@@ 120,67 128,98 @@ DOMME_init:
	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	#/
	add	%r10, %rsp	#/ <-- actually subtraction!

	## rt_sigaction returns -EFAULT if either act or oldact
	## lie outside the process's address space.
	## Since we pass neither to rt_sigaction,
	## $rt_sigaction returns -$EFAULT if either $act or $oldact
	## lie outside the process's mapped address space.
	## Since we buffer both before calling $rt_sigaction,
	## we have to check them both ourselves.
	## Remember, address 0 is fine for our purposes.
	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
	push	%rdi
	xor	%rsi, %rsi
	xor	%rdx, %rdx
	call	1f
	jnz	2f
	mov	8(%rsp), %rdi
	call	1f
	jz	3f
2:	mov	$-EFAULT, %rax
	jmp	0f
1:	loop	2b
1:	test	%rdi, %rdi
	jz	1f
	and	$~4095, %rdi		#\
	mov	$SYS_mincore, %rax	# \
	syscall				#  this is the simplest address-check i can think of
	test	%rax, %rax		# /
	ret				#/
1:	xor	%rax, %rax
	ret

	## If %rdi is null, we'll set up our own sigaction.
	## This reduces $domme initialization to a one-liner in most
	## cases, and if you only want to retrieve the old struct,
	## $rt_sigaction is right there.
3:	pop	%rdi
	test	%rdi, %rdi
	jnz	1f
	xor	%rax, %rax
	lea	(%rbp,%r10), %rdi
	mov	$sa_size, %rcx
	rep stosb
	## Later code expects this to be 0 if our handler is $SIG_DFL;
	## normally this would be set when an external sigaction was
	## being copied in. Otherwise you'll jump to whatever address
	## is in %r8 at this time, after $domme exits.
	xor	%r8, %r8
	jmp	2f
	
	## 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	libc_sa_handler(%rdi), %r8
	## We can't place it in $target until the last minute.
1:	mov	libc_sa_handler(%rdi), %r8
	
	## Bulk copy sa_mask.
	## Bulk copy $sa_mask.
	lea	libc_sa_mask(%rdi), %rsi
	lea	sa_mask(%rbp,%r10), %rdi
	mov	8(%rsp), %rcx		# saved sigsetsize
	mov	8(%rsp), %rcx		# the saved $sigsetsize
	cld
	rep movsb
	
	## Next, sa_flags and sa_restorer.
	## 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,
	## if $sa_sigaction is $SIG_IGN or $SIG_ERR,
	## since even to print a message
	## would dishonor the meanings of those values.
	cmpq	$SIG_IGN, %r8
	je	2f
	cmp	domme(%rip), %r8
	je	3f
	lea	domme(%rip), %rax
	cmp	%rax, %r8
	je	9f

	## Now for 'rt_sigaction' to install the handler.
	## Watch out: If the caller provided an oldact,
	## Now for $rt_sigaction to install the handler.
	## 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 		#\
	## 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.
	## We can do this because the Linux kernel reads $act in before writing
	## $oldact out. I checked the source just to be sure, and the full DOMME
	## source tree includes a test to make sure this is still true,
	## which it as of this writing, a few Linux versions since then.
2:	lea	domme(%rip), %r11 		#\
	mov	%r11, sa_handler(%rbp,%r10)	#_THE BIG SWITCH
2:	lea	(%rbp,%r10), %rsi
3:	lea	(%rbp,%r10), %rsi
	mov	%rsi, %rdx		     	# copy act to oldact
	lea	trampl(%rip), %r11 		#\
	mov	%r11, sa_restorer(%rbp,%r10)	# \


@@ 193,7 232,7 @@ efault:	mov	$-EFAULT, %rax
	syscall
	pop	%r10

	## Save target only if the syscall succeeds.
	## Save $target only if the syscall succeeds.
	test	%rax, %rax
	js	0f
	test	%r8, %r8		# SIG_DFL has to be handled specially


@@ 201,16 240,16 @@ efault:	mov	$-EFAULT, %rax
	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.
	## 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
	movsq			# %rsi and %rdi already point to their respective $sa_handlers
	lea	sa_mask(%rbp,%r10), %rsi
	pop	%rcx		# saved sigsetsize
	pop	%rcx		# the saved $sigsetsize
	rep movsb
	lea	sa_flags(%rbp,%r10), %rsi
	movsq


@@ 229,11 268,11 @@ efault:	mov	$-EFAULT, %rax
	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.
	syscall			# other exit codes from libdomme would also be 32+something.
	
	## domme -- SIGSEGV handler for masochists.
	## On entry:
	## %rsi =:: signal number (usually SIGSEGV).
	## %rsi =:: signal number (usually $SIGSEGV).
	## %rdi =:: pointer to signal information block.
	## %rdx =:: pointer to copy of context at time signal was raised.
	## 


@@ 244,13 283,13 @@ efault:	mov	$-EFAULT, %rax
	##  (At present %rax := trampl, in case you were wondering.)
	## 
	##  Kernel call:
	##  domme does not return--it jumps to (target) on completion
	##  $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.
	## $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,


@@ 269,13 308,13 @@ domme:
	pushq	%rsi		# syscall arg
	pushq	%rdx		# syscall arg
	pushq	%rcx		# clobbered by kernel
	pushq	%rbx		# DOMME_xlat_a
	pushq	%rbx		# $DOMME_xlat_a
	.set	savrdi, 32	# we'll need this for later
	
	## 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.
	## 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), %rdi	# we initialized the seed, right?
	test	%rdi, %rdi 		# ...right?


@@ 295,7 334,7 @@ domme:
	inc	%rax

	## For the sake of thread-safety,
	## accesses to SEED are guarded by a spinlock.
	## accesses to $seed are guarded by a spinlock.
	mov	%rax, %rdx	# save seed here for now.
2:	mov	slock(%rip), %rax
	test	%rax, %rax	# check if lock is 0 (unlocked)


@@ 307,7 346,7 @@ 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)	# seed is safe to use starting here
	xchg	%rdx, %rax		# now seed is back in %rax.


@@ 343,21 382,21 @@ domme:
	syscall

	## Let's recover the signal we were originally handed.
	## If our signal isn't SIGSEGV, nothing more needs to be done
	## 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 it's the dummy target,
	## Check $target; if it's the dummy target,
	## register default behavior and return.
	lea	dmytgt(%rip), %rax
	cmp	target(%rip), %rax
	jne	1f

	## It's the dummy target.
	## Call rt_sigaction with default behavior.
	## Call $rt_sigaction with default behavior.
	## $SIGSEGV is already in %rdi at this point.
	mov	$SYS_rt_sigaction, %rax
	lea	fallbk(%rip), %rsi


@@ 383,10 422,10 @@ domme:
	## 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,
	## by using $rt_sigaction:oldact
	## 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.


@@ 412,21 451,21 @@ domme:
	xor	%rax, %rax	   # we're now done with %rax--everything's pristine
	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_
	## The dummy target above is stored in $target by default,
	## since $domme has to jump _somewhere_
	## when called from kernelspace.

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

	
/* And now, the text.
The strings below are null-terminated
to make them easier to view in a debugger,
even though we'll be taking their exact size in practice.
Which is to say that we always subtract 1 from the size of the string
so that the null byte isn't written out by WRITE.
so that the null byte isn't written out by $write.
Note also that none of these strings end on a newline.
This is so that you can optionally attach one yourself at a higher level
(ex.: "printf("%s\n", DOMME_msgtbl[i].text)"),


@@ 612,7 651,7 @@ msg046:	.ascii	"Inasmuch as your computer is an extension of your body,\n"
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).
	## In the meantime, you can (see $DOMME_NMSGS below).
msg048:	.asciz	"Unused message #00."
	.set	siz048, .-msg048-1
msg049:	.asciz	"Unused message #01."


@@ 663,7 702,7 @@ msgsiz:	.struct msgsiz+8
	## It's not a bug, it's _extensible_.
	## In all seriousness, though, if you do this,
	## you're responsible for ensuring the pointers and sizes passed
	## are valid and safe at the time 'domme' executes--
	## are valid and safe at the time $domme executes--
	## it's no good triggering a segfault in the segfault handler.
	## I recommend using constant strings, like I just did.
	.data


@@ 761,7 800,7 @@ DOMME_xlat_a:
	.dc.b	0x06,0x06,0x0e,0x2d,0x0e,0x0e,0x17,0x17,0x17,0x17,0x1d,0x1d,0x1d,0x1d,0x1c,0x1c

	## At the moment,
	## the high bits of bytes of 'DOMME_xlat_a' are masked off before being used as indices,
	## 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.
	## (Or not.)


@@ 772,7 811,7 @@ DOMME_xlat_a:
	.align 8
mplier:	.dc.a	6364136223846793005 # from TAOCP section 3.3.4

	## An error message printed if you try to point domme at itself.
	## An error message printed if you try to point $domme at itself.
errmsg:	.ascii	"Oh, for the love of Lain,\n"
	.asciz	"make an effort.\n"
	.set	errsiz, .-errmsg-1


@@ 787,11 826,11 @@ But we can come up with one suited to this particular program:

DOMME_version[3] increments with edits to the script, comments,
		 documentation, and other factors that don't directly
		 affect execution (like the contents of 'DOMME_xlat_a');
		 affect execution (like the contents of $DOMME_xlat_a);
DOMME_version[2] increments with bug fixes and other minor changes
		 to the program as implemented, under the current design;
DOMME_version[1] increments with changes to the design and user interface
		 (if 'DOMME_init' received a new parameter, for instance,
		 (if $DOMME_init received a new parameter, for instance,
		 it would be recorded here);
DOMME_version[0] increments only when major milestones are reached
		 (for instance, it ticked from 0 to 1


@@ 800,20 839,20 @@ DOMME_version[0] increments only when major milestones are reached

At each commit, we increment the number representing the most significant
type of change since the last commit by one, and reset all less
significant numbers to 0. So in the case that 'DOMME_init' received a new
significant numbers to 0. So in the case that $DOMME_init received a new
parameter, representing a new, user-visible feature, we would increment
DOMME_version[1] by 1, and set DOMME_version[2] and DOMME_version[3]
to 0; DOMME_version[0] would remain unchanged. We only change the version
$DOMME_version[1] by 1, and set $DOMME_version[2] and $DOMME_version[3]
to 0; $DOMME_version[0] would remain unchanged. We only change the version
number once per commit, even if multiple changes of the most significant
type were made; it should be possible to obtain a copy of each version of
the program.

The initial version is 0.0.0.0.
The current version is 1.0.0.0. */
The current version is 1.1.0.0. */
	.data
	.align	16
DOMME_version:
	.dc.l	1,0,1,0
	.dc.l	1,1,0,0
	.set	VERSION_SIZE, (.-DOMME_version)/4

	## An empty sigaction struct, to be used as a fallback handler


@@ 823,7 862,7 @@ fallbk:	.dc.a	0
	.dc.a	trampl
	.skip	8

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


@@ 833,7 872,7 @@ target:	.dc.a	dmytgt		# target handler to jump to.
	## We use a linear congruental generator.
seed:	.dc.a	0		# seed for the random number generator.
seeded:	.dc.a	0		# did we set the seed?
slock:	.dc.a	0		# spinlock for 'seed'.
slock:	.dc.a	0		# spinlock for $seed.

	.text
	## Sizes of the tables assembled earlier.

M domme.h => domme.h +1 -1
@@ 1,5 1,5 @@
/* domme.h - an example header file
Copyright (C) 2022, 2023 phantom (phantom@syslbnth.com)
Copyright (C) 2022-2024 phantom (phantom@syslbnth.com)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by