The relocation type number shifted between Go 1.26 (103) and Go 1.27 (106) because new LoongArch relocations were inserted. Detect the Go version at runtime and use the correct value.
189 lines
6.4 KiB
Go
189 lines
6.4 KiB
Go
// 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) {
|
|
op := int64(dwOpcodeBase) + (dwLineRange - 1) + dwLineRange*int64(deltaPC)
|
|
if op > 255 {
|
|
op = 255
|
|
}
|
|
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{{
|
|
off: int32(addrx), siz: 4, typ: relocDWTXTADDRU4(),
|
|
pkg: pkgIdxNone, sym: uint32(fnNpIdx),
|
|
}}
|
|
return out, relocs
|
|
}
|