@@ 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.
@@ 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