x/debug/core: add initial implementation
Add library for reading core dumps.
Update golang/go#21356
Change-Id: Ia7fa09d9e78a7c30fbdec184a5cf6f73b0994c4a
Reviewed-on: https://go-review.googlesource.com/72830
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/core/address.go b/core/address.go
new file mode 100644
index 0000000..414cd61
--- /dev/null
+++ b/core/address.go
@@ -0,0 +1,40 @@
+// 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.
+
+package core
+
+// An Address is a location in the inferior's address space.
+type Address uint64
+
+// Sub subtracts b from a. Requires a >= b.
+func (a Address) Sub(b Address) int64 {
+ return int64(a - b)
+}
+
+// Add adds x to address a.
+func (a Address) Add(x int64) Address {
+ return a + Address(x)
+}
+
+// Max returns the larger of a and b.
+func (a Address) Max(b Address) Address {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+// Min returns the smaller of a and b.
+func (a Address) Min(b Address) Address {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+// Align rounds a up to a multiple of x.
+// x must be a power of 2.
+func (a Address) Align(x int64) Address {
+ return (a + Address(x) - 1) & ^(Address(x) - 1)
+}
diff --git a/core/core_test.go b/core/core_test.go
new file mode 100644
index 0000000..23a56ec
--- /dev/null
+++ b/core/core_test.go
@@ -0,0 +1,102 @@
+// 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.
+
+package core
+
+import (
+ "testing"
+)
+
+// loadTest loads a simple core file which resulted from running the
+// following program on linux/amd64 with go 1.9.0 (the earliest supported runtime):
+// package main
+// func main() {
+// _ = *(*int)(nil)
+// }
+func loadExample(t *testing.T) *Process {
+ p, err := Core("testdata/core", "testdata")
+ if err != nil {
+ t.Fatalf("can't load test core file: %s", err)
+ }
+ return p
+}
+
+// TestMappings makes sure we can find and load some data.
+func TestMappings(t *testing.T) {
+ p := loadExample(t)
+ s, err := p.Symbols()
+ if err != nil {
+ t.Errorf("can't read symbols: %s\n", err)
+ }
+
+ a := s["main.main"]
+ m := p.findMapping(a)
+ if m == nil {
+ t.Errorf("text mapping missing")
+ }
+ if m.Perm() != Read|Exec {
+ t.Errorf("bad code section permissions")
+ }
+ if opcode := p.ReadUint8(a); opcode != 0x31 {
+ // 0x31 = xorl instruction.
+ // There's no particular reason why this instruction
+ // is first. This just tests that reading code works
+ // for our specific test binary.
+ t.Errorf("opcode=0x%x, want 0x31", opcode)
+ }
+
+ a = s["runtime.class_to_size"]
+ m = p.findMapping(a)
+ if m == nil {
+ t.Errorf("data mapping missing")
+ }
+ if m.Perm() != Read|Write {
+ t.Errorf("bad data section permissions")
+ }
+ if size := p.ReadUint16(a.Add(2)); size != 8 {
+ t.Errorf("class_to_size[1]=%d, want 8", size)
+ }
+}
+
+// TestConfig checks the configuration accessors.
+func TestConfig(t *testing.T) {
+ p := loadExample(t)
+ if arch := p.Arch(); arch != "amd64" {
+ t.Errorf("arch=%s, want amd64", arch)
+ }
+ if size := p.PtrSize(); size != 8 {
+ t.Errorf("ptrSize=%d, want 8", size)
+ }
+ if log := p.LogPtrSize(); log != 3 {
+ t.Errorf("logPtrSize=%d, want 3", log)
+ }
+ if bo := p.ByteOrder(); bo.String() != "LittleEndian" {
+ t.Errorf("got %s, want LittleEndian", bo)
+ }
+}
+
+// TestThread makes sure we get information about running threads.
+func TestThread(t *testing.T) {
+ p := loadExample(t)
+ syms, err := p.Symbols()
+ if err != nil {
+ t.Errorf("can't read symbols: %s\n", err)
+ }
+ raise := syms["runtime.raise"]
+ var size int64 = 1 << 30
+ for _, a := range syms {
+ if a > raise && a.Sub(raise) < size {
+ size = a.Sub(raise)
+ }
+ }
+ found := false
+ for _, thr := range p.Threads() {
+ if thr.PC() >= raise && thr.PC() < raise.Add(size) {
+ found = true
+ }
+ }
+ if !found {
+ t.Errorf("can't find thread that did runtime.raise")
+ }
+}
diff --git a/core/mapping.go b/core/mapping.go
new file mode 100644
index 0000000..a678dc8
--- /dev/null
+++ b/core/mapping.go
@@ -0,0 +1,168 @@
+// 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.
+
+package core
+
+import (
+ "fmt"
+ "os"
+ "strings"
+)
+
+// A Mapping represents a contiguous subset of the inferior's address space.
+type Mapping struct {
+ min Address
+ max Address
+ perm Perm
+
+ f *os.File // file backing this region
+ off int64 // offset of start of this mapping in f
+
+ // For regions originally backed by a file but now in the core file,
+ // (probably because it is copy-on-write) this is the original data source.
+ // This info is just for printing; the data in this source is stale.
+ origF *os.File
+ origOff int64
+
+ // Contents of f at offset off. Length=max-min.
+ contents []byte
+}
+
+// Min returns the lowest virtual address of the mapping.
+func (m *Mapping) Min() Address {
+ return m.min
+}
+
+// Max returns the virtual address of the byte just beyond the mapping.
+func (m *Mapping) Max() Address {
+ return m.max
+}
+
+// Size returns int64(Max-Min)
+func (m *Mapping) Size() int64 {
+ return m.max.Sub(m.min)
+}
+
+// Perm returns the permissions on the mapping.
+func (m *Mapping) Perm() Perm {
+ return m.perm
+}
+
+// Source returns the backing file and offset for the mapping, or "", 0 if none.
+func (m *Mapping) Source() (string, int64) {
+ if m.f == nil {
+ return "", 0
+ }
+ return m.f.Name(), m.off
+}
+
+// CopyOnWrite reports whether the mapping is a copy-on-write region, i.e.
+// it started as a mapped file and is now writeable.
+// TODO: is this distinguishable from a write-back region?
+func (m *Mapping) CopyOnWrite() bool {
+ return m.origF != nil
+}
+
+// For CopyOnWrite mappings, OrigSource returns the file/offset of the
+// original copy of the data, or "", 0 if none.
+func (m *Mapping) OrigSource() (string, int64) {
+ if m.origF == nil {
+ return "", 0
+ }
+ return m.origF.Name(), m.origOff
+}
+
+// A Perm represents the permissions allowed for a Mapping.
+type Perm uint8
+
+const (
+ Read Perm = 1 << iota
+ Write
+ Exec
+)
+
+func (p Perm) String() string {
+ var a [3]string
+ b := a[:0]
+ if p&Read != 0 {
+ b = append(b, "Read")
+ }
+ if p&Write != 0 {
+ b = append(b, "Write")
+ }
+ if p&Exec != 0 {
+ b = append(b, "Exec")
+ }
+ if len(b) == 0 {
+ b = append(b, "None")
+ }
+ return strings.Join(b, "|")
+}
+
+// We assume that OS pages are at least 4K in size. So every mapping
+// starts and ends at a multiple of 4K.
+// We divide the other 64-12 = 52 bits into levels in a page table.
+type pageTable0 [1 << 10]*Mapping
+type pageTable1 [1 << 10]*pageTable0
+type pageTable2 [1 << 10]*pageTable1
+type pageTable3 [1 << 10]*pageTable2
+type pageTable4 [1 << 12]*pageTable3
+
+// findMapping is simple enough that it inlines.
+func (p *Process) findMapping(a Address) *Mapping {
+ t3 := p.pageTable[a>>52]
+ if t3 == nil {
+ return nil
+ }
+ t2 := t3[a>>42%(1<<10)]
+ if t2 == nil {
+ return nil
+ }
+ t1 := t2[a>>32%(1<<10)]
+ if t1 == nil {
+ return nil
+ }
+ t0 := t1[a>>22%(1<<10)]
+ if t0 == nil {
+ return nil
+ }
+ return t0[a>>12%(1<<10)]
+}
+
+func (p *Process) addMapping(m *Mapping) error {
+ if m.min%(1<<12) != 0 {
+ return fmt.Errorf("mapping start %x isn't a multiple of 4096", m.min)
+ }
+ if m.max%(1<<12) != 0 {
+ return fmt.Errorf("mapping end %x isn't a multiple of 4096", m.max)
+ }
+ for a := m.min; a < m.max; a += 1 << 12 {
+ i3 := a >> 52
+ t3 := p.pageTable[i3]
+ if t3 == nil {
+ t3 = new(pageTable3)
+ p.pageTable[i3] = t3
+ }
+ i2 := a >> 42 % (1 << 10)
+ t2 := t3[i2]
+ if t2 == nil {
+ t2 = new(pageTable2)
+ t3[i2] = t2
+ }
+ i1 := a >> 32 % (1 << 10)
+ t1 := t2[i1]
+ if t1 == nil {
+ t1 = new(pageTable1)
+ t2[i1] = t1
+ }
+ i0 := a >> 22 % (1 << 10)
+ t0 := t1[i0]
+ if t0 == nil {
+ t0 = new(pageTable0)
+ t1[i0] = t0
+ }
+ t0[a>>12%(1<<10)] = m
+ }
+ return nil
+}
diff --git a/core/process.go b/core/process.go
new file mode 100644
index 0000000..9afc491
--- /dev/null
+++ b/core/process.go
@@ -0,0 +1,530 @@
+// 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 (
+ "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
+ exec []*os.File // executables (more than one for shlibs)
+ mappings []*Mapping // 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
+}
+
+// Mappings returns a list of virtual memory mappings for p.
+func (p *Process) Mappings() []*Mapping {
+ return p.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
+}
+
+// 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 string) (*Process, error) {
+ core, err := os.Open(coreFile)
+ if err != nil {
+ return nil, err
+ }
+
+ p := new(Process)
+ p.base = base
+ if err := p.readCore(core); err != nil {
+ return nil, err
+ }
+ if err := p.readExec(); err != nil {
+ return nil, err
+ }
+
+ // Double-check that we have underlying data available for all mappings.
+ for _, m := range p.mappings {
+ if m.f == nil {
+ return nil, fmt.Errorf("incomplete mapping %x %x", m.min, m.max)
+ }
+ }
+
+ // Sort then merge mappings, just to clean up a bit.
+ sort.Slice(p.mappings, func(i, j int) bool {
+ return p.mappings[i].min < p.mappings[j].min
+ })
+ ms := p.mappings[1:]
+ p.mappings = p.mappings[:1]
+ for _, m := range ms {
+ k := p.mappings[len(p.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 {
+ p.mappings = append(p.mappings, m)
+ }
+ }
+
+ // Memory map all the mappings.
+ for _, m := range p.mappings {
+ var err error
+ m.contents, err = syscall.Mmap(int(m.f.Fd()), m.off, int(m.max.Sub(m.min)), syscall.PROT_READ, syscall.MAP_SHARED)
+ if err != nil {
+ return nil, fmt.Errorf("can't memory map %s at %d: %s\n", m.f, m.off, err)
+ }
+ }
+
+ // Build page table for mapping lookup.
+ for _, m := range p.mappings {
+ err := p.addMapping(m)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return p, 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.Filesz != prog.Memsz {
+ return fmt.Errorf("Data at address %x is not complete. The core has %x bytes, we need %x bytes", min, prog.Filesz, prog.Memsz)
+ // For non-writeable sections, the underlying data
+ // might not be in the core file itself.
+ // The underlying data will be found during readNote.
+ }
+ }
+ if prog.Flags&elf.PF_X != 0 {
+ perm |= Exec
+ }
+ if perm == 0 {
+ // TODO: keep these nothing-mapped mappings?
+ return nil
+ }
+ m := &Mapping{min: min, max: max, perm: perm}
+ p.mappings = append(p.mappings, m)
+ if prog.Filesz > 0 {
+ // Data backing this mapping is in the core file.
+ m.f = f
+ m.off = int64(prog.Off)
+ if prog.Filesz < uint64(m.max.Sub(m.min)) {
+ // We only have partial data for this mapping in the core file.
+ // Trim the mapping and allocate an anonymous mapping for the remainder.
+ m2 := &Mapping{min: m.min.Add(int64(prog.Filesz)), max: m.max, perm: m.perm}
+ m.max = m2.min
+ p.mappings = append(p.mappings, m2)
+ }
+ }
+ 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
+
+ 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" && typ == NT_FILE {
+ err := p.readNTFile(f, e, desc)
+ if err != nil {
+ return err
+ }
+ }
+ if name == "CORE" && typ == elf.NT_PRSTATUS {
+ // An OS thread (an M)
+ err := p.readPRStatus(f, e, desc)
+ if err != nil {
+ return err
+ }
+ }
+ // TODO: NT_FPREGSET for floating-point registers
+ // TODO: NT_PRPSINFO for ???
+ }
+ return nil
+}
+
+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 = ""
+ }
+
+ backing, err := os.Open(filepath.Join(p.base, name))
+ if err != nil {
+ // Can't find mapped file.
+ // TODO: if we debug on a different machine,
+ // provide a way to map from core's file spec to a real file.
+ return fmt.Errorf("can't open mapped file: %v\n", err)
+ }
+
+ // TODO: this is O(n^2). Shouldn't be a big problem in practice.
+ p.splitMappingsAt(min)
+ p.splitMappingsAt(max)
+ for _, m := range p.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")
+ }
+ if m.f == nil {
+ if m.perm&Write != 0 {
+ panic("writeable data missing from core")
+ }
+ m.f = backing
+ 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 = backing
+ m.origOff = int64(off) + m.min.Sub(min)
+ }
+
+ // Save a reference to the executable files.
+ // We keep them around so we can try to get symbols from them.
+ // TODO: we should really only keep those files for which the
+ // symbols return the correct addresses given where the file is
+ // mapped in memory. Not sure what to do here.
+ // Seems to work for the base executable, for now.
+ if m.perm&Exec != 0 {
+ found := false
+ for _, x := range p.exec {
+ if x == m.f {
+ found = true
+ break
+ }
+
+ }
+ if !found {
+ p.exec = append(p.exec, m.f)
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// 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.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.mappings = append(p.mappings, m2)
+ return
+ }
+}
+
+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) readExec() error {
+ p.syms = map[string]Address{}
+ for _, exec := range p.exec {
+ e, err := elf.NewFile(exec)
+ if err != nil {
+ return err
+ }
+ if e.Type != elf.ET_EXEC {
+ // This happens for shared libraries, the core file itself, ...
+ continue
+ }
+ syms, err := e.Symbols()
+ if err != nil {
+ p.symErr = fmt.Errorf("can't read symbols from %s", exec.Name())
+ } else {
+ for _, s := range syms {
+ p.syms[s.Name] = Address(s.Value)
+ }
+ }
+ // An error while reading DWARF info is not an immediate error,
+ // but any error will be returned if the caller asks for DWARF.
+ dwarf, err := e.DWARF()
+ if err != nil {
+ p.dwarfErr = fmt.Errorf("can't read DWARF info from %s: %s", exec.Name(), err)
+ } else {
+ p.dwarf = dwarf
+ }
+ }
+ return nil
+}
diff --git a/core/read.go b/core/read.go
new file mode 100644
index 0000000..6d3b7dd
--- /dev/null
+++ b/core/read.go
@@ -0,0 +1,145 @@
+// 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.
+
+package core
+
+import (
+ "encoding/binary"
+ "fmt"
+)
+
+// All the Read* functions below will panic if something goes wrong.
+
+// ReadAt reads len(b) bytes at address a in the inferior
+// and stores them in b.
+func (p *Process) ReadAt(b []byte, a Address) {
+ for {
+ m := p.findMapping(a)
+ if m == nil {
+ panic(fmt.Errorf("address %x is not mapped in the core file", a))
+ }
+ n := copy(b, m.contents[a.Sub(m.min):])
+ if n == len(b) {
+ return
+ }
+ // Modify request to get data from the next mapping.
+ b = b[n:]
+ a = a.Add(int64(n))
+ }
+}
+
+// ReadUint8 returns a uint8 read from address a of the inferior.
+func (p *Process) ReadUint8(a Address) uint8 {
+ m := p.findMapping(a)
+ if m == nil {
+ panic(fmt.Errorf("address %x is not mapped in the core file", a))
+ }
+ return m.contents[a.Sub(m.min)]
+}
+
+// ReadUint16 returns a uint16 read from address a of the inferior.
+func (p *Process) ReadUint16(a Address) uint16 {
+ m := p.findMapping(a)
+ if m == nil {
+ panic(fmt.Errorf("address %x is not mapped in the core file", a))
+ }
+ b := m.contents[a.Sub(m.min):]
+ if len(b) < 2 {
+ var buf [2]byte
+ b = buf[:]
+ p.ReadAt(b, a)
+ }
+ if p.littleEndian {
+ return binary.LittleEndian.Uint16(b)
+ }
+ return binary.BigEndian.Uint16(b)
+}
+
+// ReadUint32 returns a uint32 read from address a of the inferior.
+func (p *Process) ReadUint32(a Address) uint32 {
+ m := p.findMapping(a)
+ if m == nil {
+ panic(fmt.Errorf("address %x is not mapped in the core file", a))
+ }
+ b := m.contents[a.Sub(m.min):]
+ if len(b) < 4 {
+ var buf [4]byte
+ b = buf[:]
+ p.ReadAt(b, a)
+ }
+ if p.littleEndian {
+ return binary.LittleEndian.Uint32(b)
+ }
+ return binary.BigEndian.Uint32(b)
+}
+
+// ReadUint64 returns a uint64 read from address a of the inferior.
+func (p *Process) ReadUint64(a Address) uint64 {
+ m := p.findMapping(a)
+ if m == nil {
+ panic(fmt.Errorf("address %x is not mapped in the core file", a))
+ }
+ b := m.contents[a.Sub(m.min):]
+ if len(b) < 8 {
+ var buf [8]byte
+ b = buf[:]
+ p.ReadAt(b, a)
+ }
+ if p.littleEndian {
+ return binary.LittleEndian.Uint64(b)
+ }
+ return binary.BigEndian.Uint64(b)
+}
+
+// ReadInt8 returns an int8 read from address a of the inferior.
+func (p *Process) ReadInt8(a Address) int8 {
+ return int8(p.ReadUint8(a))
+}
+
+// ReadInt16 returns an int16 read from address a of the inferior.
+func (p *Process) ReadInt16(a Address) int16 {
+ return int16(p.ReadUint16(a))
+}
+
+// ReadInt32 returns an int32 read from address a of the inferior.
+func (p *Process) ReadInt32(a Address) int32 {
+ return int32(p.ReadUint32(a))
+}
+
+// ReadInt64 returns an int64 read from address a of the inferior.
+func (p *Process) ReadInt64(a Address) int64 {
+ return int64(p.ReadUint64(a))
+}
+
+// ReadUintptr returns a uint of pointer size read from address a of the inferior.
+func (p *Process) ReadUintptr(a Address) uint64 {
+ if p.ptrSize == 4 {
+ return uint64(p.ReadUint32(a))
+ }
+ return p.ReadUint64(a)
+}
+
+// ReadInt returns an int (of pointer size) read from address a of the inferior.
+func (p *Process) ReadInt(a Address) int64 {
+ if p.ptrSize == 4 {
+ return int64(p.ReadInt32(a))
+ }
+ return p.ReadInt64(a)
+}
+
+// ReadPtr returns a pointer loaded from address a of the inferior.
+func (p *Process) ReadPtr(a Address) Address {
+ return Address(p.ReadUintptr(a))
+}
+
+// ReadCString reads a null-terminated string starting at address a.
+func (p *Process) ReadCString(a Address) string {
+ for n := int64(0); ; n++ {
+ if p.ReadUint8(a.Add(n)) == 0 {
+ b := make([]byte, n)
+ p.ReadAt(b, a)
+ return string(b)
+ }
+ }
+}
diff --git a/core/testdata/README b/core/testdata/README
new file mode 100644
index 0000000..11d9761
--- /dev/null
+++ b/core/testdata/README
@@ -0,0 +1,14 @@
+This directory contains a simple core file for use in testing.
+
+It was generated by running the following program with go1.9.0.
+
+package main
+
+func main() {
+ _ = *(*int)(nil)
+}
+
+The core file includes the executable by reference using an absolute
+path. The executable was at /tmp/test, so that path is reproduced
+here so the core dump reader can find the executable using this
+testdata directory as the base directory.
diff --git a/core/testdata/core b/core/testdata/core
new file mode 100644
index 0000000..55318de
--- /dev/null
+++ b/core/testdata/core
Binary files differ
diff --git a/core/testdata/tmp/test b/core/testdata/tmp/test
new file mode 100755
index 0000000..f3353b2
--- /dev/null
+++ b/core/testdata/tmp/test
Binary files differ
diff --git a/core/thread.go b/core/thread.go
new file mode 100644
index 0000000..8c7517e
--- /dev/null
+++ b/core/thread.go
@@ -0,0 +1,35 @@
+// 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.
+
+package core
+
+// A Thread represents an operating system thread.
+type Thread struct {
+ pid uint64 // thread/process ID
+ regs []uint64 // set depends on arch
+ pc Address // program counter
+ sp Address // stack pointer
+}
+
+func (t *Thread) Pid() uint64 {
+ return t.pid
+}
+
+// Regs returns the set of register values for the thread.
+// What registers go where is architecture-dependent.
+// TODO: document for each architecture.
+// TODO: do this in some arch-independent way?
+func (t *Thread) Regs() []uint64 {
+ return t.regs
+}
+
+func (t *Thread) PC() Address {
+ return t.pc
+}
+
+func (t *Thread) SP() Address {
+ return t.sp
+}
+
+// TODO: link register?