mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
docs: run the r5.01 C toolchain end to end under 2ine
The null-selector fix in the 2ine fork unblocks gspac/gspopt/gspcg, so document the full pipeline: build 2ine, extract the tools, the four C stages, absolute placement, and the firmware round-trip. Record the two gotchas found while testing: gspcl still stops after gspac because the children exit 139 at teardown, and GSPA 5.01 rejects b15, so the 32 KB round-trip differs at one SP access (same physical register, A15/B15).
This commit is contained in:
1 parent
b75048fb87
commit
9deada6cef
3 files changed
+258
-4
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
<https://git.wut.ee/opencode.energia.arti.ee/2ine-tms34010> 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<module>.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
|
||||
```
|
||||
@@ -142,9 +142,10 @@ cd 2ine/build # lx_loader dlopen()s ./lib<module>.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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user