mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-14 13:21:26 -07:00
doing dwarf injectiion/mods for the tape atoms. syncs with vscode cursor.
This commit is contained in:
Vendored
+26
@@ -76,6 +76,32 @@
|
||||
"tbreak main",
|
||||
"continue"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Debug: Hello GTE Psy-Q! (atoms debug — DWARF-injected)",
|
||||
"type": "gdb",
|
||||
"request": "attach",
|
||||
"target": "localhost:3333",
|
||||
"remote": true,
|
||||
"cwd": "${workspaceRoot}",
|
||||
"valuesFormatting": "parseText",
|
||||
"stopAtConnect": true,
|
||||
"gdbpath": "gdb-multiarch",
|
||||
"windows": {
|
||||
"gdbpath": "gdb-multiarch.exe"
|
||||
},
|
||||
"osx": {
|
||||
"gdbpath": "gdb"
|
||||
},
|
||||
"executable": "${workspaceRoot}/build/hello_gte.dwarf-injected.elf",
|
||||
"autorun": [
|
||||
"monitor reset shellhalt",
|
||||
"load build/hello_gte.dwarf-injected.elf",
|
||||
"source scripts/gdb/gdb_tape_atoms.gdb",
|
||||
"tbreak main",
|
||||
"continue"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ internal MipsAtom_(rbind_cube_g4_face) atom_info(atom_bind(Binds_CubeTri)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
){
|
||||
/* Pop 4 arguments from the tape directly into the workspace registers */
|
||||
load_word(R_PrimCursor, R_TapePtr, O_(Binds_CubeTri,PrimCursor)),
|
||||
load_word(R_FaceCursor, R_TapePtr, O_(Binds_CubeTri,FaceCursor)),
|
||||
load_word(R_VertBase, R_TapePtr, O_(Binds_CubeTri,VertBase)),
|
||||
load_word(R_OtBase, R_TapePtr, O_(Binds_CubeTri,OtBase)),
|
||||
load_word(R_PrimCursor, R_TapePtr, O_(Binds_CubeTri,PrimCursor)),
|
||||
load_word(R_FaceCursor, R_TapePtr, O_(Binds_CubeTri,FaceCursor)),
|
||||
load_word(R_VertBase, R_TapePtr, O_(Binds_CubeTri,VertBase)),
|
||||
load_word(R_OtBase, R_TapePtr, O_(Binds_CubeTri,OtBase)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_CubeTri)),
|
||||
mac_yield()
|
||||
};
|
||||
@@ -41,7 +41,7 @@ internal MipsAtom_(rbind_cube_g4_face) atom_info(atom_bind(Binds_CubeTri)
|
||||
* Reads 4 indices from R_FaceCur (V4_S2 = 8 bytes), loads 4 vertices into
|
||||
* the GTE, runs the PsyQ RotAverageNclip4 sequence, and renders a Poly_G4.
|
||||
*/
|
||||
internal
|
||||
internal
|
||||
MipsAtom_(cube_g4_face) atom_info(
|
||||
atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase),
|
||||
atom_writes(R_PrimCursor, R_FaceCursor)
|
||||
@@ -94,21 +94,21 @@ typedef Struct_(Binds_FloorTri) {
|
||||
V3_S2* VertBase;
|
||||
U4* OtBase;
|
||||
};
|
||||
internal
|
||||
internal
|
||||
MipsAtom_(rbind_floor_f3_face) atom_info(atom_bind(Binds_FloorTri)
|
||||
, atom_reads(R_TapePtr)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
){
|
||||
/* Pop 4 arguments from the tape directly into the workspace registers */
|
||||
load_word(R_PrimCursor, R_TapePtr, O_(Binds_FloorTri,PrimCursor)),
|
||||
load_word(R_FaceCursor, R_TapePtr, O_(Binds_FloorTri,FaceCursor)),
|
||||
load_word(R_VertBase, R_TapePtr, O_(Binds_FloorTri,VertBase)),
|
||||
load_word(R_OtBase, R_TapePtr, O_(Binds_FloorTri,OtBase)),
|
||||
load_word(R_PrimCursor, R_TapePtr, O_(Binds_FloorTri,PrimCursor)),
|
||||
load_word(R_FaceCursor, R_TapePtr, O_(Binds_FloorTri,FaceCursor)),
|
||||
load_word(R_VertBase, R_TapePtr, O_(Binds_FloorTri,VertBase)),
|
||||
load_word(R_OtBase, R_TapePtr, O_(Binds_FloorTri,OtBase)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_FloorTri)),
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
internal
|
||||
internal
|
||||
MipsAtom_(floor_f3_face) atom_info(
|
||||
, atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor)
|
||||
@@ -117,7 +117,7 @@ MipsAtom_(floor_f3_face) atom_info(
|
||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
||||
nop2, gte_cmdw_rotate_translate_perspective_triple,
|
||||
nop2, gte_cmdw_nclip,
|
||||
|
||||
|
||||
/* Culling (Branch forward if Backface) */
|
||||
nop2, gte_mv_from_data_r(R_T0, C2_MAC0),
|
||||
nop,
|
||||
@@ -125,21 +125,21 @@ MipsAtom_(floor_f3_face) atom_info(
|
||||
/* Format Primitive */
|
||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||
mac_gte_store_f3_post_rtpt(),
|
||||
|
||||
|
||||
/* Calculate Depth */
|
||||
nop2, gte_avg_sort_z3,
|
||||
nop2, gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||
nop2, gte_avg_sort_z3,
|
||||
nop2, gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||
/* Bounds Check OTZ < 2048 (Branch forward to skip insertion) */
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
|
||||
/* Insert into Ordering Table Linked List */
|
||||
mac_insert_ot_tag_f3(),
|
||||
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
||||
// Note(Ed): No bounds checking, should be checked before atom runs.
|
||||
|
||||
/* Advance Input Cursor & Yield (Both branch targets land here) */
|
||||
atom_label(floor_f3_face_exit)
|
||||
atom_label(floor_f3_face_exit)
|
||||
add_ui_self(R_FaceCursor, S_(S2) * 4), /* Advance Face Cursor (4 * S2 = 8 bytes) */
|
||||
mac_yield()
|
||||
};
|
||||
@@ -150,7 +150,7 @@ internal MipsAtom_(sync_primitive_arena) atom_info(atom_bind(Binds_SyncPrimitive
|
||||
, atom_writes(R_TapePtr)
|
||||
){
|
||||
load_word(R_AT, R_TapePtr, O_(Binds_SyncPrimitiveArena,used)),
|
||||
load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimitiveArena,cursor)),
|
||||
load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimitiveArena,cursor)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_SyncPrimitiveArena)),
|
||||
/* Calculate byte offset and store directly back to RAM */
|
||||
sub_u( R_T0, R_PrimCursor, R_T0), // R_T0 = R_PrimCursor - binds.cursor
|
||||
|
||||
@@ -394,6 +394,55 @@ function build-gte_hello {
|
||||
-out_root (join-path $path_build 'gen') `
|
||||
-passes @('--gdb-runtime') `
|
||||
-extra_args @('--elf', $elf)
|
||||
ps1-meta -sources $atom_sources -metadata $path_atom_metadata `
|
||||
-out_root (join-path $path_build 'gen') `
|
||||
-passes @('--dwarf-injection') `
|
||||
-extra_args @('--elf', $elf)
|
||||
|
||||
# F' track: post-link DWARF injection. The new Lua pass writes
|
||||
# build/gen/<basename>.dwarf_*.bin blobs; we splice them into a COPY
|
||||
# of the ELF via objcopy --update-section (works fine from PowerShell).
|
||||
# The un-injected $elf + $exe are unchanged (shipping binary).
|
||||
$dwarfLineBin = Join-Path (Join-Path $path_build 'gen') 'hello_gte.dwarf_line.bin'
|
||||
$dwarfArangesBin = Join-Path (Join-Path $path_build 'gen') 'hello_gte.dwarf_aranges.bin'
|
||||
$dwarfRnglistsBin = Join-Path (Join-Path $path_build 'gen') 'hello_gte.dwarf_rnglists.bin'
|
||||
$injectElf = Join-Path $path_build 'hello_gte.dwarf-injected.elf'
|
||||
if ((Test-Path $dwarfLineBin) -and (Test-Path $dwarfArangesBin) -and (Test-Path $dwarfRnglistsBin)) {
|
||||
Write-Host "[build] DWARF-injecting $elf -> $injectElf"
|
||||
Copy-Item -LiteralPath $elf -Destination $injectElf
|
||||
& $Objcopy --update-section ".debug_line=$dwarfLineBin" $injectElf
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] objcopy .debug_line update failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
& $Objcopy --update-section ".debug_aranges=$dwarfArangesBin" $injectElf
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] objcopy .debug_aranges update failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
& $Objcopy --update-section ".debug_rnglists=$dwarfRnglistsBin" $injectElf
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] objcopy .debug_rnglists update failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
# Baked atoms execute from RAM but are emitted as C data arrays, so
|
||||
# their ELF sections lack SHF_EXECINSTR. GDB discards line rows for
|
||||
# non-code sections. Mark only the debug-copy sections executable;
|
||||
# the shipping ELF and PS-EXE remain byte/flag unchanged.
|
||||
& $Objcopy `
|
||||
--set-section-flags ".rodata=alloc,load,readonly,code,contents" `
|
||||
--set-section-flags ".data=alloc,load,data,code,contents" `
|
||||
$injectElf
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] objcopy atom-section flag update failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Write-Host "[build] DWARF-injected ELF: $injectElf"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
build-gte_hello
|
||||
|
||||
|
||||
@@ -0,0 +1,777 @@
|
||||
--- passes/dwarf_injection.lua — Per-atom DWARF injection for tape-atom step-debug (F').
|
||||
---
|
||||
--- Reads the post-link ELF directly (lfs + io.open; walks the ELF32 section
|
||||
--- header table to find `.debug_line` + `.debug_aranges` + `.debug_rnglists`),
|
||||
--- APPENDS synthetic DWARF line-program sequences for every `code_<name>` atom,
|
||||
--- EXTENDS the `.debug_aranges` and main-CU range tables with the atom ranges.
|
||||
--- Writes the new section data to `<out_root>/<basename>.dwarf_*.bin`.
|
||||
---
|
||||
--- The `build_psyq.ps1` post-link hook then splices those `.bin` files into a copy of the ELF via:
|
||||
--- mipsel-none-elf-objcopy --update-section .debug_line=<bin> <elf>
|
||||
--- mipsel-none-elf-objcopy --update-section .debug_aranges=<bin> <elf>
|
||||
--- mipsel-none-elf-objcopy --update-section .debug_rnglists=<bin> <elf>
|
||||
--- (Splice step runs from PowerShell — no Lua subprocess; no cmd /c parsing issues.
|
||||
--- objcopy's --update-section works fine in PowerShell even though Lua's `os.execute`/`io.popen` would mangle the `=` on Windows.)
|
||||
---
|
||||
--- Result: VSCode's source gutter follows per-stepi inside atom bodies.
|
||||
--- Native VSCode UX (gutter arrow + highlighted line + Run to Cursor + conditional BPs by source line).
|
||||
--- No VSCode plugin, no Python, no pyelftools — pure Lua + objcopy.
|
||||
---
|
||||
--- **Conventions:** tabs (1/level), EmmyLua annotations, no regex,
|
||||
--- Lua 5.3 compatible.
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Bootstrap
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both
|
||||
-- standalone + when require'd). Sets package.path + package.cpath then
|
||||
-- returns duffle.
|
||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
|
||||
-- Native `lfs` for file/directory operations.
|
||||
local lfs = require("lfs")
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Constants
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- DWARF line-program opcodes (per raddebugger's `dwarf_writer.c`).
|
||||
-- Standard opcodes 1-12 are single-byte + payload.
|
||||
local DW_LNS_copy = 1
|
||||
local DW_LNS_advance_pc = 2
|
||||
local DW_LNS_advance_line = 3
|
||||
local DW_LNS_set_file = 4
|
||||
|
||||
-- Byte 0 introduces every extended opcode. `opcode_base` is instead the first
|
||||
-- special opcode and must never be used as the extended marker.
|
||||
local DW_LNS_extended = 0
|
||||
|
||||
-- Extended sub-opcodes (encoded as a single byte AFTER the ULEB128 size).
|
||||
local DW_LNE_end_sequence = 1
|
||||
local DW_LNE_set_address = 2
|
||||
|
||||
-- DWARF5 range-list entry encodings used by the main CU.
|
||||
local DW_RLE_end_of_list = 0
|
||||
local DW_RLE_start_length = 7
|
||||
|
||||
-- File index 11 in the existing main line unit is hello_gte_tape.c. The
|
||||
-- injector extends that unit rather than appending an unreferenced unit.
|
||||
local ATOM_SOURCE_FILE_INDEX = 11
|
||||
|
||||
-- Path templates for the .bin outputs.
|
||||
local function dwarf_line_path(out_root, basename)
|
||||
return out_root .. "\\" .. basename .. ".dwarf_line.bin"
|
||||
end
|
||||
local function dwarf_aranges_path(out_root, basename)
|
||||
return out_root .. "\\" .. basename .. ".dwarf_aranges.bin"
|
||||
end
|
||||
local function dwarf_rnglists_path(out_root, basename)
|
||||
return out_root .. "\\" .. basename .. ".dwarf_rnglists.bin"
|
||||
end
|
||||
|
||||
-- Default basename if not provided via ctx.
|
||||
local DEFAULT_BASENAME = "hello_gte"
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Type declarations
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- @class DwarfInjectionCtx
|
||||
--- @field flags table -- ctx.flags; reads flags.elf_path + flags.dwarf_injection
|
||||
--- @field out_root string -- output root (e.g. "build/gen")
|
||||
--- @field basename string -- input ELF basename (default "hello_gte")
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- LEB128 encoders
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- ULEB128 encoder. Returns the byte string.
|
||||
--- @param n integer -- non-negative
|
||||
--- @return string
|
||||
local function uleb128(n)
|
||||
assert(n >= 0, "uleb128 requires non-negative input")
|
||||
local bytes = {}
|
||||
repeat
|
||||
local b = n % 128
|
||||
n = (n - b) / 128
|
||||
if n > 0 then b = b + 128 end
|
||||
bytes[#bytes + 1] = string.char(b)
|
||||
until n == 0
|
||||
return table.concat(bytes)
|
||||
end
|
||||
|
||||
--- SLEB128 encoder. Returns the byte string.
|
||||
--- @param n integer
|
||||
--- @return string
|
||||
local function sleb128(n)
|
||||
local bytes = {}
|
||||
local more = true
|
||||
while more do
|
||||
local b = n % 128
|
||||
n = (n - b) / 128
|
||||
if n == 0 and (b % 128) < 64 then more = false end
|
||||
if n == -1 and (b % 128) >= 64 then more = false end
|
||||
if more then b = b + 128 end
|
||||
bytes[#bytes + 1] = string.char(b)
|
||||
end
|
||||
return table.concat(bytes)
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- DWARF line-program encoder
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- Build the byte sequence for ONE atom's line program:
|
||||
--- DW_LNE_set_address(addr)
|
||||
--- DW_LNS_copy -- entry 1: addr, file, line=first.line
|
||||
--- for each subsequent word:
|
||||
--- DW_LNS_advance_pc(1 .word = 4 bytes)
|
||||
--- DW_LNS_advance_line(line - prev_line)
|
||||
--- DW_LNS_copy
|
||||
--- DW_LNE_end_sequence
|
||||
---
|
||||
--- Wire format reminder (DWARF5 §6.2.5):
|
||||
--- - Standard opcodes: 1 byte opcode + payload (per opcode length).
|
||||
--- - Extended opcode marker byte = 0.
|
||||
--- - Extended opcodes: marker byte + ULEB128 size + sub_opcode + payload.
|
||||
---
|
||||
--- @param atom table -- {name, addr, size_bytes, words, entries}
|
||||
--- @return string
|
||||
local function build_atom_sequence(atom)
|
||||
local function set_address(addr)
|
||||
-- Per DWARF5 §6.2.5.3:
|
||||
-- marker(0) + size(ULEB128, includes sub_opcode byte) + sub_opcode + payload
|
||||
-- For set_address: size = 1 (sub_opcode) + 4 (addr) = 5
|
||||
local addr_bytes = string.char(
|
||||
addr % 256,
|
||||
math.floor(addr / 256) % 256,
|
||||
math.floor(addr / 65536) % 256,
|
||||
math.floor(addr / 16777216) % 256)
|
||||
local sub_size = string.char(DW_LNE_set_address) .. addr_bytes
|
||||
return string.char(DW_LNS_extended) .. uleb128(#sub_size) .. sub_size
|
||||
end
|
||||
local function copy_op()
|
||||
return string.char(DW_LNS_copy)
|
||||
end
|
||||
local function set_file(file_index)
|
||||
return string.char(DW_LNS_set_file) .. uleb128(file_index)
|
||||
end
|
||||
local function advance_pc(bytes_delta)
|
||||
return string.char(DW_LNS_advance_pc) .. uleb128(bytes_delta)
|
||||
end
|
||||
local function advance_line(line_delta)
|
||||
return string.char(DW_LNS_advance_line) .. sleb128(line_delta)
|
||||
end
|
||||
local function end_sequence()
|
||||
-- size = 1 (just the sub_opcode byte, no payload)
|
||||
return string.char(DW_LNS_extended) .. string.char(1) .. string.char(DW_LNE_end_sequence)
|
||||
end
|
||||
|
||||
if not atom.entries or #atom.entries == 0 then
|
||||
return set_address(atom.addr) .. end_sequence()
|
||||
end
|
||||
|
||||
-- set_address sets the address register to atom.addr.
|
||||
-- line_state.line starts at 1 (per DWARF spec).
|
||||
-- To land at entries[1].line for the FIRST emitted entry, we need to advance_line by (entries[1].line - 1) + then copy.
|
||||
-- Then each subsequent entry uses the delta from the previous entry.
|
||||
local parts = {
|
||||
set_file(ATOM_SOURCE_FILE_INDEX), -- existing Unit 2 file table: hello_gte_tape.c
|
||||
set_address(atom.addr), -- 7 bytes: marker + size + sub + addr
|
||||
advance_line(atom.entries[1].line - 1), -- (entries[1].line - 1) bytes; line_state.line -> entries[1].line
|
||||
copy_op(), -- emit entry 1: addr=atom.addr, line=entries[1].line
|
||||
}
|
||||
local prev_line = atom.entries[1].line
|
||||
for idx = 2, #atom.entries do
|
||||
local entry = atom.entries[idx]
|
||||
parts[#parts + 1] = advance_pc(4) -- 1 .word = 4 bytes on MIPS
|
||||
parts[#parts + 1] = advance_line(entry.line - prev_line)
|
||||
parts[#parts + 1] = copy_op()
|
||||
prev_line = entry.line
|
||||
end
|
||||
parts[#parts + 1] = end_sequence()
|
||||
return table.concat(parts)
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- ELF32 direct reader
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- Read the existing `.debug_line` + `.debug_aranges` + `.debug_rnglists` bytes by walking the
|
||||
--- ELF32 section-header table directly (no subprocess).
|
||||
--- lfs is used only for the existence check; io.open + manual byte arithmetic does the rest.
|
||||
---
|
||||
--- ELF32 header layout (52 bytes):
|
||||
--- off 0: magic \x7fELF
|
||||
--- off 4: class (1 = ELF32), endianness (1 = LE, 2 = BE)
|
||||
--- ...
|
||||
--- off 16: e_type (2), e_machine (2), e_version (4)
|
||||
--- off 24: e_entry (4), e_phoff (4)
|
||||
--- off 32: e_shoff (4) <-- section-header table file offset
|
||||
--- off 36: e_flags (4), e_ehsize (2)
|
||||
--- off 42: e_phentsize (2), e_phnum (2)
|
||||
--- off 46: e_shentsize (2) <-- section-header entry size (40 for ELF32)
|
||||
--- off 48: e_shnum (2) <-- number of section headers
|
||||
--- off 50: e_shstrndx (2) <-- index of section-name string table
|
||||
---
|
||||
--- Section header (40 bytes for ELF32):
|
||||
--- off 0: sh_name (4)
|
||||
--- off 4: sh_type (4)
|
||||
--- off 8: sh_flags (4)
|
||||
--- off 12: sh_addr (4)
|
||||
--- off 16: sh_offset (4) <-- file offset of section data
|
||||
--- off 20: sh_size (4) <-- size of section data
|
||||
--- off 24: sh_link (4)
|
||||
--- off 28: sh_info (4)
|
||||
--- off 32: sh_addralign (4)
|
||||
--- off 36: sh_entsize (4)
|
||||
---
|
||||
--- Returns the three section byte strings (any may be empty if absent).
|
||||
--- @param elf_path string
|
||||
--- @return string, string, string
|
||||
local function read_elf_dwarf_sections(elf_path)
|
||||
-- Existence check (lfs.attributes avoids an io.open-vs-fail race).
|
||||
if lfs.attributes(elf_path, "mode") ~= "file" then
|
||||
io.stderr:write(string.format("[dwarf_injection] ELF not found: %s\n", elf_path))
|
||||
return "", "", ""
|
||||
end
|
||||
|
||||
local f = io.open(elf_path, "rb")
|
||||
if not f then
|
||||
io.stderr:write(string.format("[dwarf_injection] io.open failed: %s\n", elf_path))
|
||||
return "", "", ""
|
||||
end
|
||||
|
||||
-- Read the ELF32 header (52 bytes).
|
||||
local header = f:read(52)
|
||||
if not header or #header < 52 then
|
||||
io.stderr:write("[dwarf_injection] ELF too small for ELF32 header\n")
|
||||
f:close()
|
||||
return "", "", ""
|
||||
end
|
||||
|
||||
-- Sanity-check magic + class + endianness.
|
||||
if header:sub(1, 4) ~= "\127ELF" then
|
||||
io.stderr:write("[dwarf_injection] not an ELF file\n")
|
||||
f:close()
|
||||
return "", "", ""
|
||||
end
|
||||
local class = header:byte(5)
|
||||
local endian = header:byte(6)
|
||||
if class ~= 1 then
|
||||
io.stderr:write(string.format("[dwarf_injection] not ELF32 (class=%d)\n", class))
|
||||
f:close()
|
||||
return "", "", ""
|
||||
end
|
||||
if endian ~= 1 then
|
||||
io.stderr:write(string.format("[dwarf_injection] not little-endian (endian=%d); unsupported\n", endian))
|
||||
f:close()
|
||||
return "", "", ""
|
||||
end
|
||||
|
||||
-- Parse e_shoff (off 32, 4 bytes LE), e_shentsize
|
||||
-- (off 46, 2 bytes LE), e_shnum (off 48, 2 bytes LE), e_shstrndx (off 50, 2 bytes LE).
|
||||
local function u32_le(buf, off)
|
||||
return buf:byte(off) + buf:byte(off + 1) * 256 + buf:byte(off + 2) * 65536 + buf:byte(off + 3) * 16777216
|
||||
end
|
||||
local function u16_le(buf, off)
|
||||
return buf:byte(off) + buf:byte(off + 1) * 256
|
||||
end
|
||||
|
||||
local e_shoff = u32_le(header, 33)
|
||||
local e_shentsize = u16_le(header, 47)
|
||||
local e_shnum = u16_le(header, 49)
|
||||
local e_shstrndx = u16_le(header, 51)
|
||||
|
||||
-- Read the section-header string table (.shstrtab).
|
||||
-- Walk to section header index `e_shstrndx`, read its sh_offset + sh_size.
|
||||
f:seek("set", e_shoff + e_shstrndx * e_shentsize)
|
||||
local strtab_hdr = f:read(e_shentsize)
|
||||
if not strtab_hdr or #strtab_hdr < e_shentsize then
|
||||
io.stderr:write("[dwarf_injection] could not read .shstrtab header\n")
|
||||
f:close()
|
||||
return "", "", ""
|
||||
end
|
||||
local strtab_offset = u32_le(strtab_hdr, 17)
|
||||
local strtab_size = u32_le(strtab_hdr, 21)
|
||||
f:seek("set", strtab_offset)
|
||||
local strtab = f:read(strtab_size) or ""
|
||||
|
||||
-- Walk all section headers; collect names + (offset, size) for the
|
||||
-- sections we care about.
|
||||
local function read_section_bytes(sh_offset, sh_size)
|
||||
f:seek("set", sh_offset)
|
||||
return f:read(sh_size) or ""
|
||||
end
|
||||
|
||||
local debug_line_bytes = ""
|
||||
local debug_aranges_bytes = ""
|
||||
local debug_rnglists_bytes = ""
|
||||
|
||||
for sh_idx = 0, e_shnum - 1 do
|
||||
f:seek("set", e_shoff + sh_idx * e_shentsize)
|
||||
local sh = f:read(e_shentsize)
|
||||
if not sh or #sh < e_shentsize then break end
|
||||
local sh_name = u32_le(sh, 1)
|
||||
local sh_offset = u32_le(sh, 17)
|
||||
local sh_size = u32_le(sh, 21)
|
||||
|
||||
-- Extract the name (null-terminated C string in strtab).
|
||||
local name_end = strtab:find("\0", sh_name + 1, true) or (sh_name + 1)
|
||||
local name = strtab:sub(sh_name + 1, name_end - 1)
|
||||
if name == ".debug_line" then debug_line_bytes = read_section_bytes(sh_offset, sh_size)
|
||||
elseif name == ".debug_aranges" then debug_aranges_bytes = read_section_bytes(sh_offset, sh_size)
|
||||
elseif name == ".debug_rnglists" then debug_rnglists_bytes = read_section_bytes(sh_offset, sh_size)
|
||||
end
|
||||
end
|
||||
|
||||
f:close()
|
||||
return debug_line_bytes, debug_aranges_bytes, debug_rnglists_bytes
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Helpers: nm + source-map.txt + atom table
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- Read ELF symbol addresses via `mipsel-none-elf-nm -S`.
|
||||
--- Returns `{name -> {addr, size_bytes}}` for every `code_<name>` symbol.
|
||||
--- Duplicated from passes/atoms_source_map.lua
|
||||
--- (lift decision in scratch/phase1_helpers_audit.md: keep duplication until 3+ callers).
|
||||
--- @param elf_path string
|
||||
--- @return table
|
||||
local function read_nm(elf_path)
|
||||
local addrs = {}
|
||||
local f = io.popen(string.format('mipsel-none-elf-nm -S "%s" 2>nul', elf_path))
|
||||
if not f then return addrs end
|
||||
for line in f:lines() do
|
||||
local addr_hex, size_hex, name = line:match("^(%x+)%s+(%x+)%s+%a%s+code_(%S+)")
|
||||
if addr_hex and size_hex and name then
|
||||
addrs[name] = { tonumber(addr_hex, 16), tonumber(size_hex, 16) }
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
return addrs
|
||||
end
|
||||
|
||||
--- Parse FORMAT_VERSION 1 atoms.sourcemap.txt.
|
||||
--- Returns `{name -> {total = N, words = {{n, line}, ...}}}`.
|
||||
--- Matches the in-memory shape
|
||||
--- (atoms_source_map.lua:142 uses `{pos, line, text}`; the `.txt` file uses `WORD <n>` so the parser maps `n` -> `pos`).
|
||||
--- @param sm_path string
|
||||
--- @return table
|
||||
local function read_source_map(sm_path)
|
||||
local out = {}
|
||||
local cur_name, cur_total, cur_words = nil, 0, {}
|
||||
for raw in io.lines(sm_path) do
|
||||
local line = raw
|
||||
if line:match("^#") then
|
||||
if line:match("^# FORMAT_VERSION%s+(%d+)") then
|
||||
local ver = tonumber(line:match("^# FORMAT_VERSION%s+(%d+)"))
|
||||
if ver ~= 1 then
|
||||
io.stderr:write(string.format(
|
||||
"[dwarf_injection] source-map version mismatch (got %d, expected 1)\n", ver))
|
||||
return {}
|
||||
end
|
||||
end
|
||||
-- skip other comments
|
||||
elseif line:sub(1, 4) == "ATOM" then
|
||||
-- ATOM <name> "<abs-source-path>" <total_words>
|
||||
-- The path is quoted. Format per emission: string.format('ATOM %s "%s" 0', name, rel_path)
|
||||
-- then patched: .gsub(" 0$", " " .. total).
|
||||
-- So the wire format is: ATOM <name> "<path>" <total>.
|
||||
local _, _, name = line:find("ATOM%s+(%S+)%s+\"[^\"]*\"%s+(%d+)")
|
||||
if name then
|
||||
cur_name = name
|
||||
cur_total = 0
|
||||
cur_words = {}
|
||||
out[name] = { total = 0, words = cur_words }
|
||||
end
|
||||
elseif line == "ENDATOM" then
|
||||
-- Update the recorded total from the entries count
|
||||
-- (matches the `lines[1] = lines[1]:gsub(" 0$", " " .. total)` patch in atoms_source_map.lua:170).
|
||||
if cur_name and out[cur_name] then
|
||||
out[cur_name].total = #cur_words
|
||||
end
|
||||
cur_name, cur_total, cur_words = nil, 0, {}
|
||||
elseif line:sub(1, 4) == "WORD" and cur_name then
|
||||
-- WORD <n> LINE <line> TEXT <text...>
|
||||
local _, n, _, src_line = line:find("WORD%s+(%d+)%s+LINE%s+(%d+)")
|
||||
if n and src_line then
|
||||
cur_words[#cur_words + 1] = { n = tonumber(n), line = tonumber(src_line) }
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Build the atom table the section builders consume.
|
||||
--- Cross-references nm symbols with source-map.txt entries; sorted by addr.
|
||||
--- @param ctx DwarfInjectionCtx
|
||||
--- @return table[] -- list of {name, addr, size_bytes, words, entries}
|
||||
local function build_atom_table(ctx)
|
||||
local basename = ctx.basename or DEFAULT_BASENAME
|
||||
-- Source-map path: convention matches the α MVP's emission location.
|
||||
-- writes `<out_root>/<basename>.atoms.sourcemap.txt` (e.g. `build/gen/hello_gte_tape.atoms.sourcemap.txt`).
|
||||
-- But ctx.out_root is `build/gen` (the per-build output root) and basename defaults to `hello_gte`.
|
||||
-- The actual file emitted today is per-source; we look for any `*.atoms.sourcemap.txt` in out_root.
|
||||
local sm_files = {}
|
||||
if duffle and duffle.list_dir then
|
||||
sm_files = duffle.list_dir(ctx.out_root, "%.atoms.sourcemap%.txt$")
|
||||
elseif lfs then
|
||||
-- Manual walk via lfs.
|
||||
for entry in lfs.dir(ctx.out_root) do
|
||||
if entry:match("%.atoms.sourcemap%.txt$") then
|
||||
sm_files[#sm_files + 1] = ctx.out_root .. "\\" .. entry
|
||||
end
|
||||
end
|
||||
end
|
||||
if #sm_files == 0 then
|
||||
io.stderr:write(string.format(
|
||||
"[dwarf_injection] no *.atoms.sourcemap.txt in %s; need atoms-source-map pass first\n",
|
||||
ctx.out_root))
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Read nm + merge all source-map files.
|
||||
local addrs = read_nm(ctx.flags.elf_path)
|
||||
local merged = {}
|
||||
for _, sm_path in ipairs(sm_files) do
|
||||
local sm = read_source_map(sm_path)
|
||||
for name, sm_data in pairs(sm) do
|
||||
merged[name] = sm_data
|
||||
end
|
||||
end
|
||||
|
||||
-- Cross-ref; keep atoms that exist in both.
|
||||
local out = {}
|
||||
for name, info in pairs(addrs) do
|
||||
local sm = merged[name]
|
||||
if sm then
|
||||
out[#out + 1] = {
|
||||
name = name,
|
||||
addr = info[1],
|
||||
size_bytes = info[2],
|
||||
words = sm.total,
|
||||
entries = sm.words,
|
||||
}
|
||||
end
|
||||
end
|
||||
table.sort(out, function(a, b) return a.addr < b.addr end)
|
||||
return out
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Section builders (single dispatch table — see guide_metaprogram_ssdl.md §11)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- Append per-atom line-program sequences to the existing main .debug_line unit
|
||||
--- (the final unit, referenced by the main CU's DW_AT_stmt_list).
|
||||
---
|
||||
--- The old implementation appended a new Unit 3.
|
||||
--- No compilation unit pointed at it through DW_AT_stmt_list, so gdb ignored it.
|
||||
--- It also encoded byte 13 as the extended-opcode marker; byte 13 is actually the first special opcode.
|
||||
--- The existing final unit already contains hello_gte_tape.c as file index 11 and ends with a valid end_sequence.
|
||||
--- We preserve its bytes, append independent atom sequences, and increase only that unit's DWARF32 unit_length.
|
||||
---
|
||||
--- @param existing string -- existing section bytes (verbatim)
|
||||
--- @param atom_table table -- list of {name, addr, size_bytes, words, entries}
|
||||
--- @return string
|
||||
local function build_dwarf_line_section(existing, atom_table)
|
||||
if #atom_table == 0 then return existing end
|
||||
|
||||
-- Build the sequences.
|
||||
local sequences = {}
|
||||
for _, atom in ipairs(atom_table) do
|
||||
sequences[#sequences + 1] = build_atom_sequence(atom)
|
||||
end
|
||||
local appended = table.concat(sequences)
|
||||
|
||||
-- Walk DWARF32 line units and retain the final unit's bounds.
|
||||
-- The main C CU points at this final unit (DW_AT_stmt_list = 0x5b in today's ELF).
|
||||
local unit_pos, last_pos, last_length, last_end = 1, nil, nil, nil
|
||||
while unit_pos <= #existing do
|
||||
if unit_pos + 3 > #existing then return existing end
|
||||
local unit_length = existing:byte(unit_pos)
|
||||
+ existing:byte(unit_pos + 1) * 256
|
||||
+ existing:byte(unit_pos + 2) * 65536
|
||||
+ existing:byte(unit_pos + 3) * 16777216
|
||||
if unit_length == 0xFFFFFFFF then return existing end
|
||||
local unit_end = unit_pos + 3 + unit_length
|
||||
if unit_end > #existing then return existing end
|
||||
last_pos, last_length, last_end = unit_pos, unit_length, unit_end
|
||||
unit_pos = unit_end + 1
|
||||
end
|
||||
if unit_pos ~= #existing + 1 or not last_pos then return existing end
|
||||
|
||||
local new_length = last_length + #appended
|
||||
local new_length_bytes = string.char(
|
||||
new_length % 256,
|
||||
math.floor(new_length / 256) % 256,
|
||||
math.floor(new_length / 65536) % 256,
|
||||
math.floor(new_length / 16777216) % 256)
|
||||
|
||||
return existing:sub(1, last_pos - 1)
|
||||
.. new_length_bytes
|
||||
.. existing:sub(last_pos + 4, last_end)
|
||||
.. appended
|
||||
end
|
||||
|
||||
--- Extend .debug_aranges with one entry per atom, pointing at the same CU the existing entries point at
|
||||
--- (read from the existing header's debug_info_offset field).
|
||||
--- The 8-byte zero terminator is preserved.
|
||||
---
|
||||
--- Existing .debug_aranges layout (DWARF4 §6.1.1, 32-bit):
|
||||
--- unit_length (4 bytes) -- size of the rest
|
||||
--- version (2 bytes) -- = 2
|
||||
--- debug_info_offset (4 bytes) -- CU DIE offset in .debug_info
|
||||
--- address_size (1 byte) -- = 4 on MIPS
|
||||
--- segment_size (1 byte) -- = 0
|
||||
--- [entries...] -- address(4) + length(4) per entry
|
||||
--- terminator -- address=0 + length=0 (8 zero bytes)
|
||||
---
|
||||
--- @param existing string
|
||||
--- @param atom_table table
|
||||
--- @return string
|
||||
local function build_dwarf_aranges_section(existing, atom_table)
|
||||
if #existing < 12 then return existing end -- header sanity
|
||||
|
||||
-- .debug_aranges can contain multiple compilation units (CUs).
|
||||
-- gcc-mips-elf emits one CU per .text section TU.
|
||||
-- We extend the LAST unit by replacing its 8-byte terminator with our atom entries followed by a new 8-byte terminator.
|
||||
-- We bump the unit's length field accordingly.
|
||||
--
|
||||
-- Unit structure (DWARF4 §7.21):
|
||||
-- unit_length (4)
|
||||
-- version (2)
|
||||
-- debug_info_offset (4) -- CU DIE offset in .debug_info
|
||||
-- address_size (1)
|
||||
-- segment_size (1)
|
||||
-- entries... (4-byte addr + 4-byte length)
|
||||
-- terminator (8 bytes: addr=0, length=0)
|
||||
|
||||
-- Walk all units and emit each one (preserving existing structure).
|
||||
-- For the LAST unit, replace the terminator with my entries + new term.
|
||||
local result = {}
|
||||
local i = 1 -- 1-indexed
|
||||
local is_last_unit = false
|
||||
|
||||
while i <= #existing do
|
||||
-- Read this unit's length.
|
||||
local ul = existing:byte(i)
|
||||
+ existing:byte(i + 1) * 256
|
||||
+ existing:byte(i + 2) * 65536
|
||||
+ existing:byte(i + 3) * 16777216
|
||||
if ul == 0xFFFFFFFF then
|
||||
-- DWARF64 marker - not supported.
|
||||
return existing
|
||||
end
|
||||
|
||||
local unit_start = i
|
||||
local unit_end = i + 3 + ul -- last byte of unit content
|
||||
is_last_unit = (unit_end == #existing)
|
||||
|
||||
if is_last_unit then
|
||||
-- The old terminator is replaced by entries + a new terminator, so
|
||||
-- net section growth (and unit_length growth) is entries only.
|
||||
local added_bytes = #atom_table * 8
|
||||
local new_ul = ul + added_bytes
|
||||
local new_ul_bytes = string.char(
|
||||
new_ul % 256,
|
||||
math.floor(new_ul / 256) % 256,
|
||||
math.floor(new_ul / 65536) % 256,
|
||||
math.floor(new_ul / 16777216) % 256)
|
||||
-- Emit everything EXCEPT the last 8 bytes (terminator).
|
||||
result[#result + 1] = new_ul_bytes
|
||||
.. existing:sub(i + 4, unit_end - 8)
|
||||
-- Append my atom entries.
|
||||
for _, atom in ipairs(atom_table) do
|
||||
local a = atom.addr
|
||||
local size = atom.size_bytes
|
||||
result[#result + 1] = string.char(
|
||||
a % 256,
|
||||
math.floor(a / 256) % 256,
|
||||
math.floor(a / 65536) % 256,
|
||||
math.floor(a / 16777216) % 256,
|
||||
size % 256,
|
||||
math.floor(size / 256) % 256,
|
||||
math.floor(size / 65536) % 256,
|
||||
math.floor(size / 16777216) % 256)
|
||||
end
|
||||
-- Append a new terminator.
|
||||
result[#result + 1] = string.rep("\0", 8)
|
||||
else
|
||||
-- Emit this unit unchanged.
|
||||
result[#result + 1] = existing:sub(unit_start, unit_end)
|
||||
end
|
||||
|
||||
i = unit_end + 1
|
||||
end
|
||||
|
||||
if not is_last_unit then
|
||||
-- Malformed or no terminator found; append a new unit at the end.
|
||||
-- For now, return existing unchanged to avoid making it worse.
|
||||
return existing
|
||||
end
|
||||
|
||||
return table.concat(result)
|
||||
end
|
||||
|
||||
--- Extend the main CU's DWARF5 range list with one DW_RLE_start_length entry per atom.
|
||||
--- GDB validates an address against DW_AT_ranges before consulting the CU's line program;
|
||||
--- .debug_aranges alone is not sufficient.
|
||||
---
|
||||
--- Current section shape (one DWARF32 table):
|
||||
--- unit_length(4), version=5(2), address_size=4(1), segment_size=0(1),
|
||||
--- offset_entry_count=0(4), start_length entries..., end_of_list(1).
|
||||
---
|
||||
--- @param existing string
|
||||
--- @param atom_table table
|
||||
--- @return string
|
||||
local function build_dwarf_rnglists_section(existing, atom_table)
|
||||
if #existing < 13 or #atom_table == 0 then return existing end
|
||||
|
||||
local unit_length = existing:byte(1)
|
||||
+ existing:byte(2) * 256
|
||||
+ existing:byte(3) * 65536
|
||||
+ existing:byte(4) * 16777216
|
||||
local version = existing:byte(5) + existing:byte(6) * 256
|
||||
local address_size = existing:byte(7)
|
||||
local segment_size = existing:byte(8)
|
||||
local offset_entry_count = existing:byte(9)
|
||||
+ existing:byte(10) * 256
|
||||
+ existing:byte(11) * 65536
|
||||
+ existing:byte(12) * 16777216
|
||||
|
||||
if unit_length + 4 ~= #existing
|
||||
or version ~= 5
|
||||
or address_size ~= 4
|
||||
or segment_size ~= 0
|
||||
or offset_entry_count ~= 0
|
||||
or existing:byte(#existing) ~= DW_RLE_end_of_list then
|
||||
return existing
|
||||
end
|
||||
|
||||
local entries = {}
|
||||
for _, atom in ipairs(atom_table) do
|
||||
local addr = atom.addr
|
||||
entries[#entries + 1] = string.char(DW_RLE_start_length,
|
||||
addr % 256,
|
||||
math.floor(addr / 256) % 256,
|
||||
math.floor(addr / 65536) % 256,
|
||||
math.floor(addr / 16777216) % 256)
|
||||
.. uleb128(atom.size_bytes)
|
||||
end
|
||||
local appended = table.concat(entries)
|
||||
local new_length = unit_length + #appended
|
||||
local new_length_bytes = string.char(
|
||||
new_length % 256,
|
||||
math.floor(new_length / 256) % 256,
|
||||
math.floor(new_length / 65536) % 256,
|
||||
math.floor(new_length / 16777216) % 256)
|
||||
|
||||
return new_length_bytes
|
||||
.. existing:sub(5, #existing - 1)
|
||||
.. appended
|
||||
.. string.char(DW_RLE_end_of_list)
|
||||
end
|
||||
|
||||
local SECTION_BUILDERS = {
|
||||
debug_line = build_dwarf_line_section,
|
||||
debug_aranges = build_dwarf_aranges_section,
|
||||
debug_rnglists = build_dwarf_rnglists_section,
|
||||
}
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Pass entry
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
local M = {}
|
||||
|
||||
--- M.run — orchestrator entry. Phase 1 Tasks 3-4 read + cross-ref. Phase 2
|
||||
--- Tasks 5-7 fill in the section builders + .bin emission.
|
||||
--- @param ctx DwarfInjectionCtx
|
||||
--- @return table
|
||||
function M.run(ctx)
|
||||
-- Guard: this pass is opt-in via --dwarf-injection (not run on --all).
|
||||
if not (ctx.flags and ctx.flags.dwarf_injection) then
|
||||
return { outputs = {}, errors = {}, warnings = {} }
|
||||
end
|
||||
|
||||
-- Guard: --elf is required.
|
||||
local elf_path = ctx.flags and ctx.flags.elf_path
|
||||
if not elf_path or elf_path == "" then
|
||||
io.stderr:write("[dwarf_injection] --elf flag missing\n")
|
||||
return { outputs = {}, errors = {}, warnings = {} }
|
||||
end
|
||||
|
||||
-- Resolve relative ELF path to absolute (io.open inherits the calling
|
||||
-- process's CWD which is reliable, but absolute paths are safer when
|
||||
-- this module is required from another directory).
|
||||
if not elf_path:match("^[%a]:[\\/]") and not elf_path:match("^[/\\]") then
|
||||
local cwd = lfs.currentdir()
|
||||
elf_path = cwd:gsub("[\\/]$", "") .. "\\" .. elf_path
|
||||
end
|
||||
|
||||
-- Read the existing DWARF sections directly (no subprocess; lfs + io.open + manual ELF32 section-header walk).
|
||||
local existing_line, existing_aranges, existing_rnglists = read_elf_dwarf_sections(elf_path)
|
||||
io.stderr:write(string.format("[dwarf_injection] read .debug_line (%d) + .debug_aranges (%d) + .debug_rnglists (%d) bytes\n"
|
||||
, #existing_line, #existing_aranges, #existing_rnglists))
|
||||
|
||||
-- Build the atom table (cross-ref nm symbols with source-map.txt entries).
|
||||
local atom_table = build_atom_table(ctx)
|
||||
io.stderr:write(string.format("[dwarf_injection] matched %d atoms between nm + source-map\n", #atom_table))
|
||||
|
||||
-- Build the new sections via the SECTION_BUILDERS dispatch.
|
||||
local new_line = build_dwarf_line_section (existing_line, atom_table)
|
||||
local new_aranges = build_dwarf_aranges_section (existing_aranges, atom_table)
|
||||
local new_rnglists = build_dwarf_rnglists_section(existing_rnglists, atom_table)
|
||||
io.stderr:write(string.format("[dwarf_injection] new .debug_line = %d bytes (was %d, +%d atoms)\n"
|
||||
, #new_line, #existing_line, #atom_table))
|
||||
io.stderr:write(string.format("[dwarf_injection] new .debug_aranges = %d bytes (was %d, +%d atoms)\n"
|
||||
, #new_aranges, #existing_aranges, #atom_table))
|
||||
io.stderr:write(string.format("[dwarf_injection] new .debug_rnglists = %d bytes (was %d, +%d atoms)\n"
|
||||
, #new_rnglists, #existing_rnglists, #atom_table))
|
||||
|
||||
-- Write the .bin files. The build_psyq.ps1 post-link hook splices these into a copy of the ELF via objcopy --update-section.
|
||||
local basename = ctx.basename or DEFAULT_BASENAME
|
||||
if ctx.out_root and ctx.out_root ~= "" then
|
||||
if lfs.attributes(ctx.out_root, "mode") ~= "directory" then
|
||||
lfs.mkdir(ctx.out_root)
|
||||
end
|
||||
local line_path = dwarf_line_path (ctx.out_root, basename)
|
||||
local aranges_path = dwarf_aranges_path (ctx.out_root, basename)
|
||||
local rnglists_path = dwarf_rnglists_path(ctx.out_root, basename)
|
||||
local f1 = io.open(line_path, "wb")
|
||||
if not f1 then
|
||||
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", line_path))
|
||||
else
|
||||
f1:write(new_line); f1:close()
|
||||
end
|
||||
local f2 = io.open(aranges_path, "wb")
|
||||
if not f2 then
|
||||
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", aranges_path))
|
||||
else
|
||||
f2:write(new_aranges); f2:close()
|
||||
end
|
||||
local f3 = io.open(rnglists_path, "wb")
|
||||
if not f3 then
|
||||
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", rnglists_path))
|
||||
else
|
||||
f3:write(new_rnglists); f3:close()
|
||||
end
|
||||
io.stderr:write(string.format("[dwarf_injection] wrote %s + %s + %s\n", line_path, aranges_path, rnglists_path))
|
||||
return {
|
||||
outputs = {
|
||||
{ dwarf_line_bin = line_path },
|
||||
{ dwarf_aranges_bin = aranges_path },
|
||||
{ dwarf_rnglists_bin = rnglists_path },
|
||||
},
|
||||
errors = {}, warnings = {},
|
||||
}
|
||||
end
|
||||
return { outputs = {}, errors = {}, warnings = {} }
|
||||
end
|
||||
|
||||
return M
|
||||
+24
-4
@@ -155,6 +155,17 @@ local PASSES = {
|
||||
desc = "Emit gen/<basename>.atoms.sourcemap.txt (per-.word C source line map for gdb debugging)",
|
||||
out = { { kind = "header", path_template = "<source_dir>/gen/<basename>.atoms.sourcemap.txt" } },
|
||||
},
|
||||
["dwarf-injection"] = {
|
||||
module = "passes.dwarf_injection",
|
||||
kind = "shared",
|
||||
deps = {"atoms-source-map"},
|
||||
desc = "Inject per-atom .debug_line + .debug_aranges into the ELF (post-link; writes section .bin blobs for objcopy splice)",
|
||||
out = {
|
||||
{ kind = "report", path_template = "<out_root>/<basename>.dwarf_line.bin" },
|
||||
{ kind = "report", path_template = "<out_root>/<basename>.dwarf_aranges.bin" },
|
||||
{ kind = "report", path_template = "<out_root>/<basename>.dwarf_rnglists.bin" },
|
||||
},
|
||||
},
|
||||
report = {
|
||||
module = "passes.report",
|
||||
kind = "report",
|
||||
@@ -180,6 +191,7 @@ local PASS_FLAG_TO_NAME = {
|
||||
["--offsets"] = "offsets",
|
||||
["--static-analysis"] = "static-analysis",
|
||||
["--atoms-source-map"] = "atoms-source-map",
|
||||
["--dwarf-injection"] = "dwarf-injection",
|
||||
["--report"] = "report",
|
||||
["--scan-source"] = "scan-source",
|
||||
["--all"] = ALL_PASSES_SENTINEL,
|
||||
@@ -187,7 +199,7 @@ local PASS_FLAG_TO_NAME = {
|
||||
|
||||
local ALL_PASS_NAMES = {
|
||||
"scan-source", "word-counts", "components", "annotation",
|
||||
"offsets", "static-analysis", "atoms-source-map", "report",
|
||||
"offsets", "static-analysis", "atoms-source-map", "dwarf-injection", "report",
|
||||
}
|
||||
|
||||
--- Append every pass name to args.requested_set. Used by --all and by the "default to --all if no pass flags were given" fallback.
|
||||
@@ -221,6 +233,7 @@ PASS_FLAGS (pick one or more, or use --all):
|
||||
--validate Run atom annotation DSL validation
|
||||
--offsets Generate <module>/gen/<basename>.offsets.h
|
||||
--atoms-source-map Generate <basename>.atoms.sourcemap.txt per source
|
||||
--dwarf-injection Inject per-atom .debug_line + .debug_aranges (post-link, requires --elf)
|
||||
--static-analysis [FUTURE] GTE pipeline-fill, mac_yield uniformity
|
||||
--report Render per-project summary
|
||||
--all Equivalent to all 6 flags above (default)
|
||||
@@ -231,7 +244,8 @@ COMMON_FLAGS:
|
||||
--out-root DIR Output root for reports (default: build/gen)
|
||||
--project-root DIR Project root for .macs.h scan (default: dirname(metadata))
|
||||
--gdb-runtime Also emit <out_root>/gdb_tape_atoms_runtime.gdb (post-link, requires --elf)
|
||||
--elf PATH Path to linked .elf (for --gdb-runtime address resolution)
|
||||
--dwarf-injection Opt in to DWARF injection (writes <basename>.dwarf_*.bin blobs for objcopy splice; requires --elf)
|
||||
--elf PATH Path to linked .elf (for --gdb-runtime / --dwarf-injection)
|
||||
--dry-run Print dep order + ASCII graph; exit 0 without running
|
||||
--verbose Print per-pass debug output
|
||||
--help Show this help and exit
|
||||
@@ -269,6 +283,12 @@ FLAG_HANDLERS["--project-root"] = function(args, argv, arg_idx) args.project_roo
|
||||
-- mutates `args.flags` (which propagates into `ctx.flags`).
|
||||
FLAG_HANDLERS["--gdb-runtime"] = function(args) args.flags = args.flags or {}; args.flags.gdb_runtime = true end
|
||||
FLAG_HANDLERS["--elf"] = function(args, argv, arg_idx) args.flags = args.flags or {}; args.flags.elf_path = argv[arg_idx + 1]; return arg_idx + 1 end
|
||||
-- F' track: enable DWARF injection (default OFF; opt-in via .vscode/launch.json or ps1_meta CLI).
|
||||
FLAG_HANDLERS["--dwarf-injection"] = function(args)
|
||||
args.flags = args.flags or {}
|
||||
args.flags.dwarf_injection = true
|
||||
args.requested_set[#args.requested_set + 1] = "dwarf-injection"
|
||||
end
|
||||
|
||||
-- Pass-flag handler. Reads the closed-set table, expands --all, appends to requested_set. Single-statement, no nesting.
|
||||
FLAG_HANDLERS[PASS_FLAG_DISPATCH_KEY] = function(args, a)
|
||||
@@ -582,8 +602,8 @@ local function render_dep_graph(passes, requested, closed)
|
||||
add(" |<base>.macs.h | |<base>.errors | |<base>.offsets| |<base>.static |")
|
||||
add(" | (header) | | .h | | .h | | _analysis |")
|
||||
add(" +------+-------+ | +annot.txt | | (header) | | .txt |")
|
||||
add(" | +------+-------+ +--------------+ +------+--------+")
|
||||
add(" v v v")
|
||||
add(" | +--+-----------+ +--------------+ +------+--------+")
|
||||
add(" v v v")
|
||||
add(" +------+----------------+ +------+-------+ |")
|
||||
add(" |offsets|static-analysis| |report| |<--------------------+")
|
||||
add(" | | | +------+-------+")
|
||||
|
||||
Reference in New Issue
Block a user