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)) }