2026-08-13 11:24:44 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestDWSelectOpcode checks the special-opcode selection against
|
|
|
|
|
// hand-computed values for the boundary cases: line deltas below, inside
|
|
|
|
|
// and above the line range, and pc deltas at and beyond PC_RANGE (24).
|
|
|
|
|
func TestDWSelectOpcode(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
deltaPC uint64
|
|
|
|
|
deltaLC int64
|
|
|
|
|
want int64
|
|
|
|
|
}{
|
|
|
|
|
{0, 2, 17}, // the common single-instruction step
|
|
|
|
|
{0, -4, 11}, // deltaLC == LINE_BASE
|
|
|
|
|
{0, -5, 11}, // deltaLC below LINE_BASE: opcode adds nothing
|
|
|
|
|
{0, 6, 20}, // deltaLC == LINE_BASE+LINE_RANGE, remainder via advance_line
|
|
|
|
|
{4, 1, 56}, // the 4-byte loong64 instruction step
|
|
|
|
|
{23, 1, 246}, // deltaPC == PC_RANGE-1
|
|
|
|
|
{24, 1, 246}, // deltaPC == PC_RANGE: wraps past 255
|
|
|
|
|
{25, 1, 246}, // deltaPC past PC_RANGE (the const_add_pc remainder adjusts it later)
|
|
|
|
|
{100, 1, 246},
|
|
|
|
|
{151, 10, 255}, // deltaPC-PC_RANGE == (1<<7)-1, large line delta
|
|
|
|
|
{100, 10, 250}, // deltaPC-PC_RANGE not on a switch boundary
|
|
|
|
|
{23, 10, 250}, // large line delta inside PC_RANGE
|
|
|
|
|
}
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
if got := dwSelectOpcode(c.deltaPC, c.deltaLC); got != c.want {
|
|
|
|
|
t.Errorf("dwSelectOpcode(%d, %d) = %d, want %d", c.deltaPC, c.deltaLC, got, c.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decodeDWLineProgram decodes a .debug_line state-machine program (as
|
|
|
|
|
// emitted by goobjDwarfLines) into (pc, line) rows.
|
|
|
|
|
func decodeDWLineProgram(t *testing.T, b []byte) (pcs []uint64, lines []int64) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
pc, line := uint64(0), int64(1)
|
|
|
|
|
emit := func() {
|
|
|
|
|
if len(pcs) == 0 || pcs[len(pcs)-1] != pc || lines[len(lines)-1] != line {
|
|
|
|
|
pcs = append(pcs, pc)
|
|
|
|
|
lines = append(lines, line)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
advancePC := func(delta uint64) { pc += delta }
|
|
|
|
|
advanceLine := func(delta int64) { line += delta }
|
|
|
|
|
for i := 0; i < len(b); {
|
|
|
|
|
op := b[i]
|
|
|
|
|
i++
|
|
|
|
|
switch {
|
|
|
|
|
case op == 0: // extended opcode
|
|
|
|
|
ln, n := binary.Uvarint(b[i:])
|
|
|
|
|
i += n
|
|
|
|
|
sub := b[i]
|
|
|
|
|
i++
|
|
|
|
|
_ = ln
|
|
|
|
|
switch sub {
|
|
|
|
|
case 2: // DW_LNE_set_address: 8-byte address
|
|
|
|
|
pc = binary.LittleEndian.Uint64(b[i:])
|
|
|
|
|
i += 8
|
|
|
|
|
case 1: // DW_LNE_end_sequence
|
|
|
|
|
// terminates the sequence; no new row
|
|
|
|
|
}
|
|
|
|
|
case op == 2: // DW_LNS_advance_pc
|
|
|
|
|
v, n := binary.Uvarint(b[i:])
|
|
|
|
|
i += n
|
|
|
|
|
advancePC(v)
|
|
|
|
|
case op == 3: // DW_LNS_advance_line
|
|
|
|
|
v, n := binary.Varint(b[i:])
|
|
|
|
|
i += n
|
|
|
|
|
advanceLine(v)
|
|
|
|
|
case op == 8: // DW_LNS_const_add_pc
|
|
|
|
|
advancePC(uint64(dwPCRange))
|
|
|
|
|
case op == 9: // DW_LNS_fixed_advance_pc
|
|
|
|
|
advancePC(uint64(binary.LittleEndian.Uint16(b[i:])))
|
|
|
|
|
i += 2
|
|
|
|
|
case op >= dwOpcodeBase: // special opcode
|
|
|
|
|
advancePC(uint64((int64(op) - dwOpcodeBase) / dwLineRange))
|
|
|
|
|
advanceLine((int64(op)-dwOpcodeBase)%dwLineRange + dwLineBase)
|
|
|
|
|
emit()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return pcs, lines
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestGoobjDwarfLinesRows checks the emitted line program's rows for
|
|
|
|
|
// synthetic functions: a zero-frame function with one instruction per
|
|
|
|
|
// line, a framed function (the prologue row is prepended on the TEXT
|
|
|
|
|
// line), instructions sharing a line, and a function with a large pc gap
|
|
|
|
|
// (the const_add_pc remainder path).
|
|
|
|
|
func TestGoobjDwarfLinesRows(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn FuncLayout
|
|
|
|
|
want [][2]int64 // (pc, line)
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
"one instruction per line",
|
|
|
|
|
FuncLayout{Size: 20, Line: 2, Lines: []LineEntry{
|
|
|
|
|
{0, 3}, {4, 4}, {8, 5}, {12, 6}, {16, 7},
|
|
|
|
|
}},
|
|
|
|
|
[][2]int64{{0, 3}, {4, 4}, {8, 5}, {12, 6}, {16, 7}},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"framed: prologue row on the TEXT line",
|
|
|
|
|
FuncLayout{Size: 24, Line: 2, Lines: []LineEntry{
|
|
|
|
|
{12, 3}, {16, 4},
|
|
|
|
|
}},
|
|
|
|
|
[][2]int64{{0, 2}, {12, 3}, {16, 4}},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"instructions sharing a line fold into one row",
|
|
|
|
|
FuncLayout{Size: 16, Line: 2, Lines: []LineEntry{
|
|
|
|
|
{0, 3}, {4, 3}, {8, 4}, {12, 4},
|
|
|
|
|
}},
|
|
|
|
|
[][2]int64{{0, 3}, {8, 4}},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"large gap crosses PC_RANGE",
|
|
|
|
|
FuncLayout{Size: 60, Line: 2, Lines: []LineEntry{
|
|
|
|
|
{0, 3}, {40, 4},
|
|
|
|
|
}},
|
|
|
|
|
[][2]int64{{0, 3}, {40, 4}},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
t.Run(c.name, func(t *testing.T) {
|
|
|
|
|
prog, relocs := goobjDwarfLines(c.fn, 0)
|
|
|
|
|
if len(relocs) != 1 || relocs[0].off != 3 || relocs[0].siz != 8 || relocs[0].typ != relocAddr || relocs[0].sym != 0 {
|
|
|
|
|
t.Fatalf("relocs = %+v", relocs)
|
|
|
|
|
}
|
|
|
|
|
pcs, lines := decodeDWLineProgram(t, prog)
|
|
|
|
|
if len(pcs) != len(c.want) {
|
|
|
|
|
t.Fatalf("rows = %d (%v / %v), want %d", len(pcs), pcs, lines, len(c.want))
|
|
|
|
|
}
|
|
|
|
|
for i, w := range c.want {
|
|
|
|
|
if pcs[i] != uint64(w[0]) || lines[i] != w[1] {
|
|
|
|
|
t.Errorf("row %d = (%d, %d), want (%d, %d)", i, pcs[i], lines[i], w[0], w[1])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestGoobjDwarfInfo checks the subprogram DIE for an exported and a
|
|
|
|
|
// static function: the abbrev, name, high_pc, frame base, decl file/line,
|
|
|
|
|
// the external flag and the addrx relocation position.
|
|
|
|
|
func TestGoobjDwarfInfo(t *testing.T) {
|
|
|
|
|
fn := FuncLayout{Size: 20, Line: 2}
|
|
|
|
|
die, relocs := goobjDwarfInfo(fn, "pkg.f", 3)
|
|
|
|
|
want := []byte{
|
|
|
|
|
0x03,
|
|
|
|
|
'p', 'k', 'g', '.', 'f', 0,
|
|
|
|
|
0, 0, 0, 0, // addrx slot at offset 7
|
|
|
|
|
0x14, // high_pc: 20
|
|
|
|
|
0x01, 0x9c, // frame_base
|
|
|
|
|
0x01, 0, 0, 0, // decl_file 1
|
|
|
|
|
0x02, // decl_line 2
|
|
|
|
|
0x01, // external
|
|
|
|
|
0x00, // end of children
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(die, want) {
|
|
|
|
|
t.Errorf("DIE = %x, want %x", die, want)
|
|
|
|
|
}
|
2026-08-20 15:35:39 +02:00
|
|
|
if len(relocs) != 1 || relocs[0].off != 7 || relocs[0].siz != 4 || relocs[0].typ != relocDWTXTADDRU4() || relocs[0].sym != 3 {
|
2026-08-13 11:24:44 +02:00
|
|
|
t.Errorf("relocs = %+v", relocs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A static function carries no external flag and no package prefix.
|
|
|
|
|
fn.Static = true
|
|
|
|
|
die, _ = goobjDwarfInfo(fn, "f", 1)
|
|
|
|
|
if die[len(die)-2] != 0 {
|
|
|
|
|
t.Errorf("static external flag = %d, want 0", die[len(die)-2])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestDwPutPCLCDeltaRemainders checks the standard-opcode remainders:
|
|
|
|
|
// const_add_pc and fixed_advance_pc after a special opcode.
|
|
|
|
|
func TestDwPutPCLCDeltaRemainders(t *testing.T) {
|
|
|
|
|
// deltaPC 25 past PC_RANGE: opcode 26 covers (1, 1), const_add_pc
|
|
|
|
|
// covers the remaining 23 pc and 0 line.
|
|
|
|
|
got := dwPutPCLCDelta(nil, 25, 1)
|
|
|
|
|
if !bytes.Equal(got, []byte{8, 26}) {
|
|
|
|
|
t.Errorf("25/1 = %x, want [8 1a]", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// deltaPC 20000: opcode 246 covers 23, fixed_advance_pc covers the
|
|
|
|
|
// remaining 19977.
|
|
|
|
|
got = dwPutPCLCDelta(nil, 20000, 1)
|
|
|
|
|
if got[0] != 9 || binary.LittleEndian.Uint16(got[1:]) != 19977 || got[3] != 246 {
|
|
|
|
|
t.Errorf("20000/1 = %x, want fixed_advance_pc 19977 then 246", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Line remainder: deltaLC 10 leaves 5 past the opcode's reach, encoded
|
|
|
|
|
// as advance_line 5 (zigzag 0x0a) before opcode 250.
|
|
|
|
|
got = dwPutPCLCDelta(nil, 23, 10)
|
|
|
|
|
if !bytes.Equal(got, []byte{3, 0x0a, 250}) {
|
|
|
|
|
t.Errorf("23/10 = %x, want [03 0a fa]", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Negative line remainder: deltaLC -5 leaves advance_line -1 (zigzag
|
|
|
|
|
// 0x01) after opcode 11.
|
|
|
|
|
got = dwPutPCLCDelta(nil, 0, -5)
|
|
|
|
|
if !bytes.Equal(got, []byte{3, 1, 11}) {
|
|
|
|
|
t.Errorf("0/-5 = %x, want [03 01 0b]", got)
|
|
|
|
|
}
|
|
|
|
|
}
|