diff --git a/README.md b/README.md index 7a82b08..7255c14 100644 --- a/README.md +++ b/README.md @@ -572,6 +572,7 @@ also why a server-side change cannot cause or cure it. | `reference/third-party-code.md` | what open-source code is in the ROM | | `reference/tms34010-sdk.md` | the 1987 toolchain, buildable today | | `tools/tms34010-sdk/OS2-501.md` | using the 1990 r5.01 toolchain (ANSI C + optimizer) | +| `tools/tms34010-sdk/2INE-501.md` | the r5.01 C toolchain end to end under 2ine, no VM | | `EXPLOIT_NOTES.md` | the original code-execution audit and the crash decode | | `NEXT_SESSION.md` | the LAT soak handoff (older; see the reports for later work) | @@ -591,7 +592,7 @@ vt1200/ │ ├── ptr_constants.py # find/resolve bit-address constants │ ├── smart34010/ # dis34 (two encoding fixes) │ ├── as34/ # TMS34010 binutils, ported to 64-bit Linux -│ ├── tms34010-sdk/ # TI SDKs: 1987 DOS + 1990 OS/2 (see OS2-501.md) +│ ├── tms34010-sdk/ # TI SDKs: 1987 DOS + 1990 OS/2 (see OS2-501.md, 2INE-501.md) │ └── ghidra-tms34010/ # patched processor module + install script ├── harness/ │ ├── mame/ # the MAME driver, Lua probes, reports diff --git a/tools/tms34010-sdk/2INE-501.md b/tools/tms34010-sdk/2INE-501.md new file mode 100644 index 0000000..99d9f6b --- /dev/null +++ b/tools/tms34010-sdk/2INE-501.md @@ -0,0 +1,252 @@ +# Running the TMS340 r5.01 toolchain under 2ine + +This is the working **no-VM** route to TI's 1990 **TMS340 Family Code +Generation Tools r5.01**. The tools are 16-bit OS/2 1.x `NE` console programs; +the patched fork of [2ine](https://github.com/icculus/2ine) at + runs them on a +64-bit Linux host. + +Unlike the assembler-only state documented when the fork was first published, +the **full C pipeline works**: front end, optimizer, code generator, +assembler, linker and converter. `gspcl` (the one-command driver) still does +not chain its children reliably; drive the four C stages explicitly. + +Verified 2026-11-01 on x86_64 Arch, against the VT1200 firmware image. + +| tool | result | +|---|---| +| `gspac` (C front end) | **works** | +| `gspopt` (intermediate optimizer) | **works** | +| `gspcg` (code generator) | **works** | +| `gspa` (COFF assembler) | **works** — 11,852-line firmware disassembly, no errors | +| `gsplnk` (linker) | **works** — absolute placement; `rts.lib` + `flib.lib` | +| `gsprom` (COFF → Tek-hex) | **works** — plain and `-i` EPROM pair | +| `gspar` (archiver) | **works** — lists `rts.lib` members | +| `gspcl` (driver) | **partial** — runs `gspac`, then stops (see §7) | + +--- + +## 1. Build 2ine + +The fork's `main` carries the three fixes this needs: the null-selector +mapping (commit `91b78f2`), Arch/CMake-4 ncurses discovery (`a440128`), and +this document's status update (`3bcdfc3`). + +Dependencies (Arch): + +```sh +sudo pacman -S --needed base-devel cmake lib32-glibc lib32-gcc-libs \ + lib32-ncurses sdl2-compat +``` + +Debian/Ubuntu names: + +```sh +sudo apt install build-essential cmake libncurses-dev libncurses-dev:i386 \ + libc6-dev-i386 gcc-multilib +``` + +Build 32-bit. On CMake 4.x the policy floor must be passed explicitly: + +```sh +git clone https://git.wut.ee/opencode.energia.arti.ee/2ine-tms34010 +cd 2ine-tms34010 +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 +cmake --build build -j8 +``` + +Result: `build/lx_loader` (32-bit), `build/lx_dump`, and `build/lib*.so`. + +## 2. Extract the r5.01 tools + +Disk 1 is the executables, disk 2 the headers, runtimes and library sources. +Extract them anywhere: + +```sh +cd /path/to/vt1200 +SDK="tools/tms34010-sdk/1990-11 TMS340 Family SDK" +mkdir -p os2-501/bin os2-501/lib os2-501/work +unzip -o "$SDK/1990-11-19 TMS340 Family Code Generation Tools (1 of 2) r5.01 rev2564062-1641.zip" -d os2-501/bin +unzip -o "$SDK/1990-11-19 TMS340 Family Code Generation Tools (2 of 2) r5.01 rev2564062-1641.zip" -d os2-501/lib +``` + +## 3. Prepare the run directory + +`lx_loader` `dlopen()`s the native modules as `./lib.so`, so the +working directory matters. The OS/2 tools also cannot open Unix absolute +paths, so keep sources, objects and libraries in the same directory and pass +**bare relative names**. + +```sh +cd os2-501/work +ln -sf /path/to/2ine-tms34010/build/lib*.so . +cp ../bin/*.exe . +cp ../lib/rts.lib ../lib/flib.lib . +``` + +The tools consume their intermediate `.if` and `.opt` files on success — even +with `-k` — so regenerate them for every build. + +## 4. End-to-end C build + +Given `HELLO.C`: + +```c +int twice(int n) { return n + n; } + +int sum(int *p, int n) { + int s = 0; + int i; + for (i = 0; i < n; i++) s += p[i]; + return s + twice(n); +} +``` + +Run each stage. `LX` is the absolute path to `lx_loader`: + +```sh +LX=/path/to/2ine-tms34010/build/lx_loader + +$LX ./gspac.exe -k HELLO.C HELLO.IF # C front end -> intermediate +$LX ./gspopt.exe -k -O2 HELLO.IF HELLO.OPT # optimizer -> intermediate +$LX ./gspcg.exe -m HELLO.OPT HELLO.ASM HELLO.TMP # codegen -> .asm +$LX ./gspa.exe HELLO.ASM # assembler -> HELLO.obj +$LX ./gsplnk.exe HELLO.obj rts.lib flib.lib \ + -o hello.out -m hello.map # linker -> absolute COFF +$LX ./gsprom.exe hello.out hello.hex # converter -> Tek-hex +``` + +Notes: + +* `gspa` writes the object as **lowercase** `HELLO.obj`, even though the + source is `HELLO.ASM`. Keep the names consistent on a case-sensitive FS. +* `-O2` means level 2. `-O0` is the lowest level, not "off"; with no `-o`/`-O` + stage at all the optimizer is not run. +* Link `flib.lib` as well as `rts.lib` when the code pulls in `_sqrt`, + `_frexp`, `_ldexp` and friends: the float primitives (`FD$ADD`, `FD$DIV`, + …) live in `flib.lib`. + +### Placing the image at a TMS34010 bit address + +`gsplnk` accepts a command file whose `SECTIONS` origin is a **bit address**. +COFF section sizes are in bits too (16 bits per instruction word): a `.text` +of `0x8000` is 4096 bytes, not 32 KB. +**Write the command file with CRLF**, even on Linux: with LF-only line ends the +linker merges the `-m` line into the next one and silently loses options. + +``` +-a +-o hello.out +-m hello.map +HELLO.obj +SECTIONS +{ + .text 3F208000h: {} +} +``` + +```sh +$LX ./gsplnk.exe hello.cmd +$LX ./gsprom.exe -i hello.out hi lo # high/low EPROM pair +``` + +`0x3F208000` is the VT1200's firmware base. The converter's own hex record +addresses wrap at 64 KB; take placement from the COFF file or the map, never +from the hex. + +## 5. Assembler / linker / converter on a firmware region + +The 1987 round-trip flow in `harness/sdk-scratch/roundtrip.sh` works with the +5.01 tools, with one source-level change (`b15` → `sp`, §6): + +```sh +# disassemble 32 KB at bit 3F208000 to RT.ASM, as roundtrip.sh does +# then, in the 2ine run directory: +$LX ./gspa.exe RT.ASM +$LX ./gsplnk.exe RT.CMD +$LX ./gsprom.exe -i rt.out rthi rtlo +# interleave low/high and compare with the original region +``` + +Result on `vt1200_v22.bin`, region `0x1000` / 32,768 bytes / base `3F208000`: + +* **1987 GSPA:** 0 differing bytes, + `md5 43adb6669571041985f3e26fd9ff7cb9` (BYTE-EXACT) +* **5.01 GSPA:** 32,768/32,768 bytes, **1 differing byte** at `0x33ec` + (`3f` vs `2f`) + +The difference is the A/B register-file namespace bit of the single +`rl 09h, sp` in the region, not a semantic difference (§6). + +## 6. Why `b15` becomes `sp`, and why one byte differs + +`dis2as34.py` emits `b15` for the B-file stack pointer because **GSPA 2.00** +accepts `b15` and uses a bare `sp` for the A-file form. **GSPA 5.01 rejects +`b15`** with `INVALID REGISTER VALUE`; its `sp` encodes the A15 namespace. + +The TMS34010 has one stack-pointer register, addressable as **A15 or B15**, so +`rl 09h, sp` (5.01, `2f31`) and `rl 09h, b15` (2.00, `3f31`) address the same +register. The 5.01 object is therefore functionally identical to the ROM, but +not byte-identical at that instruction. This is the one exception to the +otherwise one-way assembler compatibility noted in `OS2-501.md`. + +## 7. `gspcl` and the exit-status trap + +`gspcl` finds `gspac` and runs it, then stops without invoking `gspopt` / +`gspcg` / `gspa`. The individual tools frequently exit non-zero after +successful work: `gspac` and `gspcg` return **139** (segfault during teardown), +`gsprom` can return 1. `gspcl` treats the child status as failure. + +**Check for output files, not exit codes.** The manual pipeline in §4 produces +correct objects and images; the one-command driver does not yet. + +## 8. What the null-selector fix was + +The original fork asserted in `lxConvert1616to32` when the C front end passed +`0003:0000`. That is the OS/2 **null far pointer**: selector index 0 with RPL +3. The converter special-cased only `addr1616 == 0`, so it indexed `ldt[0]` +and asserted. + +The fix in `lx_loader.c` is: + +```c +const uint16 selector = (uint16) (addr1616 >> 19); +const uint16 offset = (uint16) (addr1616 % 0x10000); +if (selector == 0) // segment 0x0000 or 0x0003: the OS/2 null selector. + return NULL; +``` + +Segment index 0 is the null selector by definition, so returning `NULL` is +correct, not a workaround. + +## 9. Measured results + +Bench source with a sum loop, indexed loop, `switch`, struct update, helper +call and bit loop (`BENCH.C`), compiled four ways: + +| build | `.text` | `.obj` | `.asm` | +|---|---:|---:|---:| +| no optimizer | 490 B | 950 B | 4396 B | +| `-O0` | 394 B | 854 B | 4497 B | +| `-O1` | 394 B | 854 B | 4499 B | +| **`-O2`** | **376 B** | **836 B** | **4236 B** | + +`-O2` is ~23 % smaller than no optimizer on this source. Assembly of the +11,852-line firmware disassembly takes ~0.22 s wall through `lx_loader` +(loader startup included). + +## 10. Reproduce + +A compact end-to-end run (from `os2-501/work`, with the libs symlinked and the +tools copied as in §3): + +```sh +LX=/path/to/2ine-tms34010/build/lx_loader +$LX ./gspac.exe -k HELLO.C HELLO.IF +$LX ./gspopt.exe -k -O2 HELLO.IF HELLO.OPT +$LX ./gspcg.exe -m HELLO.OPT HELLO.ASM HELLO.TMP +$LX ./gspa.exe HELLO.ASM +$LX ./gsplnk.exe HELLO.obj rts.lib flib.lib -o hello.out -m hello.map +$LX ./gsprom.exe hello.out hello.hex +``` diff --git a/tools/tms34010-sdk/OS2-501.md b/tools/tms34010-sdk/OS2-501.md index 1f2b326..bc4cc00 100644 --- a/tools/tms34010-sdk/OS2-501.md +++ b/tools/tms34010-sdk/OS2-501.md @@ -142,9 +142,10 @@ cd 2ine/build # lx_loader dlopen()s ./lib.so, so cwd matters ./lx_dump gspa.exe # NE header/segments/imports ``` -`gspcl` (the C compiler) does **not** work yet there: the `gspac` child hits -`lxConvert1616to32` with a selector 2ine has not mapped. Use 86Box when you need -to compile C. +The C pipeline **does** work under the patched fork: a null-selector fix in +`lx_loader.c` unblocks `gspac`, `gspopt` and `gspcg`. `gspcl` itself still +stops after `gspac` because the children exit non-zero at teardown, so drive +the stages explicitly. See `2INE-501.md` for the full end-to-end flow. ---