bee/vendor/github.com/go-delve/delve/pkg/proc/core/minidump/minidump.go

687 lines
20 KiB
Go

package minidump
// Package minidump provides a loader for Windows Minidump files.
// Minidump files are the Windows equivalent of unix core dumps.
// They can be created by the kernel when a program crashes (however this is
// disabled for Go programs) or programmatically using either WinDbg or the
// ProcDump utility.
//
// The file format is described on MSDN starting at:
// https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_header
// which is the structure found at offset 0 on a minidump file.
//
// Further information on the format can be found reading
// chromium-breakpad's minidump loading code, specifically:
// https://chromium.googlesource.com/breakpad/breakpad/+/master/src/google_breakpad/common/minidump_cpu_amd64.h
// and:
// https://chromium.googlesource.com/breakpad/breakpad/+/master/src/google_breakpad/common/minidump_format.h
import (
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"unicode/utf16"
"unsafe"
"github.com/go-delve/delve/pkg/proc/winutil"
)
type minidumpBuf struct {
buf []byte
kind string
off int
err error
ctx string
}
func (buf *minidumpBuf) u16() uint16 {
const stride = 2
if buf.err != nil {
return 0
}
if buf.off+stride >= len(buf.buf) {
buf.err = fmt.Errorf("minidump %s truncated at offset %#x while %s", buf.kind, buf.off, buf.ctx)
}
r := binary.LittleEndian.Uint16(buf.buf[buf.off : buf.off+stride])
buf.off += stride
return r
}
func (buf *minidumpBuf) u32() uint32 {
const stride = 4
if buf.err != nil {
return 0
}
if buf.off+stride >= len(buf.buf) {
buf.err = fmt.Errorf("minidump %s truncated at offset %#x while %s", buf.kind, buf.off, buf.ctx)
}
r := binary.LittleEndian.Uint32(buf.buf[buf.off : buf.off+stride])
buf.off += stride
return r
}
func (buf *minidumpBuf) u64() uint64 {
const stride = 8
if buf.err != nil {
return 0
}
if buf.off+stride >= len(buf.buf) {
buf.err = fmt.Errorf("minidump %s truncated at offset %#x while %s", buf.kind, buf.off, buf.ctx)
}
r := binary.LittleEndian.Uint64(buf.buf[buf.off : buf.off+stride])
buf.off += stride
return r
}
func streamBuf(stream *Stream, buf *minidumpBuf, name string) *minidumpBuf {
return &minidumpBuf{
buf: buf.buf,
kind: "stream",
off: stream.Offset,
err: nil,
ctx: fmt.Sprintf("reading %s stream at %#x", name, stream.Offset),
}
}
// ErrNotAMinidump is the error returned when the file being loaded is not a
// minidump file.
type ErrNotAMinidump struct {
what string
got uint32
}
func (err ErrNotAMinidump) Error() string {
return fmt.Sprintf("not a minidump, invalid %s %#x", err.what, err.got)
}
const (
minidumpSignature = 0x504d444d // 'MDMP'
minidumpVersion = 0xa793
)
// Minidump represents a minidump file
type Minidump struct {
Timestamp uint32
Flags FileFlags
Streams []Stream
Threads []Thread
Modules []Module
Pid uint32
MemoryRanges []MemoryRange
MemoryInfo []MemoryInfo
streamNum uint32
streamOff uint32
}
// Stream represents one (uninterpreted) stream in a minidump file.
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_directory
type Stream struct {
Type StreamType
Offset int
RawData []byte
}
// Thread represents an entry in the ThreadList stream.
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_thread
type Thread struct {
ID uint32
SuspendCount uint32
PriorityClass uint32
Priority uint32
TEB uint64
Context winutil.CONTEXT
}
// Module represents an entry in the ModuleList stream.
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_module
type Module struct {
BaseOfImage uint64
SizeOfImage uint32
Checksum uint32
TimeDateStamp uint32
Name string
VersionInfo VSFixedFileInfo
// CVRecord stores a CodeView record and is populated when a module's debug information resides in a PDB file. It identifies the PDB file.
CVRecord []byte
// MiscRecord is populated when a module's debug information resides in a DBG file. It identifies the DBG file. This field is effectively obsolete with modules built by recent toolchains.
MiscRecord []byte
}
// VSFixedFileInfo: Visual Studio Fixed File Info.
// See: https://docs.microsoft.com/en-us/windows/desktop/api/verrsrc/ns-verrsrc-tagvs_fixedfileinfo
type VSFixedFileInfo struct {
Signature uint32
StructVersion uint32
FileVersionHi uint32
FileVersionLo uint32
ProductVersionHi uint32
ProductVersionLo uint32
FileFlagsMask uint32
FileFlags uint32
FileOS uint32
FileType uint32
FileSubtype uint32
FileDateHi uint32
FileDateLo uint32
}
// MemoryRange represents a region of memory saved to the core file, it's constructed after either:
// 1. parsing an entry in the Memory64List stream.
// 2. parsing the stack field of an entry in the ThreadList stream.
type MemoryRange struct {
Addr uint64
Data []byte
}
// ReadMemory reads len(buf) bytes of memory starting at addr into buf from this memory region.
func (m *MemoryRange) ReadMemory(buf []byte, addr uintptr) (int, error) {
if len(buf) == 0 {
return 0, nil
}
if (uint64(addr) < m.Addr) || (uint64(addr)+uint64(len(buf)) > m.Addr+uint64(len(m.Data))) {
return 0, io.EOF
}
copy(buf, m.Data[uint64(addr)-m.Addr:])
return len(buf), nil
}
// MemoryInfo reprents an entry in the MemoryInfoList stream.
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory_info
type MemoryInfo struct {
Addr uint64
Size uint64
State MemoryState
Protection MemoryProtection
Type MemoryType
}
//go:generate stringer -type FileFlags,StreamType,Arch,MemoryState,MemoryType,MemoryProtection
// MemoryState is the type of the State field of MINIDUMP_MEMORY_INFO
type MemoryState uint32
const (
MemoryStateCommit MemoryState = 0x1000
MemoryStateReserve MemoryState = 0x2000
MemoryStateFree MemoryState = 0x10000
)
// MemoryType is the type of the Type field of MINIDUMP_MEMORY_INFO
type MemoryType uint32
const (
MemoryTypePrivate MemoryType = 0x20000
MemoryTypeMapped MemoryType = 0x40000
MemoryTypeImage MemoryType = 0x1000000
)
// MemoryProtection is the type of the Protection field of MINIDUMP_MEMORY_INFO
type MemoryProtection uint32
const (
MemoryProtectNoAccess MemoryProtection = 0x01 // PAGE_NOACCESS
MemoryProtectReadOnly MemoryProtection = 0x02 // PAGE_READONLY
MemoryProtectReadWrite MemoryProtection = 0x04 // PAGE_READWRITE
MemoryProtectWriteCopy MemoryProtection = 0x08 // PAGE_WRITECOPY
MemoryProtectExecute MemoryProtection = 0x10 // PAGE_EXECUTE
MemoryProtectExecuteRead MemoryProtection = 0x20 // PAGE_EXECUTE_READ
MemoryProtectExecuteReadWrite MemoryProtection = 0x40 // PAGE_EXECUTE_READWRITE
MemoryProtectExecuteWriteCopy MemoryProtection = 0x80 // PAGE_EXECUTE_WRITECOPY
// These options can be combined with the previous flags
MemoryProtectPageGuard MemoryProtection = 0x100 // PAGE_GUARD
MemoryProtectNoCache MemoryProtection = 0x200 // PAGE_NOCACHE
MemoryProtectWriteCombine MemoryProtection = 0x400 // PAGE_WRITECOMBINE
)
// FileFlags is the type of the Flags field of MINIDUMP_HEADER
type FileFlags uint64
const (
FileNormal FileFlags = 0x00000000
FileWithDataSegs FileFlags = 0x00000001
FileWithFullMemory FileFlags = 0x00000002
FileWithHandleData FileFlags = 0x00000004
FileFilterMemory FileFlags = 0x00000008
FileScanMemory FileFlags = 0x00000010
FileWithUnloadedModules FileFlags = 0x00000020
FileWithIncorrectlyReferencedMemory FileFlags = 0x00000040
FileFilterModulePaths FileFlags = 0x00000080
FileWithProcessThreadData FileFlags = 0x00000100
FileWithPrivateReadWriteMemory FileFlags = 0x00000200
FileWithoutOptionalData FileFlags = 0x00000400
FileWithFullMemoryInfo FileFlags = 0x00000800
FileWithThreadInfo FileFlags = 0x00001000
FileWithCodeSegs FileFlags = 0x00002000
FileWithoutAuxilliarySegs FileFlags = 0x00004000
FileWithFullAuxilliaryState FileFlags = 0x00008000
FileWithPrivateCopyMemory FileFlags = 0x00010000
FileIgnoreInaccessibleMemory FileFlags = 0x00020000
FileWithTokenInformation FileFlags = 0x00040000
)
// StreamType is the type of the StreamType field of MINIDUMP_DIRECTORY
type StreamType uint32
const (
UnusedStream StreamType = 0
ReservedStream0 StreamType = 1
ReservedStream1 StreamType = 2
ThreadListStream StreamType = 3
ModuleListStream StreamType = 4
MemoryListStream StreamType = 5
ExceptionStream StreamType = 6
SystemInfoStream StreamType = 7
ThreadExListStream StreamType = 8
Memory64ListStream StreamType = 9
CommentStreamA StreamType = 10
CommentStreamW StreamType = 11
HandleDataStream StreamType = 12
FunctionTableStream StreamType = 13
UnloadedModuleStream StreamType = 14
MiscInfoStream StreamType = 15
MemoryInfoListStream StreamType = 16
ThreadInfoListStream StreamType = 17
HandleOperationListStream StreamType = 18
TokenStream StreamType = 19
JavascriptDataStream StreamType = 20
SystemMemoryInfoStream StreamType = 21
ProcessVMCounterStream StreamType = 22
)
// Arch is the type of the ProcessorArchitecture field of MINIDUMP_SYSTEM_INFO.
type Arch uint16
const (
CpuArchitectureX86 Arch = 0
CpuArchitectureMips Arch = 1
CpuArchitectureAlpha Arch = 2
CpuArchitecturePPC Arch = 3
CpuArchitectureSHX Arch = 4 // Super-H
CpuArchitectureARM Arch = 5
CpuArchitectureIA64 Arch = 6
CpuArchitectureAlpha64 Arch = 7
CpuArchitectureMSIL Arch = 8 // Microsoft Intermediate Language
CpuArchitectureAMD64 Arch = 9
CpuArchitectureWoW64 Arch = 10
CpuArchitectureARM64 Arch = 12
CpuArchitectureUnknown Arch = 0xffff
)
// Open reads the minidump file at path and returns it as a Minidump structure.
func Open(path string, logfn func(fmt string, args ...interface{})) (*Minidump, error) {
rawbuf, err := ioutil.ReadFile(path) //TODO(aarzilli): mmap?
if err != nil {
return nil, err
}
buf := &minidumpBuf{buf: rawbuf, kind: "file"}
var mdmp Minidump
readMinidumpHeader(&mdmp, buf)
if buf.err != nil {
return nil, buf.err
}
if logfn != nil {
logfn("Minidump Header\n")
logfn("Num Streams: %d\n", mdmp.streamNum)
logfn("Streams offset: %#x\n", mdmp.streamOff)
logfn("File flags: %s\n", fileFlagsToString(mdmp.Flags))
logfn("Offset after header %#x\n", buf.off)
}
readDirectory(&mdmp, buf)
if buf.err != nil {
return nil, buf.err
}
for i := range mdmp.Streams {
stream := &mdmp.Streams[i]
if stream.Type != SystemInfoStream {
continue
}
sb := streamBuf(stream, buf, "system info")
if buf.err != nil {
return nil, buf.err
}
arch := Arch(sb.u16())
if logfn != nil {
logfn("Found processor architecture %s\n", arch.String())
}
if arch != CpuArchitectureAMD64 {
return nil, fmt.Errorf("unsupported architecture %s", arch.String())
}
}
for i := range mdmp.Streams {
stream := &mdmp.Streams[i]
if logfn != nil {
logfn("Stream %d: type:%s off:%#x size:%#x\n", i, stream.Type, stream.Offset, len(stream.RawData))
}
switch stream.Type {
case ThreadListStream:
readThreadList(&mdmp, streamBuf(stream, buf, "thread list"))
if logfn != nil {
for i := range mdmp.Threads {
logfn("\tID:%#x TEB:%#x\n", mdmp.Threads[i].ID, mdmp.Threads[i].TEB)
}
}
case ModuleListStream:
readModuleList(&mdmp, streamBuf(stream, buf, "module list"))
if logfn != nil {
for i := range mdmp.Modules {
logfn("\tName:%q BaseOfImage:%#x SizeOfImage:%#x\n", mdmp.Modules[i].Name, mdmp.Modules[i].BaseOfImage, mdmp.Modules[i].SizeOfImage)
}
}
case ExceptionStream:
//TODO(aarzilli): this stream contains the exception that made the
//process stop and caused the minidump to be taken. If we ever start
//caring about this we should parse this.
case Memory64ListStream:
readMemory64List(&mdmp, streamBuf(stream, buf, "memory64 list"), logfn)
case MemoryInfoListStream:
readMemoryInfoList(&mdmp, streamBuf(stream, buf, "memory info list"), logfn)
case MiscInfoStream:
readMiscInfo(&mdmp, streamBuf(stream, buf, "misc info"))
if logfn != nil {
logfn("\tPid: %#x\n", mdmp.Pid)
}
case CommentStreamW:
if logfn != nil {
logfn("\t%q\n", decodeUTF16(stream.RawData))
}
case CommentStreamA:
if logfn != nil {
logfn("\t%s\n", string(stream.RawData))
}
}
if buf.err != nil {
return nil, buf.err
}
}
return &mdmp, nil
}
// decodeUTF16 converts a NUL-terminated UTF16LE string to (non NUL-terminated) UTF8.
func decodeUTF16(in []byte) string {
utf16encoded := []uint16{}
for i := 0; i+1 < len(in); i += 2 {
var ch uint16
ch = uint16(in[i]) + uint16(in[i+1])<<8
utf16encoded = append(utf16encoded, ch)
}
s := string(utf16.Decode(utf16encoded))
if len(s) > 0 && s[len(s)-1] == 0 {
s = s[:len(s)-1]
}
return s
}
func fileFlagsToString(flags FileFlags) string {
out := []byte{}
for i, name := range _FileFlags_map {
if i == 0 {
continue
}
if flags&i != 0 {
if len(out) > 0 {
out = append(out, '|')
}
out = append(out, name...)
}
}
if len(out) == 0 {
return flags.String()
}
return string(out)
}
// readMinidumpHeader reads the minidump file header
func readMinidumpHeader(mdmp *Minidump, buf *minidumpBuf) {
buf.ctx = "reading minidump header"
if sig := buf.u32(); sig != minidumpSignature {
buf.err = ErrNotAMinidump{"signature", sig}
return
}
if ver := buf.u16(); ver != minidumpVersion {
buf.err = ErrNotAMinidump{"version", uint32(ver)}
return
}
buf.u16() // implementation specific version
mdmp.streamNum = buf.u32()
mdmp.streamOff = buf.u32()
buf.u32() // checksum, but it's always 0
mdmp.Timestamp = buf.u32()
mdmp.Flags = FileFlags(buf.u64())
}
// readDirectory reads the list of streams (i.e. the minidum "directory")
func readDirectory(mdmp *Minidump, buf *minidumpBuf) {
buf.off = int(mdmp.streamOff)
mdmp.Streams = make([]Stream, mdmp.streamNum)
for i := range mdmp.Streams {
buf.ctx = fmt.Sprintf("reading stream directory entry %d", i)
stream := &mdmp.Streams[i]
stream.Type = StreamType(buf.u32())
stream.Offset, stream.RawData = readLocationDescriptor(buf)
if buf.err != nil {
return
}
}
}
// readLocationDescriptor reads a location descriptor structure (a structure
// which describes a subregion of the file), and returns the destination
// offset and a slice into the minidump file's buffer.
func readLocationDescriptor(buf *minidumpBuf) (off int, rawData []byte) {
sz := buf.u32()
off = int(buf.u32())
if buf.err != nil {
return off, nil
}
end := off + int(sz)
if off >= len(buf.buf) || end > len(buf.buf) {
buf.err = fmt.Errorf("location starting at %#x of size %#x is past the end of file, while %s", off, sz, buf.ctx)
return 0, nil
}
rawData = buf.buf[off:end]
return
}
func readString(buf *minidumpBuf) string {
startOff := buf.off
sz := buf.u32()
if buf.err != nil {
return ""
}
end := buf.off + int(sz)
if buf.off >= len(buf.buf) || end > len(buf.buf) {
buf.err = fmt.Errorf("string starting at %#x of size %#x is past the end of file, while %s", startOff, sz, buf.ctx)
return ""
}
return decodeUTF16(buf.buf[buf.off:end])
}
// readThreadList reads a thread list stream and adds the threads to the minidump.
func readThreadList(mdmp *Minidump, buf *minidumpBuf) {
threadNum := buf.u32()
if buf.err != nil {
return
}
mdmp.Threads = make([]Thread, threadNum)
for i := range mdmp.Threads {
buf.ctx = fmt.Sprintf("reading thread list entry %d", i)
thread := &mdmp.Threads[i]
thread.ID = buf.u32()
thread.SuspendCount = buf.u32()
thread.PriorityClass = buf.u32()
thread.Priority = buf.u32()
thread.TEB = buf.u64()
if buf.err != nil {
return
}
readMemoryDescriptor(mdmp, buf) // thread stack
_, rawThreadContext := readLocationDescriptor(buf) // thread context
thread.Context = *((*winutil.CONTEXT)(unsafe.Pointer(&rawThreadContext[0])))
if buf.err != nil {
return
}
}
}
// readModuleList reads a module list stream and adds the modules to the minidump.
func readModuleList(mdmp *Minidump, buf *minidumpBuf) {
moduleNum := buf.u32()
if buf.err != nil {
return
}
mdmp.Modules = make([]Module, moduleNum)
for i := range mdmp.Modules {
buf.ctx = fmt.Sprintf("reading module list entry %d", i)
module := &mdmp.Modules[i]
module.BaseOfImage = buf.u64()
module.SizeOfImage = buf.u32()
module.Checksum = buf.u32()
module.TimeDateStamp = buf.u32()
nameOff := int(buf.u32())
versionInfoVec := make([]uint32, unsafe.Sizeof(VSFixedFileInfo{})/unsafe.Sizeof(uint32(0)))
for j := range versionInfoVec {
versionInfoVec[j] = buf.u32()
}
module.VersionInfo = *(*VSFixedFileInfo)(unsafe.Pointer(&versionInfoVec[0]))
_, module.CVRecord = readLocationDescriptor(buf)
_, module.MiscRecord = readLocationDescriptor(buf)
if buf.err != nil {
return
}
nameBuf := minidumpBuf{buf: buf.buf, kind: "file", off: nameOff, err: nil, ctx: buf.ctx}
module.Name = readString(&nameBuf)
if nameBuf.err != nil {
buf.err = nameBuf.err
return
}
}
}
// readMemory64List reads a _MINIDUMP_MEMORY64_LIST structure, containing
// the description of the process memory.
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory64_list
// And: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory_descriptor
func readMemory64List(mdmp *Minidump, buf *minidumpBuf, logfn func(fmt string, args ...interface{})) {
rangesNum := buf.u64()
baseOff := int(buf.u64())
if buf.err != nil {
return
}
for i := uint64(0); i < rangesNum; i++ {
addr := buf.u64()
sz := buf.u64()
end := baseOff + int(sz)
if baseOff >= len(buf.buf) || end > len(buf.buf) {
buf.err = fmt.Errorf("memory range at %#x of size %#x is past the end of file, while %s", baseOff, sz, buf.ctx)
return
}
mdmp.addMemory(addr, buf.buf[baseOff:end])
if logfn != nil {
logfn("\tMemory %d addr:%#x size:%#x FileOffset:%#x\n", i, addr, sz, baseOff)
}
baseOff = end
}
}
func readMemoryInfoList(mdmp *Minidump, buf *minidumpBuf, logfn func(fmt string, args ...interface{})) {
startOff := buf.off
sizeOfHeader := int(buf.u32())
sizeOfEntry := int(buf.u32())
numEntries := buf.u64()
buf.off = startOff + sizeOfHeader
mdmp.MemoryInfo = make([]MemoryInfo, numEntries)
for i := range mdmp.MemoryInfo {
memInfo := &mdmp.MemoryInfo[i]
startOff := buf.off
memInfo.Addr = buf.u64()
buf.u64() // allocation_base
buf.u32() // allocation_protection
buf.u32() // alignment
memInfo.Size = buf.u64()
memInfo.State = MemoryState(buf.u32())
memInfo.Protection = MemoryProtection(buf.u32())
memInfo.Type = MemoryType(buf.u32())
if logfn != nil {
logfn("\tMemoryInfo %d Addr:%#x Size:%#x %s %s %s\n", i, memInfo.Addr, memInfo.Size, memInfo.State, memInfo.Protection, memInfo.Type)
}
buf.off = startOff + sizeOfEntry
}
}
// readMiscInfo reads the process_id from a MiscInfo stream.
func readMiscInfo(mdmp *Minidump, buf *minidumpBuf) {
buf.u32() // size of info
buf.u32() // flags1
mdmp.Pid = buf.u32() // process_id
// there are more fields here, but we don't care about them
}
// readMemoryDescriptor reads a memory descriptor struct and adds it to the memory map of the minidump.
func readMemoryDescriptor(mdmp *Minidump, buf *minidumpBuf) {
addr := buf.u64()
if buf.err != nil {
return
}
_, rawData := readLocationDescriptor(buf)
if buf.err != nil {
return
}
mdmp.addMemory(addr, rawData)
}
func (mdmp *Minidump) addMemory(addr uint64, data []byte) {
mdmp.MemoryRanges = append(mdmp.MemoryRanges, MemoryRange{addr, data})
}