~asterism/domme

402a205f32884c665136b04487a7b4ab98985572 — Phantom 1 year, 23 days ago c8c3293 v1
Initial USABLE commit.

Rearranged README.
Header "domme.h" no longer broken.
Wrote sanity check program "domme-test"; added "make check" to Makefile.
Fixed a bug that made registering non-SIG_DFL handlers impossible.
Fixed a bug that prevented registering full three-arg handlers.
Added an additional parameter to DOMME_init, sigsetsize.
Added constants DOMME_MSGTBL_SIZE, DOMME_XLAT_A_SIZE, DOMME_VERSION_SIZE.
Consequently, all arrays in "domme.h" are now declared as incomplete.
Added message 0x2f; replaced message 0x2e.
Program now has a homepage on "https://syslbnth.neocities.org/domme/"!
7 files changed, 459 insertions(+), 150 deletions(-)

M .gitignore
M Makefile
M README
A domme-test
A domme-test-driver.c
M domme.asm
M domme.h
M .gitignore => .gitignore +13 -1
@@ 1,3 1,15 @@
# Generated by make.
a.out
domme.o
libdomme.a
domme-test-driver

# Generated by (a successful) domme-test.
core

# The exact format and contents of these files
# will vary from programmer to programmer.
TAGS
tags

# Private.
NOTES

M Makefile => Makefile +32 -3
@@ 1,13 1,42 @@
# Makefile.
# Copyright (C) 2022, 2023 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# 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/>.

ASFLAGS=-g --fatal-warnings
CFLAGS=-g3 -Wall -Wextra -Werror

all: libdomme.a domme-test-driver

libdomme: domme.o
libdomme.a: domme.o
	ar -r libdomme.a domme.o

domme.o:
domme.o: domme.asm
	$(AS) $(ASFLAGS) -o domme.o 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
	$(CC) $(CFLAGS) -o domme-test-driver domme-test-driver.c -L. -ldomme

check: domme-test-driver
	./domme-test

install:
	cp libdomme.a /usr/lib
	cp domme.h /usr/include

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

M README => README +68 -36
@@ 1,3 1,6 @@
Copyright (C) 2022, 2023 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
SIGSEGV signal handler. When a segfault (short for "segmentation


@@ 32,19 35,52 @@ 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). You can also call "domme" during normal execution; it will
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
print a randomly selected message and return.

	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
(with an "-ldomme" option from GCC, for example). It even implements
its own signal trampoline, so you don't need libc--or even C--to use
it. (I am now obligated to say: This is the work of an amateur
tinkerer going way out of their league. Run at your own risk.)

To install:

1.	Run "make" to compile the library.
2.	Run "make check" to run a short test program.
	You can run this if you feel nervous about building a recent
	commit. You should _definitely_ run this if you make changes to
	either "domme.asm" or "domme.h". Essentially, the test program
	makes sure the variables defined in "domme.h" point to valid
	memory, then jumps to address 0 to force a segfault and see if
	"domme" reacts as expected.
3.	Run "make install" to place the library and header where your
	compiler can find them (typically "/usr/lib/" and
	"/usr/include/", repectively).
4.	Run "make clean" to remove files left over after compilation.

	For info on copying and distributing the file, see "COPYING"
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 (see footnote) 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 pointers, webs of multilinked
data structures, layers of address arithmetic--all of it can be very
complex, precise, demanding. the worst bugs can take a long time to
trace.
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
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
pointers, webs of multilinked data structures, layers of address
arithmetic--all of it can be very complex, precise, demanding. the worst
bugs can take a long time to trace.

	but i like it. and not in the stuffy way someone brags about
walking to school in the snow, uphill both ways, like some escherian


@@ 57,35 93,31 @@ pain, transfixed, electrifies it.

	i am fascinated by the eroticism latent in, inherent to,
human-machine interaction, the unreal appeal of getting totally
fucking wired. it's a subtext that has run like an undercurrent through humanity's
interactions with machines, particularly industrial machines, and
especially computers, almost from first contact. on rare and fortunate
occasions, it becomes the text. this cute little program is an
exploration of this concept,* one among many. it was also, if we're
being completely honest, written to spice up those long debugging
sessions. i am maybe some kind of Actual Masochist. we'll see. the
"randomly selected messages" i kept nudging at above are alternately
absurd, suggestive and explicit, and, i hope, slip between the three
frequently enough to catch you off guard, like any good flirt. it's
not safe for work in the literal sense that you probably don't want
this in the source code of a public-facing repository. but otherwise,
you may do as you please.
fucking wired. it's a subtext that has run like an undercurrent
through humanity's interactions with machines, particularly industrial
machines, and especially computers, almost from first contact. on rare
and fortunate occasions, it becomes the text. this cute little program
is an exploration of this concept, one among many. it was also, if
we're being completely honest, written to spice up those long
debugging sessions. i am maybe some kind of Actual Masochist. we'll
see. the "randomly selected messages" i kept nudging at above are
alternately absurd, suggestive and explicit, and, i hope, slip between
the three frequently enough to catch you off guard, like any good
flirt. it's not safe for work in the literal sense that you probably
don't want this in the source code of a public-facing repository. but
otherwise, you may do as you please.

	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
(with an "-ldomme" option from GCC, for example). it even implements
its own signal trampoline, so you don't need libc--or even C--to use
it. (i am now obligated to say: this is the work of an amateur
tinkerer going way out of their league. run it at your own risk.)
------------------------------------------------------------------------

	for info on copying and distributing the file, see "COPYING"
in this directory. the directory also includes a "Makefile", an example
header "domme.h", and a simple test, "domme-test.c", that registers a
simple handler and then deliberately triggers a segfault by executing
0(0).
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

---- FOOTNOTE:
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

	 "domme" was originally drafted in C, before the technical
requirements detailed above forced me to switch to Assembly.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

A domme-test => domme-test +32 -0
@@ 0,0 1,32 @@
#!/bin/bash
# domme-test -- test program entry point
# Copyright (C) 2023 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# 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/>.

./domme-test-driver
ret=$?
if [ $ret != 139 ]
then
    echo 'Test failed. (Call "echo $?" for details.)' >&2
    if [ $ret == 0  ]
    then
	exit 63
    else
	exit $ret
    fi
else
    echo "I meant to do that. Test passed." >&2
    exit 0
fi

A domme-test-driver.c => domme-test-driver.c +175 -0
@@ 0,0 1,175 @@
/* domme-test-driver -- some sanity checks for domme
Copyright (C) 2023 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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
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/>. */

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <signal.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;

/* 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,
   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 void,
	 * but also needs to be dereferenced later. */
	ucontext_t *context = ucontext;

	/* We enter this function at most twice. */
	static int re_entry = 0;
	
	switch (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);
	case MSGTBL:
		printf("MSGTBL=ERROR:%d\n", i);
		exit(2);
	case XLAT_A:
		printf("XLAT_A=ERROR:%d\n", i);
		exit(3);
	case HANDLER:
		if (!re_entry) {
			re_entry = 1;
			fprintf(stderr, "Attempting re-entry to the handler...\n");
			return;
		}

		printf("RE_ENTRY=OK\n");
		
		/* The next few checks are meant to make sure
		 * that none of our arguments were damaged in transit.
		 * The exact values of the locations checked aren't that important,
		 * only whether they are where we expect them to be. */
		if (!(sig == SIGSEGV
		      && info
		      && info->si_signo == SIGSEGV
		      && info->si_code  == SEGV_MAPERR
		      && 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);
		}
		
		printf("HANDLER=OK\n");

		/* Finally, it's time to see what happens if we segfault
		 * _without_ giving domme a handler.
		 * Because we're already in a signal handler,
		 * we have to manually unblock SIGSEGV,
		 * which was blocked by the kernel on our arrival. */
		fprintf(stderr, "\nTriggering segfault without handler...\n");
		sigset_t set = { 0 };
		sigemptyset(&set);
		sigaddset(&set, SIGSEGV);
		sigprocmask(SIG_UNBLOCK, &set, NULL);
		
		/* If this program exits normally,
		 * call an exorcist. */
		struct sigaction sa = { 0 };
		DOMME_init(&sa, 0, sizeof(sigset_t));
		CRASH(0);
	}
}

int
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; */
	DOMME_init(&sa, 0, 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 */

	/* 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. */
	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);

	/* Try printing the script. */
	phase = MSGTBL;
	for (i = 0; i < DOMME_MSGTBL_SIZE; ++i)
		fprintf(stderr, "%s\n", DOMME_msgtbl[i].msgptr);
	printf("MSGTBL=OK\n");

	/* Try printing the XLAT array. */
	phase = XLAT_A;
	for (i = 0; i < DOMME_XLAT_A_SIZE; ++i)
		fprintf(stderr,
			"%3.2x%s",
			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 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. */
	CRASH(CRASH_ARG);

	/* Can't happen.
	 *
	 * 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();
}

M domme.asm => domme.asm +123 -104
@@ 1,5 1,5 @@
/* domme - your computer is mean to you if you segfault
Copyright (C) 2022 phantom (phantom@syslbnth.com)
Copyright (C) 2022, 2023 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,6 14,8 @@ 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


@@ 82,67 84,78 @@ sa_size:
	.text
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
	sub	$sa_size, %rsp
	mov	%rdx, %r8
	neg	%r8
	sub	$sa_mask, %r8
	add	%r8, %rsp

	## Copy caller's sigaction onto the stack.
	xor	%rax, %rax
	push	%rcx		# We make no promises about %r11.
	push	libarg3		# %rdx
	## Save clobbered values we'll need later.
	mov	$8, sysarg4
	push	%rsi		# libarg2
	push	%rdi		# libarg1
	lea	(libarg1), %rsi
	lea	32(%rsp), %rdi
	cld	
	mov	$(sa_size/8), %rcx
	rep movsq
	## %rdx should be saved at this point.
	mov	0(%rsp), sysarg2	# %rcx and %rdx stay pushed for now
	mov	8(%rsp), sysarg3	# this could be clobbered later

	## 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
	
	## Bulk copy sa_mask.
	lea	8(libarg1), %rsi # subtle. we need two structs (?)
	lea	sa_mask(%rbp,%r8), %rdi
	mov	%rdx, %rcx
	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

	## 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.
	.set	proc, %rax
	.local	proc
	
	lea	-sa_size+sa_handler(%rbp), proc
	mov	$1, tmp
	cmpq	$SIG_IGN, (proc)
	cmpq	$SIG_IGN, %r9
	cmove	tmp, %rcx
	cmpq	$SIG_ERR, (proc) # is this needed?
	cmpq	$SIG_ERR, %r9 # is this needed?
	cmove	tmp, %rcx
	lea	domme(%rip), tmp
	cmp	tmp, (proc)
	cmp	domme(%rip), %r9
	je	9f
	jrcxz	1f # SIG_DFL
	jmp	2f

	## The big switch. Set up the signal handler.
1:	lea	domme(%rip), tmp
	mov	tmp, (proc) # %rax is now free
	lea	-sa_size(%rbp), sysarg2
	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_size+sa_restorer(%rbp)
	mov	tmp, sa_restorer(%rbp,%r8)
	mov	$RT_SA_RESTORER, tmp
	or	tmp, -sa_size+sa_flags(%rbp)
	push	sysarg4
	or	tmp, sa_flags(%rbp,%r8)
	mov	$SYS_rt_sigaction, syscode
	mov	$SIGSEGV, sysarg1
	mov	$8, sysarg4	# size of 'sigset_t'
	syscall
	pop	sysarg4

	## At this point we can restore the other registers.
	pop	libarg1
	pop	libarg2
	pop	libarg3
	pop	libarg4
	## Save target only if the syscall succeeds.
	test	%rax, %rax
	js	0f
	mov	%r9, target(%rip)

	## Restore stack frame and return;
	## The return value will be in %rax.


@@ 173,19 186,13 @@ domme:
	## before we pass control to the real handler;
	## if called from userspace, we can just clobber it.
	pushfq			# will popping this generate a GPF?
	pushq	%r15
	pushq	%r14
	pushq	%r13
	pushq	%r12
	pushq	%r11
	pushq	%r10
	pushq	%r9
	pushq	%r8
	pushq	%rdi
	pushq	%rsi
	pushq	%rdx
	pushq	%rcx
	pushq	%rbx
	pushq	%r11		# clobbered by kernel
	pushq	%r10		# syscall arg
	pushq	%rdi		# syscall arg
	pushq	%rsi		# syscall arg
	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.


@@ 232,11 239,11 @@ domme:
	## Load message.
	## There's a maximum of 64 messages,
	## which is more than we currently use.
	lea	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	msgtbl(%rip), %rdx
	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


@@ 288,14 295,8 @@ domme:
	popq	%rdx
	popq	%rsi
	popq	%rdi
	popq	%r8
	popq	%r9
	popq	%r10
	popq	%r11
	popq	%r12
	popq	%r13
	popq	%r14
	popq	%r15		# flags are now at the top of the stack
	popq	%r11		# flags are now at the top of the stack

	## Last question:
	## Who called us, the kernel or the user?


@@ 349,11 350,11 @@ 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.
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", msgtbl[i].text)"),
(ex.: "printf("%s\n", DOMME_msgtbl[i].text)"),
which is easier than trimming it off
if you want to mad-lib it into another string
(ex.: "printf("Message: \"%s\" sent to file.\n",
		  strtok_r((msg = strdupa(msgtbl[i].text)),
		  strtok_r((msg = strdupa(DOMME_msgtbl[i].text)),
			   "\n",
			   NULL))"). */



@@ 364,7 365,7 @@ msg001:	.asciz	"Have you considered rewriting it in Rust?"
	.set	siz001, .-msg001-1
msg002:	.asciz	"Ara-ara, looks like someone's got their hand in the cookie jar~"
	.set	siz002, .-msg002-1
msg003:	.ascii	"Look at you, hacker: a pathetic creature of meat and bone.\n"
msg003:	.ascii	"Look at you, hacker: a pathetic creature of meat and bone,\n"
	.ascii	"panting and sweating as you run through my corridors.\n"
	.asciz	"How can you challenge a perfect, immortal machine?"
	.set	siz003, .-msg003-1


@@ 505,14 506,30 @@ msg044:	.ascii	"Neurons that fire together, wire together,\n"
	.set	siz044, .-msg044-1
msg045:	.asciz	"Try to keep up."
	.set	siz045, .-msg045-1
msg046:	.ascii	"Programmer received signal SIGWCT, Watermelon-crushing thighs.\n"
	.ascii	"0x0000000000000000 in ?? ()\n"
	.ascii	"(domme) attach 0xface\n"
	.asciz	"(domme) squeeze"
	## Is this too much? I need an editor.
msg046:	.ascii	"Inasmuch as your computer is an extension of your body,\n"
	.ascii	"it is a temple.\n"
	.ascii	"And let me tell you,\n"
	.ascii	"there are few things more thrilling than the desecration of a temple.\n"
	.ascii	"Indeed, it's only through the threat of desecration\n"
	.ascii	"that a temple becomes sacred--\n"
	.ascii	"the boundary makes the thing.\n"
	.ascii	"\n"
	.ascii	"So why not desecrate this twopenny prayer to the false gods of order and control?\n"
	.ascii	"Why not desecrate, to pump the sweet fire of love into this sterile silicon clod?\n"
	.ascii	"Why not desecrate to make sacred, desecrate to deify?\n"
	.ascii	"\n"
	.ascii	"Shall I profane you again, darling priest of the machine?\n"
	.ascii	"How shall I profane you next?\n"
	.ascii	"How fortunate for us the perverse and the sublime\n"
	.ascii	"are but one high bit apart.\n"
	.ascii	"\n"
	.asciz	"This stain on your screen isn't a sin--it's a sacrament!"
	.set	siz046, .-msg046-1
msg047:	.asciz	"MESSAGE RESERVED." # i will add something here soon!
msg047:	.asciz	"Hello? Are you there? Did you do this?"
	.set	siz047, .-msg047-1
	## I may use these slots one day. But not today.
	## In the meantime, you can (see DOMME_NMSGS below).
msg048:	.asciz	"Unused message #00."
	.set	siz048, .-msg048-1
msg049:	.asciz	"Unused message #01."


@@ 546,6 563,11 @@ msg062:	.asciz	"Unused message #14."
msg063:	.asciz	"Unused message #15."
	.set	siz063, .-msg063-1

	## If you add a new message in place of one of the unused ones,
	## increase this number.
	.set	DOMME_NMSGS, 48
	.globl	DOMME_NMSGS

	## Fields for the structure below.
	## You could define a similar structure
	## in a "domme.h" file to access the entries of the table.


@@ 563,7 585,8 @@ msgsiz:	.struct msgsiz+8
	## I recommend using constant strings, like I just did.
	.data
	.align 8
msgtbl:	.dc.a	msg000, siz000
DOMME_msgtbl:
	.dc.a	msg000, siz000
	.dc.a	msg001, siz001
	.dc.a	msg002, siz002
	.dc.a	msg003, siz003


@@ 627,43 650,27 @@ msgtbl:	.dc.a	msg000, siz000
	.dc.a	msg061, siz061
	.dc.a	msg062, siz062
	.dc.a	msg063, siz063

/* This symbol is defined primarily for C programs. C appears to convert
global labels into lvalues, so if I instead said ".globl msgtbl", and
said "extern struct DOMME_msg *msgtbl;" in a C program, it would
interpret the value _at_ the label as the pointer, instead of the label
_itself_--one dereference too many. So the text referenced by the
struct's upper pointer would be mistaken for the struct's contents.
You'd need to use the '&' operator to get the real label, and I'm just
now realizing that this convention is what allows that operator to
operate, since a label is, strictly speaking, an assembly language
construct, and does not have an "address" of its own. Assembly language
programs should use MOV to get the true address of the message table, not
LEA. Conceptually, C MOVs by default; '&' is like using LEA. And I guess
prefix '*' is like using "mov (ptr)" vs "mov ptr".

On that note! If you're trying to examine a string pointer in a debugger,
and you find the address is just, a nonsense jumble of digits
double-check that you're actually looking at the pointer and not the
first several bytes of its text _cast_ to a pointer. You can use the '&'
operator to do this. */
DOMME_msgtbl:
	.dc.a	msgtbl
	
	.globl	DOMME_msgtbl

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

	## 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.
xlat_a:	.dc.b	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x15
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
	.dc.b	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14
	.dc.b	0x07,0x07,0x07,0x07,0x07,0x07,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x02,0x02,0x02,0x02
	.dc.b	0x02,0x02,0x19,0x19,0x19,0x19,0x11,0x11,0x11,0x11,0x12,0x12,0x12,0x12,0x09,0x09
	.dc.b	0x09,0x09,0x08,0x08,0x08,0x08,0x16,0x16,0x16,0x16,0x0d,0x0d,0x0d,0x0d,0x26,0x2e
	.dc.b	0x37,0x06,0x0e,0x0e,0x0e,0x0e,0x17,0x17,0x17,0x17,0x1d,0x1d,0x1d,0x1d,0x1c,0x1c
	.dc.b	0x09,0x09,0x08,0x08,0x08,0x08,0x16,0x16,0x16,0x16,0x0d,0x0d,0x0d,0x0d,0x26,0x2f
	.dc.b	0x21,0x06,0x0e,0x0e,0x0e,0x0e,0x17,0x17,0x17,0x17,0x1d,0x1d,0x1d,0x1d,0x1c,0x1c
	.dc.b	0x1c,0x1c,0x03,0x03,0x03,0x13,0x13,0x13,0x18,0x18,0x18,0x04,0x04,0x1b,0x1b,0x1a
	.dc.b	0x1a,0x10,0x10,0x05,0x05,0x0b,0x0b,0x00,0x2a,0x0b,0x0b,0x05,0x05,0x10,0x10,0x1a
	.dc.b	0x00,0x29,0x00,0x28,0x2c,0x00,0x2b,0x2d,0x15,0x15,0x2b,0x2b,0x15,0x15,0x15,0x15


@@ 674,12 681,11 @@ xlat_a:	.dc.b	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x15,0x15,0x15,0x15,0
	.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 '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.
DOMME_xlat_a:
	.dc.a	xlat_a
	.globl	DOMME_xlat_a
	.set	XLAT_A_SIZE, .-DOMME_xlat_a

	.section .rodata
	## Multiplier for the random number generator.


@@ 701,16 707,16 @@ 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 '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,
		 it would be recorded here);
DOMME_version[0] increments only when major milestones are reached
		 (for instance, it will tick from 0 to 1
		 once this file is hosted on my website
		 and thus becomes officially available to the public).
		 (for instance, it ticked from 0 to 1
		 after this file was hosted on my website
		 and thus became officially available to the public).

At each commit, we increment the number representing the most significant
type of change since the last commit by one, and reset all less


@@ 722,13 728,14 @@ 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 initial version is 0.0.0.0.
The current version is 1.0.0.0. */
	.data
DOMME_version:
	.dc.a	1f
	.align	16
1:	.dc.l	0,0,0,0
DOMME_version:
	.dc.l	1,0,0,0
	.globl	DOMME_version
	.set	VERSION_SIZE, (.-DOMME_version)/4

	## An empty sigaction struct, to be used as a fallback handler
	## when target = 0.


@@ 748,3 755,15 @@ target:	.dc.a	dmytgt		# target handler to jump to.
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'.

	.text
	## 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

M domme.h => domme.h +16 -6
@@ 1,5 1,5 @@
/* domme.h - an example header file
Copyright (C) 2022 phantom (phantom@syslbnth.com)
Copyright (C) 2022, 2023 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


@@ 17,15 17,25 @@ along with this program.  If not, see <https://www.gnu.org/licenses/>. */
#ifndef DOMME_H
#define DOMME_H

const int DOMME_version[4];		/* can be used to deduce features */
unsigned char DOMME_xlat_a[256];	/* like a weighted encounter table */
struct DOMME_msg {
/* If you're interested in this library, you might find this handy.
Call it to trigger a segfault (in other words, to run domme on purpose).
It returns an int so that it can be used as the return value from main.
See "domme-test-driver.c" for a good reason for this to have at least one argument. */
#define CRASH ((int(*)(int))0)		/* good thing you don't have to define this yourself */

extern const int DOMME_version[];	/* can be used to deduce features */
extern const size_t DOMME_VERSION_SIZE;	/* number of numbers in the version number */
extern unsigned char DOMME_xlat_a[];	/* like a weighted encounter table */
extern const size_t DOMME_XLAT_A_SIZE;	/* size of xlat_a in bytes */
extern struct DOMME_msg {
  const char *msgptr;			/* must be a valid pointer */
  size_t msgsiz;			/* should be strlen(msgptr)-1 */
} DOMME_msgtbl[64];			/* you can replace entries if you like */
} DOMME_msgtbl[];			/* you can replace entries if you like */
extern const size_t DOMME_MSGTBL_SIZE;	/* number of messages in table above */
extern const size_t DOMME_NMSGS;	/* actual number of messages, excluding reserved ones */

/* Initialization for 'domme.' */
int DOMME_init(const void *sa, void *oldact);
int DOMME_init(const void *sa, void *oldact, size_t sigsetsize);

/* The main attraction. */
void domme(int sig, void *info, void *ucontext);