提交 85e4ba43 编写于 作者: A aarzilli 提交者: Derek Parker

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
上级 31fff845
......@@ -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 ".")
```
......
package main
import "time"
import (
"os"
"time"
)
func f() {
for {
time.Sleep(10 * time.Millisecond)
time.Sleep(10 * time.Second)
if len(os.Args) > 1 {
break
}
}
}
......
......@@ -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)
......
// 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) + ")"
}
}
此差异已折叠。
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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册