ND-500-MON J04: the outer command dispatch table, carved¶
Date: 2026-08-02
Subject: MON-DEBUG:PROG (ND-500/5000 monitor, version J04)
Status: [V] - byte-verified, and the dispatch instruction decoded field by field.
This closes open question 9 of
nd-500-mon-j04.prog.md ("Command-name -> handler-address
binding"), which had stood as PARTIAL since the original carve: the command-name
strings were extracted but the table binding a name to a handler address had never
been located, so every name -> subfunction claim was name-correspondence only.
It is now located, and the binding is exact.
1. The answer in one line¶
Command ordinal i -> handler address = bank2[0o020671 + i], a plain
one-word-per-command array of bank-1 code addresses, 151 entries long.
2. The dispatch instruction¶
Three words in bank 1 at 003260:
003260 146157 RADD CLD SA DX ; X := A (A = the command ordinal)
003261 057050 LDX I ,X 50 ; X := bank2[ bank1[003331] + X ]
003262 126000 JMP ,X 0 ; jump to that address
057050 decodes as opcode 054 (LDX) with I = 1 and X = 1. On the ND-100 that
combination is post-indexing: the effective address is M[P + disp] + X, not
M[P + disp + X]. So:
P + disp=003261 + 0o50=003331bank1[003331]=0o020671<- the table base, held in the pointer pool at003314-003333(the disassembler renders that pool as garbage instructions; it is data)- final operand
bank2[0o020671 + X]- the indexed load runs with PTM = 1, so it reads the data bank, exactly the "pointer word from bank 1, final operand from bank 2" rule already established in section 2.2 of the main analysis
JMP ,X 0 then jumps to the word just loaded. One indexed load and one jump - there
is no thunk, no parameter block and no intermediate table on this path.
Note the sequence is only three words long. That is why it was never found by searching for a large dispatch structure: the structure is the array in the data bank, and the code that uses it is three instructions in the middle of the command loop.
3. How the ordinal is produced: the descriptor array¶
The command names live at bank2 012454-020050 as a packed text region. They are
indexed by a 3-word-per-command descriptor array at bank2 011547, running
011547-012453 - 151 x 3 = 453 words, ending exactly where the string region
begins:
+0 name pointer (into 012454..020050)
+1 0 (always zero in all 151 entries)
+2 byte length of the entry text
The entry text is NAME \ <parameter descriptor>, so the command name is
everything up to the first backslash; commands that take no parameters have no
backslash at all. Examples read verbatim from the bytes:
RECOVER-DOMAIN\F DSCRATCH'Domain name:
OUTPUT-FILE\F D1'File name:
CONTINUE
The descriptor array and the handler table are parallel and the same length, so the ordinal that indexes one indexes the other.
4. Why the extent of the table is not a guess¶
Three independent checks, all from bytes:
- The descriptor array ends where the string region starts. 151 entries x 3
words from
011547lands on012454, which is the first command name (EX). A 150- or 152-entry reading does not. - The word after the handler table is
0o000010- not a plausible bank-1 code address, and nothing like the values inside the table. - Two entries collide, and the collision is meaningful. Entry 0 (
EX) and entry 7 (EXIT) both dispatch to010570.EXis the abbreviation ofEXIT, so the two ordinals sharing one handler is the expected result, not a misalignment artefact. Entries 20 (RESET-TRACE) and 22 (RESET-GUARD) share004207for the same kind of reason.
5. RECOVER-DOMAIN, worked end to end¶
This is the command that motivated the carve, so it is written out in full.
Ordinal 8 -> bank2[0o020671 + 8] = bank2[020701] = 003577.
003577 034665 LDF ,B -113 ; the parsed parameter (domain name)
003600 054602 LDX ,B -176 ; X := stack top = base of the callee frame
003601 032006 STF ,X 6 ; place it at callee local +6
003602 135151 JPL I 151 ; -> pointer at 003753 = 030302 <- the worker
003603 135145 JPL I 145 ; -> pointer at 003750 = 002673 <- error return
003604 125323 JMP I -55 ; -> 003527, back to the command loop
The LDX ,B -176 / STx ,X 6 idiom is the documented caller-marshals-into-callee
convention from section 4.4 of the main analysis, and 003527 is the command loop's
re-entry point - it is also the target of 003340 JMP I 167. The handler is
coherent with everything already known about this program's calling convention.
So the RECOVER-DOMAIN worker is bank1 030302, and it begins a normal routine:
030302 146547 RADD AD1 CLD SL DX
030303 135077 JPL I 77 ; -> 030402
030304 000004
030305 124020 JMP 20 ; -> 030325
...
Carving that routine is the next step (task 1.6) and is what open question 9 was blocking.
6. What this does NOT settle¶
- The parameter descriptor language is only partly read.
\F D<default>'<prompt>is clear enough to recover names and prompts, but the meaning of each type letter (F,D,O,M,E,I,W,R,P) is[OPEN]. Nothing in this document depends on it. - The handler addresses are verified as table contents, not as routines. Only
003577(RECOVER-DOMAIN) and its callee030302were disassembled and checked for coherence. The other 149 are read correctly out of the table but have not each been confirmed to start a real routine. - The relationship to the MON 60 thunk table is not traced. The thunks at
146310-147070(123 x 3 words, section 8.3 of the main analysis) are reached from inside the handlers, not from this table. Which handler calls which thunk is still open, and that - not this table - is what finally binds a command name to a MON 60 subfunction number.
7. Method note¶
The table was not found by scanning for it, and two scans that looked reasonable both failed first:
- Scanning bank 1 for a word holding the string-table base
012456found nothing (the base is held in bank 2, at011552, as part of the descriptor array). - Scanning for a 151-word window of plausible code addresses returned 8 candidate windows in bank 1 and 65 in bank 2, all false - the bank-2 hits were ASCII text whose word values happen to fall in the code-address range, and the bank-1 hits were ordinary code.
What worked was searching for the descriptor array base 0o011547 as a constant.
That value has exactly one occurrence in bank 1, at 003321 - inside the pointer
pool of the command loop itself, three words away from the dispatch. A single-value
search on a distinctive constant beat every range scan, which is the same lesson
already recorded in the carving skill: a range scan over addresses that overlap
instruction encodings is noise, and picking the most distinctive constant available
is what makes the search decisive.
8. Reproducing it¶
cd E:\Dev\Ronny\NDInsight\SINTRAN\ND500\nd-500-mon
wsl python3 <path-to>/gen_cmdtable.py nd-500-mon-j04-bank1.bin nd-500-mon-j04-bank2.bin
The generator walks the descriptor array from 0o011547 and reads
bank2[0o020671 + i] for each entry. It terminates on two conditions - the name
pointer leaving the string range, and the middle word being non-zero - so a single
bad word cannot run it off the end of the array.
9. The full map¶
151 commands, in table order. Addresses are octal. "Handler" is a bank 1 code address; "Descriptor" and "Name ptr" are bank 2 word addresses.
| # | Command | Descriptor | Name ptr | Handler (bank 1) |
|---|---|---|---|---|
| 0 | EX |
011547 |
012454 |
010570 |
| 1 | GO |
011552 |
012456 |
003263 |
| 2 | CONTINUE |
011555 |
012466 |
003341 |
| 3 | RUN |
011560 |
012473 |
003371 |
| 4 | HELP |
011563 |
012475 |
003433 |
| 5 | OUTPUT-FILE |
011566 |
012507 |
003456 |
| 6 | CC |
011571 |
012526 |
003576 |
| 7 | EXIT |
011574 |
012530 |
010570 |
| 8 | RECOVER-DOMAIN |
011577 |
012533 |
003577 |
| 9 | LOOK-AT-PROGRAM |
011602 |
012557 |
004036 |
| 10 | LOOK-AT-DATA |
011605 |
012613 |
004076 |
| 11 | LOOK-AT-STACK |
011610 |
012641 |
004011 |
| 12 | LOOK-AT-RELATIVE |
011613 |
012650 |
003736 |
| 13 | LOOK-AT-REGISTER |
011616 |
012672 |
003605 |
| 14 | LOOK-AT-FILE |
011621 |
012714 |
003631 |
| 15 | INSPECT-DUMP |
011624 |
012742 |
003671 |
| 16 | RESET-INSPECT-DUMP |
011627 |
012760 |
003733 |
| 17 | MAIN-FORMAT |
011632 |
012772 |
004160 |
| 18 | EXTRA-FORMAT |
011635 |
013011 |
004136 |
| 19 | TRACE |
011640 |
013026 |
004166 |
| 20 | RESET-TRACE |
011643 |
013050 |
004207 |
| 21 | GUARD |
011646 |
013056 |
004231 |
| 22 | RESET-GUARD |
011651 |
013117 |
004207 |
| 23 | BRANCH-TRACE |
011654 |
013125 |
004335 |
| 24 | RESET-BRANCH-TRACE |
011657 |
013170 |
004215 |
| 25 | CALL-TRACE |
011662 |
013202 |
004260 |
| 26 | RESET-CALL-TRACE |
011665 |
013244 |
004223 |
| 27 | BREAK |
011670 |
013255 |
004423 |
| 28 | TEMPORARY-BREAK |
011673 |
013301 |
004464 |
| 29 | RESET-LAST-BREAK |
011676 |
013332 |
004525 |
| 30 | EXHIBIT-ADDRESS |
011701 |
013343 |
004530 |
| 31 | RESET-BREAKS |
011704 |
013413 |
004565 |
| 32 | DEBUG-STATUS |
011707 |
013432 |
004577 |
| 33 | DEBUGGER |
011712 |
013441 |
004602 |
| 34 | SPECIAL-DEBUGGER |
011715 |
013455 |
004644 |
| 35 | STEP |
011720 |
013517 |
010446 |
| 36 | PLACE-DOMAIN |
011723 |
013560 |
003376 |
| 37 | DEBUG-PLACE |
011726 |
013577 |
003407 |
| 38 | RESIDENT-PLACE |
011731 |
013616 |
003421 |
| 39 | LOCAL-TRAP-ENABLE |
011734 |
013636 |
010460 |
| 40 | SYSTEM-TRAP-ENABLE |
011737 |
013673 |
010504 |
| 41 | LOCAL-TRAP-DISABLE |
011742 |
013717 |
010504 |
| 42 | SYSTEM-TRAP-DISABLE |
011745 |
013743 |
010504 |
| 43 | RESET-DEBUG |
011750 |
013767 |
004212 |
| 44 | STATUS |
011753 |
013775 |
010557 |
| 45 | ENABLED-TRAPS |
011756 |
014001 |
010562 |
| 46 | RESTART-PROCESS |
011761 |
014010 |
010573 |
| 47 | OPEN-FILE |
011764 |
014031 |
005061 |
| 48 | CLOSE-FILE |
011767 |
014071 |
005113 |
| 49 | SET-BLOCK-SIZE |
011772 |
014111 |
005126 |
| 50 | LIST-OPEN-FILES |
011775 |
014145 |
005157 |
| 51 | SET-HISTOGRAM |
012000 |
014155 |
010370 |
| 52 | PRINT-HISTOGRAM |
012003 |
014225 |
010432 |
| 53 | STOP-HISTOGRAM |
012006 |
014235 |
010435 |
| 54 | START-HISTOGRAM |
012011 |
014245 |
010440 |
| 55 | RELEASE-HISTOGRAM |
012014 |
014255 |
010443 |
| 56 | TIME-USED |
012017 |
014266 |
005162 |
| 57 | WHO-IS-ON |
012022 |
014273 |
005165 |
| 58 | DEFINE-MACRO |
012025 |
014300 |
004704 |
| 59 | EXECUTE-MACRO |
012030 |
014317 |
004747 |
| 60 | ERASE-MACRO |
012033 |
014346 |
004712 |
| 61 | LIST-MACRO |
012036 |
014365 |
004724 |
| 62 | DUMP-MACRO |
012041 |
014404 |
004736 |
| 63 | RESUME-MACRO |
012044 |
014422 |
004744 |
| 64 | ABORT-BATCH-ON-ERROR |
012047 |
014431 |
005055 |
| 65 | AUTOMATIC-ERROR-MESSAGE |
012052 |
014453 |
005170 |
| 66 | RESET-AUTOMATIC-ERROR-MESSAGE |
012055 |
014467 |
005176 |
| 67 | SET-MEMORY-CONTENTS |
012060 |
014506 |
004773 |
| 68 | SET-FLAG |
012063 |
014562 |
005204 |
| 69 | GET-FLAG |
012066 |
014606 |
005226 |
| 70 | DEFINE-MEMORY-CONFIGURATION |
012071 |
014625 |
005274 |
| 71 | MEMORY-CONFIGURATION |
012074 |
014676 |
005310 |
| 72 | VERSION |
012077 |
014711 |
005313 |
| 73 | MASTER-CLEAR |
012102 |
014721 |
005673 |
| 74 | RUN-SELFTEST |
012105 |
014730 |
005741 |
| 75 | RESET-CPU |
012110 |
014737 |
005744 |
| 76 | INIT-TRACER |
012113 |
014744 |
005747 |
| 77 | ARM-TRACER |
012116 |
015036 |
006024 |
| 78 | DISARM-TRACER |
012121 |
015044 |
006027 |
| 79 | CLEAR-TRACE-ADDRESS |
012124 |
015053 |
006032 |
| 80 | CLEAR-TRACE-MEMORY |
012127 |
015065 |
006035 |
| 81 | DUMP-TRACE-MEMORY |
012132 |
015077 |
006040 |
| 82 | EXAMINE-TRACE |
012135 |
015110 |
006045 |
| 83 | READ-TRACE-FILE |
012140 |
015117 |
006050 |
| 84 | WRITE-TRACE-FILE |
012143 |
015137 |
006056 |
| 85 | LOAD-CONTROL-STORE |
012146 |
015157 |
006064 |
| 86 | COMPARE-CONTROL-STORE |
012151 |
015231 |
006117 |
| 87 | LOOK-AT-CONTROL-STORE |
012154 |
015331 |
006220 |
| 88 | LOOK-AT-RESIDENT-MEMORY |
012157 |
015354 |
006237 |
| 89 | LOOK-AT-HARDWARE |
012162 |
015400 |
006231 |
| 90 | MICRO-START |
012165 |
015423 |
006277 |
| 91 | MICRO-STOP |
012170 |
015444 |
006312 |
| 92 | SET-PRIORITY |
012173 |
015452 |
006325 |
| 93 | START-PROCESS-LOG-ALL |
012176 |
015513 |
006573 |
| 94 | START-PROCESS-LOG-ONE |
012201 |
015526 |
006573 |
| 95 | PROCESS-LOG-ALL |
012204 |
015553 |
006516 |
| 96 | PROCESS-LOG-ONE |
012207 |
015606 |
006573 |
| 97 | PRINT-PROCESS-LOG |
012212 |
015641 |
006573 |
| 98 | SWAPPING-LOG |
012215 |
015665 |
006635 |
| 99 | RELEASE-LOG-BUFFER |
012220 |
015704 |
006676 |
| 100 | ATTACH-PROCESS |
012223 |
015716 |
006701 |
| 101 | LOOK-AT-PHYSICAL-SEGMENT |
012226 |
015741 |
006715 |
| 102 | LOOK-AT-SRF |
012231 |
015777 |
006755 |
| 103 | SET-SEGMENT-LIMITS |
012234 |
016017 |
007037 |
| 104 | FIX-SEGMENT-SCATTERED |
012237 |
016117 |
007112 |
| 105 | FIX-SEGMENT-CONTIGUOUS |
012242 |
016201 |
007112 |
| 106 | FIX-SEGMENT-ABSOLUTE |
012245 |
016264 |
007112 |
| 107 | UNFIX-SEGMENT |
012250 |
016357 |
007222 |
| 108 | LIST-SYSTEM-PARAMETERS |
012253 |
016412 |
007252 |
| 109 | SET-SYSTEM-PARAMETERS |
012256 |
016426 |
007257 |
| 110 | VALUE-ENTRIES |
012261 |
016441 |
007265 |
| 111 | START-MONCALL-LOG |
012264 |
016457 |
007277 |
| 112 | PRINT-MONCALL-LOG |
012267 |
016501 |
007315 |
| 113 | STOP-MONCALL-LOG |
012272 |
016512 |
007320 |
| 114 | DEFINE-STANDARD-DOMAIN |
012275 |
016523 |
007323 |
| 115 | DELETE-STANDARD-DOMAIN |
012300 |
016564 |
007344 |
| 116 | LIST-STANDARD-DOMAINS |
012303 |
016615 |
007352 |
| 117 | LIST-EXECUTION-QUEUE |
012306 |
016630 |
007355 |
| 118 | LIST-TIME-QUEUE |
012311 |
016654 |
007366 |
| 119 | DEFINE-SWAP-FILE |
012314 |
016675 |
007417 |
| 120 | DELETE-SWAP-FILE |
012317 |
016715 |
007425 |
| 121 | SET-ND-500-AVAILABLE |
012322 |
016735 |
007477 |
| 122 | SET-ND-500-UNAVAILABLE |
012325 |
016750 |
007433 |
| 123 | STOP-ND-500 |
012330 |
016766 |
007510 |
| 124 | STOP-ND-500 |
012333 |
016774 |
007510 |
| 125 | LOGOUT-PROCESS |
012336 |
017002 |
007517 |
| 126 | ABORT-PROCESS |
012341 |
017024 |
007517 |
| 127 | LIST-ACTIVE-PROCESSES |
012344 |
017045 |
007751 |
| 128 | LIST-DOMAIN |
012347 |
017060 |
007765 |
| 129 | DOMAIN-STATUS |
012352 |
017077 |
010073 |
| 130 | LIST-STATUS |
012355 |
017117 |
010101 |
| 131 | SET-PROCESS-NAME |
012360 |
017136 |
010107 |
| 132 | LIST-PROCESS-TABLE-ENTRY |
012363 |
017160 |
007563 |
| 133 | LIST-ACTIVE-SEGMENTS |
012366 |
017211 |
007563 |
| 134 | PROCESS-STATUS |
012371 |
017240 |
007563 |
| 135 | LIST-SEGMENT-TABLE-ENTRY |
012374 |
017250 |
007563 |
| 136 | LIST-SWAP-FILE-INFO |
012377 |
017303 |
007563 |
| 137 | DEBUG-SWAPPER |
012402 |
017330 |
010115 |
| 138 | DUMP-SWAPPER |
012405 |
017347 |
010137 |
| 139 | DUMP-PHYSICAL-SEGMENT |
012410 |
017365 |
010147 |
| 140 | LIST-TABLE |
012413 |
017417 |
010172 |
| 141 | LOAD-SWAPPER |
012416 |
017442 |
010211 |
| 142 | START-SWAPPER |
012421 |
017464 |
010217 |
| 143 | GIVE-N500-PAGES |
012424 |
017473 |
010222 |
| 144 | TAKE-N500-PAGES |
012427 |
017516 |
010235 |
| 145 | SET-CPU-STATUS |
012432 |
017541 |
010250 |
| 146 | CHANGE-CPU |
012435 |
017606 |
010320 |
| 147 | SET-PHYSICAL-SEGMENT-ADDRESS |
012440 |
017624 |
010350 |
| 148 | CACHE-MODE |
012443 |
017672 |
006315 |
| 149 | REMOVE-FROM-TIME-SLICE |
012446 |
017730 |
006411 |
| 150 | INSERT-IN-TIME-SLICE |
012451 |
017757 |
006447 |
commands: 151 descriptor array: 0o11547..0o12453 handler table: 0o20671..0o21117 word immediately after the handler table: 0o000010