delve/pkg/proc/mem.go
Zeke Lu 463b97dd36
pkg/proc: pad variable mem in extractVarInfoFromEntry (#3365)
* pkg/proc: pad variable mem in extractVarInfoFromEntry

On 64 bit system, the byte size of the following struct is 16:
    type myStruct struct {
       a int
       b uint32
    }
But extractVarInfoFromEntry only allocates a mem of 12 bytes for it.
When calling method of this struct with the "call" command, it will
result in this error:
    write out of bounds

This patch extends the mem by adding padding bytes to the end of the
mem.

Fixes #3364.

* move the padding logic into newCompositeMemory
2023-05-15 14:46:33 -07:00

225 lines
6.7 KiB
Go

package proc
import (
"encoding/binary"
"errors"
"fmt"
"github.com/go-delve/delve/pkg/dwarf/op"
)
const cacheEnabled = true
// MemoryReader is like io.ReaderAt, but the offset is a uint64 so that it
// can address all of 64-bit memory.
// Redundant with memoryReadWriter but more easily suited to working with
// the standard io package.
type MemoryReader interface {
// ReadMemory is just like io.ReaderAt.ReadAt.
ReadMemory(buf []byte, addr uint64) (n int, err error)
}
// MemoryReadWriter is an interface for reading or writing to
// the targets memory. This allows us to read from the actual
// target memory or possibly a cache.
type MemoryReadWriter interface {
MemoryReader
WriteMemory(addr uint64, data []byte) (written int, err error)
}
type memCache struct {
loaded bool
cacheAddr uint64
cache []byte
mem MemoryReadWriter
}
func (m *memCache) contains(addr uint64, size int) bool {
return addr >= m.cacheAddr && addr <= (m.cacheAddr+uint64(len(m.cache)-size))
}
func (m *memCache) ReadMemory(data []byte, addr uint64) (n int, err error) {
if m.contains(addr, len(data)) {
if !m.loaded {
_, err := m.mem.ReadMemory(m.cache, m.cacheAddr)
if err != nil {
return 0, err
}
m.loaded = true
}
copy(data, m.cache[addr-m.cacheAddr:])
return len(data), nil
}
return m.mem.ReadMemory(data, addr)
}
func (m *memCache) WriteMemory(addr uint64, data []byte) (written int, err error) {
return m.mem.WriteMemory(addr, data)
}
func CreateLoadedCachedMemory(data []byte) MemoryReadWriter {
return &memCache{loaded: true, cacheAddr: fakeAddressUnresolv, cache: data, mem: nil}
}
func cacheMemory(mem MemoryReadWriter, addr uint64, size int) MemoryReadWriter {
if !cacheEnabled {
return mem
}
if size <= 0 {
return mem
}
switch cacheMem := mem.(type) {
case *memCache:
if cacheMem.contains(addr, size) {
return mem
}
case *compositeMemory:
return mem
}
return &memCache{false, addr, make([]byte, size), mem}
}
// compositeMemory represents a chunk of memory that is stored in CPU
// registers or non-contiguously.
//
// When optimizations are enabled the compiler will store some variables
// into registers and sometimes it will also store structs non-contiguously
// with some fields stored into CPU registers and other fields stored in
// memory.
type compositeMemory struct {
base uint64 // base address for this composite memory
realmem MemoryReadWriter
arch *Arch
regs op.DwarfRegisters
pieces []op.Piece
data []byte
}
// CreateCompositeMemory created a new composite memory type using the provided MemoryReadWriter as the
// underlying memory buffer.
func CreateCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece, size int64) (*compositeMemory, error) {
// This is basically a small wrapper to avoid having to change all callers
// of newCompositeMemory since it existed first.
cm, err := newCompositeMemory(mem, arch, regs, pieces, size)
if cm != nil {
cm.base = fakeAddressUnresolv
}
return cm, err
}
func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece, size int64) (*compositeMemory, error) {
cmem := &compositeMemory{realmem: mem, arch: arch, regs: regs, pieces: pieces, data: []byte{}}
for i := range pieces {
piece := &pieces[i]
switch piece.Kind {
case op.RegPiece:
reg := regs.Bytes(piece.Val)
if piece.Size == 0 && i == len(pieces)-1 {
piece.Size = len(reg)
}
if piece.Size > len(reg) {
if regs.FloatLoadError != nil {
return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d), also error loading floating point registers: %v", piece.Size, piece.Val, len(reg), regs.FloatLoadError)
}
return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", piece.Size, piece.Val, len(reg))
}
cmem.data = append(cmem.data, reg[:piece.Size]...)
case op.AddrPiece:
buf := make([]byte, piece.Size)
mem.ReadMemory(buf, uint64(piece.Val))
cmem.data = append(cmem.data, buf...)
case op.ImmPiece:
buf := piece.Bytes
if buf == nil {
sz := 8
if piece.Size > sz {
sz = piece.Size
}
if piece.Size == 0 && i == len(pieces)-1 {
piece.Size = arch.PtrSize() // DWARF doesn't say what this should be
}
buf = make([]byte, sz)
binary.LittleEndian.PutUint64(buf, piece.Val)
}
cmem.data = append(cmem.data, buf[:piece.Size]...)
default:
panic("unsupported piece kind")
}
}
paddingBytes := int(size) - len(cmem.data)
if paddingBytes > 0 && paddingBytes < arch.ptrSize {
padding := make([]byte, paddingBytes)
cmem.data = append(cmem.data, padding...)
}
return cmem, nil
}
func (mem *compositeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
addr -= mem.base
if addr >= uint64(len(mem.data)) || addr+uint64(len(data)) > uint64(len(mem.data)) {
return 0, errors.New("read out of bounds")
}
copy(data, mem.data[addr:addr+uint64(len(data))])
return len(data), nil
}
func (mem *compositeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
addr -= mem.base
if addr >= uint64(len(mem.data)) || addr+uint64(len(data)) > uint64(len(mem.data)) {
return 0, errors.New("write out of bounds")
}
if mem.regs.ChangeFunc == nil {
return 0, errors.New("can not write registers")
}
copy(mem.data[addr:], data)
curAddr := uint64(0)
donesz := 0
for _, piece := range mem.pieces {
if curAddr < (addr+uint64(len(data))) && addr < (curAddr+uint64(piece.Size)) {
// changed memory interval overlaps current piece
pieceMem := mem.data[curAddr : curAddr+uint64(piece.Size)]
switch piece.Kind {
case op.RegPiece:
oldReg := mem.regs.Reg(piece.Val)
newReg := op.DwarfRegisterFromBytes(pieceMem)
err := mem.regs.ChangeFunc(piece.Val, oldReg.Overwrite(newReg))
if err != nil {
return donesz, err
}
case op.AddrPiece:
n, err := mem.realmem.WriteMemory(uint64(piece.Val), pieceMem)
if err != nil {
return donesz + n, err
}
case op.ImmPiece:
//TODO(aarzilli): maybe return an error if the user tried to change the value?
// nothing to do
default:
panic("unsupported piece kind")
}
donesz += piece.Size
}
curAddr += uint64(piece.Size)
}
return len(data), nil
}
// DereferenceMemory returns a MemoryReadWriter that can read and write the
// memory pointed to by pointers in this memory.
// Normally mem and mem.Dereference are the same object, they are different
// only if this MemoryReadWriter is used to access memory outside of the
// normal address space of the inferior process (such as data contained in
// registers, or composite memory).
func DereferenceMemory(mem MemoryReadWriter) MemoryReadWriter {
switch mem := mem.(type) {
case *compositeMemory:
return mem.realmem
}
return mem
}