147 lines
4.6 KiB
Go
147 lines
4.6 KiB
Go
![]() |
package proc
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
)
|
||
|
|
||
|
// MemoryReader is like io.ReaderAt, but the offset is a uintptr 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 uintptr) (n int, err error)
|
||
|
}
|
||
|
|
||
|
// A SplicedMemory represents a memory space formed from multiple regions,
|
||
|
// each of which may override previously regions. For example, in the following
|
||
|
// core, the program text was loaded at 0x400000:
|
||
|
// Start End Page Offset
|
||
|
// 0x0000000000400000 0x000000000044f000 0x0000000000000000
|
||
|
// but then it's partially overwritten with an RW mapping whose data is stored
|
||
|
// in the core file:
|
||
|
// Type Offset VirtAddr PhysAddr
|
||
|
// FileSiz MemSiz Flags Align
|
||
|
// LOAD 0x0000000000004000 0x000000000049a000 0x0000000000000000
|
||
|
// 0x0000000000002000 0x0000000000002000 RW 1000
|
||
|
// This can be represented in a SplicedMemory by adding the original region,
|
||
|
// then putting the RW mapping on top of it.
|
||
|
type SplicedMemory struct {
|
||
|
readers []readerEntry
|
||
|
}
|
||
|
|
||
|
type readerEntry struct {
|
||
|
offset uintptr
|
||
|
length uintptr
|
||
|
reader MemoryReader
|
||
|
}
|
||
|
|
||
|
// Add adds a new region to the SplicedMemory, which may override existing regions.
|
||
|
func (r *SplicedMemory) Add(reader MemoryReader, off, length uintptr) {
|
||
|
if length == 0 {
|
||
|
return
|
||
|
}
|
||
|
end := off + length - 1
|
||
|
newReaders := make([]readerEntry, 0, len(r.readers))
|
||
|
add := func(e readerEntry) {
|
||
|
if e.length == 0 {
|
||
|
return
|
||
|
}
|
||
|
newReaders = append(newReaders, e)
|
||
|
}
|
||
|
inserted := false
|
||
|
// Walk through the list of regions, fixing up any that overlap and inserting the new one.
|
||
|
for _, entry := range r.readers {
|
||
|
entryEnd := entry.offset + entry.length - 1
|
||
|
switch {
|
||
|
case entryEnd < off:
|
||
|
// Entry is completely before the new region.
|
||
|
add(entry)
|
||
|
case end < entry.offset:
|
||
|
// Entry is completely after the new region.
|
||
|
if !inserted {
|
||
|
add(readerEntry{off, length, reader})
|
||
|
inserted = true
|
||
|
}
|
||
|
add(entry)
|
||
|
case off <= entry.offset && entryEnd <= end:
|
||
|
// Entry is completely overwritten by the new region. Drop.
|
||
|
case entry.offset < off && entryEnd <= end:
|
||
|
// New region overwrites the end of the entry.
|
||
|
entry.length = off - entry.offset
|
||
|
add(entry)
|
||
|
case off <= entry.offset && end < entryEnd:
|
||
|
// New reader overwrites the beginning of the entry.
|
||
|
if !inserted {
|
||
|
add(readerEntry{off, length, reader})
|
||
|
inserted = true
|
||
|
}
|
||
|
overlap := entry.offset - off
|
||
|
entry.offset += overlap
|
||
|
entry.length -= overlap
|
||
|
add(entry)
|
||
|
case entry.offset < off && end < entryEnd:
|
||
|
// New region punches a hole in the entry. Split it in two and put the new region in the middle.
|
||
|
add(readerEntry{entry.offset, off - entry.offset, entry.reader})
|
||
|
add(readerEntry{off, length, reader})
|
||
|
add(readerEntry{end + 1, entryEnd - end, entry.reader})
|
||
|
inserted = true
|
||
|
default:
|
||
|
panic(fmt.Sprintf("Unhandled case: existing entry is %v len %v, new is %v len %v", entry.offset, entry.length, off, length))
|
||
|
}
|
||
|
}
|
||
|
if !inserted {
|
||
|
newReaders = append(newReaders, readerEntry{off, length, reader})
|
||
|
}
|
||
|
r.readers = newReaders
|
||
|
}
|
||
|
|
||
|
// ReadMemory implements MemoryReader.ReadMemory.
|
||
|
func (r *SplicedMemory) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
|
||
|
started := false
|
||
|
for _, entry := range r.readers {
|
||
|
if entry.offset+entry.length < addr {
|
||
|
if !started {
|
||
|
continue
|
||
|
}
|
||
|
return n, fmt.Errorf("hit unmapped area at %v after %v bytes", addr, n)
|
||
|
}
|
||
|
|
||
|
// Don't go past the region.
|
||
|
pb := buf
|
||
|
if addr+uintptr(len(buf)) > entry.offset+entry.length {
|
||
|
pb = pb[:entry.offset+entry.length-addr]
|
||
|
}
|
||
|
pn, err := entry.reader.ReadMemory(pb, addr)
|
||
|
n += pn
|
||
|
if err != nil || pn != len(pb) {
|
||
|
return n, err
|
||
|
}
|
||
|
buf = buf[pn:]
|
||
|
addr += uintptr(pn)
|
||
|
if len(buf) == 0 {
|
||
|
// Done, don't bother scanning the rest.
|
||
|
return n, nil
|
||
|
}
|
||
|
}
|
||
|
if n == 0 {
|
||
|
return 0, fmt.Errorf("offset %v did not match any regions", addr)
|
||
|
}
|
||
|
return n, nil
|
||
|
}
|
||
|
|
||
|
// OffsetReaderAt wraps a ReaderAt into a MemoryReader, subtracting a fixed
|
||
|
// offset from the address. This is useful to represent a mapping in an address
|
||
|
// space. For example, if program text is mapped in at 0x400000, an
|
||
|
// OffsetReaderAt with offset 0x400000 can be wrapped around file.Open(program)
|
||
|
// to return the results of a read in that part of the address space.
|
||
|
type OffsetReaderAt struct {
|
||
|
reader io.ReaderAt
|
||
|
offset uintptr
|
||
|
}
|
||
|
|
||
|
func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
|
||
|
return r.reader.ReadAt(buf, int64(addr-r.offset))
|
||
|
}
|