Gopher2600/coprocessor/developer/dwarf/dwarf_loclist.go
JetSetIlly cd2a00d4ba logger.Log() and logger.Logf() now require a logger.Permission instance
the logger.Permission interface indicates whether the environment making
the logging request is allowed to create new log entries. the
environment.Environment type satisifies the Permission interface

logger.Allow is provided as a convienient way of indicating the the log
entry should always be created
2024-04-30 11:23:40 +01:00

366 lines
10 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package dwarf
import (
"debug/elf"
"encoding/binary"
"fmt"
"io"
"github.com/jetsetilly/gopher2600/coprocessor"
"github.com/jetsetilly/gopher2600/logger"
)
type loclistSection struct {
coproc coprocessor.CartCoProc
byteOrder binary.ByteOrder
data []uint8
}
func newLoclistSectionFromFile(ef *elf.File, coproc coprocessor.CartCoProc) (*loclistSection, error) {
sec := ef.Section(".debug_loc")
if sec == nil {
return nil, fmt.Errorf("no .debug_loc section")
}
data, err := sec.Data()
if err != nil {
return nil, err
}
return newLoclistSection(data, ef.ByteOrder, coproc)
}
func newLoclistSection(data []uint8, byteOrder binary.ByteOrder, coproc coprocessor.CartCoProc) (*loclistSection, error) {
sec := &loclistSection{
data: data,
coproc: coproc,
byteOrder: byteOrder,
}
if len(data) == 0 {
return nil, fmt.Errorf("empty .debug_loc section")
}
return sec, nil
}
// loclistFramebase provides context to the location list. implemented by
// SourceVariable, SourceFunction and the frame section.
type loclistFramebase interface {
framebase() (uint64, error)
}
type loclistStackClass int
const (
stackClassNOP loclistStackClass = iota
stackClassPush
stackClassIsValue
stackClassSingleAddress
stackClassPiece
)
type loclistStack struct {
class loclistStackClass
value uint32
}
type loclistPiece struct {
value uint32
isAddress bool
size uint32
}
type loclistOperator struct {
operator string
resolve func(*loclist) (loclistStack, error)
}
type loclistResult struct {
address uint64
hasAddress bool
value uint32
// the result only has pieces for composite variable types (ie. structs, etc.)
pieces []loclistPiece
}
type loclist struct {
coproc coprocessor.CartCoProc
ctx loclistFramebase
list []loclistOperator
stack []loclistStack
pieces []loclistPiece
singleLoc bool
loclistPtr int64
// the derivation for the loclist is written to the io.Writer
derivation io.Writer
}
func (sec *loclistSection) newLoclistJustContext(ctx loclistFramebase) *loclist {
return &loclist{
coproc: sec.coproc,
ctx: ctx,
singleLoc: true,
}
}
func (sec *loclistSection) newLoclistFromSingleOperator(ctx loclistFramebase, expr []uint8) (*loclist, error) {
loc := &loclist{
coproc: sec.coproc,
ctx: ctx,
singleLoc: true,
}
op, n, err := sec.decodeLoclistOperation(expr)
if err != nil {
return nil, err
}
if n == 0 {
return nil, fmt.Errorf("unhandled expression operator %02x", expr[0])
}
loc.list = append(loc.list, op)
return loc, nil
}
type commitLoclist func(start, end uint64, loc *loclist)
func (sec *loclistSection) newLoclist(ctx loclistFramebase, ptr int64,
compilationUnitAddress uint64, commit commitLoclist) error {
// "Location lists, which are used to describe objects that have a limited lifetime or change
// their location during their lifetime. Location lists are more completely described below."
// page 26 of "DWARF4 Standard"
//
// "Location lists are used in place of location expressions whenever the object whose location is
// being described can change location during its lifetime. Location lists are contained in a separate
// object file section called .debug_loc . A location list is indicated by a location attribute whose
// value is an offset from the beginning of the .debug_loc section to the first byte of the list for the
// object in question"
// page 30 of "DWARF4 Standard"
//
// "loclistptr: This is an offset into the .debug_loc section (DW_FORM_sec_offset). It consists
// of an offset from the beginning of the .debug_loc section to the first byte of the data making up
// the location list for the compilation unit. It is relocatable in a relocatable object file, and
// relocated in an executable or shared object. In the 32-bit DWARF format, this offset is a 4-
// byte unsigned value; in the 64-bit DWARF format, it is an 8-byte unsigned value (see
// Section 7.4)"
// page 148 of "DWARF4 Standard"
// "The applicable base address of a location list entry is determined by the closest preceding base
// address selection entry (see below) in the same location list. If there is no such selection entry,
// then the applicable base address defaults to the base address of the compilation unit (see
// Section 3.1.1)"
//
// "A base address selection entry affects only the list in which it is contained"
// page 31 of "DWARF4 Standard"
baseAddress := compilationUnitAddress
loclistNumber := ptr
// start and end address. this will be updated at the end of every for loop iteration
startAddress := uint64(sec.byteOrder.Uint32(sec.data[ptr:]))
ptr += 4
endAddress := uint64(sec.byteOrder.Uint32(sec.data[ptr:]))
ptr += 4
// "The end of any given location list is marked by an end of list entry, which consists of a 0 for the
// beginning address offset and a 0 for the ending address offset. A location list containing only an
// end of list entry describes an object that exists in the source code but not in the executable
// program". page 31 of "DWARF4 Standard"
for !(startAddress == 0x0 && endAddress == 0x0) {
loc := &loclist{
coproc: sec.coproc,
ctx: ctx,
loclistPtr: loclistNumber,
}
// "A base address selection entry consists of:
// 1. The value of the largest representable address offset (for example, 0xffffffff when the size of
// an address is 32 bits).
// 2. An address, which defines the appropriate base address for use in interpreting the beginning
// and ending address offsets of subsequent entries of the location list"
// page 31 of "DWARF4 Standard"
if startAddress == 0xffffffff {
baseAddress = endAddress
} else {
// reduce end address by one. this is because the value we've read "marks the
// first address past the end of the address range over which the location is
// valid" (page 30 of "DWARF4 Standard")
endAddress -= 1
// length of expression
length := int(sec.byteOrder.Uint16(sec.data[ptr:]))
ptr += 2
// loop through stack operations
for length > 0 {
r, n, err := sec.decodeLoclistOperation(sec.data[ptr:])
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("unhandled expression operator %02x", sec.data[ptr])
}
// add resolver to variable
loc.addOperator(r)
// reduce length value
length -= n
// advance sec pointer by length value
ptr += int64(n)
}
// "A location list entry (but not a base address selection or end of list entry) whose beginning
// and ending addresses are equal has no effect because the size of the range covered by such
// an entry is zero". page 31 of "DWARF4 Standard"
//
// "The ending address must be greater than or equal to the beginning address"
// page 30 of "DWARF4 Standard"
if startAddress < endAddress {
commit(startAddress+baseAddress, endAddress+baseAddress, loc)
}
}
// update loclist number
loclistNumber = ptr
// read next address range
startAddress = uint64(sec.byteOrder.Uint32(sec.data[ptr:]))
ptr += 4
endAddress = uint64(sec.byteOrder.Uint32(sec.data[ptr:]))
ptr += 4
}
return nil
}
func (loc *loclist) addOperator(r loclistOperator) {
loc.list = append(loc.list, r)
}
func (loc *loclist) resolve() (loclistResult, error) {
if loc.ctx == nil {
return loclistResult{}, fmt.Errorf("no context [%x]", loc.loclistPtr)
}
if len(loc.list) == 0 {
return loclistResult{}, fmt.Errorf("no loclist operations defined [%x]", loc.loclistPtr)
}
// clear lists
loc.stack = loc.stack[:0]
loc.pieces = loc.pieces[:0]
// whether the top of the stack is a value or an address
var isValue bool
// resolve every entry in the loclist
for i := range loc.list {
s, err := loc.list[i].resolve(loc)
if err != nil {
return loclistResult{}, fmt.Errorf("%s: %w", loc.list[i].operator, err)
}
// process result according to the result class
switch s.class {
case stackClassNOP:
// do nothing
case stackClassPush:
loc.stack = append(loc.stack, s)
case stackClassIsValue:
loc.stack = append(loc.stack, s)
isValue = true
case stackClassSingleAddress:
r := loclistResult{
address: uint64(s.value),
hasAddress: true,
}
var ok bool
r.value, ok = loc.coproc.Peek(s.value)
if !ok {
return loclistResult{}, fmt.Errorf("%s: error resolving address %08x", loc.list[i].operator, s.value)
}
return r, nil
case stackClassPiece:
// all functionality of a piece operation is contained in the actual loclistOperator implementation
}
if loc.derivation != nil {
loc.derivation.Write([]byte(fmt.Sprintf("%s %08x", loc.list[i].operator, s.value)))
}
}
// return assembled pieces
if len(loc.pieces) > 0 {
var r loclistResult
r.pieces = append(r.pieces, loc.pieces...)
return r, nil
}
// no pieces so just use top of stack
if len(loc.stack) == 0 {
return loclistResult{}, fmt.Errorf("stack is empty [%x]", loc.loclistPtr)
}
// stack should only have one entry in it
if len(loc.stack) > 1 {
logger.Logf(logger.Allow, "dwarf", "loclist stack has more than one entry after resolve [%x]", loc.loclistPtr)
}
// top of stack is the result
s := loc.stack[len(loc.stack)-1]
// is the top of the stack a valid value or is it an address
if isValue {
r := loclistResult{
value: s.value,
}
return r, nil
}
// top of the stack is an address. how this address is interpreted depends
// on context
return loclistResult{
address: uint64(s.value),
hasAddress: true,
}, nil
}
func (loc *loclist) peek() loclistStack {
if len(loc.stack) == 0 {
return loclistStack{}
}
return loc.stack[len(loc.stack)-1]
}
func (loc *loclist) pop() (loclistStack, bool) {
l := len(loc.stack)
if l == 0 {
return loclistStack{}, false
}
s := loc.stack[l-1]
loc.stack = loc.stack[:l-1]
return s, true
}