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
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -39,6 +39,7 @@ dlv attach pid [executable]
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -34,6 +34,7 @@ dlv connect addr
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--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
|
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.
|
||||||
|
|
||||||
```
|
```
|
||||||
dlv core <executable> <core>
|
dlv core <executable> <core>
|
||||||
```
|
```
|
||||||
@ -38,6 +40,7 @@ dlv core <executable> <core>
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -45,6 +45,7 @@ dlv debug [package]
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--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
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -38,6 +38,7 @@ dlv replay [trace directory]
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -34,6 +34,7 @@ dlv run
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -45,6 +45,7 @@ dlv test [package]
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -49,6 +49,7 @@ dlv trace [package] regexp
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--wd string Working directory for running the program. (default ".")
|
||||||
```
|
```
|
||||||
|
@ -34,6 +34,7 @@ dlv version
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.
|
Defaults to "debugger" when logging is enabled with --log.
|
||||||
--wd string Working directory for running the program. (default ".")
|
--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
|
Invoke-WebRequest -UserAgent wget -Uri $url -OutFile $file
|
||||||
&7z x -oC:\ $file > $null
|
&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 %PATH%
|
||||||
- echo %GOPATH%
|
- echo %GOPATH%
|
||||||
- go version
|
- go version
|
||||||
- go env
|
- go env
|
||||||
cache: C:\mingw64
|
cache:
|
||||||
|
- C:\mingw64
|
||||||
|
- C:\procdump
|
||||||
build_script:
|
build_script:
|
||||||
- mingw32-make install
|
- mingw32-make install
|
||||||
test_script:
|
test_script:
|
||||||
|
@ -98,6 +98,7 @@ func New(docCall bool) *cobra.Command {
|
|||||||
debuglineerr Log recoverable errors reading .debug_line
|
debuglineerr Log recoverable errors reading .debug_line
|
||||||
rpc Log all RPC messages
|
rpc Log all RPC messages
|
||||||
fncall Log function call protocol
|
fncall Log function call protocol
|
||||||
|
minidump Log minidump loading
|
||||||
Defaults to "debugger" when logging is enabled with --log.`)
|
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(&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.")
|
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
|
The core command will open the specified core file and the associated
|
||||||
executable and let you examine the state of the process when the
|
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 {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
return errors.New("you must provide a core file and an executable")
|
return errors.New("you must provide a core file and an executable")
|
||||||
|
@ -13,6 +13,7 @@ var lldbServerOutput = false
|
|||||||
var debugLineErrors = false
|
var debugLineErrors = false
|
||||||
var rpc = false
|
var rpc = false
|
||||||
var fnCall = false
|
var fnCall = false
|
||||||
|
var minidump = false
|
||||||
|
|
||||||
// GdbWire returns true if the gdbserial package should log all the packets
|
// GdbWire returns true if the gdbserial package should log all the packets
|
||||||
// exchanged with the stub.
|
// exchanged with the stub.
|
||||||
@ -47,6 +48,11 @@ func FnCall() bool {
|
|||||||
return fnCall
|
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")
|
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
||||||
|
|
||||||
// Setup sets debugger flags based on the contents of logstr.
|
// Setup sets debugger flags based on the contents of logstr.
|
||||||
@ -77,6 +83,8 @@ func Setup(logFlag bool, logstr string) error {
|
|||||||
rpc = true
|
rpc = true
|
||||||
case "fncall":
|
case "fncall":
|
||||||
fnCall = true
|
fnCall = true
|
||||||
|
case "minidump":
|
||||||
|
minidump = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -183,11 +183,26 @@ var (
|
|||||||
ErrChangeRegisterCore = errors.New("can not change register values of core process")
|
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.
|
// OpenCore will open the core file and return a Process struct.
|
||||||
// If the DWARF information cannot be found in the binary, Delve will look
|
// If the DWARF information cannot be found in the binary, Delve will look
|
||||||
// for external debug files in the directories passed in.
|
// for external debug files in the directories passed in.
|
||||||
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -367,3 +367,88 @@ mainSearch:
|
|||||||
assertNoError(v2.Unreadable, t, "unreadable variable 's'")
|
assertNoError(v2.Unreadable, t, "unreadable variable 's'")
|
||||||
t.Logf("s = %#v\n", v2)
|
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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/derekparker/delve/pkg/proc"
|
||||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
"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
|
// NT_AUXV is the note type for notes containing a copy of the Auxv array
|
||||||
const NT_AUXV elf.NType = 0x6
|
const NT_AUXV elf.NType = 0x6
|
||||||
|
|
||||||
|
const elfErrorBadMagicNumber = "bad magic number"
|
||||||
|
|
||||||
// readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
|
// readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
|
||||||
// exePath. For details on the Linux ELF core format, see:
|
// exePath. For details on the Linux ELF core format, see:
|
||||||
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
// 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) {
|
func readLinuxAMD64Core(corePath, exePath string) (*Process, error) {
|
||||||
coreFile, err := elf.Open(corePath)
|
coreFile, err := elf.Open(corePath)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
exe, err := os.Open(exePath)
|
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) {
|
func TestIssue1145(t *testing.T) {
|
||||||
withTestProcess("issue1145", t, func(p proc.Process, fixture protest.Fixture) {
|
withTestProcess("sleep", t, func(p proc.Process, fixture protest.Fixture) {
|
||||||
setFileBreakpoint(p, t, fixture, 12)
|
setFileBreakpoint(p, t, fixture, 18)
|
||||||
assertNoError(proc.Continue(p), t, "Continue()")
|
assertNoError(proc.Continue(p), t, "Continue()")
|
||||||
resumeChan := make(chan struct{}, 1)
|
resumeChan := make(chan struct{}, 1)
|
||||||
p.ResumeNotify(resumeChan)
|
p.ResumeNotify(resumeChan)
|
||||||
|
Loading…
Reference in New Issue
Block a user