delve/pkg/elfwriter/writer.go
Alessandro Arzilli 2c1a822632
terminal,service,proc/*: adds dump command (gcore equivalent) (#2173)
* proc/core: off-by-one error reading ELF core files

core.(*splicedMemory).ReadMemory checked the entry interval
erroneously when dealing with contiguous entries.

* terminal,service,proc/*: adds dump command (gcore equivalent)

Adds the `dump` command that creates a core file from the target process.

Backends will need to implement a new, optional, method `MemoryMap` that
returns a list of mapped memory regions.
Additionally the method `DumpProcessNotes` can be implemented to write out
to the core file notes describing the target process and its threads. If
DumpProcessNotes is not implemented `proc.Dump` will write a description of
the process and its threads in a OS/arch-independent format (that only Delve
understands).

Currently only linux/amd64 implements `DumpProcessNotes`.

Core files are only written in ELF, there is no minidump or macho-o writers.

# Conflicts:
#	pkg/proc/proc_test.go
2021-01-29 13:39:33 -08:00

184 lines
4.0 KiB
Go

// elfwriter is a package to write ELF files without having their entire
// contents in memory at any one time.
// This package is incomplete, only features needed to write core files are
// implemented, notably missing:
// - section headers
// - program headers at the beginning of the file
package elfwriter
import (
"debug/elf"
"encoding/binary"
"io"
)
// WriteCloserSeeker is the union of io.Writer, io.Closer and io.Seeker.
type WriteCloserSeeker interface {
io.Writer
io.Seeker
io.Closer
}
// Writer writes ELF files.
type Writer struct {
w WriteCloserSeeker
Err error
Progs []*elf.ProgHeader
seekProgHeader int64
seekProgNum int64
}
type Note struct {
Type elf.NType
Name string
Data []byte
}
// New creates a new Writer.
func New(w WriteCloserSeeker, fhdr *elf.FileHeader) *Writer {
const (
ehsize = 64
phentsize = 56
)
if seek, _ := w.Seek(0, io.SeekCurrent); seek != 0 {
panic("can't write halfway through a file")
}
r := &Writer{w: w}
if fhdr.Class != elf.ELFCLASS64 {
panic("unsupported")
}
if fhdr.Data != elf.ELFDATA2LSB {
panic("unsupported")
}
// e_ident
r.Write([]byte{0x7f, 'E', 'L', 'F', byte(fhdr.Class), byte(fhdr.Data), byte(fhdr.Version), byte(fhdr.OSABI), byte(fhdr.ABIVersion), 0, 0, 0, 0, 0, 0, 0})
r.u16(uint16(fhdr.Type)) // e_type
r.u16(uint16(fhdr.Machine)) // e_machine
r.u32(uint32(fhdr.Version)) // e_version
r.u64(0) // e_entry
r.seekProgHeader = r.Here()
r.u64(0) // e_phoff
r.u64(0) // e_shoff
r.u32(0) // e_flags
r.u16(ehsize) // e_ehsize
r.u16(phentsize) // e_phentsize
r.seekProgNum = r.Here()
r.u16(0) // e_phnum
r.u16(0) // e_shentsize
r.u16(0) // e_shnum
r.u16(uint16(elf.SHN_UNDEF)) // e_shstrndx
// Sanity check, size of file header should be the same as ehsize
if sz, _ := w.Seek(0, io.SeekCurrent); sz != ehsize {
panic("internal error, ELF header size")
}
return r
}
// WriteNotes writes notes to the current location, returns a ProgHeader describing the
// notes.
func (w *Writer) WriteNotes(notes []Note) *elf.ProgHeader {
if len(notes) == 0 {
return nil
}
h := &elf.ProgHeader{
Type: elf.PT_NOTE,
Align: 4,
}
for i := range notes {
note := &notes[i]
w.Align(4)
if h.Off == 0 {
h.Off = uint64(w.Here())
}
w.u32(uint32(len(note.Name)))
w.u32(uint32(len(note.Data)))
w.u32(uint32(note.Type))
w.Write([]byte(note.Name))
w.Align(4)
w.Write(note.Data)
}
h.Filesz = uint64(w.Here()) - h.Off
return h
}
// WriteProgramHeaders writes the program headers at the current location
// and patches the file header accordingly.
func (w *Writer) WriteProgramHeaders() {
phoff := w.Here()
// Patch File Header
w.w.Seek(w.seekProgHeader, io.SeekStart)
w.u64(uint64(phoff))
w.w.Seek(w.seekProgNum, io.SeekStart)
w.u64(uint64(len(w.Progs)))
w.w.Seek(0, io.SeekEnd)
for _, prog := range w.Progs {
w.u32(uint32(prog.Type))
w.u32(uint32(prog.Flags))
w.u64(prog.Off)
w.u64(prog.Vaddr)
w.u64(prog.Paddr)
w.u64(prog.Filesz)
w.u64(prog.Memsz)
w.u64(prog.Align)
}
}
// Here returns the current seek offset from the start of the file.
func (w *Writer) Here() int64 {
r, err := w.w.Seek(0, io.SeekCurrent)
if err != nil && w.Err == nil {
w.Err = err
}
return r
}
// Align writes as many padding bytes as needed to make the current file
// offset a multiple of align.
func (w *Writer) Align(align int64) {
off := w.Here()
alignOff := (off + (align - 1)) &^ (align - 1)
if alignOff-off > 0 {
w.Write(make([]byte, alignOff-off))
}
}
func (w *Writer) Write(buf []byte) {
_, err := w.w.Write(buf)
if err != nil && w.Err == nil {
w.Err = err
}
}
func (w *Writer) u16(n uint16) {
err := binary.Write(w.w, binary.LittleEndian, n)
if err != nil && w.Err == nil {
w.Err = err
}
}
func (w *Writer) u32(n uint32) {
err := binary.Write(w.w, binary.LittleEndian, n)
if err != nil && w.Err == nil {
w.Err = err
}
}
func (w *Writer) u64(n uint64) {
err := binary.Write(w.w, binary.LittleEndian, n)
if err != nil && w.Err == nil {
w.Err = err
}
}