delve/pkg/dwarf/op/op.go
Alessandro Arzilli 5660f9a415
dwarf/op,proc: output register name when printing location exprs (#3052)
Output the architecture specific register name when printing location
expression after DW_OP_regN and DW_OP_bregN operators.
2022-07-15 14:56:00 +02:00

572 lines
14 KiB
Go

package op
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/go-delve/delve/pkg/dwarf/util"
)
// Opcode represent a DWARF stack program instruction.
// See ./opcodes.go for a full list.
type Opcode byte
//go:generate go run ../../../_scripts/gen-opcodes.go opcodes.table opcodes.go
type stackfn func(Opcode, *context) error
type ReadMemoryFunc func([]byte, uint64) (int, error)
type context struct {
buf *bytes.Buffer
prog []byte
stack []int64
pieces []Piece
ptrSize int
DwarfRegisters
readMemory ReadMemoryFunc
}
// Piece is a piece of memory stored either at an address or in a register.
type Piece struct {
Size int
Kind PieceKind
Val uint64
Bytes []byte
}
// PieceKind describes the kind of a piece.
type PieceKind uint8
const (
AddrPiece PieceKind = iota // The piece is stored in memory, Val is the address
RegPiece // The piece is stored in a register, Val is the register number
ImmPiece // The piece is an immediate value, Val or Bytes is the value
)
var (
ErrStackUnderflow = errors.New("DWARF stack underflow")
ErrStackIndexOutOfBounds = errors.New("DWARF stack index out of bounds")
ErrMemoryReadUnavailable = errors.New("memory read unavailable")
)
const arbitraryExecutionLimitFactor = 10
// ExecuteStackProgram executes a DWARF location expression and returns
// either an address (int64), or a slice of Pieces for location expressions
// that don't evaluate to an address (such as register and composite expressions).
func ExecuteStackProgram(regs DwarfRegisters, instructions []byte, ptrSize int, readMemory ReadMemoryFunc) (int64, []Piece, error) {
ctxt := &context{
buf: bytes.NewBuffer(instructions),
prog: instructions,
stack: make([]int64, 0, 3),
DwarfRegisters: regs,
ptrSize: ptrSize,
}
for tick := 0; tick < len(instructions)*arbitraryExecutionLimitFactor; tick++ {
opcodeByte, err := ctxt.buf.ReadByte()
if err != nil {
break
}
opcode := Opcode(opcodeByte)
if opcode == DW_OP_nop {
continue
}
fn, ok := oplut[opcode]
if !ok {
return 0, nil, fmt.Errorf("invalid instruction %#v", opcode)
}
err = fn(opcode, ctxt)
if err != nil {
return 0, nil, err
}
}
if ctxt.pieces != nil {
if len(ctxt.pieces) == 1 && ctxt.pieces[0].Kind == RegPiece {
return int64(regs.Uint64Val(ctxt.pieces[0].Val)), ctxt.pieces, nil
}
return 0, ctxt.pieces, nil
}
if len(ctxt.stack) == 0 {
return 0, nil, errors.New("empty OP stack")
}
return ctxt.stack[len(ctxt.stack)-1], nil, nil
}
// PrettyPrint prints the DWARF stack program instructions to `out`.
func PrettyPrint(out io.Writer, instructions []byte, regnumToName func(uint64) string) {
in := bytes.NewBuffer(instructions)
for {
opcode, err := in.ReadByte()
if err != nil {
break
}
op := Opcode(opcode)
if name, hasname := opcodeName[op]; hasname {
io.WriteString(out, name)
if regnumToName != nil {
if op >= DW_OP_reg0 && op <= DW_OP_reg31 {
fmt.Fprintf(out, "(%s)", regnumToName(uint64(op-DW_OP_reg0)))
} else if op >= DW_OP_breg0 && op <= DW_OP_breg31 {
fmt.Fprintf(out, "(%s)", regnumToName(uint64(op-DW_OP_breg0)))
}
}
out.Write([]byte{' '})
} else {
fmt.Fprintf(out, "%#x ", opcode)
}
for i, arg := range opcodeArgs[Opcode(opcode)] {
var regnum uint64
switch arg {
case 's':
n, _ := util.DecodeSLEB128(in)
regnum = uint64(n)
fmt.Fprintf(out, "%#x ", n)
case 'u':
n, _ := util.DecodeULEB128(in)
regnum = n
fmt.Fprintf(out, "%#x ", n)
case '1':
var x uint8
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case '2':
var x uint16
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case '4':
var x uint32
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case '8':
var x uint64
binary.Read(in, binary.LittleEndian, &x)
fmt.Fprintf(out, "%#x ", x)
case 'B':
sz, _ := util.DecodeULEB128(in)
data := make([]byte, sz)
sz2, _ := in.Read(data)
data = data[:sz2]
fmt.Fprintf(out, "%d [%x] ", sz, data)
}
if regnumToName != nil && i == 0 && (op == DW_OP_regx || op == DW_OP_bregx) {
fmt.Fprintf(out, "(%s)", regnumToName(regnum))
}
}
}
}
// closeLoc is called by opcodes that can only appear at the end of a
// location expression (DW_OP_regN, DW_OP_regx, DW_OP_stack_value...).
// It checks that we are at the end of the program or that the following
// opcode is DW_OP_piece or DW_OP_bit_piece and processes them.
func (ctxt *context) closeLoc(opcode0 Opcode, piece Piece) error {
if ctxt.buf.Len() == 0 {
ctxt.pieces = append(ctxt.pieces, piece)
return nil
}
// DWARF doesn't say what happens to the operand stack at the end of a
// location expression, resetting it here.
ctxt.stack = ctxt.stack[:0]
b, err := ctxt.buf.ReadByte()
if err != nil {
return err
}
opcode := Opcode(b)
switch opcode {
case DW_OP_piece:
sz, _ := util.DecodeULEB128(ctxt.buf)
piece.Size = int(sz)
ctxt.pieces = append(ctxt.pieces, piece)
return nil
case DW_OP_bit_piece:
// not supported
return fmt.Errorf("invalid instruction %#v", opcode)
default:
return fmt.Errorf("invalid instruction %#v after %#v", opcode, opcode0)
}
}
func callframecfa(opcode Opcode, ctxt *context) error {
if ctxt.CFA == 0 {
return errors.New("could not retrieve CFA for current PC")
}
ctxt.stack = append(ctxt.stack, int64(ctxt.CFA))
return nil
}
func addr(opcode Opcode, ctxt *context) error {
buf := ctxt.buf.Next(ctxt.ptrSize)
stack, err := util.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, ctxt.ptrSize)
if err != nil {
return err
}
ctxt.stack = append(ctxt.stack, int64(stack+ctxt.StaticBase))
return nil
}
func plusuconsts(opcode Opcode, ctxt *context) error {
slen := len(ctxt.stack)
num, _ := util.DecodeULEB128(ctxt.buf)
ctxt.stack[slen-1] = ctxt.stack[slen-1] + int64(num)
return nil
}
func consts(opcode Opcode, ctxt *context) error {
num, _ := util.DecodeSLEB128(ctxt.buf)
ctxt.stack = append(ctxt.stack, num)
return nil
}
func framebase(opcode Opcode, ctxt *context) error {
num, _ := util.DecodeSLEB128(ctxt.buf)
ctxt.stack = append(ctxt.stack, ctxt.FrameBase+num)
return nil
}
func register(opcode Opcode, ctxt *context) error {
var regnum uint64
if opcode == DW_OP_regx {
regnum, _ = util.DecodeULEB128(ctxt.buf)
} else {
regnum = uint64(opcode - DW_OP_reg0)
}
return ctxt.closeLoc(opcode, Piece{Kind: RegPiece, Val: regnum})
}
func bregister(opcode Opcode, ctxt *context) error {
var regnum uint64
if opcode == DW_OP_bregx {
regnum, _ = util.DecodeULEB128(ctxt.buf)
} else {
regnum = uint64(opcode - DW_OP_breg0)
}
offset, _ := util.DecodeSLEB128(ctxt.buf)
if ctxt.Reg(regnum) == nil {
return fmt.Errorf("register %d not available", regnum)
}
ctxt.stack = append(ctxt.stack, int64(ctxt.Uint64Val(regnum))+offset)
return nil
}
func piece(opcode Opcode, ctxt *context) error {
sz, _ := util.DecodeULEB128(ctxt.buf)
if len(ctxt.stack) == 0 {
// nothing on the stack means this piece is unavailable (padding,
// optimized away...), see DWARFv4 sec. 2.6.1.3 page 30.
ctxt.pieces = append(ctxt.pieces, Piece{Size: int(sz), Kind: ImmPiece, Val: 0})
return nil
}
addr := ctxt.stack[len(ctxt.stack)-1]
ctxt.pieces = append(ctxt.pieces, Piece{Size: int(sz), Kind: AddrPiece, Val: uint64(addr)})
ctxt.stack = ctxt.stack[:0]
return nil
}
func literal(opcode Opcode, ctxt *context) error {
ctxt.stack = append(ctxt.stack, int64(opcode-DW_OP_lit0))
return nil
}
func constnu(opcode Opcode, ctxt *context) error {
var (
n uint64
err error
)
switch opcode {
case DW_OP_const1u:
var b uint8
b, err = ctxt.buf.ReadByte()
n = uint64(b)
case DW_OP_const2u:
n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 2)
case DW_OP_const4u:
n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 4)
case DW_OP_const8u:
n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 8)
default:
panic("internal error")
}
if err != nil {
return err
}
ctxt.stack = append(ctxt.stack, int64(n))
return nil
}
func constns(opcode Opcode, ctxt *context) error {
var (
n uint64
err error
)
switch opcode {
case DW_OP_const1s:
var b uint8
b, err = ctxt.buf.ReadByte()
n = uint64(int64(int8(b)))
case DW_OP_const2s:
n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 2)
n = uint64(int64(int16(n)))
case DW_OP_const4s:
n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 4)
n = uint64(int64(int32(n)))
case DW_OP_const8s:
n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 8)
default:
panic("internal error")
}
if err != nil {
return err
}
ctxt.stack = append(ctxt.stack, int64(n))
return nil
}
func constu(opcode Opcode, ctxt *context) error {
num, _ := util.DecodeULEB128(ctxt.buf)
ctxt.stack = append(ctxt.stack, int64(num))
return nil
}
func dup(_ Opcode, ctxt *context) error {
if len(ctxt.stack) <= 0 {
return ErrStackUnderflow
}
ctxt.stack = append(ctxt.stack, ctxt.stack[len(ctxt.stack)-1])
return nil
}
func drop(_ Opcode, ctxt *context) error {
if len(ctxt.stack) <= 0 {
return ErrStackUnderflow
}
ctxt.stack = ctxt.stack[:len(ctxt.stack)-1]
return nil
}
func pick(opcode Opcode, ctxt *context) error {
var n byte
switch opcode {
case DW_OP_pick:
n, _ = ctxt.buf.ReadByte()
case DW_OP_over:
n = 1
default:
panic("internal error")
}
idx := len(ctxt.stack) - 1 - int(uint8(n))
if idx < 0 || idx >= len(ctxt.stack) {
return ErrStackIndexOutOfBounds
}
ctxt.stack = append(ctxt.stack, ctxt.stack[idx])
return nil
}
func swap(_ Opcode, ctxt *context) error {
if len(ctxt.stack) < 2 {
return ErrStackUnderflow
}
ctxt.stack[len(ctxt.stack)-1], ctxt.stack[len(ctxt.stack)-2] = ctxt.stack[len(ctxt.stack)-2], ctxt.stack[len(ctxt.stack)-1]
return nil
}
func rot(_ Opcode, ctxt *context) error {
if len(ctxt.stack) < 3 {
return ErrStackUnderflow
}
ctxt.stack[len(ctxt.stack)-1], ctxt.stack[len(ctxt.stack)-2], ctxt.stack[len(ctxt.stack)-3] = ctxt.stack[len(ctxt.stack)-2], ctxt.stack[len(ctxt.stack)-3], ctxt.stack[len(ctxt.stack)-1]
return nil
}
func unaryop(opcode Opcode, ctxt *context) error {
if len(ctxt.stack) < 1 {
return ErrStackUnderflow
}
operand := ctxt.stack[len(ctxt.stack)-1]
switch opcode {
case DW_OP_abs:
if operand < 0 {
operand = -operand
}
case DW_OP_neg:
operand = -operand
case DW_OP_not:
operand = ^operand
default:
panic("internal error")
}
ctxt.stack[len(ctxt.stack)-1] = operand
return nil
}
func binaryop(opcode Opcode, ctxt *context) error {
if len(ctxt.stack) < 2 {
return ErrStackUnderflow
}
second := ctxt.stack[len(ctxt.stack)-2]
top := ctxt.stack[len(ctxt.stack)-1]
var r int64
ctxt.stack = ctxt.stack[:len(ctxt.stack)-2]
switch opcode {
case DW_OP_and:
r = second & top
case DW_OP_div:
r = second / top
case DW_OP_minus:
r = second - top
case DW_OP_mod:
r = second % top
case DW_OP_mul:
r = second * top
case DW_OP_or:
r = second | top
case DW_OP_plus:
r = second + top
case DW_OP_shl:
r = second << uint64(top)
case DW_OP_shr:
r = second >> uint64(top)
case DW_OP_shra:
r = int64(uint64(second) >> uint64(top))
case DW_OP_xor:
r = second ^ top
case DW_OP_le:
r = bool2int(second <= top)
case DW_OP_ge:
r = bool2int(second >= top)
case DW_OP_eq:
r = bool2int(second == top)
case DW_OP_lt:
r = bool2int(second < top)
case DW_OP_gt:
r = bool2int(second > top)
case DW_OP_ne:
r = bool2int(second != top)
default:
panic("internal error")
}
ctxt.stack = append(ctxt.stack, r)
return nil
}
func bool2int(b bool) int64 {
if b {
return 1
}
return 0
}
func (ctxt *context) jump(n int16) error {
i := len(ctxt.prog) - ctxt.buf.Len() + int(n)
if i < 0 {
return ErrStackUnderflow
}
if i >= len(ctxt.prog) {
i = len(ctxt.prog)
}
ctxt.buf = bytes.NewBuffer(ctxt.prog[i:])
return nil
}
func skip(_ Opcode, ctxt *context) error {
var n int16
binary.Read(ctxt.buf, binary.LittleEndian, &n)
return ctxt.jump(n)
}
func bra(_ Opcode, ctxt *context) error {
var n int16
binary.Read(ctxt.buf, binary.LittleEndian, &n)
if len(ctxt.stack) < 1 {
return ErrStackUnderflow
}
top := ctxt.stack[len(ctxt.stack)-1]
ctxt.stack = ctxt.stack[:len(ctxt.stack)-1]
if top != 0 {
return ctxt.jump(n)
}
return nil
}
func stackvalue(_ Opcode, ctxt *context) error {
if len(ctxt.stack) < 1 {
return ErrStackUnderflow
}
val := ctxt.stack[len(ctxt.stack)-1]
ctxt.stack = ctxt.stack[:len(ctxt.stack)-1]
return ctxt.closeLoc(DW_OP_stack_value, Piece{Kind: ImmPiece, Val: uint64(val)})
}
func implicitvalue(_ Opcode, ctxt *context) error {
sz, _ := util.DecodeULEB128(ctxt.buf)
block := make([]byte, sz)
n, _ := ctxt.buf.Read(block)
if uint64(n) != sz {
return fmt.Errorf("insufficient bytes read while reading DW_OP_implicit_value's block %d (expected: %d)", n, sz)
}
return ctxt.closeLoc(DW_OP_implicit_value, Piece{Kind: ImmPiece, Bytes: block, Size: int(sz)})
}
func deref(op Opcode, ctxt *context) error {
if ctxt.readMemory == nil {
return ErrMemoryReadUnavailable
}
sz := ctxt.ptrSize
if op == DW_OP_deref_size || op == DW_OP_xderef_size {
n, err := ctxt.buf.ReadByte()
if err != nil {
return err
}
sz = int(n)
}
if len(ctxt.stack) <= 0 {
return ErrStackUnderflow
}
addr := ctxt.stack[len(ctxt.stack)-1]
ctxt.stack = ctxt.stack[:len(ctxt.stack)-1]
if op == DW_OP_xderef || op == DW_OP_xderef_size {
if len(ctxt.stack) <= 0 {
return ErrStackUnderflow
}
// the second element on the stack is the "address space identifier" which we don't do anything with
ctxt.stack = ctxt.stack[:len(ctxt.stack)-1]
}
buf := make([]byte, sz)
_, err := ctxt.readMemory(buf, uint64(addr))
if err != nil {
return err
}
x, err := util.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, sz)
if err != nil {
return err
}
ctxt.stack = append(ctxt.stack, int64(x))
return nil
}