diff --git a/cmd/hunks/agent_cmds.go b/cmd/hunks/agent_cmds.go index 76ed212..98d9205 100644 --- a/cmd/hunks/agent_cmds.go +++ b/cmd/hunks/agent_cmds.go @@ -1853,7 +1853,7 @@ func runDumpCommand(proj *project.Project, section string, format string) (strin if rawName == "" { rawName = proj.AutoLabel(addr) } - if newName == rawName && strings.HasPrefix(newName, "LAB_") { + if newName == rawName && project.IsAutoLabel(newName) { continue } seg := proj.SegmentAtAddr(addr) diff --git a/internal/amiga/equates.go b/internal/amiga/equates.go index 18be441..fe01ce8 100644 --- a/internal/amiga/equates.go +++ b/internal/amiga/equates.go @@ -52,6 +52,31 @@ func GetTrapName(addr uint32) (string, bool) { return "", false } +// GetM68kVectorLabel returns the canonical 68000 exception, autovector, or +// TRAP vector name for a 4-byte-aligned vector table address in [$00..$BC]. +func GetM68kVectorLabel(addr uint32) (string, bool) { + if name, ok := m68kVectors[addr]; ok { + return name, true + } + return GetTrapName(addr) +} + +// ParseM68kVectorLabelAddr resolves a canonical 68000 vector name (e.g. +// "Level_3_Interrupt", "Privilege_Violation", "Trap_0") to its vector address. +func ParseM68kVectorLabelAddr(label string) (uint32, bool) { + for addr, name := range m68kVectors { + if strings.EqualFold(name, label) { + return addr, true + } + } + if len(label) >= 6 && strings.EqualFold(label[:5], "Trap_") { + if n, err := strconv.ParseUint(label[5:], 10, 32); err == nil && n < 16 { + return 0x80 + uint32(n)*4, true + } + } + return 0, false +} + var customRegs = map[uint32]string{ 0x000: "BLTDDAT", 0x002: "DMACONR", 0x004: "VPOSR", 0x006: "VHPOSR", 0x008: "DSKDATR", 0x00A: "JOY0DAT", diff --git a/internal/analyzer/analyzer.go b/internal/analyzer/analyzer.go index d10f9c3..3a14ce5 100644 --- a/internal/analyzer/analyzer.go +++ b/internal/analyzer/analyzer.go @@ -339,7 +339,8 @@ func (a *Analyzer) AddLabel(srcAddr *uint32, targetAddr uint32) { return } inMemRegion := a.IsInMemoryRegion(targetAddr) - if !a.IsInAnySegment(targetAddr) && !inMemRegion { + isFlatOut := srcAddr != nil && !a.IsRelocatableAt(*srcAddr) && !a.IsInAnyProjectSegment(targetAddr) && targetAddr > 0 && (targetAddr&0xFF000000) == 0 + if !a.IsInAnySegment(targetAddr) && !inMemRegion && !isFlatOut { return } @@ -348,7 +349,18 @@ func (a *Analyzer) AddLabel(srcAddr *uint32, targetAddr uint32) { isCrossSeg = !a.IsSameSegment(*srcAddr, targetAddr) && a.IsInAnySegment(targetAddr) } - if _, exists := a.LabelsCache[targetAddr]; exists { + if existing, exists := a.LabelsCache[targetAddr]; exists { + if isFlatOut && strings.HasPrefix(existing, "LAB_") { + if lbl, err := a.DB.GetLabelFull(targetAddr); err == nil && lbl != nil && lbl.IsAuto && lbl.Name == existing { + auto := a.Proj.AutoLabel(targetAddr) + _ = a.DB.SetLabel(targetAddr, auto, db.LabelLab, true) + if resolved, err := a.DB.GetAllLabelsResolved(); err == nil { + if r, ok := resolved[targetAddr]; ok { + a.LabelsCache[targetAddr] = r + } + } + } + } if isCrossSeg { if _, exp := a.ExportedCache[targetAddr]; !exp { a.ExportedCache[targetAddr] = struct{}{} diff --git a/internal/analyzer/analyzer_test.go b/internal/analyzer/analyzer_test.go index 6dfbbdb..febe0eb 100644 --- a/internal/analyzer/analyzer_test.go +++ b/internal/analyzer/analyzer_test.go @@ -1278,6 +1278,13 @@ func TestAbsShortXrefsAndRangeRollup(t *testing.T) { if len(rangeXrefs) != 3 { t.Fatalf("expected 3 xrefs in range 0x1000..0x1200, got %+v", rangeXrefs) } + + _ = ctrl.SetLabel(0x1178, "Matrix_Active", db.LabelLab, false) + a.ReanalyzeXrefs(0x1178) + cell1178, err := ctrl.GetCell(0x10008) + if err != nil || cell1178 == nil || cell1178.OpStr == nil || *cell1178.OpStr != "Matrix_Active.w,a0" { + t.Fatalf("expected EAAbsShort at 0x10008 to format as Matrix_Active.w,a0, got %+v", cell1178) + } } func TestTraceLowRAMTrampolineBouncing(t *testing.T) { diff --git a/internal/analyzer/format.go b/internal/analyzer/format.go index b5962e8..5a92922 100644 --- a/internal/analyzer/format.go +++ b/internal/analyzer/format.go @@ -129,6 +129,12 @@ func (a *Analyzer) formatOperand(addr uint32, insn *m68k.Insn, op m68k.Operand, if hw, ok := amiga.GetAmigaHWOperand(target); ok { return hw + ".w" } + if (!hunkMode || !a.IsInAnyProjectSegment(target)) && insn.Op != m68k.OpPEA && int16(uint16(target)) > 0 { + label := a.GetLabelName(target) + if label != "" && !a.PointsMidCell(target) { + return label + ".w" + } + } return op.FormatASM(insn.Op) case m68k.EAAbsLong: diff --git a/internal/analyzer/stage2_code.go b/internal/analyzer/stage2_code.go index c53bf40..428230e 100644 --- a/internal/analyzer/stage2_code.go +++ b/internal/analyzer/stage2_code.go @@ -183,9 +183,17 @@ func (a *Analyzer) stage2DiscoverCode(initialEntries []uint32, jumpTable map[uin } src := addr shouldLabel := op.Mode != m68k.EAAbsShort && a.isValidTarget(ref.Target) - if op.Mode == m68k.EAImm && !a.IsInAnyProjectSegment(ref.Target) { + if op.Mode == m68k.EAImm && !a.IsInAnyProjectSegment(ref.Target) && !a.IsInMemoryRegion(ref.Target) { shouldLabel = false } + if !shouldLabel && !a.IsRelocatableAt(addr) && !a.IsInAnyProjectSegment(ref.Target) && ref.Target > 0 && (ref.Target&0xFF000000) == 0 { + switch op.Mode { + case m68k.EAAbsLong, m68k.EAImm: + shouldLabel = true + case m68k.EAAbsShort: + shouldLabel = int16(uint16(op.UVal)) >= 0x100 + } + } switch ref.Kind { case RefCodeCall: target := ref.Target diff --git a/internal/callgraph/callgraph.go b/internal/callgraph/callgraph.go index 8c32041..bbd0374 100644 --- a/internal/callgraph/callgraph.go +++ b/internal/callgraph/callgraph.go @@ -412,7 +412,7 @@ func Build(proj *project.Project) (*CallGraph, error) { } for lAddr, lName := range labels { rawName := rawLabels[lAddr] - if (lName != rawName || !strings.HasPrefix(lName, "LAB_")) && !branchTargets[lAddr] && isCodeAt(lAddr) { + if (lName != rawName || !project.IsAutoLabel(lName)) && !branchTargets[lAddr] && isCodeAt(lAddr) { funcStartsSet[lAddr] = true } } @@ -535,7 +535,7 @@ func Build(proj *project.Project) (*CallGraph, error) { if !ok || name == "" { name = rawName } - isRenamed := name != rawName || !strings.HasPrefix(name, "LAB_") + isRenamed := name != rawName || !project.IsAutoLabel(name) isExported := exportedSet[entryAddr] var blockComment *string diff --git a/internal/db/controller.go b/internal/db/controller.go index fb19a1c..dede1ac 100644 --- a/internal/db/controller.go +++ b/internal/db/controller.go @@ -719,8 +719,13 @@ func (c *Controller) GetExportedLabelsInRange(startAddr, endAddr uint32) ([]stri func (c *Controller) GetAllLabelsResolved() (map[uint32]string, error) { m := make(map[uint32]string) err := c.forEachSegRow( - `SELECT l.addr, COALESCE(r.renamed, l.name) - FROM labels l LEFT JOIN renames r ON r.seg = l.seg AND r.original = l.name + `SELECT l.addr, COALESCE(r.renamed, r2.renamed, l.name) + FROM labels l + LEFT JOIN renames r ON r.seg = l.seg AND r.original = l.name + LEFT JOIN renames r2 ON r2.seg = l.seg AND ( + (l.name LIKE 'EQU_%' AND r2.original = 'LAB_' || SUBSTR(l.name, 5)) OR + (l.name LIKE 'LAB_%' AND r2.original = 'EQU_' || SUBSTR(l.name, 5)) + ) WHERE l.seg = ?1 ORDER BY l.addr ASC`, true, func(rows *sql.Rows) error { diff --git a/internal/emitter/emitter_test.go b/internal/emitter/emitter_test.go index b1b1232..9737f9a 100644 --- a/internal/emitter/emitter_test.go +++ b/internal/emitter/emitter_test.go @@ -673,3 +673,116 @@ func TestIncbinInteriorLabelsEmitEquates(t *testing.T) { t.Fatalf("expected interior label LAB_1000C equ CarvedBlob+$4 after incbin, got:\n%s", text) } } + +func TestOutOfSegmentEquPrefixAndStaticVectorEquates(t *testing.T) { + dir := t.TempDir() + dbPath := filepath.Join(dir, "test.db") + ctrl, err := db.New(dbPath) + if err != nil { + t.Fatal(err) + } + defer ctrl.Close() + + binPath := filepath.Join(dir, "flat.bin") + rawBytes := make([]byte, 32) + if err := os.WriteFile(binPath, rawBytes, 0o644); err != nil { + t.Fatal(err) + } + + sz := uint32(len(rawBytes)) + proj := project.New(filepath.Join(dir, "project.json")) + proj.Data.Segments = append(proj.Data.Segments, project.Segment{ + Name: "main", + HunkIndex: 0, + HunkType: hunk.HunkTypeCode, + MemoryType: hunk.MemoryPublic, + AllocSize: sz, + FileDataLen: &sz, + File: &binPath, + Base: 0xE700, + DB: dbPath, + Relocatable: false, + }) + proj.Data.Spaces = append(proj.Data.Spaces, project.Space{ + Name: "default", + Segments: []string{"main"}, + }) + proj.Data.ActiveSpace = "default" + proj.ActiveSegmentName = "main" + proj.SyncDBScope(ctrl) + + if got := proj.AutoLabel(0x006C); got != "Level_3_Interrupt" { + t.Fatalf("AutoLabel(0x6C) = %q, want Level_3_Interrupt", got) + } + if got := proj.AutoLabel(0x0080); got != "Trap_0" { + t.Fatalf("AutoLabel(0x80) = %q, want Trap_0", got) + } + if got := proj.AutoLabel(0x7B08A); got != "EQU_7B08A" { + t.Fatalf("AutoLabel(0x7B08A) = %q, want EQU_7B08A", got) + } + if got := proj.AutoLabel(0xE700); got != "LAB_0E700" { + t.Fatalf("AutoLabel(0xE700) = %q, want LAB_0E700", got) + } + + for _, addr := range []uint32{0x0020, 0x006C, 0x0080, 0x00FF, 0x3D80, 0x7A01A, 0x7B08A} { + _ = ctrl.SetLabel(addr, proj.AutoLabel(addr), db.LabelLab, true) + } + _ = ctrl.SetRename(proj.AutoLabel(0x7A01A), "EdgeSpan_DirectoryTable") + + mkCode := func(addr, size uint32, mnem, op string) *db.Cell { + m := mnem + o := op + return &db.Cell{ + Seg: "main", + Addr: addr, + Size: size, + Type: db.CellCode, + Confidence: 100, + Mnemonic: &m, + OpStr: &o, + RawBytes: make([]byte, size), + } + } + _ = ctrl.InsertCell(mkCode(0xE700, 6, "move.l", "a0,$6C.l")) + _ = ctrl.InsertCell(mkCode(0xE706, 6, "movea.l", "#$7B08A,a1")) + _ = ctrl.InsertCell(mkCode(0xE70C, 6, "move.l", "$3D80.l,d0")) + _ = ctrl.InsertCell(mkCode(0xE712, 6, "movea.l", "#$7A01A,a2")) + + em, err := New(proj, ctrl) + if err != nil { + t.Fatal(err) + } + if err := em.GenerateItems(false); err != nil { + t.Fatal(err) + } + text := strings.Join(em.FormatLines(), "\n") + for _, want := range []string{ + "Privilege_Violation\tequ\t$20", + "Level_3_Interrupt\tequ\t$6C", + "Trap_0\tequ\t$80", + "EQU_000FF\tequ\t$FF", + "EQU_03D80\tequ\t$3D80", + "EdgeSpan_DirectoryTable\tequ\t$7A01A", + "EQU_7B08A\tequ\t$7B08A", + "move.l\ta0,Level_3_Interrupt.l", + "movea.l\t#EQU_7B08A,a1", + "move.l\tEQU_03D80.l,d0", + "movea.l\t#EdgeSpan_DirectoryTable,a2", + } { + if !strings.Contains(text, want) { + t.Errorf("missing %q in emitted output:\n%s", want, text) + } + } + for _, unwanted := range []string{"LAB_0006C", "LAB_7B08A", "LAB_03D80"} { + if strings.Contains(text, unwanted) { + t.Errorf("unexpected legacy %q in emitted output:\n%s", unwanted, text) + } + } + + if addr, ok := proj.ResolveTarget(ctrl, "EQU_7B08A"); !ok || addr != 0x7B08A { + t.Errorf("ResolveTarget(EQU_7B08A) = ($%X, %v), want ($7B08A, true)", addr, ok) + } + if addr, ok := proj.ResolveTarget(ctrl, "Level_3_Interrupt"); !ok || addr != 0x6C { + t.Errorf("ResolveTarget(Level_3_Interrupt) = ($%X, %v), want ($6C, true)", addr, ok) + } +} diff --git a/internal/project/project.go b/internal/project/project.go index 1c8fef9..3afa938 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -9,10 +9,21 @@ import ( "strconv" "strings" + "github.com/mikolas/hunks/internal/amiga" "github.com/mikolas/hunks/internal/db" "github.com/mikolas/hunks/internal/hunk" ) +// IsAutoLabel reports whether name is an auto-generated symbol ("LAB_...", "EQU_...") +// or a canonical 68000 exception/interrupt/trap vector equate name. +func IsAutoLabel(name string) bool { + if strings.HasPrefix(name, "LAB_") || strings.HasPrefix(name, "EQU_") { + return true + } + _, isVec := amiga.ParseM68kVectorLabelAddr(name) + return isVec +} + func formatHex(v uint32) string { return fmt.Sprintf("0x%X", v) } @@ -273,6 +284,12 @@ func (s *Segment) AutoLabel(addr uint32) string { } return fmt.Sprintf("LAB_%d_%05X", idx, offset) } + if s.EndAddr() > s.Base && !s.Contains(addr) { + if vec, ok := amiga.GetM68kVectorLabel(addr); ok { + return vec + } + return fmt.Sprintf("EQU_%05X", addr) + } return fmt.Sprintf("LAB_%05X", addr) } @@ -538,6 +555,12 @@ func (p *Project) AutoLabel(addr uint32) string { return p.Data.Segments[i].AutoLabel(addr) } } + if len(p.Data.Segments) > 0 { + if vec, ok := amiga.GetM68kVectorLabel(addr); ok { + return vec + } + return fmt.Sprintf("EQU_%05X", addr) + } return fmt.Sprintf("LAB_%05X", addr) } @@ -768,12 +791,18 @@ func (p *Project) ResolveTarget(ctrl *db.Controller, input string) (uint32, bool } } - // 5. Pattern `LAB__` even if not in DB labels table - rest, hasLab := strings.CutPrefix(s, "LAB_") - if !hasLab { - rest, hasLab = strings.CutPrefix(s, "lab_") + // 5. Pattern `LAB__`, `LAB_`, `EQU_`, or known vector/HW equate + rest, hasPrefix := strings.CutPrefix(s, "LAB_") + if !hasPrefix { + rest, hasPrefix = strings.CutPrefix(s, "lab_") + } + if !hasPrefix { + rest, hasPrefix = strings.CutPrefix(s, "EQU_") + } + if !hasPrefix { + rest, hasPrefix = strings.CutPrefix(s, "equ_") } - if hasLab { + if hasPrefix { if hunkStr, offStr, hasUnderscore := strings.Cut(rest, "_"); hasUnderscore { hunkIdx, err1 := strconv.Atoi(hunkStr) off, err2 := strconv.ParseUint(offStr, 16, 32) @@ -785,8 +814,16 @@ func (p *Project) ResolveTarget(ctrl *db.Controller, input string) (uint32, bool } } } + } else if val, err := strconv.ParseUint(rest, 16, 32); err == nil { + return uint32(val), true } } + if vecAddr, ok := amiga.ParseM68kVectorLabelAddr(s); ok { + return vecAddr, true + } + if hwAddr, ok := amiga.ParseHWLabelAddr(s); ok { + return hwAddr, true + } // 6. Explicit hex `0x...`, `$...`, or bare hex if v, err := ParseHex(s); err == nil {