blob: 8a12849d77255be30b30a79d51acacba773152e8 [file] [log] [blame]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Writes dwarf information to object files.
package obj
import (
"cmd/internal/dwarf"
"cmd/internal/src"
"fmt"
)
// Generate a sequence of opcodes that is as short as possible.
// See section 6.2.5
const (
LINE_BASE = -4
LINE_RANGE = 10
PC_RANGE = (255 - OPCODE_BASE) / LINE_RANGE
OPCODE_BASE = 11
)
// generateDebugLinesSymbol fills the debug lines symbol of a given function.
//
// It's worth noting that this function doesn't generate the full debug_lines
// DWARF section, saving that for the linker. This function just generates the
// state machine part of debug_lines. The full table is generated by the
// linker. Also, we use the file numbers from the full package (not just the
// function in question) when generating the state machine. We do this so we
// don't have to do a fixup on the indices when writing the full section.
func (ctxt *Link) generateDebugLinesSymbol(s, lines *LSym) {
dctxt := dwCtxt{ctxt}
// The Pcfile table is used to generate the debug_lines section, and the file
// indices for that data could differ from the files we write out for the
// debug_lines section. Here we generate a LUT between those two indices.
fileNums := make(map[int32]int64)
for i, filename := range s.Func.Pcln.File {
if symbolIndex := ctxt.PosTable.FileIndex(filename); symbolIndex >= 0 {
fileNums[int32(i)] = int64(symbolIndex) + 1
} else {
panic(fmt.Sprintf("First time we've seen filename: %q", filename))
}
}
// Set up the debug_lines state machine to the default values
// we expect at the start of a new sequence.
stmt := true
line := int64(1)
pc := s.Func.Text.Pc
var lastpc int64 // last PC written to line table, not last PC in func
name := ""
prologue, wrotePrologue := false, false
// Walk the progs, generating the DWARF table.
for p := s.Func.Text; p != nil; p = p.Link {
prologue = prologue || (p.Pos.Xlogue() == src.PosPrologueEnd)
// If we're not at a real instruction, keep looping!
if p.Pos.Line() == 0 || (p.Link != nil && p.Link.Pc == p.Pc) {
continue
}
newStmt := p.Pos.IsStmt() != src.PosNotStmt
newName, newLine := linkgetlineFromPos(ctxt, p.Pos)
// Output debug info.
wrote := false
if name != newName {
newFile := ctxt.PosTable.FileIndex(newName) + 1 // 1 indexing for the table.
dctxt.AddUint8(lines, dwarf.DW_LNS_set_file)
dwarf.Uleb128put(dctxt, lines, int64(newFile))
name = newName
wrote = true
}
if prologue && !wrotePrologue {
dctxt.AddUint8(lines, uint8(dwarf.DW_LNS_set_prologue_end))
wrotePrologue = true
wrote = true
}
if stmt != newStmt {
dctxt.AddUint8(lines, uint8(dwarf.DW_LNS_negate_stmt))
stmt = newStmt
wrote = true
}
if line != int64(newLine) || wrote {
pcdelta := p.Pc - pc
lastpc = p.Pc
putpclcdelta(ctxt, dctxt, lines, uint64(pcdelta), int64(newLine)-line)
line, pc = int64(newLine), p.Pc
}
}
// Because these symbols will be concatenated together by the
// linker, we need to reset the state machine that controls the
// debug symbols. Do this using an end-of-sequence operator.
//
// Note: at one point in time, Delve did not support multiple end
// sequence ops within a compilation unit (bug for this:
// https://github.com/go-delve/delve/issues/1694), however the bug
// has since been fixed (Oct 2019).
//
// Issue 38192: the DWARF standard specifies that when you issue
// an end-sequence op, the PC value should be one past the last
// text address in the translation unit, so apply a delta to the
// text address before the end sequence op. If this isn't done,
// GDB will assign a line number of zero the last row in the line
// table, which we don't want.
lastlen := uint64(s.Size - (lastpc - s.Func.Text.Pc))
dctxt.AddUint8(lines, dwarf.DW_LNS_advance_pc)
dwarf.Uleb128put(dctxt, lines, int64(lastlen))
dctxt.AddUint8(lines, 0) // start extended opcode
dwarf.Uleb128put(dctxt, lines, 1)
dctxt.AddUint8(lines, dwarf.DW_LNE_end_sequence)
}
func putpclcdelta(linkctxt *Link, dctxt dwCtxt, s *LSym, deltaPC uint64, deltaLC int64) {
// Choose a special opcode that minimizes the number of bytes needed to
// encode the remaining PC delta and LC delta.
var opcode int64
if deltaLC < LINE_BASE {
if deltaPC >= PC_RANGE {
opcode = OPCODE_BASE + (LINE_RANGE * PC_RANGE)
} else {
opcode = OPCODE_BASE + (LINE_RANGE * int64(deltaPC))
}
} else if deltaLC < LINE_BASE+LINE_RANGE {
if deltaPC >= PC_RANGE {
opcode = OPCODE_BASE + (deltaLC - LINE_BASE) + (LINE_RANGE * PC_RANGE)
if opcode > 255 {
opcode -= LINE_RANGE
}
} else {
opcode = OPCODE_BASE + (deltaLC - LINE_BASE) + (LINE_RANGE * int64(deltaPC))
}
} else {
if deltaPC <= PC_RANGE {
opcode = OPCODE_BASE + (LINE_RANGE - 1) + (LINE_RANGE * int64(deltaPC))
if opcode > 255 {
opcode = 255
}
} else {
// Use opcode 249 (pc+=23, lc+=5) or 255 (pc+=24, lc+=1).
//
// Let x=deltaPC-PC_RANGE. If we use opcode 255, x will be the remaining
// deltaPC that we need to encode separately before emitting 255. If we
// use opcode 249, we will need to encode x+1. If x+1 takes one more
// byte to encode than x, then we use opcode 255.
//
// In all other cases x and x+1 take the same number of bytes to encode,
// so we use opcode 249, which may save us a byte in encoding deltaLC,
// for similar reasons.
switch deltaPC - PC_RANGE {
// PC_RANGE is the largest deltaPC we can encode in one byte, using
// DW_LNS_const_add_pc.
//
// (1<<16)-1 is the largest deltaPC we can encode in three bytes, using
// DW_LNS_fixed_advance_pc.
//
// (1<<(7n))-1 is the largest deltaPC we can encode in n+1 bytes for
// n=1,3,4,5,..., using DW_LNS_advance_pc.
case PC_RANGE, (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:
opcode = 255
default:
opcode = OPCODE_BASE + LINE_RANGE*PC_RANGE - 1 // 249
}
}
}
if opcode < OPCODE_BASE || opcode > 255 {
panic(fmt.Sprintf("produced invalid special opcode %d", opcode))
}
// Subtract from deltaPC and deltaLC the amounts that the opcode will add.
deltaPC -= uint64((opcode - OPCODE_BASE) / LINE_RANGE)
deltaLC -= (opcode-OPCODE_BASE)%LINE_RANGE + LINE_BASE
// Encode deltaPC.
if deltaPC != 0 {
if deltaPC <= PC_RANGE {
// Adjust the opcode so that we can use the 1-byte DW_LNS_const_add_pc
// instruction.
opcode -= LINE_RANGE * int64(PC_RANGE-deltaPC)
if opcode < OPCODE_BASE {
panic(fmt.Sprintf("produced invalid special opcode %d", opcode))
}
dctxt.AddUint8(s, dwarf.DW_LNS_const_add_pc)
} else if (1<<14) <= deltaPC && deltaPC < (1<<16) {
dctxt.AddUint8(s, dwarf.DW_LNS_fixed_advance_pc)
dctxt.AddUint16(s, uint16(deltaPC))
} else {
dctxt.AddUint8(s, dwarf.DW_LNS_advance_pc)
dwarf.Uleb128put(dctxt, s, int64(deltaPC))
}
}
// Encode deltaLC.
if deltaLC != 0 {
dctxt.AddUint8(s, dwarf.DW_LNS_advance_line)
dwarf.Sleb128put(dctxt, s, deltaLC)
}
// Output the special opcode.
dctxt.AddUint8(s, uint8(opcode))
}