code wiki / _hdl_build / nx_nishi_usb_longmode.nx
nx_nishi_usb_longmode.nx source
↩ module page · 258 lines · 15911 B
1// nx_nishi_usb_longmode.nx -- x86 ladder R9-HANDOFF: the loaded kernel boots THROUGH to LONG mode.
2//
3// The previous rung (nx_nishi_usb_image) proved a persisted multi-sector image boots MBR->loader->
4// kernel, but every stage was 16-bit real-mode. THIS rung crosses the seam the OS ladder has been
5// stuck at: the kernel that the MBR loads OFF DISK now performs the real -> protected -> LONG mode
6// transition (GDT-less minimal model: CR0.PE, then EFER.LME via WRMSR + CR0.PG) and the emu tracks it.
7//
8// It fuses two previously-separate emus into ONE unified boot-disk emu:
9// * the 16-bit real-mode + BIOS INT 13h/INT 10h loader (from nx_emu_x86_stage2 / nx_nishi_usb_image)
10// * the CR0/EFER mode-transition decode (from nx_emu_x86_boot_kernel / nx_nishios_boot_gui)
11// The two opcode sets are disjoint (0xBE/0xB8/0xCD... vs 0x48 0xC7 / 0x0F 0x22 / 0x0F 0x30), so one
12// flat decoder runs both and reports the final CPU mode.
13//
14// Boot flow, executed off the PERSISTED on-disk bytes:
15// sector 0 (MBR) --INT 13h AH=02--> loads sector 1 (the kernel) to 0x8000, jmps to it
16// sector 1 (kernel) prints "NishiOS" over INT 10h, then: mov cr0,1 (->PROTECTED)
17// wrmsr EFER.LME=1 ; mov cr0,0x80000001 (PE|PG) -> LONG mode ; HLT
18//
19// KAT: (T1) persisted byte-faithful + 0x55AA; (T2) INT 13h loaded the kernel off disk (0x8000 0->0xBE);
20// (T3) console banner == 'NishiOS'; (T4) the LOADED kernel reached LONG mode (mode==2); (T5) clean HLT;
21// (T6) NEG/liar-kill: a sibling image whose MBR loads ZERO sectors never runs the kernel, so it NEVER
22// reaches long mode and prints no banner -- the long-mode result MUST come from the loaded kernel.
23//
24// HONEST SEAM (no overclaim, same as the boot-kernel organs): the emu EXECUTES the loader + the
25// CR0/EFER transition as real x86 and TRACKS the mode; it does not yet model a real GDT/IDT/paging
26// table walk, nor decode-width change by mode. The next rung is loading the real 64-bit desktop kernel
27// (nx_nishios_boot_gui) at the long-mode entry instead of a banner stub, then GDT/IDT/paging.
28// NEVER-BRICK (Rule 26): writes a FILE (knowledge/status/nishi_os_lm.img); models INT 13h *reads* only;
29// no /dev, no firmware. expect_exit: 0 license_tier: ORIGINAL
30import "nx_syscalls.nx"
31import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
32const IMG_MAGIC_200000: i64 = 200000
33const IMG_MAGIC_32767: i64 = 32767
34const IMG_MAGIC_65536: i64 = 65536
35
36func ul_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
37// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
38// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
39// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
40// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
41func ul_num(v: i64) -> i64 { nxi_out(v); return 0 }
42func ul_beq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 }
43func ul_contains(hay: *u8, hn: i64, ndl: *u8, nn: i64) -> i64 {
44 if nn==0 { return 1 }
45 var i: i64 = 0
46 while i + nn <= hn {
47 var j: i64 = 0
48 var ok: i64 = 1
49 while j < nn { if hay[i+j]!=ndl[j] { ok=0; j=nn } else { j=j+1 } }
50 if ok==1 { return 1 }
51 i=i+1
52 }
53 return 0
54}
55// CPU mode from CR0/EFER: 0=real, 1=protected, 2=long. (same rule as the boot-kernel organs)
56func ul_mode(cr0: i64, efer: i64) -> i64 { let pe: i64=cr0&1; let pg: i64=(cr0>>31)&1; let lme: i64=(efer>>8)&1; if pe==0 { return 0 } if pg==1 { if lme==1 { return 2 } } return 1 }
57
58// UNIFIED boot-disk emu: 16-bit real-mode + INT 13h/INT 10h PLUS 64-bit mov + CR0/EFER mode transition.
59// returns 0 = clean HLT, -1 = unknown opcode, -2 = ran away. st[0]=final cr0, st[1]=final mode.
60func emu_x86_boot_disk(mem: *u8, disk: *u8, entry: i64, console: *u8, clen: *i64, st: *i64) -> i64 {
61 var ip: i64 = entry
62 var ax: i64 = 0
63 var bx: i64 = 0
64 var cx: i64 = 0
65 var dx: i64 = 0
66 var si: i64 = 0
67 var zf: i64 = 0
68 var rax: i64 = 0 // 64-bit reg used by the mode-transition setup (distinct from 16-bit ax)
69 var cr0: i64 = 0
70 var efer: i64 = 0
71 var guard: i64 = 0
72 st[0]=0; st[1]=0
73 while guard < IMG_MAGIC_200000 {
74 guard = guard + 1
75 let op: i64 = mem[ip] as i64
76 if op == 0xF4 { st[0]=cr0; st[1]=ul_mode(cr0,efer); return 0 } // hlt
77 var h: i64 = 0
78 // ---- 64-bit mov r64,imm32 (48 C7 modrm imm32) ----
79 if h==0 { if op==0x48 {
80 if mem[ip+1]==(0xC7 as u8) {
81 let modrm: i64 = mem[ip+2] as i64
82 let imm: i64 = (mem[ip+3] as i64) | ((mem[ip+4] as i64)<<8) | ((mem[ip+5] as i64)<<16) | ((mem[ip+6] as i64)<<24)
83 if modrm==0xC0 { rax = imm } // mov rax,imm32 (the only target we use)
84 ip=ip+7; h=1
85 }
86 } }
87 // ---- two-byte 0F ops: mov cr0,rax / wrmsr / syscall ----
88 if h==0 { if op==0x0F {
89 let b1: i64 = mem[ip+1] as i64
90 if b1==0x22 { let m: i64=mem[ip+2] as i64; if ((m>>3)&7)==0 { cr0=rax }; ip=ip+3; h=1 } // mov cr0,rax
91 if h==0 { if b1==0x30 { efer=rax; ip=ip+2; h=1 } } // wrmsr -> EFER=rax
92 if h==0 { if b1==0x05 { st[0]=cr0; st[1]=ul_mode(cr0,efer); return rax & 0xff } } // syscall (exit)
93 if h==0 { return 0 - 1 }
94 } }
95 // ---- 16-bit real-mode + BIOS INT services (from nx_nishi_usb_image / stage2) ----
96 if h==0 { if op==0xBE { si = (mem[ip+1] as i64) | ((mem[ip+2] as i64)<<8); ip=ip+3; h=1 } } // mov si,imm16
97 if h==0 { if op==0xB8 { ax = (mem[ip+1] as i64) | ((mem[ip+2] as i64)<<8); ip=ip+3; h=1 } } // mov ax,imm16
98 if h==0 { if op==0xBB { bx = (mem[ip+1] as i64) | ((mem[ip+2] as i64)<<8); ip=ip+3; h=1 } } // mov bx,imm16
99 if h==0 { if op==0xB9 { cx = (mem[ip+1] as i64) | ((mem[ip+2] as i64)<<8); ip=ip+3; h=1 } } // mov cx,imm16
100 if h==0 { if op==0xBA { dx = (mem[ip+1] as i64) | ((mem[ip+2] as i64)<<8); ip=ip+3; h=1 } } // mov dx,imm16
101 if h==0 { if op==0xAC { let al: i64 = mem[si] as i64; ax = (ax & 0xFF00) | al; si=si+1; ip=ip+1; h=1 } } // lodsb
102 if h==0 { if op==0x08 { let al2: i64 = ax & 0xFF; if al2==0 { zf=1 } else { zf=0 } ip=ip+2; h=1 } } // or al,al
103 if h==0 { if op==0x74 { var r: i64 = mem[ip+1] as i64; if r>127 { r=r-256 } if zf==1 { ip=ip+2+r } else { ip=ip+2 } h=1 } } // jz rel8
104 if h==0 { if op==0xB4 { ax = (ax & 0xFF) | ((mem[ip+1] as i64)<<8); ip=ip+2; h=1 } } // mov ah,imm8
105 if h==0 { if op==0xEB { var r2: i64 = mem[ip+1] as i64; if r2>127 { r2=r2-256 } ip=ip+2+r2; h=1 } } // jmp rel8
106 if h==0 { if op==0xE9 { var r3: i64 = (mem[ip+1] as i64) | ((mem[ip+2] as i64)<<8); if r3>IMG_MAGIC_32767 { r3=r3-IMG_MAGIC_65536 } ip=ip+3+r3; h=1 } } // jmp rel16
107 if h==0 { if op==0xCD {
108 let vec: i64 = mem[ip+1] as i64
109 let ah: i64 = (ax >> 8) & 0xFF
110 if vec==0x10 { if ah==0x0E { console[clen[0]]=(ax & 0xFF) as u8; clen[0]=clen[0]+1 } } // BIOS teletype
111 if vec==0x13 { if ah==0x02 { // BIOS read sectors
112 let count: i64 = ax & 0xFF
113 let sector: i64 = cx & 0xFF
114 let lba: i64 = sector - 1
115 var s: i64 = 0
116 while s < count*512 { mem[bx + s] = disk[lba*512 + s]; s=s+1 }
117 ax = ax & 0xFF
118 } }
119 ip=ip+2; h=1
120 } }
121 if h==0 { return 0 - 1 }
122 }
123 return 0 - 2
124}
125
126const IMG_SZ: i64 = 1024 // 2 sectors: MBR + the long-mode kernel
127
128// emit "mov rax,imm32" (48 C7 C0 + 4 LE bytes) at img[base+o]; returns next offset.
129func ul_movrax(img: *u8, base: i64, o: i64, b0: i64, b1: i64, b2: i64, b3: i64) -> i64 {
130 img[base+o]=0x48 as u8; img[base+o+1]=0xC7 as u8; img[base+o+2]=0xC0 as u8
131 img[base+o+3]=(b0&0xff) as u8; img[base+o+4]=(b1&0xff) as u8; img[base+o+5]=(b2&0xff) as u8; img[base+o+6]=(b3&0xff) as u8
132 return o+7
133}
134
135func build_image(img: *u8) -> i64 {
136 var z: i64=0
137 while z<IMG_SZ { img[z]=0 as u8; z=z+1 }
138
139 // ---- sector 0: MBR (loads at 0x7C00); loads sector 1 (kernel) to 0x8000 then jmps to it ----
140 img[0]=0xB8 as u8; img[1]=0x01 as u8; img[2]=0x02 as u8 // mov ax,0x0201 (AH=02 read, AL=01 -> ONE sector)
141 img[3]=0xBB as u8; img[4]=0x00 as u8; img[5]=0x80 as u8 // mov bx,0x8000
142 img[6]=0xB9 as u8; img[7]=0x02 as u8; img[8]=0x00 as u8 // mov cx,0x0002 (CL=2 -> LBA1 = sector1)
143 img[9]=0xBA as u8; img[10]=0x80 as u8; img[11]=0x00 as u8 // mov dx,0x0080
144 img[12]=0xCD as u8; img[13]=0x13 as u8 // int 0x13 (load kernel)
145 let rel16: i64 = 0x8000 - (0x7C00 + 14 + 3) // jmp 0x8000 -> kernel
146 img[14]=0xE9 as u8; img[15]=(rel16 & 0xFF) as u8; img[16]=((rel16>>8) & 0xFF) as u8
147 img[510]=0x55 as u8; img[511]=0xAA as u8
148
149 // ---- sector 1: the long-mode kernel (loads at 0x8000); offsets r relative to kernel start ----
150 var r: i64 = 0
151 img[512+r]=0xBE as u8; let si_r: i64 = r+1; r=r+3 // mov si, imm16 (patched -> 0x8000+msg)
152 let loop_r: i64 = r
153 img[512+r]=0xAC as u8; r=r+1 // lodsb
154 img[512+r]=0x08 as u8; img[512+r+1]=0xC0 as u8; r=r+2 // or al,al
155 img[512+r]=0x74 as u8; let jz_r: i64 = r+1; r=r+2 // jz afterprint (patched)
156 img[512+r]=0xB4 as u8; img[512+r+1]=0x0E as u8; r=r+2 // mov ah,0x0E
157 img[512+r]=0xCD as u8; img[512+r+1]=0x10 as u8; r=r+2 // int 0x10
158 img[512+r]=0xEB as u8; img[512+r+1]=((loop_r-(r+2)) & 0xFF) as u8; r=r+2 // jmp loop
159 let after_r: i64 = r
160 // enter PROTECTED: mov rax,1 ; mov cr0,rax
161 r = ul_movrax(img, 512, r, 1, 0, 0, 0)
162 img[512+r]=0x0F as u8; img[512+r+1]=0x22 as u8; img[512+r+2]=0xC0 as u8; r=r+3 // mov cr0,rax
163 // enter LONG: EFER.LME=1 (rax=0x100 ; wrmsr)
164 r = ul_movrax(img, 512, r, 0x00, 0x01, 0, 0)
165 img[512+r]=0x0F as u8; img[512+r+1]=0x30 as u8; r=r+2 // wrmsr -> EFER=rax
166 // CR0 = PE|PG = 0x80000001 ; mov cr0,rax -> with EFER.LME = LONG mode
167 r = ul_movrax(img, 512, r, 0x01, 0x00, 0x00, 0x80)
168 img[512+r]=0x0F as u8; img[512+r+1]=0x22 as u8; img[512+r+2]=0xC0 as u8; r=r+3 // mov cr0,rax
169 img[512+r]=0xF4 as u8; r=r+1 // hlt
170 let msg_r: i64 = r
171 let msg: *u8 = "NishiOS\x0D\x0A\x00"
172 var mi: i64=0
173 while msg[mi]!=(0 as u8) { img[512+r]=msg[mi]; r=r+1; mi=mi+1 }
174 img[512+r]=0 as u8; r=r+1
175 img[512+jz_r] = ((after_r - (jz_r+1)) & 0xFF) as u8 // patch jz rel8 -> afterprint
176 let si_abs: i64 = 0x8000 + msg_r // patch si = 0x8000 + msg offset
177 img[512+si_r] = (si_abs & 0xFF) as u8
178 img[512+si_r+1] = ((si_abs>>8) & 0xFF) as u8
179 return 0
180}
181
182// boot an image: copy sector0 -> mem[0x7C00], run the on-disk bytes. loaded[0]=0x8000 before, [1]=after.
183func boot_image(img: *u8, console: *u8, clen: *i64, st: *i64, loaded: *i64) -> i64 {
184 let mem: *u8 = sys_mmap(IMG_MAGIC_65536)
185 var k: i64=0
186 while k<IMG_MAGIC_65536 { mem[k]=0 as u8; k=k+1 }
187 var j: i64=0
188 while j<512 { mem[0x7C00+j]=img[j]; j=j+1 }
189 loaded[0] = mem[0x8000] as i64
190 clen[0]=0
191 let rc: i64 = emu_x86_boot_disk(mem, img, 0x7C00, console, clen, st)
192 loaded[1] = mem[0x8000] as i64
193 return rc
194}
195
196func ul_read(path: *u8, out: *u8, cap: i64) -> i64 {
197 let fd: i64 = sys_openat_rd(path)
198 if fd < 0 { return 0 - 1 }
199 var n: i64 = 0; var go: i64 = 1
200 while go==1 {
201 let rr: i64 = sys_read(fd, ((out as i64)+n) as *u8, cap-n)
202 if rr<=0 { go=0 } else { n=n+rr }
203 if n>=cap { go=0 }
204 }
205 sys_close(fd)
206 return n
207}
208
209func main() -> i64 {
210 ul_puts("x86 ladder R9-HANDOFF: the disk-loaded kernel boots real -> protected -> LONG mode\n" as *u8)
211
212 let img: *u8 = sys_mmap(IMG_SZ + 16)
213 build_image(img)
214
215 let fd: i64 = sys_openat_wr("knowledge/status/nishi_os_lm.img\x00" as *u8, 0x1a4)
216 if fd<=0 { ul_puts("R9-HANDOFF RED: cannot write image\n" as *u8); sys_exit(1); return 1 }
217 sys_write(fd, img, IMG_SZ)
218 sys_close(fd)
219 ul_puts(" wrote knowledge/status/nishi_os_lm.img (" as *u8); ul_num(IMG_SZ); ul_puts(" bytes, 2 sectors)\n" as *u8)
220
221 let rd: *u8 = sys_mmap(IMG_SZ + 16)
222 let rn: i64 = ul_read("knowledge/status/nishi_os_lm.img\x00" as *u8, rd, IMG_SZ)
223
224 // GOOD boot (executes the ON-DISK bytes).
225 let con: *u8 = sys_mmap(256)
226 let clen: *i64 = sys_mmap(8) as *i64
227 let st: *i64 = sys_mmap(64) as *i64
228 let ld: *i64 = sys_mmap(64) as *i64
229 let rc: i64 = boot_image(rd, con, clen, st, ld)
230 ul_puts(" MBR loaded kernel (0x8000 " as *u8); ul_num(ld[0]); ul_puts("->" as *u8); ul_num(ld[1]); ul_puts("), kernel ran -> mode=" as *u8); ul_num(st[1]); ul_puts(" (0=real 1=protected 2=long) rc=" as *u8); ul_num(rc); ul_puts("\n" as *u8)
231 ul_puts(" SOVEREIGN-EMU console: " as *u8); sys_write(1, con, clen[0]); ul_puts(" [" as *u8); ul_num(clen[0]); ul_puts(" chars]\n" as *u8)
232
233 // NEG CONTROL: MBR loads ZERO sectors (AL=00) -> kernel never loaded/run -> never reaches long mode.
234 let bad: *u8 = sys_mmap(IMG_SZ + 16)
235 var c: i64=0
236 while c<IMG_SZ { bad[c]=rd[c]; c=c+1 }
237 bad[1]=0x00 as u8 // mov ax,0x0200 -> read 0 sectors
238 let con2: *u8 = sys_mmap(256)
239 let clen2: *i64 = sys_mmap(8) as *i64
240 let st2: *i64 = sys_mmap(64) as *i64
241 let ld2: *i64 = sys_mmap(64) as *i64
242 let rc2: i64 = boot_image(bad, con2, clen2, st2, ld2)
243 ul_puts(" NEG (MBR loads 0 sectors): kernel-slot 0x8000 after=" as *u8); ul_num(ld2[1]); ul_puts(" mode=" as *u8); ul_num(st2[1]); ul_puts(" rc=" as *u8); ul_num(rc2); ul_puts("\n" as *u8)
244
245 let banner: *u8 = "NishiOS" as *u8
246 var pass: i64=0
247 var ttl: i64=0
248 ttl=ttl+1; ul_puts(" T1 persisted byte-faithfully + 0x55AA signature: " as *u8); if rn==IMG_SZ { if ul_beq(rd, img, IMG_SZ)==1 { if rd[510]==(0x55 as u8) { if rd[511]==(0xAA as u8) { pass=pass+1; ul_puts("PASS\n" as *u8) } else { ul_puts("FAIL\n" as *u8) } } else { ul_puts("FAIL\n" as *u8) } } else { ul_puts("FAIL\n" as *u8) } } else { ul_puts("FAIL\n" as *u8) }
249 ttl=ttl+1; ul_puts(" T2 INT 13h loaded the kernel off disk (0x8000: 0->0xBE): " as *u8); if ld[0]==0 { if ld[1]==0xBE { pass=pass+1; ul_puts("PASS\n" as *u8) } else { ul_puts("FAIL\n" as *u8) } } else { ul_puts("FAIL\n" as *u8) }
250 ttl=ttl+1; ul_puts(" T3 console banner == 'NishiOS': " as *u8); if ul_contains(con, clen[0], banner, 7)==1 { pass=pass+1; ul_puts("PASS\n" as *u8) } else { ul_puts("FAIL\n" as *u8) }
251 ttl=ttl+1; ul_puts(" T4 the LOADED kernel reached LONG mode (mode==2): " as *u8); if st[1]==2 { pass=pass+1; ul_puts("PASS\n" as *u8) } else { ul_puts("FAIL\n" as *u8) }
252 ttl=ttl+1; ul_puts(" T5 clean HLT (rc=0): " as *u8); if rc==0 { pass=pass+1; ul_puts("PASS\n" as *u8) } else { ul_puts("FAIL\n" as *u8) }
253 ttl=ttl+1; ul_puts(" T6 NEG: 0-sector load never reaches long mode + no banner (liar-kill): " as *u8); if st2[1]!=2 { if ul_contains(con2, clen2[0], banner, 7)==0 { pass=pass+1; ul_puts("PASS\n" as *u8) } else { ul_puts("FAIL\n" as *u8) } } else { ul_puts("FAIL\n" as *u8) }
254
255 ul_puts("X86-USB-LONGMODE-GATE passed " as *u8); ul_num(pass); ul_puts("/" as *u8); ul_num(ttl)
256 if pass==ttl { ul_puts(" verdict=GREEN (a disk-loaded kernel crosses real->protected->LONG mode by execution; loading the real 64-bit desktop kernel = next rung)\n" as *u8); sys_exit(0); return 0 }
257 ul_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
258}