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 (
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// This file generates the per-function DWARF symbols the linker's DWARF
|
|
|
|
|
// pass requires of an assembly object, byte-identical to what cmd/asm
|
|
|
|
|
// emits: the .debug_line state-machine program (SDWARFLINES) and the
|
|
|
|
|
// subprogram DIE (SDWARFFCN). The linker copies the DIE and line-program
|
|
|
|
|
// bytes verbatim into .debug_info and .debug_line, fixing up their
|
|
|
|
|
// relocations, so the formats here must match cmd/internal/dwarf's
|
|
|
|
|
// DW_ABRV_FUNCTION and generateDebugLinesSymbol exactly.
|
|
|
|
|
//
|
|
|
|
|
// DWARF5 is assumed throughout (the toolchain's default on Linux and the
|
|
|
|
|
// other non-Darwin targets gasm supports).
|
|
|
|
|
|
|
|
|
|
// Line-program parameters (cmd/internal/obj/dwarf.go).
|
|
|
|
|
const (
|
|
|
|
|
dwLineBase = -4
|
|
|
|
|
dwLineRange = 10
|
|
|
|
|
dwOpcodeBase = 11
|
|
|
|
|
dwPCRange = (255 - dwOpcodeBase) / dwLineRange
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// goobjReloc is one relocation attached to an emitter-generated symbol
|
|
|
|
|
// (the DWARF lines/info symbols), in goobj's on-disk encoding fields.
|
|
|
|
|
type goobjReloc struct {
|
|
|
|
|
off int32
|
|
|
|
|
siz uint8
|
|
|
|
|
typ uint16
|
|
|
|
|
add int64
|
|
|
|
|
pkg uint32
|
|
|
|
|
sym uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// goobjDwarfLines builds the function's .debug_line state-machine program:
|
|
|
|
|
// an LNE_set_address extended opcode establishing the function's start
|
|
|
|
|
// address (carrying the R_ADDR relocation), one row per source line
|
|
|
|
|
// change across the function's instructions, an advance to the end of the
|
|
|
|
|
// function and an end-of-sequence opcode. The linker appends these bytes
|
|
|
|
|
// after the unit's line header, so they must start with the address and
|
|
|
|
|
// leave the state machine terminated.
|
|
|
|
|
func goobjDwarfLines(fn FuncLayout, fnNpIdx int) ([]byte, []goobjReloc) {
|
|
|
|
|
// Rows: the prologue, if any, then the body instructions (fn.Lines
|
|
|
|
|
// covers the body only). The first body offset > 0 means a prologue
|
|
|
|
|
// precedes it; the toolchain reports the prologue on the TEXT line.
|
|
|
|
|
pts := make([]LineEntry, 0, len(fn.Lines)+1)
|
|
|
|
|
if len(fn.Lines) == 0 || fn.Lines[0].Offset > 0 {
|
|
|
|
|
pts = append(pts, LineEntry{Offset: 0, Line: fn.Line})
|
|
|
|
|
}
|
|
|
|
|
pts = append(pts, fn.Lines...)
|
|
|
|
|
|
|
|
|
|
out := []byte{0, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0} // LNE_set_address, address zeroed
|
|
|
|
|
relocs := []goobjReloc{{
|
|
|
|
|
off: 3, siz: 8, typ: relocAddr,
|
|
|
|
|
pkg: pkgIdxNone, sym: uint32(fnNpIdx),
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
// The state machine starts at line 1, pc 0 (function-relative); the
|
|
|
|
|
// implicit initial pc is the function entry, so the first pc delta is
|
|
|
|
|
// against 0.
|
|
|
|
|
line := int64(1)
|
|
|
|
|
pc := uint64(0)
|
|
|
|
|
for _, p := range pts {
|
|
|
|
|
if p.Line == 0 || uint64(p.Offset) < pc {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// Rows mark source-line changes only; the pc delta is measured from
|
|
|
|
|
// the previous row, not the previous instruction.
|
|
|
|
|
if int64(p.Line) == line {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
deltaPC := uint64(p.Offset) - pc
|
|
|
|
|
deltaLC := int64(p.Line) - line
|
|
|
|
|
out = dwPutPCLCDelta(out, deltaPC, deltaLC)
|
|
|
|
|
line, pc = int64(p.Line), uint64(p.Offset)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cover the rest of the function and close the sequence.
|
|
|
|
|
if end := uint64(fn.Size) - pc; end > 0 {
|
|
|
|
|
out = append(out, 2) // DW_LNS_advance_pc
|
|
|
|
|
out = binary.AppendUvarint(out, end)
|
|
|
|
|
}
|
|
|
|
|
out = append(out, 0, 1, 1) // LNE_end_sequence
|
|
|
|
|
return out, relocs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dwPutPCLCDelta encodes one (pcDelta, lineDelta) step as the shortest
|
|
|
|
|
// special opcode plus any standard-opcode remainder, exactly like
|
|
|
|
|
// cmd/internal/obj's putpclcdelta.
|
|
|
|
|
func dwPutPCLCDelta(b []byte, deltaPC uint64, deltaLC int64) []byte {
|
|
|
|
|
opcode := dwSelectOpcode(deltaPC, deltaLC)
|
|
|
|
|
deltaPC -= uint64((opcode - dwOpcodeBase) / dwLineRange)
|
|
|
|
|
deltaLC -= (opcode-dwOpcodeBase)%dwLineRange + dwLineBase
|
|
|
|
|
|
|
|
|
|
// The remainder: standard opcodes first, then the special opcode
|
|
|
|
|
// (which emits the row).
|
|
|
|
|
if deltaPC != 0 {
|
|
|
|
|
switch {
|
|
|
|
|
case deltaPC <= uint64(dwPCRange):
|
|
|
|
|
opcode -= dwLineRange * int64(uint64(dwPCRange)-deltaPC)
|
|
|
|
|
b = append(b, 8) // DW_LNS_const_add_pc
|
|
|
|
|
case (1<<14) <= deltaPC && deltaPC < (1<<16):
|
|
|
|
|
b = append(b, 9) // DW_LNS_fixed_advance_pc
|
|
|
|
|
b = binary.LittleEndian.AppendUint16(b, uint16(deltaPC))
|
|
|
|
|
default:
|
|
|
|
|
b = append(b, 2) // DW_LNS_advance_pc
|
|
|
|
|
b = binary.AppendUvarint(b, deltaPC)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if deltaLC != 0 {
|
|
|
|
|
b = append(b, 3) // DW_LNS_advance_line
|
|
|
|
|
b = binary.AppendVarint(b, deltaLC)
|
|
|
|
|
}
|
|
|
|
|
return append(b, byte(opcode))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dwSelectOpcode picks the special opcode for (deltaPC, deltaLC) per
|
|
|
|
|
// cmd/internal/obj's putpclcdelta selection logic.
|
|
|
|
|
func dwSelectOpcode(deltaPC uint64, deltaLC int64) int64 {
|
|
|
|
|
switch {
|
|
|
|
|
case deltaLC < dwLineBase:
|
|
|
|
|
if deltaPC >= uint64(dwPCRange) {
|
|
|
|
|
return dwOpcodeBase + dwLineRange*dwPCRange
|
|
|
|
|
}
|
|
|
|
|
return dwOpcodeBase + dwLineRange*int64(deltaPC)
|
|
|
|
|
case deltaLC < dwLineBase+dwLineRange:
|
|
|
|
|
if deltaPC >= uint64(dwPCRange) {
|
|
|
|
|
op := int64(dwOpcodeBase) + (deltaLC - dwLineBase) + dwLineRange*dwPCRange
|
|
|
|
|
if op > 255 {
|
|
|
|
|
op -= dwLineRange
|
|
|
|
|
}
|
|
|
|
|
return op
|
|
|
|
|
}
|
|
|
|
|
return int64(dwOpcodeBase) + (deltaLC - dwLineBase) + dwLineRange*int64(deltaPC)
|
|
|
|
|
default:
|
|
|
|
|
if deltaPC <= uint64(dwPCRange) {
|
2026-08-29 15:40:31 +02:00
|
|
|
op := min(int64(dwOpcodeBase)+(dwLineRange-1)+dwLineRange*int64(deltaPC), 255)
|
2026-08-13 11:24:44 +02:00
|
|
|
return op
|
|
|
|
|
}
|
|
|
|
|
switch deltaPC - uint64(dwPCRange) {
|
|
|
|
|
case uint64(dwPCRange), (1 << 7) - 1, (1 << 16) - 1, (1 << 21) - 1,
|
|
|
|
|
(1 << 28) - 1, (1 << 35) - 1, (1 << 42) - 1, (1 << 49) - 1,
|
|
|
|
|
(1 << 56) - 1, (1 << 63) - 1:
|
|
|
|
|
return 255
|
|
|
|
|
default:
|
|
|
|
|
// 250: the toolchain's "249" comment is stale.
|
|
|
|
|
return dwOpcodeBase + dwLineRange*dwPCRange - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// goobjDwarfInfo builds the function's DWARF5 subprogram DIE (abbrev
|
|
|
|
|
// DW_ABRV_FUNCTION): name, low_pc as a .debug_addr index (the
|
|
|
|
|
// R_DWTXTADDR_U4 relocation), high_pc as the size, the call-frame-CFA
|
|
|
|
|
// frame base, the decl file/line and the external flag. name is the
|
|
|
|
|
// symbol's object name (package-qualified unless static).
|
|
|
|
|
func goobjDwarfInfo(fn FuncLayout, name string, fnNpIdx int) ([]byte, []goobjReloc) {
|
|
|
|
|
out := []byte{3} // DW_ABRV_FUNCTION
|
|
|
|
|
out = append(out, name...)
|
|
|
|
|
out = append(out, 0)
|
|
|
|
|
|
|
|
|
|
addrx := len(out)
|
|
|
|
|
out = append(out, 0, 0, 0, 0) // DW_AT_low_pc: addrx slot, zeroed
|
|
|
|
|
out = binary.AppendUvarint(out, uint64(fn.Size))
|
|
|
|
|
out = append(out, 1, 0x9c) // DW_AT_frame_base: block1, DW_OP_call_frame_cfa
|
|
|
|
|
out = binary.LittleEndian.AppendUint32(out, 1)
|
|
|
|
|
out = binary.AppendUvarint(out, uint64(fn.Line))
|
|
|
|
|
if fn.Static {
|
|
|
|
|
out = append(out, 0)
|
|
|
|
|
} else {
|
|
|
|
|
out = append(out, 1) // DW_AT_external
|
|
|
|
|
}
|
|
|
|
|
out = append(out, 0) // end of children
|
|
|
|
|
|
|
|
|
|
relocs := []goobjReloc{{
|
2026-08-20 15:35:39 +02:00
|
|
|
off: int32(addrx), siz: 4, typ: relocDWTXTADDRU4(),
|
2026-08-13 11:24:44 +02:00
|
|
|
pkg: pkgIdxNone, sym: uint32(fnNpIdx),
|
|
|
|
|
}}
|
|
|
|
|
return out, relocs
|
|
|
|
|
}
|