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?