proc/core: add support for windows minidumps
Minidumps are the windows equivalent of unix core files. This commit updates pkg/proc/core so that it can open and read windows minidumps. Updates #794
This commit is contained in:
parent
31fff84519
commit
85e4ba439c
@ -39,6 +39,7 @@ Pass flags to the program you are debugging using `--`, for example:
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -39,6 +39,7 @@ dlv attach pid [executable]
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -34,6 +34,7 @@ dlv connect addr
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -11,6 +11,8 @@ The core command will open the specified core file and the associated
|
||||
executable and let you examine the state of the process when the
|
||||
core dump was taken.
|
||||
|
||||
Currently supports linux/amd64 core files and windows/amd64 minidumps.
|
||||
|
||||
```
|
||||
dlv core <executable> <core>
|
||||
```
|
||||
@ -38,6 +40,7 @@ dlv core <executable> <core>
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -45,6 +45,7 @@ dlv debug [package]
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -39,6 +39,7 @@ dlv exec <path/to/binary>
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -38,6 +38,7 @@ dlv replay [trace directory]
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -34,6 +34,7 @@ dlv run
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -45,6 +45,7 @@ dlv test [package]
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -49,6 +49,7 @@ dlv trace [package] regexp
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -34,6 +34,7 @@ dlv version
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -1,13 +0,0 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
func f() {
|
||||
for {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
f()
|
||||
}
|
19
_fixtures/sleep.go
Normal file
19
_fixtures/sleep.go
Normal file
@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func f() {
|
||||
for {
|
||||
time.Sleep(10 * time.Second)
|
||||
if len(os.Args) > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
f()
|
||||
}
|
12
appveyor.yml
12
appveyor.yml
@ -13,12 +13,20 @@ install:
|
||||
Invoke-WebRequest -UserAgent wget -Uri $url -OutFile $file
|
||||
&7z x -oC:\ $file > $null
|
||||
}
|
||||
- set PATH=c:\mingw64\bin;%GOPATH%\bin;%PATH%
|
||||
# Install Procdump
|
||||
if (-Not (Test-Path "C:\procdump")) {
|
||||
mkdir c:\procdump
|
||||
Invoke-WebRequest -UserAgent wget -Uri https://download.sysinternals.com/files/Procdump.zip -OutFile C:\procdump\procdump.zip
|
||||
&7z x -oC:\procdump\ C:\procdump\procdump.zip > $null
|
||||
}
|
||||
- set PATH=c:\procdump;c:\mingw64\bin;%GOPATH%\bin;%PATH%
|
||||
- echo %PATH%
|
||||
- echo %GOPATH%
|
||||
- go version
|
||||
- go env
|
||||
cache: C:\mingw64
|
||||
cache:
|
||||
- C:\mingw64
|
||||
- C:\procdump
|
||||
build_script:
|
||||
- mingw32-make install
|
||||
test_script:
|
||||
|
@ -98,6 +98,7 @@ func New(docCall bool) *cobra.Command {
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
Defaults to "debugger" when logging is enabled with --log.`)
|
||||
RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
|
||||
RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.")
|
||||
@ -236,7 +237,9 @@ to know what functions your process is executing.`,
|
||||
|
||||
The core command will open the specified core file and the associated
|
||||
executable and let you examine the state of the process when the
|
||||
core dump was taken.`,
|
||||
core dump was taken.
|
||||
|
||||
Currently supports linux/amd64 core files and windows/amd64 minidumps.`,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 2 {
|
||||
return errors.New("you must provide a core file and an executable")
|
||||
|
@ -13,6 +13,7 @@ var lldbServerOutput = false
|
||||
var debugLineErrors = false
|
||||
var rpc = false
|
||||
var fnCall = false
|
||||
var minidump = false
|
||||
|
||||
// GdbWire returns true if the gdbserial package should log all the packets
|
||||
// exchanged with the stub.
|
||||
@ -47,6 +48,11 @@ func FnCall() bool {
|
||||
return fnCall
|
||||
}
|
||||
|
||||
// Minidump returns true if the minidump loader should be logged.
|
||||
func Minidump() bool {
|
||||
return minidump
|
||||
}
|
||||
|
||||
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
||||
|
||||
// Setup sets debugger flags based on the contents of logstr.
|
||||
@ -77,6 +83,8 @@ func Setup(logFlag bool, logstr string) error {
|
||||
rpc = true
|
||||
case "fncall":
|
||||
fnCall = true
|
||||
case "minidump":
|
||||
minidump = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -183,11 +183,26 @@ var (
|
||||
ErrChangeRegisterCore = errors.New("can not change register values of core process")
|
||||
)
|
||||
|
||||
type openFn func(string, string) (*Process, error)
|
||||
|
||||
var openFns = []openFn{readLinuxAMD64Core, readAMD64Minidump}
|
||||
|
||||
// ErrUnrecognizedFormat is returned when the core file is not recognized as
|
||||
// any of the supported formats.
|
||||
var ErrUnrecognizedFormat = errors.New("unrecognized core format")
|
||||
|
||||
// OpenCore will open the core file and return a Process struct.
|
||||
// If the DWARF information cannot be found in the binary, Delve will look
|
||||
// for external debug files in the directories passed in.
|
||||
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
|
||||
p, err := readLinuxAMD64Core(corePath, exePath)
|
||||
var p *Process
|
||||
var err error
|
||||
for _, openFn := range openFns {
|
||||
p, err = openFn(corePath, exePath)
|
||||
if err != ErrUnrecognizedFormat {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -367,3 +367,88 @@ mainSearch:
|
||||
assertNoError(v2.Unreadable, t, "unreadable variable 's'")
|
||||
t.Logf("s = %#v\n", v2)
|
||||
}
|
||||
|
||||
func TestMinidump(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("minidumps can only be produced on windows")
|
||||
}
|
||||
var buildFlags test.BuildFlags
|
||||
if buildMode == "pie" {
|
||||
buildFlags = test.BuildModePIE
|
||||
}
|
||||
fix := test.BuildFixture("sleep", buildFlags)
|
||||
mdmpPath := procdump(t, fix.Path)
|
||||
|
||||
p, err := OpenCore(mdmpPath, fix.Path, []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("OpenCore: %v", err)
|
||||
}
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
if err != nil || len(gs) == 0 {
|
||||
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
|
||||
}
|
||||
t.Logf("%d goroutines", len(gs))
|
||||
foundMain, foundTime := false, false
|
||||
for _, g := range gs {
|
||||
stack, err := g.Stacktrace(10, false)
|
||||
if err != nil {
|
||||
t.Errorf("Stacktrace() on goroutine %v = %v", g, err)
|
||||
}
|
||||
t.Logf("goroutine %d", g.ID)
|
||||
for _, frame := range stack {
|
||||
name := "?"
|
||||
if frame.Current.Fn != nil {
|
||||
name = frame.Current.Fn.Name
|
||||
}
|
||||
t.Logf("\t%s:%d in %s %#x", frame.Current.File, frame.Current.Line, name, frame.Current.PC)
|
||||
if frame.Current.Fn == nil {
|
||||
continue
|
||||
}
|
||||
switch frame.Current.Fn.Name {
|
||||
case "main.main":
|
||||
foundMain = true
|
||||
case "time.Sleep":
|
||||
foundTime = true
|
||||
}
|
||||
}
|
||||
if foundMain != foundTime {
|
||||
t.Errorf("found main.main but no time.Sleep (or viceversa) %v %v", foundMain, foundTime)
|
||||
}
|
||||
}
|
||||
if !foundMain {
|
||||
t.Fatalf("could not find main goroutine")
|
||||
}
|
||||
}
|
||||
|
||||
func procdump(t *testing.T, exePath string) string {
|
||||
exeDir := filepath.Dir(exePath)
|
||||
cmd := exec.Command("procdump64", "-accepteula", "-ma", "-n", "1", "-s", "3", "-x", exeDir, exePath, "quit")
|
||||
out, err := cmd.CombinedOutput() // procdump exits with non-zero status on success, so we have to ignore the error here
|
||||
if !strings.Contains(string(out), "Dump count reached.") {
|
||||
t.Fatalf("possible error running procdump64, output: %q, error: %v", string(out), err)
|
||||
}
|
||||
|
||||
dh, err := os.Open(exeDir)
|
||||
if err != nil {
|
||||
t.Fatalf("could not open executable file directory %q: %v", exeDir, err)
|
||||
}
|
||||
defer dh.Close()
|
||||
fis, err := dh.Readdir(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("could not read executable file directory %q: %v", exeDir, err)
|
||||
}
|
||||
t.Logf("looking for dump file")
|
||||
exeName := filepath.Base(exePath)
|
||||
for _, fi := range fis {
|
||||
name := fi.Name()
|
||||
t.Logf("\t%s", name)
|
||||
if strings.HasPrefix(name, exeName) && strings.HasSuffix(name, ".dmp") {
|
||||
mdmpPath := filepath.Join(exeDir, name)
|
||||
test.PathsToRemove = append(test.PathsToRemove, mdmpPath)
|
||||
return mdmpPath
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("could not find dump file")
|
||||
return ""
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
||||
@ -28,6 +29,8 @@ const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAV
|
||||
// NT_AUXV is the note type for notes containing a copy of the Auxv array
|
||||
const NT_AUXV elf.NType = 0x6
|
||||
|
||||
const elfErrorBadMagicNumber = "bad magic number"
|
||||
|
||||
// readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
|
||||
// exePath. For details on the Linux ELF core format, see:
|
||||
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
||||
@ -37,6 +40,10 @@ const NT_AUXV elf.NType = 0x6
|
||||
func readLinuxAMD64Core(corePath, exePath string) (*Process, error) {
|
||||
coreFile, err := elf.Open(corePath)
|
||||
if err != nil {
|
||||
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
|
||||
// Go >=1.11 and <1.11 produce different errors when reading a non-elf file.
|
||||
return nil, ErrUnrecognizedFormat
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
exe, err := os.Open(exePath)
|
||||
|
154
pkg/proc/core/minidump/fileflags_string.go
Normal file
154
pkg/proc/core/minidump/fileflags_string.go
Normal file
@ -0,0 +1,154 @@
|
||||
// Code generated by "stringer -type FileFlags,StreamType,Arch,MemoryState,MemoryType,MemoryProtection"; DO NOT EDIT.
|
||||
|
||||
package minidump
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _FileFlags_name = "FileNormalFileWithDataSegsFileWithFullMemoryFileWithHandleDataFileFilterMemoryFileScanMemoryFileWithUnloadedModulesFileWithIncorrectlyReferencedMemoryFileFilterModulePathsFileWithProcessThreadDataFileWithPrivateReadWriteMemoryFileWithoutOptionalDataFileWithFullMemoryInfoFileWithThreadInfoFileWithCodeSegsFileWithoutAuxilliarySegsFileWithFullAuxilliaryStateFileWithPrivateCopyMemoryFileIgnoreInaccessibleMemoryFileWithTokenInformation"
|
||||
|
||||
var _FileFlags_map = map[FileFlags]string{
|
||||
0: _FileFlags_name[0:10],
|
||||
1: _FileFlags_name[10:26],
|
||||
2: _FileFlags_name[26:44],
|
||||
4: _FileFlags_name[44:62],
|
||||
8: _FileFlags_name[62:78],
|
||||
16: _FileFlags_name[78:92],
|
||||
32: _FileFlags_name[92:115],
|
||||
64: _FileFlags_name[115:150],
|
||||
128: _FileFlags_name[150:171],
|
||||
256: _FileFlags_name[171:196],
|
||||
512: _FileFlags_name[196:226],
|
||||
1024: _FileFlags_name[226:249],
|
||||
2048: _FileFlags_name[249:271],
|
||||
4096: _FileFlags_name[271:289],
|
||||
8192: _FileFlags_name[289:305],
|
||||
16384: _FileFlags_name[305:330],
|
||||
32768: _FileFlags_name[330:357],
|
||||
65536: _FileFlags_name[357:382],
|
||||
131072: _FileFlags_name[382:410],
|
||||
262144: _FileFlags_name[410:434],
|
||||
}
|
||||
|
||||
func (i FileFlags) String() string {
|
||||
if str, ok := _FileFlags_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "FileFlags(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
|
||||
const _StreamType_name = "UnusedStreamReservedStream0ReservedStream1ThreadListStreamModuleListStreamMemoryListStreamExceptionStreamSystemInfoStreamThreadExListStreamMemory64ListStreamCommentStreamACommentStreamWHandleDataStreamFunctionTableStreamUnloadedModuleStreamMiscInfoStreamMemoryInfoListStreamThreadInfoListStreamHandleOperationListStreamTokenStreamJavascriptDataStreamSystemMemoryInfoStreamProcessVMCounterStream"
|
||||
|
||||
var _StreamType_index = [...]uint16{0, 12, 27, 42, 58, 74, 90, 105, 121, 139, 157, 171, 185, 201, 220, 240, 254, 274, 294, 319, 330, 350, 372, 394}
|
||||
|
||||
func (i StreamType) String() string {
|
||||
if i >= StreamType(len(_StreamType_index)-1) {
|
||||
return "StreamType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _StreamType_name[_StreamType_index[i]:_StreamType_index[i+1]]
|
||||
}
|
||||
|
||||
const (
|
||||
_Arch_name_0 = "CpuArchitectureX86CpuArchitectureMipsCpuArchitectureAlphaCpuArchitecturePPCCpuArchitectureSHXCpuArchitectureARMCpuArchitectureIA64CpuArchitectureAlpha64CpuArchitectureMSILCpuArchitectureAMD64CpuArchitectureWoW64"
|
||||
_Arch_name_1 = "CpuArchitectureARM64"
|
||||
_Arch_name_2 = "CpuArchitectureUnknown"
|
||||
)
|
||||
|
||||
var (
|
||||
_Arch_index_0 = [...]uint8{0, 18, 37, 57, 75, 93, 111, 130, 152, 171, 191, 211}
|
||||
)
|
||||
|
||||
func (i Arch) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 10:
|
||||
return _Arch_name_0[_Arch_index_0[i]:_Arch_index_0[i+1]]
|
||||
case i == 12:
|
||||
return _Arch_name_1
|
||||
case i == 65535:
|
||||
return _Arch_name_2
|
||||
default:
|
||||
return "Arch(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
_MemoryState_name_0 = "MemoryStateCommit"
|
||||
_MemoryState_name_1 = "MemoryStateReserve"
|
||||
_MemoryState_name_2 = "MemoryStateFree"
|
||||
)
|
||||
|
||||
func (i MemoryState) String() string {
|
||||
switch {
|
||||
case i == 4096:
|
||||
return _MemoryState_name_0
|
||||
case i == 8192:
|
||||
return _MemoryState_name_1
|
||||
case i == 65536:
|
||||
return _MemoryState_name_2
|
||||
default:
|
||||
return "MemoryState(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
_MemoryType_name_0 = "MemoryTypePrivate"
|
||||
_MemoryType_name_1 = "MemoryTypeMapped"
|
||||
_MemoryType_name_2 = "MemoryTypeImage"
|
||||
)
|
||||
|
||||
func (i MemoryType) String() string {
|
||||
switch {
|
||||
case i == 131072:
|
||||
return _MemoryType_name_0
|
||||
case i == 262144:
|
||||
return _MemoryType_name_1
|
||||
case i == 16777216:
|
||||
return _MemoryType_name_2
|
||||
default:
|
||||
return "MemoryType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
_MemoryProtection_name_0 = "MemoryProtectNoAccessMemoryProtectReadOnly"
|
||||
_MemoryProtection_name_1 = "MemoryProtectReadWrite"
|
||||
_MemoryProtection_name_2 = "MemoryProtectWriteCopy"
|
||||
_MemoryProtection_name_3 = "MemoryProtectExecute"
|
||||
_MemoryProtection_name_4 = "MemoryProtectExecuteRead"
|
||||
_MemoryProtection_name_5 = "MemoryProtectExecuteReadWrite"
|
||||
_MemoryProtection_name_6 = "MemoryProtectExecuteWriteCopy"
|
||||
_MemoryProtection_name_7 = "MemoryProtectPageGuard"
|
||||
_MemoryProtection_name_8 = "MemoryProtectNoCache"
|
||||
_MemoryProtection_name_9 = "MemoryProtectWriteCombine"
|
||||
)
|
||||
|
||||
var (
|
||||
_MemoryProtection_index_0 = [...]uint8{0, 21, 42}
|
||||
)
|
||||
|
||||
func (i MemoryProtection) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 2:
|
||||
i -= 1
|
||||
return _MemoryProtection_name_0[_MemoryProtection_index_0[i]:_MemoryProtection_index_0[i+1]]
|
||||
case i == 4:
|
||||
return _MemoryProtection_name_1
|
||||
case i == 8:
|
||||
return _MemoryProtection_name_2
|
||||
case i == 16:
|
||||
return _MemoryProtection_name_3
|
||||
case i == 32:
|
||||
return _MemoryProtection_name_4
|
||||
case i == 64:
|
||||
return _MemoryProtection_name_5
|
||||
case i == 128:
|
||||
return _MemoryProtection_name_6
|
||||
case i == 256:
|
||||
return _MemoryProtection_name_7
|
||||
case i == 512:
|
||||
return _MemoryProtection_name_8
|
||||
case i == 1024:
|
||||
return _MemoryProtection_name_9
|
||||
default:
|
||||
return "MemoryProtection(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
686
pkg/proc/core/minidump/minidump.go
Normal file
686
pkg/proc/core/minidump/minidump.go
Normal file
@ -0,0 +1,686 @@
|
||||
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/derekparker/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})
|
||||
}
|
60
pkg/proc/core/windows_amd64_minidump.go
Normal file
60
pkg/proc/core/windows_amd64_minidump.go
Normal file
@ -0,0 +1,60 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/proc/core/minidump"
|
||||
"github.com/derekparker/delve/pkg/proc/winutil"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func readAMD64Minidump(minidumpPath, exePath string) (*Process, error) {
|
||||
var logfn func(string, ...interface{})
|
||||
if logflags.Minidump() {
|
||||
logfn = logrus.WithFields(logrus.Fields{"layer": "core", "kind": "minidump"}).Infof
|
||||
}
|
||||
|
||||
mdmp, err := minidump.Open(minidumpPath, logfn)
|
||||
if err != nil {
|
||||
if _, isNotAMinidump := err.(minidump.ErrNotAMinidump); isNotAMinidump {
|
||||
return nil, ErrUnrecognizedFormat
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memory := &SplicedMemory{}
|
||||
|
||||
for i := range mdmp.MemoryRanges {
|
||||
m := &mdmp.MemoryRanges[i]
|
||||
memory.Add(m, uintptr(m.Addr), uintptr(len(m.Data)))
|
||||
}
|
||||
|
||||
p := &Process{
|
||||
mem: memory,
|
||||
Threads: map[int]*Thread{},
|
||||
bi: proc.NewBinaryInfo("windows", "amd64"),
|
||||
breakpoints: proc.NewBreakpointMap(),
|
||||
pid: int(mdmp.Pid),
|
||||
}
|
||||
|
||||
for i := range mdmp.Threads {
|
||||
th := &mdmp.Threads[i]
|
||||
p.Threads[int(th.ID)] = &Thread{&windowsAMD64Thread{th}, p, proc.CommonThread{}}
|
||||
if p.currentThread == nil {
|
||||
p.currentThread = p.Threads[int(th.ID)]
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
type windowsAMD64Thread struct {
|
||||
th *minidump.Thread
|
||||
}
|
||||
|
||||
func (th *windowsAMD64Thread) pid() int {
|
||||
return int(th.th.ID)
|
||||
}
|
||||
|
||||
func (th *windowsAMD64Thread) registers(floatingPoint bool) (proc.Registers, error) {
|
||||
return winutil.NewAMD64Registers(&th.th.Context, th.th.TEB, floatingPoint), nil
|
||||
}
|
@ -3566,8 +3566,8 @@ func TestIssue1101(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue1145(t *testing.T) {
|
||||
withTestProcess("issue1145", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
setFileBreakpoint(p, t, fixture, 12)
|
||||
withTestProcess("sleep", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
setFileBreakpoint(p, t, fixture, 18)
|
||||
assertNoError(proc.Continue(p), t, "Continue()")
|
||||
resumeChan := make(chan struct{}, 1)
|
||||
p.ResumeNotify(resumeChan)
|
||||
|
Loading…
Reference in New Issue
Block a user