blob: d4da13e32651ed0231824e4f3534dbf711a0595e [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The core library is used to process ELF core dump files. You can
// open a core dump file and read from addresses in the process that
// dumped core, called the "inferior". Some ancillary information
// about the inferior is also provided, like architecture and OS
// thread state.
//
// There's nothing Go-specific about this library, it could
// just as easily be used to read a C++ core dump. See ../gocore
// for the next layer up, a Go-specific core dump reader.
//
// The Read* operations all panic with an error (the builtin Go type)
// if the inferior is not readable at the address requested.
package core
import (
"bytes"
"debug/dwarf"
"debug/elf" // TODO: use golang.org/x/debug/elf instead?
"encoding/binary"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"syscall"
)
// A Process represents the state of the process that core dumped.
type Process struct {
base string // base directory from which files in the core can be found
exe *os.File // user-supplied main executable path
files map[string]*file // files found from the note section
mainExecName string // open main executable name
entryPoint Address
memory splicedMemory // virtual address mappings
threads []*Thread // os threads (TODO: map from pid?)
arch string // amd64, ...
ptrSize int64 // 4 or 8
logPtrSize uint // 2 or 3
byteOrder binary.ByteOrder //
littleEndian bool // redundant with byteOrder
syms map[string]Address // symbols (could be empty if executable is stripped)
symErr error // an error encountered while reading symbols
dwarf *dwarf.Data // debugging info (could be nil)
dwarfErr error // an error encountered while reading DWARF
pageTable pageTable4 // for fast address->mapping lookups
args string // first part of args retrieved from NT_PRPSINFO
warnings []string // warnings generated during loading
}
type file struct {
f *os.File
err error
}
// Mappings returns a list of virtual memory mappings for p.
func (p *Process) Mappings() []*Mapping {
return p.memory.mappings
}
// Readable reports whether the address a is readable.
func (p *Process) Readable(a Address) bool {
return p.findMapping(a) != nil
}
// ReadableN reports whether the n bytes starting at address a are readable.
func (p *Process) ReadableN(a Address, n int64) bool {
for {
m := p.findMapping(a)
if m == nil || m.perm&Read == 0 {
return false
}
c := m.max.Sub(a)
if n <= c {
return true
}
n -= c
a = a.Add(c)
}
}
// Writeable reports whether the address a was writeable (by the inferior at the time of the core dump).
func (p *Process) Writeable(a Address) bool {
m := p.findMapping(a)
if m == nil {
return false
}
return m.perm&Write != 0
}
// Threads returns information about each OS thread in the inferior.
func (p *Process) Threads() []*Thread {
return p.threads
}
func (p *Process) Arch() string {
return p.arch
}
// PtrSize returns the size in bytes of a pointer in the inferior.
func (p *Process) PtrSize() int64 {
return p.ptrSize
}
func (p *Process) LogPtrSize() uint {
return p.logPtrSize
}
func (p *Process) ByteOrder() binary.ByteOrder {
return p.byteOrder
}
func (p *Process) DWARF() (*dwarf.Data, error) {
return p.dwarf, p.dwarfErr
}
// Symbols returns a mapping from name to inferior address, along with
// any error encountered during reading the symbol information.
// (There may be both an error and some returned symbols.)
// Symbols might not be available with core files from stripped binaries.
func (p *Process) Symbols() (map[string]Address, error) {
return p.syms, p.symErr
}
var mapFile = func(fd int, offset int64, length int) (data []byte, err error) {
return nil, fmt.Errorf("file mapping is not implemented yet")
}
// Core takes the name of a core file and returns a Process that
// represents the state of the inferior that generated the core file.
func Core(coreFile, base, exePath string) (*Process, error) {
core, err := os.Open(coreFile)
if err != nil {
return nil, fmt.Errorf("failed to open core file: %v", err)
}
p := &Process{base: base, files: make(map[string]*file)}
if exePath != "" {
bin, err := os.Open(exePath)
if err != nil {
return nil, fmt.Errorf("failed to open executable file: %v", err)
}
p.exe = bin
}
if err := p.readExec(p.exe); err != nil {
return nil, err
}
if err := p.readCore(core); err != nil {
return nil, err
}
if err := p.readDebugInfo(); err != nil {
return nil, err
}
// Sort then merge mappings, just to clean up a bit.
mappings := p.memory.mappings
sort.Slice(mappings, func(i, j int) bool {
return mappings[i].min < mappings[j].min
})
ms := mappings[1:]
mappings = mappings[:1]
for _, m := range ms {
k := mappings[len(mappings)-1]
if m.min == k.max &&
m.perm == k.perm &&
m.f == k.f &&
m.off == k.off+k.Size() {
k.max = m.max
// TODO: also check origF?
} else {
mappings = append(mappings, m)
}
}
p.memory.mappings = mappings
// Memory map all the mappings.
hostPageSize := int64(syscall.Getpagesize())
for _, m := range p.memory.mappings {
size := m.max.Sub(m.min)
if m.f == nil {
// We don't have any source for this data.
// Could be a mapped file that we couldn't find.
// Could be a mapping madvised as MADV_DONTDUMP.
// Pretend this is read-as-zero.
// The other option is to just throw away
// the mapping (and thus make Read*s of this
// mapping fail).
p.warnings = append(p.warnings,
fmt.Sprintf("Missing data at addresses [%x %x]. Assuming all zero.", m.min, m.max))
// TODO: this allocation could be large.
// Use mmap to avoid real backing store for all those zeros, or
// perhaps split the mapping up into chunks and share the zero contents among them.
m.contents = make([]byte, size)
continue
}
if m.perm&Write != 0 && m.f != core {
p.warnings = append(p.warnings,
fmt.Sprintf("Writeable data at [%x %x] missing from core. Using possibly stale backup source %s.", m.min, m.max, m.f.Name()))
}
// Data in core file might not be aligned enough for the host.
// Expand memory range so we can map full pages.
minOff := m.off
maxOff := m.off + size
minOff -= minOff % hostPageSize
if maxOff%hostPageSize != 0 {
maxOff += hostPageSize - maxOff%hostPageSize
}
// Read data from file.
data, err := mapFile(int(m.f.Fd()), minOff, int(maxOff-minOff))
if err != nil {
return nil, fmt.Errorf("can't memory map %s at %x: %s\n", m.f.Name(), minOff, err)
}
// Trim any data we mapped but don't need.
data = data[m.off-minOff:]
data = data[:size]
m.contents = data
}
// Build page table for mapping lookup.
for _, m := range p.memory.mappings {
err := p.addMapping(m)
if err != nil {
return nil, err
}
}
return p, nil
}
func (p *Process) readExec(exe *os.File) error {
if exe == nil {
return nil
}
e, err := elf.NewFile(exe)
if err != nil {
return err
}
// Load virtual memory mappings.
for _, prog := range e.Progs {
if prog.Type == elf.PT_LOAD {
if err := p.readLoad(exe, e, prog); err != nil {
return err
}
}
}
return nil
}
func (p *Process) readCore(core *os.File) error {
e, err := elf.NewFile(core)
if err != nil {
return err
}
if e.Type != elf.ET_CORE {
return fmt.Errorf("%s is not a core file", core.Name())
}
switch e.Class {
case elf.ELFCLASS32:
p.ptrSize = 4
p.logPtrSize = 2
case elf.ELFCLASS64:
p.ptrSize = 8
p.logPtrSize = 3
default:
return fmt.Errorf("unknown elf class %s\n", e.Class)
}
switch e.Machine {
case elf.EM_386:
p.arch = "386"
case elf.EM_X86_64:
p.arch = "amd64"
// TODO: detect amd64p32?
case elf.EM_ARM:
p.arch = "arm"
case elf.EM_AARCH64:
p.arch = "arm64"
case elf.EM_MIPS:
p.arch = "mips"
case elf.EM_MIPS_RS3_LE:
p.arch = "mipsle"
// TODO: value for mips64?
case elf.EM_PPC64:
if e.ByteOrder.String() == "LittleEndian" {
p.arch = "ppc64le"
} else {
p.arch = "ppc64"
}
case elf.EM_S390:
p.arch = "s390x"
default:
return fmt.Errorf("unknown arch %s\n", e.Machine)
}
p.byteOrder = e.ByteOrder
// We also compute explicitly what byte order the inferior is.
// Just using p.byteOrder to decode fields makes any arguments passed to it
// escape to the heap. We use explicit binary.{Little,Big}Endian.UintXX
// calls when we want to avoid heap-allocating the buffer.
p.littleEndian = e.ByteOrder.String() == "LittleEndian"
// Load virtual memory mappings.
for _, prog := range e.Progs {
if prog.Type == elf.PT_LOAD {
if err := p.readLoad(core, e, prog); err != nil {
return err
}
}
}
// Load notes (includes file mapping information).
for _, prog := range e.Progs {
if prog.Type == elf.PT_NOTE {
if err := p.readNote(core, e, prog.Off, prog.Filesz); err != nil {
return err
}
}
}
return nil
}
func (p *Process) readLoad(f *os.File, e *elf.File, prog *elf.Prog) error {
min := Address(prog.Vaddr)
max := min.Add(int64(prog.Memsz))
var perm Perm
if prog.Flags&elf.PF_R != 0 {
perm |= Read
}
if prog.Flags&elf.PF_W != 0 {
perm |= Write
}
if prog.Flags&elf.PF_X != 0 {
perm |= Exec
}
if perm == 0 {
// TODO: keep these nothing-mapped mappings?
return nil
}
if prog.Filesz > 0 {
// Data backing this mapping is in the core file.
p.memory.Add(min, max, perm, f, int64(prog.Off))
} else {
p.memory.Add(min, max, perm, nil, 0)
}
if prog.Filesz < prog.Memsz {
// We only have partial data for this mapping in the core file.
// Trim the mapping and allocate an anonymous mapping for the remainder.
p.memory.Add(min.Add(int64(prog.Filesz)), max, perm, nil, 0)
}
return nil
}
func (p *Process) readNote(f *os.File, e *elf.File, off, size uint64) error {
// TODO: add this to debug/elf?
const NT_FILE elf.NType = 0x46494c45
const NT_AUXV elf.NType = 0x6 // auxv
b := make([]byte, size)
_, err := f.ReadAt(b, int64(off))
if err != nil {
return err
}
for len(b) > 0 {
namesz := e.ByteOrder.Uint32(b)
b = b[4:]
descsz := e.ByteOrder.Uint32(b)
b = b[4:]
typ := elf.NType(e.ByteOrder.Uint32(b))
b = b[4:]
name := string(b[:namesz-1])
b = b[(namesz+3)/4*4:]
desc := b[:descsz]
b = b[(descsz+3)/4*4:]
if name != "CORE" { // what does this mean?
continue
}
switch typ {
case NT_FILE:
if err := p.readNTFile(f, e, desc); err != nil {
return fmt.Errorf("reading NT_FILE: %v", err)
}
case elf.NT_PRSTATUS:
// An OS thread (an M)
if err := p.readPRStatus(f, e, desc); err != nil {
return fmt.Errorf("reading NT_PRSTATUS: %v", err)
}
case elf.NT_PRPSINFO:
if err := p.readPRPSInfo(desc); err != nil {
return fmt.Errorf("reading NT_PRPSINFO: %v", err)
}
case NT_AUXV:
if entry, ok := findEntryPoint(desc, e.ByteOrder); ok {
p.entryPoint = entry
}
}
// TODO: NT_FPREGSET for floating-point registers
}
return nil
}
func findEntryPoint(auxvDesc []byte, order binary.ByteOrder) (Address, bool) {
// amd64 only?
const _AT_ENTRY_AMD64 = 9
buf := bytes.NewBuffer(auxvDesc)
for {
var tag, val uint64
if err := binary.Read(buf, order, &tag); err != nil {
panic(err)
}
if err := binary.Read(buf, order, &val); err != nil {
panic(err)
}
if tag == _AT_ENTRY_AMD64 {
return Address(val), true
}
}
return 0, false
}
func (p *Process) readNTFile(f *os.File, e *elf.File, desc []byte) error {
// TODO: 4 instead of 8 for 32-bit machines?
count := e.ByteOrder.Uint64(desc)
desc = desc[8:]
pagesize := e.ByteOrder.Uint64(desc)
desc = desc[8:]
filenames := string(desc[3*8*count:])
desc = desc[:3*8*count]
for i := uint64(0); i < count; i++ {
min := Address(e.ByteOrder.Uint64(desc))
desc = desc[8:]
max := Address(e.ByteOrder.Uint64(desc))
desc = desc[8:]
off := int64(e.ByteOrder.Uint64(desc) * pagesize)
desc = desc[8:]
var name string
j := strings.IndexByte(filenames, 0)
if j >= 0 {
name = filenames[:j]
filenames = filenames[j+1:]
} else {
name = filenames
filenames = ""
}
// TODO: this is O(n^2). Shouldn't be a big problem in practice.
p.splitMappingsAt(min)
p.splitMappingsAt(max)
for _, m := range p.memory.mappings {
if m.max <= min || m.min >= max {
continue
}
// m should now be entirely in [min,max]
if !(m.min >= min && m.max <= max) {
panic("mapping overlapping end of file region")
}
f, err := p.openMappedFile(name, m)
if err != nil {
// Can't find mapped file.
// We don't want to make this a hard error because there are
// lots of possible missing files that probably aren't critical,
// like a random shared library.
p.warnings = append(p.warnings, fmt.Sprintf("Missing data for addresses [%x %x] because of failure to %s. Assuming all zero.", m.min, m.max, err))
}
if m.f == nil {
m.f = f
m.off = int64(off) + m.min.Sub(min)
} else {
// Data is both in the core file and in a mapped file.
// The mapped file may be stale (even if it is readonly now,
// it may have been writeable at some point).
// Keep the file+offset just for printing.
m.origF = f
m.origOff = int64(off) + m.min.Sub(min)
}
}
}
return nil
}
func (p *Process) openMappedFile(fname string, m *Mapping) (*os.File, error) {
if fname == "" {
return nil, nil
}
if backing := p.files[fname]; backing != nil {
return backing.f, backing.err
}
backing := &file{}
isMainExe := m.perm&Exec != 0 && p.mainExecName == "" // first executable region
if p.entryPoint != 0 && m.Min() <= p.entryPoint && p.entryPoint < m.Max() {
// Or if we have the entry point info and it falls into this mappint, this is the region
// the main executable is mapped.
isMainExe = true
}
if !isMainExe {
backing.f, backing.err = os.Open(filepath.Join(p.base, fname))
} else { // keep main executable in p.mainExecName
p.mainExecName = fname
if p.exe != nil {
backing.f, backing.err = p.exe, nil
} else {
backing.f, backing.err = os.Open(filepath.Join(p.base, fname))
}
}
p.files[fname] = backing
return backing.f, backing.err
}
// splitMappingsAt ensures that a is not in the middle of any mapping.
// Splits mappings as necessary.
func (p *Process) splitMappingsAt(a Address) {
for _, m := range p.memory.mappings {
if a < m.min || a > m.max {
continue
}
if a == m.min || a == m.max {
return
}
// Split this mapping at a.
m2 := new(Mapping)
*m2 = *m
m.max = a
m2.min = a
if m2.f != nil {
m2.off += m.Size()
}
if m2.origF != nil {
m2.origOff += m.Size()
}
p.memory.mappings = append(p.memory.mappings, m2)
return
}
}
func (p *Process) readPRPSInfo(desc []byte) error {
r := bytes.NewReader(desc)
switch p.arch {
default:
// TODO: return error?
case "amd64":
prpsinfo := &linuxPrPsInfo{}
if err := binary.Read(r, binary.LittleEndian, prpsinfo); err != nil {
return err
}
p.args = strings.Trim(string(prpsinfo.Args[:]), "\x00 ")
}
return nil
}
func (p *Process) readPRStatus(f *os.File, e *elf.File, desc []byte) error {
t := &Thread{}
p.threads = append(p.threads, t)
// Linux
// sys/procfs.h:
// struct elf_prstatus {
// ...
// pid_t pr_pid;
// ...
// elf_gregset_t pr_reg; /* GP registers */
// ...
// };
// typedef struct elf_prstatus prstatus_t;
// Register numberings are listed in sys/user.h.
// prstatus layout will probably be different for each arch/os combo.
switch p.arch {
default:
// TODO: return error here?
case "amd64":
// 32 = offsetof(prstatus_t, pr_pid), 4 = sizeof(pid_t)
t.pid = uint64(p.byteOrder.Uint32(desc[32 : 32+4]))
// 112 = offsetof(prstatus_t, pr_reg), 216 = sizeof(elf_gregset_t)
reg := desc[112 : 112+216]
for i := 0; i < len(reg); i += 8 {
t.regs = append(t.regs, p.byteOrder.Uint64(reg[i:]))
}
// Registers are:
// 0: r15
// 1: r14
// 2: r13
// 3: r12
// 4: rbp
// 5: rbx
// 6: r11
// 7: r10
// 8: r9
// 9: r8
// 10: rax
// 11: rcx
// 12: rdx
// 13: rsi
// 14: rdi
// 15: orig_rax
// 16: rip
// 17: cs
// 18: eflags
// 19: rsp
// 20: ss
// 21: fs_base
// 22: gs_base
// 23: ds
// 24: es
// 25: fs
// 26: gs
t.pc = Address(t.regs[16])
t.sp = Address(t.regs[19])
}
return nil
}
func (p *Process) readDebugInfo() error {
p.syms = map[string]Address{}
// Read symbols from all available files.
for _, f := range p.files {
if f.f == nil {
continue
}
e, err := elf.NewFile(f.f)
if err != nil {
return err
}
syms, err := e.Symbols()
if err != nil {
p.symErr = fmt.Errorf("can't read symbols from %s", f.f.Name())
continue
}
for _, s := range syms {
p.syms[s.Name] = Address(s.Value)
}
}
// Prepare DWARF from the main exe.
// An error while reading DWARF info is not an immediate error,
// but any error will be returned if the caller asks for DWARF.
exe := p.exe
if exe == nil {
f := p.files[p.mainExecName]
if f.err != nil {
p.dwarfErr = f.err
return nil
}
exe = f.f
}
if exe == nil {
p.dwarfErr = fmt.Errorf("can't find mappings for the main executable")
return nil
}
e, err := elf.NewFile(exe)
if err != nil {
p.dwarfErr = fmt.Errorf("can't read DWARF info from %s: %s", exe.Name(), err)
return nil
}
dwarf, err := e.DWARF()
if err != nil {
p.dwarfErr = fmt.Errorf("can't read DWARF info from %s: %s", exe.Name(), err)
return nil
}
p.dwarf = dwarf
return nil
}
func (p *Process) Warnings() []string {
return p.warnings
}
// Args returns the initial part of the program arguments.
func (p *Process) Args() string {
return p.args
}
// ELF/Linux types
// linuxPrPsInfo is the info embedded in NT_PRPSINFO.
type linuxPrPsInfo struct {
State uint8
Sname int8
Zomb uint8
Nice int8
_ [4]uint8
Flag uint64
Uid, Gid uint32
Pid, Ppid, Pgrp, Sid int32
Fname [16]uint8 // filename of executables
Args [80]uint8 // first part of program args
}