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}) }