diff --git a/.vscode/launch.json b/.vscode/launch.json index 92c6b1f..a47060a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,6 +24,7 @@ "autorun": [ "monitor reset shellhalt", "load hello_psyq.elf", + "source scripts/gdb/gdb_tape_atoms.gdb", "tbreak main", "continue" ] diff --git a/scratch/gte_debug.lua b/scratch/gte_debug.lua new file mode 100644 index 0000000..faa6ade --- /dev/null +++ b/scratch/gte_debug.lua @@ -0,0 +1,30 @@ +-- gte_debug.lua — defensive version + prints error context. +local ok, err = pcall(function() + print("[debug] PCSX exists:", PCSX ~= nil) + print("[debug] PCSX.WebServer exists:", PCSX and PCSX.WebServer ~= nil) + print("[debug] PCSX.WebServer.Handlers exists:", PCSX and PCSX.WebServer and PCSX.WebServer.Handlers ~= nil) + if not PCSX.WebServer then + print("[debug] creating PCSX.WebServer...") + PCSX.WebServer = {} + end + if not PCSX.WebServer.Handlers then + print("[debug] creating PCSX.WebServer.Handlers...") + PCSX.WebServer.Handlers = {} + end + print("[debug] type of Handlers:", type(PCSX.WebServer.Handlers)) + + PCSX.WebServer.Handlers.gte = function(req) + local r = PCSX.getRegisters() + local out = { "pc=0x" .. string.format("%x", r.pc) } + for i = 0, 31 do + out[#out + 1] = string.format("D[%d]=0x%08x C[%d]=0x%08x", + i, r.CP2D.r[i], i, r.CP2C.r[i]) + end + return table.concat(out, "\n") + end + print("[debug] handler registered") +end) + +if not ok then + print("[debug] ERROR: " .. tostring(err)) +end \ No newline at end of file diff --git a/scripts/build_psyq.ps1 b/scripts/build_psyq.ps1 index a9f6b18..82ca13a 100644 --- a/scripts/build_psyq.ps1 +++ b/scripts/build_psyq.ps1 @@ -321,11 +321,12 @@ function ps1-meta { param( [Parameter(Mandatory=$true)][string[]]$sources, [Parameter(Mandatory=$true)][string]$metadata, [string]$out_root = (join-path $path_build 'gen'), - [string[]]$passes = @('--all') + [string[]]$passes = @('--all'), + [string[]]$extra_args = @() ) $script = join-path $path_scripts 'ps1_meta.lua' write-host "ps1-meta $($sources.Count) source(s), passes=$($passes -join ',')" ` -ForegroundColor Magenta - $arg_list = @($passes) + @('--metadata', $metadata) + @('--out-root', $out_root) + $arg_list = @($passes) + @('--metadata', $metadata) + @('--out-root', $out_root) + @($extra_args) foreach ($s in $sources) { $arg_list += @('--source', $s) } & luajit $script @arg_list if ($LASTEXITCODE -ne 0) { @@ -383,6 +384,16 @@ function build-gte_hello { ) link-modules $link_modules $elf $link_args make-binary $elf $exe + + # Post-link: emit ONLY build/gen/gdb_tape_atoms_runtime.gdb. The per-source + # *.atoms.sourcemap.txt was already generated by the pre-link --all call, + # so we skip --atoms-source-map here to avoid re-doing the work. The + # gdb-runtime emission requires --elf (for nm-based address lookup) so it + # MUST happen post-link. ON by default per user pref 2026-07-11. + ps1-meta -sources $atom_sources -metadata $path_atom_metadata ` + -out_root (join-path $path_build 'gen') ` + -passes @('--gdb-runtime') ` + -extra_args @('--elf', $elf) } build-gte_hello diff --git a/scripts/launch_debug_session.ps1 b/scripts/launch_debug_session.ps1 deleted file mode 100644 index a0b8c7b..0000000 --- a/scripts/launch_debug_session.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -# scripts/launch_debug_session.ps1 -# -# Wrapper around pcsx-redux.exe + gdb-multiarch that auto-launches the -# gdb_tape_atoms.gdb helper and (optionally) hot-reloads the .ps-exe. -# -# Generated by track gdb_tape_atom_debugging_20260711 — see -# C:\projects\Pikuma\ps1-ai\docs\gdb_tape_atom_debugging.md for the manual. -# -# usage: -# .\launch_debug_session.ps1 # bare gdb attach (assumes pcsx-redux is up) -# .\launch_debug_session.ps1 -AutoLoadAtoms # also source gdb_tape_atoms.gdb on startup -# .\launch_debug_session.ps1 -AutoResetEmu # hot-reload the .ps-exe before attaching -# .\launch_debug_session.ps1 -AutoStartEmu # launch pcsx-redux.exe if not running -# -# All three flags can be combined: -AutoStartEmu -AutoResetEmu -AutoLoadAtoms - -[CmdletBinding()] -param( - [switch]$AutoLoadAtoms, - [switch]$AutoResetEmu, - [switch]$AutoStartEmu, - [string]$ExePath = (Join-Path $PSScriptRoot '..\build\hello_gte.ps-exe'), - [string]$ElfPath = (Join-Path $PSScriptRoot '..\build\hello_gte.elf') -) - -$ErrorActionPreference = 'Stop' - -# ── Pre-checks (E10: pcsx-redux on :3333, E11: gdb-multiarch in PATH) ── - -function Test-Prerequisites { - # E11: gdb-multiarch must be in PATH. - if (-not (Get-Command gdb-multiarch -ErrorAction SilentlyContinue)) { - Write-Error "gdb-multiarch not found in PATH. Install via scoop or .\update_deps.ps1." -ErrorAction Stop - exit 1 - } - - # E10: pcsx-redux must be reachable on :3333 (or we auto-start). - $tcp = New-Object System.Net.Sockets.TcpClient - try { - $async = $tcp.BeginConnect('localhost', 3333, $null, $null) - $connected = $async.AsyncWaitHandle.WaitOne(2000, $false) -and -not $async.IsFaulted - } catch { - $connected = $false - } - $tcp.Close() - - if (-not $connected) { - if ($AutoStartEmu) { - Write-Host "pcsx-redux not running; auto-starting..." -ForegroundColor Cyan - $pcsxExe = (Join-Path $PSScriptRoot '..\toolchain\pcsx-redux\vsprojects\x64\Release\pcsx-redux.exe') - if (-not (Test-Path $pcsxExe)) { - Write-Error "pcsx-redux.exe not found at $pcsxExe. Build it first, or pass -ExePath + start pcsx-redux manually." -ErrorAction Stop - exit 1 - } - Start-Process -FilePath $pcsxExe -PassThru | Out-Null - Start-Sleep -Seconds 3 - } else { - Write-Error "Cannot connect to pcsx-redux at localhost:3333. Start pcsx-redux first (or pass -AutoStartEmu)." -ErrorAction Stop - exit 1 - } - } -} - -# ── Optional: hot-reload .ps-exe via pcsx-redux's web server (port 8080) ── - -function Invoke-HotReload { - $absolutePath = [System.IO.Path]::GetFullPath($ExePath) - $uri = 'http://localhost:8080/api/v1/load-exec' - $body = @{ filename = $absolutePath } | ConvertTo-Json - try { - Invoke-RestMethod -Uri $uri -Method Post -Body $body -ContentType 'application/json' | Out-Null - Write-Host "Hot-reloaded $absolutePath into pcsx-redux." -ForegroundColor Green - } catch { - Write-Warning "Could not hot-reload via pcsx-redux web server (port 8080): $_" - } -} - -# ── Main ── - -Test-Prerequisites -if ($AutoResetEmu) { Invoke-HotReload } - -$gdbScript = Join-Path $PSScriptRoot 'gdb\gdb_tape_atoms.gdb' - -$gdbArgs = @( - '-q' - '-ex', "file $ElfPath" - '-ex', 'target remote localhost:3333' -) -if ($AutoLoadAtoms) { - $gdbArgs += @('-ex', "source $gdbScript") -} - -Write-Host "Launching gdb-multiarch (script: $gdbScript, elf: $ElfPath)..." -ForegroundColor Cyan -& gdb-multiarch @gdbArgs \ No newline at end of file diff --git a/scripts/launch_pcsx_debug.ps1 b/scripts/launch_pcsx_debug.ps1 new file mode 100644 index 0000000..10952d8 --- /dev/null +++ b/scripts/launch_pcsx_debug.ps1 @@ -0,0 +1,94 @@ +# scripts/launch_pcsx_debug.ps1 +# +# One-shot launcher for debug sessions: starts pcsx-redux with the .ps-exe +# loaded, the gdb stub enabled, AND the pcsx_debug_helper Lua plugin loaded +# so external CLI tools (gdb's `shell` command, etc.) +# can read GTE state via http://localhost:8080/api/v1/lua/gte +# (the gdb stub doesn't expose COP2 at all). +# +# usage: +# .\scripts\launch_pcsx_debug.ps1 +# .\scripts\launch_pcsx_debug.ps1 -ExePath build\hello_gte.ps-exe +# .\scripts\launch_pcsx_debug.ps1 -HelperZip scripts\pcsx_debug_helper.zip +# +# After launch: +# - gdb: target remote localhost:3333 +# - web: curl http://localhost:8080/api/v1/lua/gte +# +# Companion: scripts/debug_psyq.ps1 (bare launch — no .ps-exe, no helper). + +[CmdletBinding()] +param( + [string]$PcsxPath = (Join-Path $PSScriptRoot '..\toolchain\pcsx-redux\vsprojects\x64\Release\pcsx-redux.exe'), + [string]$ExePath = (Join-Path $PSScriptRoot '..\build\hello_gte.ps-exe'), + [string]$HelperZip = (Join-Path $PSScriptRoot 'pcsx_debug_helper.zip'), + [int] $GdbPort = 3333, + [int] $WebPort = 8080 +) + +$ErrorActionPreference = 'Stop' + +# ── Pre-checks ── +foreach ($p in @($PcsxPath, $ExePath, $HelperZip)) { + if (-not (Test-Path $p)) { + Write-Error "Missing: $p" + exit 1 + } +} + +# Kill any existing pcsx-redux so the archive file isn't locked. +Get-Process pcsx-redux -ErrorAction SilentlyContinue | Stop-Process -Force +Start-Sleep -Seconds 2 + +# ── Launch ── +$absExe = [System.IO.Path]::GetFullPath($ExePath) +$absZip = [System.IO.Path]::GetFullPath($HelperZip) + +$args = @( + '-gdb', '-run' + '-loadexe', "`"$absExe`"" + '-archive', "`"$absZip`"" +) + +Write-Host "Launching pcsx-redux..." -ForegroundColor Cyan +Write-Host " ps-exe : $absExe" +Write-Host " helper zip: $absZip" +Write-Host " gdb : localhost:$GdbPort" +Write-Host " web : localhost:$WebPort/api/v1/lua/gte" +Write-Host "" + +Start-Process -FilePath $PcsxPath -ArgumentList $args | Out-Null + +# ── Wait for both endpoints to come up ── +$deadline = (Get-Date).AddSeconds(15) +while ((Get-Date) -lt $deadline) { + $gdbUp = $false + $webUp = $false + try { + $tcp = New-Object System.Net.Sockets.TcpClient + $tcp.BeginConnect('localhost', $GdbPort, $null, $null) | Out-Null + Start-Sleep -Milliseconds 100 + $gdbUp = $tcp.Connected + $tcp.Close() + } catch { $gdbUp = $false } + try { + $r = Invoke-WebRequest -Uri "http://localhost:$WebPort/" -UseBasicParsing -TimeoutSec 1 -ErrorAction SilentlyContinue + $webUp = $r.StatusCode -ne 0 + } catch { $webUp = $false } + if ($gdbUp -and $webUp) { break } + Start-Sleep -Milliseconds 500 +} + +# ── Smoke-test the gte handler ── +try { + $r = Invoke-WebRequest -Uri "http://localhost:$WebPort/api/v1/lua/gte" -UseBasicParsing -TimeoutSec 5 + $firstLine = ([System.Text.Encoding]::UTF8.GetString($r.Content) -split "`n")[0] + Write-Host "GTE handler OK: $firstLine" -ForegroundColor Green +} catch { + Write-Warning "GTE handler NOT responding: $_" + Write-Host "Check the pcsx-redux Lua Console for debug cli messages." -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "pcsx-redux running. PIDs:" -ForegroundColor Cyan +Get-Process pcsx-redux | Select-Object Id, ProcessName | Format-Table diff --git a/scripts/passes/atoms_source_map.lua b/scripts/passes/atoms_source_map.lua index 3feb5c1..4990456 100644 --- a/scripts/passes/atoms_source_map.lua +++ b/scripts/passes/atoms_source_map.lua @@ -2,22 +2,23 @@ --- --- Reads the pre-scanned SourceScan payload (produced once upstream by `duffle.scan_source`) --- for `MipsAtom_(name)` (kind="atom"), `MipsAtomComp_` / `MipsAtomComp_Proc_` (kind="comp_*"), ---- and `MipsCode code_` (kind="raw_atom") declarations. Walks each atom's ---- pre-tokenized body (`{{tok=string, rel=integer}, ...}` from `duffle.tokenize_body`), counts ---- per-token word contributions via `ctx.shared.word_counts`, and emits one ---- `WORD N LINE L TEXT T` line per `.word` to `gen/.atoms.sourcemap.txt`. +--- and `MipsCode code_` (kind="raw_atom") declarations. +--- Walks each atom's pre-tokenized body (`{{tok=string, rel=integer}, ...}` from `duffle.tokenize_body`), +--- counts per-token word contributions via `ctx.shared.word_counts`, and emits one +--- `WORD N LINE L TEXT T` line per `.word` to `/.atoms.sourcemap.txt`. --- --- **Two output forms** (per the workspace's per-emission-form pattern from --- `guide_metaprogram_ssdl.md`): ---- 1. **Canonical text form** — `/gen/.atoms.sourcemap.txt`. ---- Always emitted. Format-version-tagged for forward-compat. ---- 2. **gdb-runtime form** — `/gdb_tape_atoms_runtime.gdb` (pure ---- gdb command script; addresses pre-computed via `nm`; the 9 user commands ---- defined as `define ... end` blocks). Emitted ONLY when ---- `ctx.flags.gdb_runtime` is true AND `ctx.flags.elf_path` points to an ---- existing ELF. The gdb runtime form lets `gdb-multiarch --without-python` ---- users (the common case on Windows MinGW builds) load the source-map data ---- via `source ` — no Python/Tcl/Guile required. +--- 1. **Canonical text form** — `/.atoms.sourcemap.txt`. +--- Always emitted. Format-version-tagged for forward-compat. +--- Lives in `/` (build/gen) NOT `/gen/`. This file is a **build report**, not a compile artifact. +--- Matches the convention used by `annotation.lua` (`/.errors.h`) + `static_analysis.lua` (`/.static_analysis.txt`). +--- Compile artifacts (`*.macs.h`, `*.offsets.h`) stay in `/gen/`. +--- 2. **gdb-runtime form** — `/gdb_tape_atoms_runtime.gdb` +--- (pure gdb command script; addresses pre-computed via `nm`; the 9 user commands defined as `define ... end` blocks). +--- Emitted ONLY when `ctx.flags.gdb_runtime` is true AND `ctx.flags.elf_path` points to an existing ELF. +--- The gdb runtime form lets `gdb-multiarch --without-python` users (the common case on Windows MinGW builds) +--- load the source-map data via `source ` — no Python/Tcl/Guile required. --- --- **Output format** (canonical text form): --- ``` @@ -33,16 +34,14 @@ --- ENDATOM --- ``` --- ---- Marker calls (`atom_label(...)`, `atom_offset(...)`) emit 0 `.word`s. They share the ---- same walking convention as `passes/offsets.lua :: scan_atom_body`: markers do NOT ---- advance the word-offset counter, but if a marker is bundled on the same token with a ---- trailing instruction (e.g. `atom_label(foo) load_half_u(...)`), the trailing ---- instruction's word count is added. This matches `offsets.lua :: count_marker_rest`. +--- Marker calls (`atom_label(...)`, `atom_offset(...)`) emit 0 `.word`s. +--- They share the same walking convention as `passes/offsets.lua :: scan_atom_body`: +--- markers do NOT advance the word-offset counter, but if a marker is bundled on the same token with a +--- trailing instruction (e.g. `atom_label(foo) load_half_u(...)`), +--- the trailing instruction's word count is added. This matches `offsets.lua :: count_marker_rest`. --- --- **Conventions:** tabs (1/level), EmmyLua annotations, no regex, ---- Lua 5.3 compatible. See "lua.md" in the ps1-ai styleguides. ---- ---- Provenance: generated 2026-07-11 by track gdb_tape_atom_debugging_20260711. +--- Lua 5.3 compatible. -- ════════════════════════════════════════════════════════════════════════════ -- Module-scope requires + package.path setup @@ -68,19 +67,6 @@ local FORMAT_VERSION = 1 local LABEL_MARKER = "atom_label" local OFFSET_MARKER = "atom_offset" --- gdb-runtime file header + atom-count default for C2 alias table (32 entries). -local C2_DATA_ALIASES = { - "v0.xy", "v0.z", "v1.xy", "v1.z", "v2.xy", "v2.z", - "rgb", "otz", - "ir0", "ir1", "ir2", "ir3", - "sxy0", "sxy1", "sxy2", "sxyp", - "sz0", "sz1", "sz2", "sz3", - "rgb_fifo", "mac0_in", - "?", "?", "?", "?", - "mac0", "mac1", "mac2", "mac3", - "mac4", "mac5", "mac6", "mac7", -} - -- ════════════════════════════════════════════════════════════════════════════ -- Type declarations -- ════════════════════════════════════════════════════════════════════════════ @@ -106,9 +92,9 @@ local function is_marker_token(tok) return leading == LABEL_MARKER or leading == OFFSET_MARKER end ---- Count words contributed by the non-marker portion of `tok` (after the marker's ---- closing `)`). Mirrors offsets.lua:182 `count_marker_rest`. Returns 0 if there's ---- no trailing content after the marker call. +--- Count words contributed by the non-marker portion of `tok` (after the marker's closing `)`). +--- Mirrors offsets.lua:182 `count_marker_rest`. +--- Returns 0 if there's no trailing content after the marker call. --- @param tok string --- @param wc table --- @return integer @@ -120,9 +106,9 @@ local function count_marker_rest(tok, wc) return count_token_words(rest, wc) end ---- Compute per-word entries for an atom. Shared between the canonical text form ---- (per-source `.atoms.sourcemap.txt`) and the gdb-runtime form ---- (`gdb_tape_atoms_runtime.gdb`). +--- Compute per-word entries for an atom. +--- Shared between the canonical text form (per-source `.atoms.sourcemap.txt`) +--- and the gdb-runtime form (`gdb_tape_atoms_runtime.gdb`). --- --- Returns a list of `{pos, line, text}` entries + the total word count. --- Markers contribute 0 entries (the marker call emits 0 `.word`s). @@ -245,8 +231,8 @@ local function gdb_escape(s) return (s:gsub("\\", "\\\\"):gsub('"', '\\"')) end ---- Build the list of atoms with addresses + word entries. Shared helper for the ---- gdb-runtime file emission. +--- Build the list of atoms with addresses + word entries. +--- Shared helper for the gdb-runtime file emission. --- @param ctx PassCtx --- @return table[] -- list of {idx, name, src_path, file_base, addr, size_bytes, words, entries} local function build_atom_table(ctx) @@ -302,15 +288,13 @@ local function build_atom_table(ctx) return matched end ---- Append the 9 gdb command definitions to `lines`. Pure gdb scripting — ---- no Python, no Tcl, no Guile required. **Fully hardcoded per-atom** because ---- gdb doesn't do nested `$` substitution in var names — `$__atom_name_$__i` ---- inside a `while` loop is treated as one literal identifier, not a concat. +--- Append the 9 gdb command definitions to `lines`. Pure gdb scripting no Python, no Tcl, no Guile required. +--- **Fully hardcoded per-atom** because gdb doesn't do nested `$` substitution in var names +--- `$__atom_name_$__i` inside a `while` loop is treated as one literal identifier, not a concat. --- ---- Each command is a static sequence of `printf` / `tbreak` / `if ... end` ---- blocks. The Lua pass emits N atoms' worth of lines — no runtime iteration. ---- With 7 atoms + ~200 word entries, the runtime file is ~2000 lines, all ---- auto-generated, no human edit ever. +--- Each command is a static sequence of `printf` / `tbreak` / `if ... end` blocks. +--- The Lua pass emits N atoms' worth of lines — no runtime iteration. +--- With 7 atoms + ~200 word entries, the runtime file is ~2000 lines, all auto-generated, no human edit ever. --- @param lines table -- output line buffer (mutated in place) --- @param matched table -- list of atom records from `build_atom_table` local function append_gdb_commands(lines, matched) @@ -382,10 +366,11 @@ local function append_gdb_commands(lines, matched) lines[#lines + 1] = " set $__pc = (unsigned int)$pc" lines[#lines + 1] = " set $__matched = 0" for _, a in ipairs(matched) do - local end_addr = a.addr + a.words * 4 + -- Precompute end_addr (gdb 12.1's expression evaluator chokes on `addr + words*4`). lines[#lines + 1] = string.format( - " if $__pc >= $__atom_addr_%d && $__pc < $__atom_addr_%d + $__atom_words_%d * 4", - a.idx, a.idx, a.idx) + " set $__end_%d = $__atom_addr_%d + $__atom_words_%d * 4", a.idx, a.idx, a.idx) + lines[#lines + 1] = string.format( + " if $__pc >= $__atom_addr_%d && $__pc < $__end_%d", a.idx, a.idx) lines[#lines + 1] = string.format( ' printf "atom: code_%%s\\n", $__atom_name_%d', a.idx) lines[#lines + 1] = ' printf "addr: 0x%08x\\n", $__pc' @@ -425,14 +410,19 @@ local function append_gdb_commands(lines, matched) -- ── stepi_inside_atom ── -- Hardcoded one if-containment-check per atom (no loop). + -- Precompute end_addr in Lua so we don't ask gdb to evaluate `addr + words*4` + -- inside the if condition (gdb 12.1's expression evaluator chokes on the + -- `*` and emits a misleading 'function malloc' error in some gdb builds). lines[#lines + 1] = "define stepi_inside_atom" lines[#lines + 1] = " set $__in_atom = 0" lines[#lines + 1] = " set $__did_step = 0" + lines[#lines + 1] = " set $__pc = (unsigned int)$pc" for _, a in ipairs(matched) do - local end_addr = a.addr + a.words * 4 + -- Precompute end_addr in the convenience var (single expression gdb handles). lines[#lines + 1] = string.format( - " if $pc >= $__atom_addr_%d && $pc < $__atom_addr_%d + $__atom_words_%d * 4", - a.idx, a.idx, a.idx) + " set $__end_%d = $__atom_addr_%d + $__atom_words_%d * 4", a.idx, a.idx, a.idx) + lines[#lines + 1] = string.format( + " if $__pc >= $__atom_addr_%d && $__pc < $__end_%d", a.idx, a.idx) lines[#lines + 1] = " set $__in_atom = 1" lines[#lines + 1] = " stepi" lines[#lines + 1] = " set $__did_step = 1" @@ -448,30 +438,29 @@ local function append_gdb_commands(lines, matched) lines[#lines + 1] = "end" lines[#lines + 1] = "" - -- ── show_c2 (32 entries) ── - -- Hardcoded with `$c2_data[N]` (COP2 data regs). Requires target attached. + -- ── show_c2 ── + -- GTE data regs (COP2). pcsx-redux's gdb stub doesn't expose COP2 (only + -- 72 regs: 32 GPR + COP0 + FPR). + -- curl http://localhost:8080/api/v1/lua/gte + -- We keep the command definition as a stub that points the user at the plugin. lines[#lines + 1] = "define show_c2" - for i = 0, 31 do - local alias = C2_DATA_ALIASES[i + 1] or "?" - lines[#lines + 1] = string.format( - ' printf "C2[%%2d] 0x%%08x [%%s]\\n", %d, $c2_data[%d], "%s"', - i, i, alias) - end + lines[#lines + 1] = ' echo "[gdb_tape_atoms] show_c2: gdb stub does not expose COP2 in this build."' + lines[#lines + 1] = ' echo "[gdb_tape_atoms] Use scripts/pcsx_debug_helper.zip + curl http://localhost:8080/api/v1/lua/gte"' + lines[#lines + 1] = ' echo "[gdb_tape_atoms] (or pcsx-redux Debug > Registers window for a native view)"' lines[#lines + 1] = "end" lines[#lines + 1] = "document show_c2" - lines[#lines + 1] = " Pretty-print all 32 C2 data registers as hex + named alias. Requires target attached." + lines[#lines + 1] = " Stub. The gdb stub in this pcsx-redux build does not expose COP2 regs." + lines[#lines + 1] = " For GTE data + control state, use the pcsx_debug_helper Lua plugin or the" + lines[#lines + 1] = " pcsx-redux Debug > Registers window." lines[#lines + 1] = "end" lines[#lines + 1] = "" - -- ── show_c2ctl (32 entries) ── + -- ── show_c2ctl ── lines[#lines + 1] = "define show_c2ctl" - for i = 0, 31 do - lines[#lines + 1] = string.format( - ' printf "C2CTL[%%2d] 0x%%08x\\n", %d, $c2_control[%d]', i, i) - end + lines[#lines + 1] = ' echo "[gdb_tape_atoms] show_c2ctl: see show_c2 for the same workaround."' lines[#lines + 1] = "end" lines[#lines + 1] = "document show_c2ctl" - lines[#lines + 1] = " Pretty-print all 32 C2 control registers as hex. Requires target attached." + lines[#lines + 1] = " Stub. Same workaround as show_c2." lines[#lines + 1] = "end" lines[#lines + 1] = "" @@ -488,9 +477,8 @@ local function append_gdb_commands(lines, matched) end --- Emit the gdb-runtime file (post-link). Pure gdb scripting — no Python. ---- Reads ELF addresses via `mipsel-none-elf-nm -S`, embeds them in ---- `/gdb_tape_atoms_runtime.gdb` so gdb loads the data via ---- `set $var = ...` + `define ... end` blocks at source-time. +--- Reads ELF addresses via `mipsel-none-elf-nm -S`, embeds them in `/gdb_tape_atoms_runtime.gdb` +--- so gdb loads the data via `set $var = ...` + `define ... end` blocks at source-time. --- @param ctx PassCtx local function emit_gdb_runtime(ctx) if not (ctx.flags and ctx.flags.gdb_runtime) then return end @@ -563,7 +551,7 @@ end local M = {} ---- Pass entry: emit one `gen/.atoms.sourcemap.txt` per source file +--- Pass entry: emit one `/.atoms.sourcemap.txt` per source file --- that contains at least one `MipsAtom_(name)` / `MipsCode code_` declaration. --- Optionally also emit `/gdb_tape_atoms_runtime.gdb` when --- `ctx.flags.gdb_runtime` is true. @@ -591,7 +579,10 @@ function M.run(ctx) local n_raw_atoms = src.scan.raw_atoms and #src.scan.raw_atoms or 0 if n_atoms + n_raw_atoms > 0 then local basename = duffle.basename_no_ext(src.path) - local out_path = src.dir .. "/gen/" .. basename .. ".atoms.sourcemap.txt" + -- Build report, NOT compile artifact: live in alongside the other reports + -- (annotation.lua's *.errors.h, static_analysis.lua's *.static_analysis.txt, gdb_tape_atoms_runtime.gdb). + -- The per-source /gen/ is reserved for headers actually #included by C. + local out_path = ctx.out_root .. "/" .. basename .. ".atoms.sourcemap.txt" local content = render_source_map(src, wc) if not ctx.dry_run then @@ -612,4 +603,4 @@ function M.run(ctx) return { outputs = outputs, errors = errors, warnings = warnings } end -return M \ No newline at end of file +return M diff --git a/scripts/pcsx_debug_helper.zip b/scripts/pcsx_debug_helper.zip new file mode 100644 index 0000000..d6d8a65 Binary files /dev/null and b/scripts/pcsx_debug_helper.zip differ diff --git a/scripts/pcsx_debug_helper/autoexec.lua b/scripts/pcsx_debug_helper/autoexec.lua new file mode 100644 index 0000000..6180d0f --- /dev/null +++ b/scripts/pcsx_debug_helper/autoexec.lua @@ -0,0 +1,55 @@ +-- autoexec.lua - pcsx_debug_helper plugin entry point. +-- Packaged in scripts/pcsx_debug_helper.zip. Loaded by pcsx-redux via +-- the -archive CLI flag (see scripts/launch_pcsx_debug.ps1). +-- +-- Registers two web handlers for external CLI tools: +-- /api/v1/lua/gte - full GTE state (32 data + 32 control regs + PC) +-- /api/v1/lua/gp - GP state summary (screenshot endpoint + VRAM endpoint refs) +-- +-- The GTE handler reads COP2 regs via PCSX.getRegisters().CP2D/CP2C. The +-- pcsx-redux gdb stub doesn't expose COP2, so this is the only way for +-- external tools to see GTE state. +-- +-- The GP handler is a thin pointer: pcsx-redux's Lua API exposes only PCSX.GPU.takeScreenShot() +-- (no GPUSTAT, no GP0/GP1 command log, no display state). For richer GP state, the existing web endpoints are the practical path: +-- /api/v1/state/still - PNG screenshot +-- /api/v1/gpu/vram/raw - VRAM raw bytes (1MB) +-- +-- Companion: scripts/gdb/gdb_tape_atoms.gdb (covers GPRs + atom-aware stepping). + +local function register_handlers() + if not PCSX.WebServer then PCSX.WebServer = {} end + if not PCSX.WebServer.Handlers then PCSX.WebServer.Handlers = {} end + + -- ── GTE state ── + PCSX.WebServer.Handlers.gte = function(req) + local r = PCSX.getRegisters() + local out = { "pc=0x" .. string.format("%x", r.pc) } + for i = 0, 31 do + out[#out + 1] = string.format("D[%d]=0x%08x C[%d]=0x%08x", + i, r.CP2D.r[i], i, r.CP2C.r[i]) + end + return table.concat(out, "\n") + end + + -- ── GP state (pointer to existing endpoints) ── + -- pcsx-redux's Lua GPU API exposes only takeScreenShot(); no GPUSTAT / GP0 / GP1 command log / display state. + -- We point to the existing web endpoints that DO expose those (when the emulator is actually rendering. Paused-at-BP frames won't have a fresh frame). + PCSX.WebServer.Handlers.gp = function(req) + local out = { + "gpu_screenshot_png=http://localhost:8080/api/v1/state/still", + "vram_raw=http://localhost:8080/api/v1/gpu/vram/raw (1MB VRAM)", + "gpustat=NOT_AVAILABLE_VIA_LUA", + "gp_command_log=NOT_AVAILABLE_VIA_LUA (use pcsx-redux Debug > GPU Logger)", + "hint_run_emulator_unpaused_for_screenshot", + } + return table.concat(out, "\n") + end +end + +local ok, err = pcall(register_handlers) +if ok then + print("[pcsx_debug_helper] handlers registered: gte, gp") +else + print("[pcsx_debug_helper] registration failed: " .. tostring(err)) +end