diff --git a/tools/tests/stktst/Makefile b/tools/tests/stktst/Makefile index 8d7bd6f0..515a96db 100644 --- a/tools/tests/stktst/Makefile +++ b/tools/tests/stktst/Makefile @@ -1,4 +1,4 @@ -# $Id: $ +# $Id: Makefile 1267 2022-08-02 06:27:29Z mueller $ # SPDX-License-Identifier: GPL-3.0-or-later # Copyright 2022- by Walter F.J. Mueller # diff --git a/tools/tests/stktst/README.md b/tools/tests/stktst/README.md new file mode 100644 index 00000000..1197dbbe --- /dev/null +++ b/tools/tests/stktst/README.md @@ -0,0 +1,83 @@ +## stktst: a program testing 2.11BSD stack extension logic + +The `stktst` program exercises the 2.11BSD stack extension logic. +In a first step, the `sp` can be aligned to a click (64 byte) or a +segment (8129 byte) boundary. +An offset can also be applied after this alignment. +In a second step, a sequence of integer and floating point instructions with +a `-(sp)` destination is executed. +This allows to set up almost every possible stack extension situation. + +Motivation for `stktst` were differences in the `MMR1` register implementation +in different PDP-11 CPUs, and differences in the modeling of `MMR1` in +different PDP-11 simulators. +That combined with the 2.11BSD stack extension handling prior to #473 can lead +to unexpected "Memory fault" aborts in 2.11BSD. + +The results are collected in the [data](data) folder. + +`stktst` has an assembler core [dotst.s](dotst.s) which is called from a +C main program [stktst.c](stktst.c). It is called as +``` + ./stktst [-s ns] [-c nc] [-o no] +``` +The options control the initial stack alignment: +- **`-s ns`**: aligns to 8192 byte segment boundaries. ns=1 to the next one, + nc=2 to the second next one, etc. + Obviously, `ns` should be smaller than 8. + The option is ignored if ns<=0. +- **`-c nc`**: aligns to 64 byte click boundaries. nc=1 to the next one, + nc=2 to the second next one, etc. + The first alignment step will not change the stack if it was alreay on a + click bounday, it will therefore add 0 to 64 bytes to the stack. + The option is ignored if nc<=0. + Click alignment is done after segment alignment. +- **`-o no`**: adds `no` to `sp`. `no` must be even and can be positive or + negative. + +**Notes**: +- no range checks is done for `no`. + After a **-s** is is save to use small positive `no` values to position `sp` + a bit before a segment boundary. + After a **-c** is is better to use negative `no` values to position `sp` + before the next click boundary. +- the stack allocated below the argument and environment values. + The initial `sp` value will therefore decrease when the number of characters + in the argument list increases because the stack base moves down. + In some cases it is therefore prudent to specify the numbers as quoted + strings with some leading blanks, like + ``` + ./stktst d ' 3' -c ' 2' -o ' 4' + ``` + That allows to change the counts without changing the length of the + argument list. + +The `cmd` argument selects the instruction that does the stack push and +`count` determines how often it is executed. +The available modes for `cmd` are +- **`I`**: use `clr -(sp)` --> integer word push +- **`i`**: use `movfi -(sp)` after `seti` --> word push from FPP +- **`l`**: use `movfi -(sp)` after `setl` --> double word push from FPP +- **`f`**: use `movf -(sp)` after `setf` --> double word push from FPP +- **`d`**: use `movf -(sp)` after `setd` --> quad word push from FPP + +For debug purposes three additional `cmd` modes are available: +- **`r`**: uses `count` as an address and reads +- **`w`**: uses `count` as an address, reads and re-writes +- **`h`**: runs a `halt` + +`stktst` prints the `sp` after alignment and after the stack pushes like +``` + stktst-I: before sp 177304 (0, 4,60); 177200 (0, 5,64); + stktst-I: after sp 177304 (0, 4,60); 177200 (0, 5,64); 167200 (0, 69,64); +``` +and gives the `sp` value +- after `dotst.s` is called +- after alignments and offsets were applied +- after stack pushes were executed + +and prints it in octal and broken down in segment, click and byte offset. +Because the stack is a downward growing segment, all offsets measure the +distance to the top of memory and increase when the `sp` decreases. + +When a stack extension fails, the program will print the first line and abort. diff --git a/tools/tests/stktst/data/2022-08-03_simh_3.11_bsd_447.md b/tools/tests/stktst/data/2022-08-03_simh_3.11_bsd_447.md new file mode 100644 index 00000000..3003ab08 --- /dev/null +++ b/tools/tests/stktst/data/2022-08-03_simh_3.11_bsd_447.md @@ -0,0 +1,109 @@ +## 2022-08-03: tests with SimH V3.11-0 and 2.11BSD 447 (plus 453 patch) + +### Background +The `MMR1` response after an MMU abort in an FPP instruction depends on the CPU. +In an 11/70, the registers reflect the state at abort and `MMR1` shows the +change. In a J11, the registers are unchanged, and `MMR1` shows zero. + +SimH V3.11-0 used the FPP MMU abort handling for _all_ CPU models. +So even when an 11/70 is modeled, the behavior is like a J11. + +The 2.11BSD stack extension logic checks whether the `sp` is below the +current stack allocation, and only in that case, the stack is extended. +On a J11-based system that fails, that's why 2.11BSD up to #473 has a +workaround and shifts the `sp` down by 4 _if and only if_ a J11 was +probed at boot time. The pertinent code in `/usr/src/sys/pdp/trap.c` is +``` + osp = sp; + if (kdj11) + osp -= 4; + if (backup(u.u_ar0) == 0) + if (!(u.u_sigstk.ss_flags & SA_ONSTACK) && grow((u_int)osp)) + goto out; + i = SIGSEGV; +``` + +That leads to two vulnerabilities: +- a `double` push of 8 bytes might fail on J11 systems, real or simulated, +- in a SimH 11/70, which probes as an 11/70 but behaves like a J11, any + push from an FPP instruction might fail. + +The first is a 2.11BSD issue, the second is a SimH issue. + +The tests were run under `tcsh`, it gives "Segmentation fault" in case of +a problem. Under `sh` one gets "Memory fault". + +### SimH in 11/94 mode +SimH pdp11 started with +``` +set cpu 11/94 +``` +and 2.11BSD starts with +``` +94Boot from xp(0,0,0) at 0176700 +``` +Extending the stack with `float` pushes is no problem until the stack segment +has grown to 020000 and memory is really exhausted: +``` +./stktst f ' 1024' + # stktst-I: before sp 177334 (0, 4,36); 177334 (0, 4,36); + # stktst-I: after sp 177334 (0, 4,36); 177334 (0, 4,36); 167334 (0, 68,36); +./stktst f '14263' + # stktst-I: before sp 177334 (0, 4,36); 177334 (0, 4,36); + # stktst-I: after sp 177334 (0, 4,36); 177334 (0, 4,36); 020000 (6,127,64); +./stktst f '14264' + # stktst-I: before sp 177334 (0, 4,36); 177334 (0, 4,36); + # Segmentation fault (core dumped) + +``` + +Extending with `double` pushes works when the `double` is aligned on an +8 byte border. In this case the 4 byte correction done in 2.11BSD #473 +ensures that the `sp` is interpreted as below current allocation. +The alignment is set up with `-c 2 -o 0`: +``` +./stktst d ' 512' -c ' 2' -o ' 0' + # stktst-I: before sp 177304 (0, 4,60); 177200 (0, 5,64); + # stktst-I: after sp 177304 (0, 4,60); 177200 (0, 5,64); 167200 (0, 69,64); +./stktst d '7120' -c ' 2' -o ' 0' + # stktst-I: before sp 177304 (0, 4,60); 177200 (0, 5,64); + # stktst-I: after sp 177304 (0, 4,60); 177200 (0, 5,64); 020000 (6,127,64); +./stktst d '7121' -c ' 2' -o ' 0' + # stktst-I: before sp 177304 (0, 4,60); 177200 (0, 5,64); + # Segmentation fault (core dumped) +``` + +Misaligned `double` pushes fail as expected. +A 4 byte misalignment is set up with `-c 2 -o -4`. +Even with the 4 byte correction the `sp` appears to be in the allocated area, +and a `SIGSEGV` is taken: +``` +./stktst d ' 1' -c ' 2' -o ' -4' + # stktst-I: before sp 177304 (0, 4,60); 177174 (0, 6, 4); + # stktst-I: after sp 177304 (0, 4,60); 177174 (0, 6, 4); 177164 (0, 6,12); +./stktst d ' 319' -c ' 2' -o ' -4' + # stktst-I: before sp 177304 (0, 4,60); 177174 (0, 6, 4); + # stktst-I: after sp 177304 (0, 4,60); 177174 (0, 6, 4); 172204 (0, 45,60); +./stktst d ' 320' -c ' 2' -o ' -4' + # stktst-I: before sp 177304 (0, 4,60); 177174 (0, 6, 4); + # Segmentation fault (core dumped) +``` + +The initial stack size is 20 clicks +(see [SSIZE](https://www.retro11.de/ouxr/211bsd/usr/src/sys/pdp/machparam.h.html#m:SSIZE)), +the stack increment is also 20 clicks +(see [SINCR](https://www.retro11.de/ouxr/211bsd/usr/src/sys/pdp/machparam.h.html#m:SINCR)). +The initial stack segment size is a bit larger due to argument and environment +allocations. +In the tests it was 24 clicks as tests with the **r** command show: +``` +./stktst r 0175004 + # OK +./stktst r 0175000 + # OK +./stktst r 0174776 + # Segmentation fault (core dumped) +``` + +It is therefore a bit surprising that the failure happens at the border +expected for the second stack extend and not around 0175000. diff --git a/tools/tests/stktst/dotst.s b/tools/tests/stktst/dotst.s index 2034d83a..203272a8 100644 --- a/tools/tests/stktst/dotst.s +++ b/tools/tests/stktst/dotst.s @@ -1,4 +1,4 @@ -/ $Id: $ +/ $Id: dotst.s 1268 2022-08-04 07:03:08Z mueller $ / SPDX-License-Identifier: GPL-3.0-or-later / Copyright 2022- by Walter F.J. Mueller / @@ -17,7 +17,7 @@ / / Revision History: / Date Rev Version Comment -/ 2022-08-01 1267 1.0 Initial version +/ 2022-08-03 1268 1.0 Initial version / .globl _dotst @@ -82,7 +82,7 @@ testx: mov sp,r4 / handle -s tst 6(r2) / idat[3] -s count > 0 ? - beq optc + ble optc bic $017777,r4 / sp &= 017777 mov 6(r2),r1 dec r1 / idat[3]-1 @@ -90,7 +90,7 @@ testx: sub r1,r4 / sp -= 8192 * (idat[3]-1) / handle -c optc: tst 4(r2) / idat[2] -c count > 0 ? - beq opto + ble opto bic $000077,r4 / sp &= 077 mov 4(r2),r1 dec r1 / idat[2]-1 diff --git a/tools/tests/stktst/stktst.c b/tools/tests/stktst/stktst.c index 775d1e45..58cc88c7 100644 --- a/tools/tests/stktst/stktst.c +++ b/tools/tests/stktst/stktst.c @@ -1,10 +1,10 @@ -/* $Id: $ */ +/* $Id: stktst.c 1268 2022-08-04 07:03:08Z mueller $ */ /* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright 2022- by Walter F.J. Mueller */ /* Revision History: */ /* Date Rev Version Comment */ -/* 2022-08-01 1267 1.0 Initial version */ +/* 2022-08-03 1268 1.0 Initial version */ #include #include @@ -37,6 +37,15 @@ int doconv(arg) return ires; } +print_stack(val) + unsigned int val; +{ + unsigned int ns = 7-(val>>13); + unsigned int nc = 127-((val&017777)>>6); + unsigned int no = 64-(val&077); + fprintf(stdout," %06o (%1d,%3d,%2d);", val, ns, nc, no); +} + int main(argc, argv) int argc; @@ -47,7 +56,7 @@ int main(argc, argv) int rcnt = 0; int cnt = 0; int idat[5]; - int odat[3]; + unsigned int odat[3]; int optrwh = 0; int dotst(); @@ -105,12 +114,19 @@ int main(argc, argv) /* call test: 1st round */ dotst(idat, odat); if (optrwh) exit(0); - fprintf(stdout, "stktst-I: before sp %06o %06o\n", odat[0], odat[1]); + printf("stktst-I: before sp"); + print_stack(odat[0]); + print_stack(odat[1]); + printf("\n"); /* call test: 2nd round */ idat[1] = rcnt; dotst(idat, odat); - fprintf(stdout, "stktst-I: after sp %06o %06o %6o\n", - odat[0], odat[1], odat[2]); + printf("stktst-I: after sp"); + print_stack(odat[0]); + print_stack(odat[1]); + print_stack(odat[2]); + printf("\n"); + exit(0); }