mirror of
https://github.com/beego/bee.git
synced 2024-11-25 15:00:55 +00:00
Merge branch 'hotfix/1.10.1'
This commit is contained in:
commit
6bf5f9dbfe
@ -1,20 +1,17 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.10.3
|
- 1.11.9
|
||||||
|
- 1.12.4
|
||||||
install:
|
install:
|
||||||
- export PATH=$PATH:$HOME/gopath/bin
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
- go get -u github.com/opennota/check/cmd/structcheck
|
- go get -u github.com/opennota/check/cmd/structcheck
|
||||||
- go get -u honnef.co/go/tools/cmd/gosimple
|
|
||||||
- go get -u honnef.co/go/tools/cmd/staticcheck
|
- go get -u honnef.co/go/tools/cmd/staticcheck
|
||||||
- go get -u honnef.co/go/tools/cmd/unused
|
|
||||||
- go get -u github.com/mdempsky/unconvert
|
- go get -u github.com/mdempsky/unconvert
|
||||||
- go get -u github.com/gordonklaus/ineffassign
|
- go get -u github.com/gordonklaus/ineffassign
|
||||||
script:
|
script:
|
||||||
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
- go vet $(go list ./... | grep -v /vendor/)
|
||||||
- structcheck $(go list ./... | grep -v /vendor/)
|
- structcheck $(go list ./... | grep -v /vendor/)
|
||||||
- gosimple -ignore "$(cat gosimple.ignore)" $(go list ./... | grep -v /vendor/)
|
|
||||||
- staticcheck -ignore "$(cat staticcheck.ignore)" $(go list ./... | grep -v /vendor/)
|
- staticcheck -ignore "$(cat staticcheck.ignore)" $(go list ./... | grep -v /vendor/)
|
||||||
- unused $(go list ./... | grep -v /vendor/)
|
|
||||||
- unconvert $(go list ./... | grep -v /vendor/)
|
- unconvert $(go list ./... | grep -v /vendor/)
|
||||||
- ineffassign .
|
- ineffassign .
|
||||||
|
@ -28,11 +28,11 @@ import (
|
|||||||
"github.com/beego/bee/cmd/commands/version"
|
"github.com/beego/bee/cmd/commands/version"
|
||||||
beeLogger "github.com/beego/bee/logger"
|
beeLogger "github.com/beego/bee/logger"
|
||||||
"github.com/beego/bee/utils"
|
"github.com/beego/bee/utils"
|
||||||
"github.com/derekparker/delve/pkg/terminal"
|
|
||||||
"github.com/derekparker/delve/service"
|
|
||||||
"github.com/derekparker/delve/service/rpc2"
|
|
||||||
"github.com/derekparker/delve/service/rpccommon"
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/go-delve/delve/pkg/terminal"
|
||||||
|
"github.com/go-delve/delve/service"
|
||||||
|
"github.com/go-delve/delve/service/rpc2"
|
||||||
|
"github.com/go-delve/delve/service/rpccommon"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdDlv = &commands.Command{
|
var cmdDlv = &commands.Command{
|
||||||
@ -43,7 +43,7 @@ var cmdDlv = &commands.Command{
|
|||||||
|
|
||||||
To debug your application using Delve, use: {{"$ bee dlv" | bold}}
|
To debug your application using Delve, use: {{"$ bee dlv" | bold}}
|
||||||
|
|
||||||
For more information on Delve: https://github.com/derekparker/delve
|
For more information on Delve: https://github.com/go-delve/delve
|
||||||
`,
|
`,
|
||||||
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
|
||||||
Run: runDlv,
|
Run: runDlv,
|
||||||
@ -228,7 +228,7 @@ func startWatcher(paths []string, ch chan int) {
|
|||||||
|
|
||||||
// Wait 1s before re-build until there is no file change
|
// Wait 1s before re-build until there is no file change
|
||||||
scheduleTime := time.Now().Add(1 * time.Second)
|
scheduleTime := time.Now().Add(1 * time.Second)
|
||||||
time.Sleep(scheduleTime.Sub(time.Now()))
|
time.Sleep(time.Until(scheduleTime))
|
||||||
_, err := buildDebug()
|
_, err := buildDebug()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Notify("Build Failed: "+err.Error(), "bee")
|
utils.Notify("Build Failed: "+err.Error(), "bee")
|
||||||
|
@ -85,7 +85,7 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
|
|||||||
go func() {
|
go func() {
|
||||||
// Wait 1s before autobuild until there is no file change.
|
// Wait 1s before autobuild until there is no file change.
|
||||||
scheduleTime = time.Now().Add(1 * time.Second)
|
scheduleTime = time.Now().Add(1 * time.Second)
|
||||||
time.Sleep(scheduleTime.Sub(time.Now()))
|
time.Sleep(time.Until(scheduleTime))
|
||||||
AutoBuild(files, isgenerate)
|
AutoBuild(files, isgenerate)
|
||||||
|
|
||||||
if config.Conf.EnableReload {
|
if config.Conf.EnableReload {
|
||||||
|
@ -57,7 +57,7 @@ Prints the current Bee, Beego and Go version alongside the platform information.
|
|||||||
}
|
}
|
||||||
var outputFormat string
|
var outputFormat string
|
||||||
|
|
||||||
const version = "1.10.0"
|
const version = "1.10.1"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
fs := flag.NewFlagSet("version", flag.ContinueOnError)
|
fs := flag.NewFlagSet("version", flag.ContinueOnError)
|
||||||
|
@ -116,7 +116,7 @@ func ParsePackagesFromDir(dirpath string) {
|
|||||||
err = parsePackageFromDir(fpath)
|
err = parsePackageFromDir(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Send the error to through the channel and continue walking
|
// Send the error to through the channel and continue walking
|
||||||
c <- fmt.Errorf("Error while parsing directory: %s", err.Error())
|
c <- fmt.Errorf("error while parsing directory: %s", err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ func CheckEnv(appname string) (apppath, packpath string, err error) {
|
|||||||
apppath = filepath.Join(gosrcpath, appname)
|
apppath = filepath.Join(gosrcpath, appname)
|
||||||
|
|
||||||
if _, e := os.Stat(apppath); !os.IsNotExist(e) {
|
if _, e := os.Stat(apppath); !os.IsNotExist(e) {
|
||||||
err = fmt.Errorf("Cannot create application without removing '%s' first", apppath)
|
err = fmt.Errorf("cannot create application without removing '%s' first", apppath)
|
||||||
beeLogger.Log.Errorf("Path '%s' already exists", apppath)
|
beeLogger.Log.Errorf("Path '%s' already exists", apppath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
13
vendor/github.com/astaxie/beego/LICENSE
generated
vendored
Normal file
13
vendor/github.com/astaxie/beego/LICENSE
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2014 astaxie
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
31
vendor/github.com/cosiner/argv/README.md
generated
vendored
Normal file
31
vendor/github.com/cosiner/argv/README.md
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Argv
|
||||||
|
|
||||||
|
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/cosiner/argv)
|
||||||
|
[![Build Status](https://travis-ci.org/cosiner/argv.svg?branch=master&style=flat)](https://travis-ci.org/cosiner/argv)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/cosiner/argv/badge.svg?style=flat)](https://coveralls.io/github/cosiner/argv)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/cosiner/argv?style=flat)](https://goreportcard.com/report/github.com/cosiner/argv)
|
||||||
|
|
||||||
|
Argv is a library for [Go](https://golang.org) to split command line string into arguments array.
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
Documentation can be found at [Godoc](https://godoc.org/github.com/cosiner/argv)
|
||||||
|
|
||||||
|
# Example
|
||||||
|
```Go
|
||||||
|
func TestArgv(t *testing.T) {
|
||||||
|
args, err := argv.Argv([]rune(" ls `echo /` | wc -l "), os.Environ(), argv.Run)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expects := [][]string{
|
||||||
|
[]string{"ls", "/"},
|
||||||
|
[]string{"wc", "-l"},
|
||||||
|
}
|
||||||
|
if !reflect.DeepDqual(args, expects) {
|
||||||
|
t.Fatal(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# LICENSE
|
||||||
|
MIT.
|
83
vendor/github.com/derekparker/delve/pkg/logflags/logflags.go
generated
vendored
83
vendor/github.com/derekparker/delve/pkg/logflags/logflags.go
generated
vendored
@ -1,83 +0,0 @@
|
|||||||
package logflags
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var debugger = false
|
|
||||||
var gdbWire = false
|
|
||||||
var lldbServerOutput = false
|
|
||||||
var debugLineErrors = false
|
|
||||||
var rpc = false
|
|
||||||
var fnCall = false
|
|
||||||
|
|
||||||
// GdbWire returns true if the gdbserial package should log all the packets
|
|
||||||
// exchanged with the stub.
|
|
||||||
func GdbWire() bool {
|
|
||||||
return gdbWire
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugger returns true if the debugger package should log.
|
|
||||||
func Debugger() bool {
|
|
||||||
return debugger
|
|
||||||
}
|
|
||||||
|
|
||||||
// LLDBServerOutput returns true if the output of the LLDB server should be
|
|
||||||
// redirected to standard output instead of suppressed.
|
|
||||||
func LLDBServerOutput() bool {
|
|
||||||
return lldbServerOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugLineErrors returns true if pkg/dwarf/line should log its recoverable
|
|
||||||
// errors.
|
|
||||||
func DebugLineErrors() bool {
|
|
||||||
return debugLineErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPC returns true if rpc messages should be logged.
|
|
||||||
func RPC() bool {
|
|
||||||
return rpc
|
|
||||||
}
|
|
||||||
|
|
||||||
// FnCall returns true if the function call protocol should be logged.
|
|
||||||
func FnCall() bool {
|
|
||||||
return fnCall
|
|
||||||
}
|
|
||||||
|
|
||||||
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
|
||||||
|
|
||||||
// Setup sets debugger flags based on the contents of logstr.
|
|
||||||
func Setup(logFlag bool, logstr string) error {
|
|
||||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
|
||||||
if !logFlag {
|
|
||||||
log.SetOutput(ioutil.Discard)
|
|
||||||
if logstr != "" {
|
|
||||||
return errLogstrWithoutLog
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if logstr == "" {
|
|
||||||
logstr = "debugger"
|
|
||||||
}
|
|
||||||
v := strings.Split(logstr, ",")
|
|
||||||
for _, logcmd := range v {
|
|
||||||
switch logcmd {
|
|
||||||
case "debugger":
|
|
||||||
debugger = true
|
|
||||||
case "gdbwire":
|
|
||||||
gdbWire = true
|
|
||||||
case "lldbout":
|
|
||||||
lldbServerOutput = true
|
|
||||||
case "debuglineerr":
|
|
||||||
debugLineErrors = true
|
|
||||||
case "rpc":
|
|
||||||
rpc = true
|
|
||||||
case "fncall":
|
|
||||||
fnCall = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
119
vendor/github.com/derekparker/delve/pkg/proc/native/mach_exc.defs
generated
vendored
119
vendor/github.com/derekparker/delve/pkg/proc/native/mach_exc.defs
generated
vendored
@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
||||||
*
|
|
||||||
* This file contains Original Code and/or Modifications of Original Code
|
|
||||||
* as defined in and that are subject to the Apple Public Source License
|
|
||||||
* Version 2.0 (the 'License'). You may not use this file except in
|
|
||||||
* compliance with the License. The rights granted to you under the License
|
|
||||||
* may not be used to create, or enable the creation or redistribution of,
|
|
||||||
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
||||||
* circumvent, violate, or enable the circumvention or violation of, any
|
|
||||||
* terms of an Apple operating system software license agreement.
|
|
||||||
*
|
|
||||||
* Please obtain a copy of the License at
|
|
||||||
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
||||||
*
|
|
||||||
* The Original Code and all software distributed under the License are
|
|
||||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
||||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
||||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
||||||
* Please see the License for the specific language governing rights and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* @OSF_COPYRIGHT@
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* Mach Operating System
|
|
||||||
* Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University
|
|
||||||
* All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify and distribute this software and its
|
|
||||||
* documentation is hereby granted, provided that both the copyright
|
|
||||||
* notice and this permission notice appear in all copies of the
|
|
||||||
* software, derivative works or modified versions, and any portions
|
|
||||||
* thereof, and that both notices appear in supporting documentation.
|
|
||||||
*
|
|
||||||
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
|
|
||||||
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
|
|
||||||
* ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
|
|
||||||
*
|
|
||||||
* Carnegie Mellon requests users of this software to return to
|
|
||||||
*
|
|
||||||
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
|
|
||||||
* School of Computer Science
|
|
||||||
* Carnegie Mellon University
|
|
||||||
* Pittsburgh PA 15213-3890
|
|
||||||
*
|
|
||||||
* any improvements or extensions that they make and grant Carnegie Mellon
|
|
||||||
* the rights to redistribute these changes.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* Abstract:
|
|
||||||
* MiG definitions file for Mach exception interface.
|
|
||||||
*/
|
|
||||||
|
|
||||||
subsystem
|
|
||||||
#if KERNEL_USER
|
|
||||||
KernelUser
|
|
||||||
#endif
|
|
||||||
mach_exc 2405;
|
|
||||||
|
|
||||||
#include <mach/std_types.defs>
|
|
||||||
#include <mach/mach_types.defs>
|
|
||||||
|
|
||||||
ServerPrefix catch_;
|
|
||||||
|
|
||||||
type mach_exception_data_t = array[*:2] of int64_t;
|
|
||||||
type exception_type_t = int;
|
|
||||||
|
|
||||||
routine mach_exception_raise(
|
|
||||||
#if KERNEL_USER
|
|
||||||
exception_port : mach_port_move_send_t;
|
|
||||||
thread : mach_port_move_send_t;
|
|
||||||
task : mach_port_move_send_t;
|
|
||||||
#else /* KERNEL_USER */
|
|
||||||
exception_port : mach_port_t;
|
|
||||||
thread : mach_port_t;
|
|
||||||
task : mach_port_t;
|
|
||||||
#endif /* KERNEL_USER */
|
|
||||||
exception : exception_type_t;
|
|
||||||
code : mach_exception_data_t
|
|
||||||
);
|
|
||||||
|
|
||||||
routine mach_exception_raise_state(
|
|
||||||
#if KERNEL_USER
|
|
||||||
exception_port : mach_port_move_send_t;
|
|
||||||
#else /* KERNEL_USER */
|
|
||||||
exception_port : mach_port_t;
|
|
||||||
#endif /* KERNEL_USER */
|
|
||||||
exception : exception_type_t;
|
|
||||||
code : mach_exception_data_t, const;
|
|
||||||
inout flavor : int;
|
|
||||||
old_state : thread_state_t, const;
|
|
||||||
out new_state : thread_state_t);
|
|
||||||
|
|
||||||
routine mach_exception_raise_state_identity(
|
|
||||||
#if KERNEL_USER
|
|
||||||
exception_port : mach_port_move_send_t;
|
|
||||||
thread : mach_port_move_send_t;
|
|
||||||
task : mach_port_move_send_t;
|
|
||||||
#else /* KERNEL_USER */
|
|
||||||
exception_port : mach_port_t;
|
|
||||||
thread : mach_port_t;
|
|
||||||
task : mach_port_t;
|
|
||||||
#endif /* KERNEL_USER */
|
|
||||||
exception : exception_type_t;
|
|
||||||
code : mach_exception_data_t;
|
|
||||||
inout flavor : int;
|
|
||||||
old_state : thread_state_t;
|
|
||||||
out new_state : thread_state_t);
|
|
||||||
|
|
||||||
/* vim: set ft=c : */
|
|
342
vendor/github.com/derekparker/delve/pkg/proc/native/registers_linux_amd64.go
generated
vendored
342
vendor/github.com/derekparker/delve/pkg/proc/native/registers_linux_amd64.go
generated
vendored
@ -1,342 +0,0 @@
|
|||||||
package native
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
|
||||||
sys "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regs is a wrapper for sys.PtraceRegs.
|
|
||||||
type Regs struct {
|
|
||||||
regs *sys.PtraceRegs
|
|
||||||
fpregs []proc.Register
|
|
||||||
fpregset *proc.LinuxX86Xstate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Regs) Slice() []proc.Register {
|
|
||||||
var regs = []struct {
|
|
||||||
k string
|
|
||||||
v uint64
|
|
||||||
}{
|
|
||||||
{"Rip", r.regs.Rip},
|
|
||||||
{"Rsp", r.regs.Rsp},
|
|
||||||
{"Rax", r.regs.Rax},
|
|
||||||
{"Rbx", r.regs.Rbx},
|
|
||||||
{"Rcx", r.regs.Rcx},
|
|
||||||
{"Rdx", r.regs.Rdx},
|
|
||||||
{"Rdi", r.regs.Rdi},
|
|
||||||
{"Rsi", r.regs.Rsi},
|
|
||||||
{"Rbp", r.regs.Rbp},
|
|
||||||
{"R8", r.regs.R8},
|
|
||||||
{"R9", r.regs.R9},
|
|
||||||
{"R10", r.regs.R10},
|
|
||||||
{"R11", r.regs.R11},
|
|
||||||
{"R12", r.regs.R12},
|
|
||||||
{"R13", r.regs.R13},
|
|
||||||
{"R14", r.regs.R14},
|
|
||||||
{"R15", r.regs.R15},
|
|
||||||
{"Orig_rax", r.regs.Orig_rax},
|
|
||||||
{"Cs", r.regs.Cs},
|
|
||||||
{"Eflags", r.regs.Eflags},
|
|
||||||
{"Ss", r.regs.Ss},
|
|
||||||
{"Fs_base", r.regs.Fs_base},
|
|
||||||
{"Gs_base", r.regs.Gs_base},
|
|
||||||
{"Ds", r.regs.Ds},
|
|
||||||
{"Es", r.regs.Es},
|
|
||||||
{"Fs", r.regs.Fs},
|
|
||||||
{"Gs", r.regs.Gs},
|
|
||||||
}
|
|
||||||
out := make([]proc.Register, 0, len(regs)+len(r.fpregs))
|
|
||||||
for _, reg := range regs {
|
|
||||||
if reg.k == "Eflags" {
|
|
||||||
out = proc.AppendEflagReg(out, reg.k, reg.v)
|
|
||||||
} else {
|
|
||||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = append(out, r.fpregs...)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// PC returns the value of RIP register.
|
|
||||||
func (r *Regs) PC() uint64 {
|
|
||||||
return r.regs.PC()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SP returns the value of RSP register.
|
|
||||||
func (r *Regs) SP() uint64 {
|
|
||||||
return r.regs.Rsp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Regs) BP() uint64 {
|
|
||||||
return r.regs.Rbp
|
|
||||||
}
|
|
||||||
|
|
||||||
// CX returns the value of RCX register.
|
|
||||||
func (r *Regs) CX() uint64 {
|
|
||||||
return r.regs.Rcx
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLS returns the address of the thread
|
|
||||||
// local storage memory segment.
|
|
||||||
func (r *Regs) TLS() uint64 {
|
|
||||||
return r.regs.Fs_base
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Regs) GAddr() (uint64, bool) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPC sets RIP to the value specified by 'pc'.
|
|
||||||
func (thread *Thread) SetPC(pc uint64) error {
|
|
||||||
ir, err := registers(thread, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r := ir.(*Regs)
|
|
||||||
r.regs.SetPC(pc)
|
|
||||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSP sets RSP to the value specified by 'sp'
|
|
||||||
func (thread *Thread) SetSP(sp uint64) (err error) {
|
|
||||||
var ir proc.Registers
|
|
||||||
ir, err = registers(thread, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r := ir.(*Regs)
|
|
||||||
r.regs.Rsp = sp
|
|
||||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (thread *Thread) SetDX(dx uint64) (err error) {
|
|
||||||
var ir proc.Registers
|
|
||||||
ir, err = registers(thread, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r := ir.(*Regs)
|
|
||||||
r.regs.Rdx = dx
|
|
||||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, r.regs) })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Regs) Get(n int) (uint64, error) {
|
|
||||||
reg := x86asm.Reg(n)
|
|
||||||
const (
|
|
||||||
mask8 = 0x000f
|
|
||||||
mask16 = 0x00ff
|
|
||||||
mask32 = 0xffff
|
|
||||||
)
|
|
||||||
|
|
||||||
switch reg {
|
|
||||||
// 8-bit
|
|
||||||
case x86asm.AL:
|
|
||||||
return r.regs.Rax & mask8, nil
|
|
||||||
case x86asm.CL:
|
|
||||||
return r.regs.Rcx & mask8, nil
|
|
||||||
case x86asm.DL:
|
|
||||||
return r.regs.Rdx & mask8, nil
|
|
||||||
case x86asm.BL:
|
|
||||||
return r.regs.Rbx & mask8, nil
|
|
||||||
case x86asm.AH:
|
|
||||||
return (r.regs.Rax >> 8) & mask8, nil
|
|
||||||
case x86asm.CH:
|
|
||||||
return (r.regs.Rcx >> 8) & mask8, nil
|
|
||||||
case x86asm.DH:
|
|
||||||
return (r.regs.Rdx >> 8) & mask8, nil
|
|
||||||
case x86asm.BH:
|
|
||||||
return (r.regs.Rbx >> 8) & mask8, nil
|
|
||||||
case x86asm.SPB:
|
|
||||||
return r.regs.Rsp & mask8, nil
|
|
||||||
case x86asm.BPB:
|
|
||||||
return r.regs.Rbp & mask8, nil
|
|
||||||
case x86asm.SIB:
|
|
||||||
return r.regs.Rsi & mask8, nil
|
|
||||||
case x86asm.DIB:
|
|
||||||
return r.regs.Rdi & mask8, nil
|
|
||||||
case x86asm.R8B:
|
|
||||||
return r.regs.R8 & mask8, nil
|
|
||||||
case x86asm.R9B:
|
|
||||||
return r.regs.R9 & mask8, nil
|
|
||||||
case x86asm.R10B:
|
|
||||||
return r.regs.R10 & mask8, nil
|
|
||||||
case x86asm.R11B:
|
|
||||||
return r.regs.R11 & mask8, nil
|
|
||||||
case x86asm.R12B:
|
|
||||||
return r.regs.R12 & mask8, nil
|
|
||||||
case x86asm.R13B:
|
|
||||||
return r.regs.R13 & mask8, nil
|
|
||||||
case x86asm.R14B:
|
|
||||||
return r.regs.R14 & mask8, nil
|
|
||||||
case x86asm.R15B:
|
|
||||||
return r.regs.R15 & mask8, nil
|
|
||||||
|
|
||||||
// 16-bit
|
|
||||||
case x86asm.AX:
|
|
||||||
return r.regs.Rax & mask16, nil
|
|
||||||
case x86asm.CX:
|
|
||||||
return r.regs.Rcx & mask16, nil
|
|
||||||
case x86asm.DX:
|
|
||||||
return r.regs.Rdx & mask16, nil
|
|
||||||
case x86asm.BX:
|
|
||||||
return r.regs.Rbx & mask16, nil
|
|
||||||
case x86asm.SP:
|
|
||||||
return r.regs.Rsp & mask16, nil
|
|
||||||
case x86asm.BP:
|
|
||||||
return r.regs.Rbp & mask16, nil
|
|
||||||
case x86asm.SI:
|
|
||||||
return r.regs.Rsi & mask16, nil
|
|
||||||
case x86asm.DI:
|
|
||||||
return r.regs.Rdi & mask16, nil
|
|
||||||
case x86asm.R8W:
|
|
||||||
return r.regs.R8 & mask16, nil
|
|
||||||
case x86asm.R9W:
|
|
||||||
return r.regs.R9 & mask16, nil
|
|
||||||
case x86asm.R10W:
|
|
||||||
return r.regs.R10 & mask16, nil
|
|
||||||
case x86asm.R11W:
|
|
||||||
return r.regs.R11 & mask16, nil
|
|
||||||
case x86asm.R12W:
|
|
||||||
return r.regs.R12 & mask16, nil
|
|
||||||
case x86asm.R13W:
|
|
||||||
return r.regs.R13 & mask16, nil
|
|
||||||
case x86asm.R14W:
|
|
||||||
return r.regs.R14 & mask16, nil
|
|
||||||
case x86asm.R15W:
|
|
||||||
return r.regs.R15 & mask16, nil
|
|
||||||
|
|
||||||
// 32-bit
|
|
||||||
case x86asm.EAX:
|
|
||||||
return r.regs.Rax & mask32, nil
|
|
||||||
case x86asm.ECX:
|
|
||||||
return r.regs.Rcx & mask32, nil
|
|
||||||
case x86asm.EDX:
|
|
||||||
return r.regs.Rdx & mask32, nil
|
|
||||||
case x86asm.EBX:
|
|
||||||
return r.regs.Rbx & mask32, nil
|
|
||||||
case x86asm.ESP:
|
|
||||||
return r.regs.Rsp & mask32, nil
|
|
||||||
case x86asm.EBP:
|
|
||||||
return r.regs.Rbp & mask32, nil
|
|
||||||
case x86asm.ESI:
|
|
||||||
return r.regs.Rsi & mask32, nil
|
|
||||||
case x86asm.EDI:
|
|
||||||
return r.regs.Rdi & mask32, nil
|
|
||||||
case x86asm.R8L:
|
|
||||||
return r.regs.R8 & mask32, nil
|
|
||||||
case x86asm.R9L:
|
|
||||||
return r.regs.R9 & mask32, nil
|
|
||||||
case x86asm.R10L:
|
|
||||||
return r.regs.R10 & mask32, nil
|
|
||||||
case x86asm.R11L:
|
|
||||||
return r.regs.R11 & mask32, nil
|
|
||||||
case x86asm.R12L:
|
|
||||||
return r.regs.R12 & mask32, nil
|
|
||||||
case x86asm.R13L:
|
|
||||||
return r.regs.R13 & mask32, nil
|
|
||||||
case x86asm.R14L:
|
|
||||||
return r.regs.R14 & mask32, nil
|
|
||||||
case x86asm.R15L:
|
|
||||||
return r.regs.R15 & mask32, nil
|
|
||||||
|
|
||||||
// 64-bit
|
|
||||||
case x86asm.RAX:
|
|
||||||
return r.regs.Rax, nil
|
|
||||||
case x86asm.RCX:
|
|
||||||
return r.regs.Rcx, nil
|
|
||||||
case x86asm.RDX:
|
|
||||||
return r.regs.Rdx, nil
|
|
||||||
case x86asm.RBX:
|
|
||||||
return r.regs.Rbx, nil
|
|
||||||
case x86asm.RSP:
|
|
||||||
return r.regs.Rsp, nil
|
|
||||||
case x86asm.RBP:
|
|
||||||
return r.regs.Rbp, nil
|
|
||||||
case x86asm.RSI:
|
|
||||||
return r.regs.Rsi, nil
|
|
||||||
case x86asm.RDI:
|
|
||||||
return r.regs.Rdi, nil
|
|
||||||
case x86asm.R8:
|
|
||||||
return r.regs.R8, nil
|
|
||||||
case x86asm.R9:
|
|
||||||
return r.regs.R9, nil
|
|
||||||
case x86asm.R10:
|
|
||||||
return r.regs.R10, nil
|
|
||||||
case x86asm.R11:
|
|
||||||
return r.regs.R11, nil
|
|
||||||
case x86asm.R12:
|
|
||||||
return r.regs.R12, nil
|
|
||||||
case x86asm.R13:
|
|
||||||
return r.regs.R13, nil
|
|
||||||
case x86asm.R14:
|
|
||||||
return r.regs.R14, nil
|
|
||||||
case x86asm.R15:
|
|
||||||
return r.regs.R15, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, proc.ErrUnknownRegister
|
|
||||||
}
|
|
||||||
|
|
||||||
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
|
||||||
var (
|
|
||||||
regs sys.PtraceRegs
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, ®s) })
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := &Regs{®s, nil, nil}
|
|
||||||
if floatingPoint {
|
|
||||||
var fpregset proc.LinuxX86Xstate
|
|
||||||
r.fpregs, fpregset, err = thread.fpRegisters()
|
|
||||||
r.fpregset = &fpregset
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
_X86_XSTATE_MAX_SIZE = 2688
|
|
||||||
_NT_X86_XSTATE = 0x202
|
|
||||||
|
|
||||||
_XSAVE_HEADER_START = 512
|
|
||||||
_XSAVE_HEADER_LEN = 64
|
|
||||||
_XSAVE_EXTENDED_REGION_START = 576
|
|
||||||
_XSAVE_SSE_REGION_LEN = 416
|
|
||||||
)
|
|
||||||
|
|
||||||
func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs proc.LinuxX86Xstate, err error) {
|
|
||||||
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
|
|
||||||
regs = fpregs.Decode()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a copy of these registers that is
|
|
||||||
// guarenteed not to change.
|
|
||||||
func (r *Regs) Copy() proc.Registers {
|
|
||||||
var rr Regs
|
|
||||||
rr.regs = &sys.PtraceRegs{}
|
|
||||||
rr.fpregset = &proc.LinuxX86Xstate{}
|
|
||||||
*(rr.regs) = *(r.regs)
|
|
||||||
if r.fpregset != nil {
|
|
||||||
*(rr.fpregset) = *(r.fpregset)
|
|
||||||
}
|
|
||||||
if r.fpregs != nil {
|
|
||||||
rr.fpregs = make([]proc.Register, len(r.fpregs))
|
|
||||||
copy(rr.fpregs, r.fpregs)
|
|
||||||
}
|
|
||||||
return &rr
|
|
||||||
}
|
|
114
vendor/github.com/derekparker/delve/pkg/proc/native/syscall_windows_amd64.go
generated
vendored
114
vendor/github.com/derekparker/delve/pkg/proc/native/syscall_windows_amd64.go
generated
vendored
@ -1,114 +0,0 @@
|
|||||||
package native
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
const (
|
|
||||||
_CONTEXT_AMD64 = 0x100000
|
|
||||||
_CONTEXT_CONTROL = (_CONTEXT_AMD64 | 0x1)
|
|
||||||
_CONTEXT_INTEGER = (_CONTEXT_AMD64 | 0x2)
|
|
||||||
_CONTEXT_SEGMENTS = (_CONTEXT_AMD64 | 0x4)
|
|
||||||
_CONTEXT_FLOATING_POINT = (_CONTEXT_AMD64 | 0x8)
|
|
||||||
_CONTEXT_DEBUG_REGISTERS = (_CONTEXT_AMD64 | 0x10)
|
|
||||||
_CONTEXT_FULL = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_FLOATING_POINT)
|
|
||||||
_CONTEXT_ALL = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_SEGMENTS | _CONTEXT_FLOATING_POINT | _CONTEXT_DEBUG_REGISTERS)
|
|
||||||
_CONTEXT_EXCEPTION_ACTIVE = 0x8000000
|
|
||||||
_CONTEXT_SERVICE_ACTIVE = 0x10000000
|
|
||||||
_CONTEXT_EXCEPTION_REQUEST = 0x40000000
|
|
||||||
_CONTEXT_EXCEPTION_REPORTING = 0x80000000
|
|
||||||
)
|
|
||||||
|
|
||||||
type _M128A struct {
|
|
||||||
Low uint64
|
|
||||||
High int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type _XMM_SAVE_AREA32 struct {
|
|
||||||
ControlWord uint16
|
|
||||||
StatusWord uint16
|
|
||||||
TagWord byte
|
|
||||||
Reserved1 byte
|
|
||||||
ErrorOpcode uint16
|
|
||||||
ErrorOffset uint32
|
|
||||||
ErrorSelector uint16
|
|
||||||
Reserved2 uint16
|
|
||||||
DataOffset uint32
|
|
||||||
DataSelector uint16
|
|
||||||
Reserved3 uint16
|
|
||||||
MxCsr uint32
|
|
||||||
MxCsr_Mask uint32
|
|
||||||
FloatRegisters [8]_M128A
|
|
||||||
XmmRegisters [256]byte
|
|
||||||
Reserved4 [96]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type _CONTEXT struct {
|
|
||||||
P1Home uint64
|
|
||||||
P2Home uint64
|
|
||||||
P3Home uint64
|
|
||||||
P4Home uint64
|
|
||||||
P5Home uint64
|
|
||||||
P6Home uint64
|
|
||||||
|
|
||||||
ContextFlags uint32
|
|
||||||
MxCsr uint32
|
|
||||||
|
|
||||||
SegCs uint16
|
|
||||||
SegDs uint16
|
|
||||||
SegEs uint16
|
|
||||||
SegFs uint16
|
|
||||||
SegGs uint16
|
|
||||||
SegSs uint16
|
|
||||||
EFlags uint32
|
|
||||||
|
|
||||||
Dr0 uint64
|
|
||||||
Dr1 uint64
|
|
||||||
Dr2 uint64
|
|
||||||
Dr3 uint64
|
|
||||||
Dr6 uint64
|
|
||||||
Dr7 uint64
|
|
||||||
|
|
||||||
Rax uint64
|
|
||||||
Rcx uint64
|
|
||||||
Rdx uint64
|
|
||||||
Rbx uint64
|
|
||||||
Rsp uint64
|
|
||||||
Rbp uint64
|
|
||||||
Rsi uint64
|
|
||||||
Rdi uint64
|
|
||||||
R8 uint64
|
|
||||||
R9 uint64
|
|
||||||
R10 uint64
|
|
||||||
R11 uint64
|
|
||||||
R12 uint64
|
|
||||||
R13 uint64
|
|
||||||
R14 uint64
|
|
||||||
R15 uint64
|
|
||||||
|
|
||||||
Rip uint64
|
|
||||||
|
|
||||||
FltSave _XMM_SAVE_AREA32
|
|
||||||
|
|
||||||
VectorRegister [26]_M128A
|
|
||||||
VectorControl uint64
|
|
||||||
|
|
||||||
DebugControl uint64
|
|
||||||
LastBranchToRip uint64
|
|
||||||
LastBranchFromRip uint64
|
|
||||||
LastExceptionToRip uint64
|
|
||||||
LastExceptionFromRip uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCONTEXT allocates Windows CONTEXT structure aligned to 16 bytes.
|
|
||||||
func newCONTEXT() *_CONTEXT {
|
|
||||||
var c *_CONTEXT
|
|
||||||
buf := make([]byte, unsafe.Sizeof(*c)+15)
|
|
||||||
return (*_CONTEXT)(unsafe.Pointer((uintptr(unsafe.Pointer(&buf[15]))) &^ 15))
|
|
||||||
}
|
|
||||||
|
|
||||||
type _DEBUG_EVENT struct {
|
|
||||||
DebugEventCode uint32
|
|
||||||
ProcessId uint32
|
|
||||||
ThreadId uint32
|
|
||||||
_ uint32 // to align Union properly
|
|
||||||
U [160]byte
|
|
||||||
}
|
|
@ -2,20 +2,23 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configDir string = ".dlv"
|
configDir string = "dlv"
|
||||||
configFile string = "config.yml"
|
configDirHidden string = ".dlv"
|
||||||
|
configFile string = "config.yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Describes a rule for substitution of path to source code file.
|
// SubstitutePathRule describes a rule for substitution of path to source code file.
|
||||||
type SubstitutePathRule struct {
|
type SubstitutePathRule struct {
|
||||||
// Directory path will be substituted if it matches `From`.
|
// Directory path will be substituted if it matches `From`.
|
||||||
From string
|
From string
|
||||||
@ -23,7 +26,7 @@ type SubstitutePathRule struct {
|
|||||||
To string
|
To string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slice of source code path substitution rules.
|
// SubstitutePathRules is a slice of source code path substitution rules.
|
||||||
type SubstitutePathRules []SubstitutePathRule
|
type SubstitutePathRules []SubstitutePathRule
|
||||||
|
|
||||||
// Config defines all configuration options available to be set through the config file.
|
// Config defines all configuration options available to be set through the config file.
|
||||||
@ -47,6 +50,10 @@ type Config struct {
|
|||||||
// Source list line-number color (3/4 bit color codes as defined
|
// Source list line-number color (3/4 bit color codes as defined
|
||||||
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
|
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
|
||||||
SourceListLineColor int `yaml:"source-list-line-color"`
|
SourceListLineColor int `yaml:"source-list-line-color"`
|
||||||
|
|
||||||
|
// DebugFileDirectories is the list of directories Delve will use
|
||||||
|
// in order to resolve external debug info files.
|
||||||
|
DebugInfoDirectories []string `yaml:"debug-info-directories"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig attempts to populate a Config object from the config.yml file.
|
// LoadConfig attempts to populate a Config object from the config.yml file.
|
||||||
@ -54,12 +61,32 @@ func LoadConfig() *Config {
|
|||||||
err := createConfigPath()
|
err := createConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Could not create config directory: %v.", err)
|
fmt.Printf("Could not create config directory: %v.", err)
|
||||||
return nil
|
return &Config{}
|
||||||
}
|
}
|
||||||
fullConfigFile, err := GetConfigFilePath(configFile)
|
fullConfigFile, err := GetConfigFilePath(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unable to get config file path: %v.", err)
|
fmt.Printf("Unable to get config file path: %v.", err)
|
||||||
return nil
|
return &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasOldConfig, err := hasOldConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to determine if old config exists: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasOldConfig {
|
||||||
|
userHomeDir := getUserHomeDir()
|
||||||
|
oldLocation := path.Join(userHomeDir, configDirHidden)
|
||||||
|
if err := moveOldConfig(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to move old config: %v\n", err)
|
||||||
|
return &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(oldLocation); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to remove old config location: %v\n", err)
|
||||||
|
return &Config{}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Successfully moved config from: %s to: %s\n", oldLocation, fullConfigFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(fullConfigFile)
|
f, err := os.Open(fullConfigFile)
|
||||||
@ -67,7 +94,7 @@ func LoadConfig() *Config {
|
|||||||
f, err = createDefaultConfig(fullConfigFile)
|
f, err = createDefaultConfig(fullConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error creating default config file: %v", err)
|
fmt.Printf("Error creating default config file: %v", err)
|
||||||
return nil
|
return &Config{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -80,19 +107,25 @@ func LoadConfig() *Config {
|
|||||||
data, err := ioutil.ReadAll(f)
|
data, err := ioutil.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unable to read config data: %v.", err)
|
fmt.Printf("Unable to read config data: %v.", err)
|
||||||
return nil
|
return &Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
err = yaml.Unmarshal(data, &c)
|
err = yaml.Unmarshal(data, &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unable to decode config file: %v.", err)
|
fmt.Printf("Unable to decode config file: %v.", err)
|
||||||
return nil
|
return &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.DebugInfoDirectories) == 0 {
|
||||||
|
c.DebugInfoDirectories = []string{"/usr/lib/debug/.build-id"}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveConfig will marshal and save the config struct
|
||||||
|
// to disk.
|
||||||
func SaveConfig(conf *Config) error {
|
func SaveConfig(conf *Config) error {
|
||||||
fullConfigFile, err := GetConfigFilePath(configFile)
|
fullConfigFile, err := GetConfigFilePath(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,15 +147,42 @@ func SaveConfig(conf *Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// moveOldConfig attempts to move config to new location
|
||||||
|
// $HOME/.dlv to $XDG_CONFIG_HOME/dlv
|
||||||
|
func moveOldConfig() error {
|
||||||
|
if os.Getenv("XDG_CONFIG_HOME") == "" && runtime.GOOS != "linux" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userHomeDir := getUserHomeDir()
|
||||||
|
|
||||||
|
p := path.Join(userHomeDir, configDirHidden, configFile)
|
||||||
|
_, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read config file located at: %s", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
newFile, err := GetConfigFilePath(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to read config file located at: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(p, newFile); err != nil {
|
||||||
|
return fmt.Errorf("unable to move %s to %s", p, newFile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func createDefaultConfig(path string) (*os.File, error) {
|
func createDefaultConfig(path string) (*os.File, error) {
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Unable to create config file: %v.", err)
|
return nil, fmt.Errorf("unable to create config file: %v", err)
|
||||||
}
|
}
|
||||||
err = writeDefaultConfig(f)
|
err = writeDefaultConfig(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Unable to write default configuration: %v.", err)
|
return nil, fmt.Errorf("unable to write default configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
f.Seek(0, io.SeekStart)
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +218,9 @@ substitute-path:
|
|||||||
|
|
||||||
# Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
|
# Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
|
||||||
# show-location-expr: true
|
# show-location-expr: true
|
||||||
|
|
||||||
|
# List of directories to use when searching for separate debug info files.
|
||||||
|
debug-info-directories: ["/usr/lib/debug/.build-id"]
|
||||||
`)
|
`)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -173,9 +236,43 @@ func createConfigPath() error {
|
|||||||
|
|
||||||
// GetConfigFilePath gets the full path to the given config file name.
|
// GetConfigFilePath gets the full path to the given config file name.
|
||||||
func GetConfigFilePath(file string) (string, error) {
|
func GetConfigFilePath(file string) (string, error) {
|
||||||
usr, err := user.Current()
|
if configPath := os.Getenv("XDG_CONFIG_HOME"); configPath != "" {
|
||||||
if err != nil {
|
return path.Join(configPath, configDir, file), nil
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
return path.Join(usr.HomeDir, configDir, file), nil
|
|
||||||
|
userHomeDir := getUserHomeDir()
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
return path.Join(userHomeDir, ".config", configDir, file), nil
|
||||||
|
}
|
||||||
|
return path.Join(userHomeDir, configDirHidden, file), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the user has a config at the old location: $HOME/.dlv
|
||||||
|
func hasOldConfig() (bool, error) {
|
||||||
|
// If you don't have XDG_CONFIG_HOME set and aren't on Linux you have nothing to move
|
||||||
|
if os.Getenv("XDG_CONFIG_HOME") == "" && runtime.GOOS != "linux" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userHomeDir := getUserHomeDir()
|
||||||
|
|
||||||
|
o := path.Join(userHomeDir, configDirHidden, configFile)
|
||||||
|
_, err := os.Stat(o)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserHomeDir() string {
|
||||||
|
userHomeDir := "."
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
userHomeDir = usr.HomeDir
|
||||||
|
}
|
||||||
|
return userHomeDir
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Like strings.Fields but ignores spaces inside areas surrounded
|
// SplitQuotedFields is like strings.Fields but ignores spaces inside areas surrounded
|
||||||
// by the specified quote character.
|
// by the specified quote character.
|
||||||
// To specify a single quote use backslash to escape it: '\''
|
// To specify a single quote use backslash to escape it: '\''
|
||||||
func SplitQuotedFields(in string, quote rune) []string {
|
func SplitQuotedFields(in string, quote rune) []string {
|
@ -7,7 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type parsefunc func(*parseContext) parsefunc
|
type parsefunc func(*parseContext) parsefunc
|
@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DWRule struct {
|
type DWRule struct {
|
@ -16,8 +16,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -41,6 +41,20 @@ const (
|
|||||||
encImaginaryFloat = 0x09
|
encImaginaryFloat = 0x09
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const cyclicalTypeStop = "<cyclical>" // guard value printed for types with a cyclical definition, to avoid inifinite recursion in Type.String
|
||||||
|
|
||||||
|
type recCheck map[dwarf.Offset]struct{}
|
||||||
|
|
||||||
|
func (recCheck recCheck) acquire(off dwarf.Offset) (release func()) {
|
||||||
|
if _, rec := recCheck[off]; rec {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
recCheck[off] = struct{}{}
|
||||||
|
return func() {
|
||||||
|
delete(recCheck, off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A Type conventionally represents a pointer to any of the
|
// A Type conventionally represents a pointer to any of the
|
||||||
// specific Type structures (CharType, StructType, etc.).
|
// specific Type structures (CharType, StructType, etc.).
|
||||||
//TODO: remove this use dwarf.Type
|
//TODO: remove this use dwarf.Type
|
||||||
@ -48,6 +62,9 @@ type Type interface {
|
|||||||
Common() *CommonType
|
Common() *CommonType
|
||||||
String() string
|
String() string
|
||||||
Size() int64
|
Size() int64
|
||||||
|
|
||||||
|
stringIntl(recCheck) string
|
||||||
|
sizeIntl(recCheck) int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// A CommonType holds fields common to multiple types.
|
// A CommonType holds fields common to multiple types.
|
||||||
@ -62,7 +79,8 @@ type CommonType struct {
|
|||||||
|
|
||||||
func (c *CommonType) Common() *CommonType { return c }
|
func (c *CommonType) Common() *CommonType { return c }
|
||||||
|
|
||||||
func (c *CommonType) Size() int64 { return c.ByteSize }
|
func (c *CommonType) Size() int64 { return c.ByteSize }
|
||||||
|
func (c *CommonType) sizeIntl(recCheck) int64 { return c.ByteSize }
|
||||||
|
|
||||||
// Basic types
|
// Basic types
|
||||||
|
|
||||||
@ -75,7 +93,9 @@ type BasicType struct {
|
|||||||
|
|
||||||
func (b *BasicType) Basic() *BasicType { return b }
|
func (b *BasicType) Basic() *BasicType { return b }
|
||||||
|
|
||||||
func (t *BasicType) String() string {
|
func (t *BasicType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
|
func (t *BasicType) stringIntl(recCheck) string {
|
||||||
if t.Name != "" {
|
if t.Name != "" {
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
@ -136,9 +156,27 @@ type QualType struct {
|
|||||||
Type Type
|
Type Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *QualType) String() string { return t.Qual + " " + t.Type.String() }
|
func (t *QualType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
func (t *QualType) Size() int64 { return t.Type.Size() }
|
func (t *QualType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return t.Qual + " " + t.Type.stringIntl(recCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *QualType) Size() int64 { return t.sizeIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *QualType) sizeIntl(recCheck recCheck) int64 {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return t.CommonType.ByteSize
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return t.Type.sizeIntl(recCheck)
|
||||||
|
}
|
||||||
|
|
||||||
// An ArrayType represents a fixed size array type.
|
// An ArrayType represents a fixed size array type.
|
||||||
type ArrayType struct {
|
type ArrayType struct {
|
||||||
@ -148,18 +186,36 @@ type ArrayType struct {
|
|||||||
Count int64 // if == -1, an incomplete array, like char x[].
|
Count int64 // if == -1, an incomplete array, like char x[].
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ArrayType) String() string {
|
func (t *ArrayType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.String()
|
|
||||||
|
func (t *ArrayType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.stringIntl(recCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ArrayType) Size() int64 { return t.Count * t.Type.Size() }
|
func (t *ArrayType) Size() int64 { return t.sizeIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *ArrayType) sizeIntl(recCheck recCheck) int64 {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return t.CommonType.ByteSize
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return t.Count * t.Type.sizeIntl(recCheck)
|
||||||
|
}
|
||||||
|
|
||||||
// A VoidType represents the C void type.
|
// A VoidType represents the C void type.
|
||||||
type VoidType struct {
|
type VoidType struct {
|
||||||
CommonType
|
CommonType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *VoidType) String() string { return "void" }
|
func (t *VoidType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
|
func (t *VoidType) stringIntl(recCheck) string { return "void" }
|
||||||
|
|
||||||
// A PtrType represents a pointer type.
|
// A PtrType represents a pointer type.
|
||||||
type PtrType struct {
|
type PtrType struct {
|
||||||
@ -167,7 +223,16 @@ type PtrType struct {
|
|||||||
Type Type
|
Type Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *PtrType) String() string { return "*" + t.Type.String() }
|
func (t *PtrType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *PtrType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return "*" + t.Type.stringIntl(recCheck)
|
||||||
|
}
|
||||||
|
|
||||||
// A StructType represents a struct, union, or C++ class type.
|
// A StructType represents a struct, union, or C++ class type.
|
||||||
type StructType struct {
|
type StructType struct {
|
||||||
@ -189,14 +254,21 @@ type StructField struct {
|
|||||||
Embedded bool
|
Embedded bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *StructType) String() string {
|
func (t *StructType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *StructType) stringIntl(recCheck recCheck) string {
|
||||||
if t.StructName != "" {
|
if t.StructName != "" {
|
||||||
return t.Kind + " " + t.StructName
|
return t.Kind + " " + t.StructName
|
||||||
}
|
}
|
||||||
return t.Defn()
|
return t.Defn(recCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *StructType) Defn() string {
|
func (t *StructType) Defn(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
s := t.Kind
|
s := t.Kind
|
||||||
if t.StructName != "" {
|
if t.StructName != "" {
|
||||||
s += " " + t.StructName
|
s += " " + t.StructName
|
||||||
@ -210,7 +282,7 @@ func (t *StructType) Defn() string {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
s += "; "
|
s += "; "
|
||||||
}
|
}
|
||||||
s += f.Name + " " + f.Type.String()
|
s += f.Name + " " + f.Type.stringIntl(recCheck)
|
||||||
s += "@" + strconv.FormatInt(f.ByteOffset, 10)
|
s += "@" + strconv.FormatInt(f.ByteOffset, 10)
|
||||||
if f.BitSize > 0 {
|
if f.BitSize > 0 {
|
||||||
s += " : " + strconv.FormatInt(f.BitSize, 10)
|
s += " : " + strconv.FormatInt(f.BitSize, 10)
|
||||||
@ -228,11 +300,18 @@ type SliceType struct {
|
|||||||
ElemType Type
|
ElemType Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *SliceType) String() string {
|
func (t *SliceType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *SliceType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
if t.Name != "" {
|
if t.Name != "" {
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
return "[]" + t.ElemType.String()
|
return "[]" + t.ElemType.stringIntl(recCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A StringType represents a Go string type. It looks like a StructType, describing
|
// A StringType represents a Go string type. It looks like a StructType, describing
|
||||||
@ -241,7 +320,9 @@ type StringType struct {
|
|||||||
StructType
|
StructType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *StringType) String() string {
|
func (t *StringType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
|
func (t *StringType) stringIntl(recCheck recCheck) string {
|
||||||
if t.Name != "" {
|
if t.Name != "" {
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
@ -253,7 +334,9 @@ type InterfaceType struct {
|
|||||||
TypedefType
|
TypedefType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *InterfaceType) String() string {
|
func (t *InterfaceType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
|
func (t *InterfaceType) stringIntl(recCheck recCheck) string {
|
||||||
if t.Name != "" {
|
if t.Name != "" {
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
@ -275,7 +358,9 @@ type EnumValue struct {
|
|||||||
Val int64
|
Val int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *EnumType) String() string {
|
func (t *EnumType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
|
func (t *EnumType) stringIntl(recCheck recCheck) string {
|
||||||
s := "enum"
|
s := "enum"
|
||||||
if t.EnumName != "" {
|
if t.EnumName != "" {
|
||||||
s += " " + t.EnumName
|
s += " " + t.EnumName
|
||||||
@ -298,17 +383,24 @@ type FuncType struct {
|
|||||||
ParamType []Type
|
ParamType []Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FuncType) String() string {
|
func (t *FuncType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *FuncType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
s := "func("
|
s := "func("
|
||||||
for i, t := range t.ParamType {
|
for i, t := range t.ParamType {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
s += ", "
|
s += ", "
|
||||||
}
|
}
|
||||||
s += t.String()
|
s += t.stringIntl(recCheck)
|
||||||
}
|
}
|
||||||
s += ")"
|
s += ")"
|
||||||
if t.ReturnType != nil {
|
if t.ReturnType != nil {
|
||||||
s += " " + t.ReturnType.String()
|
s += " " + t.ReturnType.stringIntl(recCheck)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@ -318,7 +410,9 @@ type DotDotDotType struct {
|
|||||||
CommonType
|
CommonType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DotDotDotType) String() string { return "..." }
|
func (t *DotDotDotType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
|
func (t *DotDotDotType) stringIntl(recCheck recCheck) string { return "..." }
|
||||||
|
|
||||||
// A TypedefType represents a named type.
|
// A TypedefType represents a named type.
|
||||||
type TypedefType struct {
|
type TypedefType struct {
|
||||||
@ -326,9 +420,20 @@ type TypedefType struct {
|
|||||||
Type Type
|
Type Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TypedefType) String() string { return t.Name }
|
func (t *TypedefType) String() string { return t.stringIntl(nil) }
|
||||||
|
|
||||||
func (t *TypedefType) Size() int64 { return t.Type.Size() }
|
func (t *TypedefType) stringIntl(recCheck recCheck) string { return t.Name }
|
||||||
|
|
||||||
|
func (t *TypedefType) Size() int64 { return t.sizeIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *TypedefType) sizeIntl(recCheck recCheck) int64 {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return t.CommonType.ByteSize
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return t.Type.sizeIntl(recCheck)
|
||||||
|
}
|
||||||
|
|
||||||
// A MapType represents a Go map type. It looks like a TypedefType, describing
|
// A MapType represents a Go map type. It looks like a TypedefType, describing
|
||||||
// the runtime-internal structure, with extra fields.
|
// the runtime-internal structure, with extra fields.
|
||||||
@ -338,7 +443,14 @@ type MapType struct {
|
|||||||
ElemType Type
|
ElemType Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MapType) String() string {
|
func (t *MapType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *MapType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
if t.Name != "" {
|
if t.Name != "" {
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
@ -351,7 +463,14 @@ type ChanType struct {
|
|||||||
ElemType Type
|
ElemType Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ChanType) String() string {
|
func (t *ChanType) String() string { return t.stringIntl(make(recCheck)) }
|
||||||
|
|
||||||
|
func (t *ChanType) stringIntl(recCheck recCheck) string {
|
||||||
|
release := recCheck.acquire(t.CommonType.Offset)
|
||||||
|
if release == nil {
|
||||||
|
return cyclicalTypeStop
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
if t.Name != "" {
|
if t.Name != "" {
|
||||||
return t.Name
|
return t.Name
|
||||||
}
|
}
|
||||||
@ -586,9 +705,10 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
|
|||||||
switch t.ReflectKind {
|
switch t.ReflectKind {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
slice := new(SliceType)
|
slice := new(SliceType)
|
||||||
|
typ = slice
|
||||||
|
typeCache[off] = slice
|
||||||
slice.ElemType = typeOf(e, AttrGoElem)
|
slice.ElemType = typeOf(e, AttrGoElem)
|
||||||
t = &slice.StructType
|
t = &slice.StructType
|
||||||
typ = slice
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
str := new(StringType)
|
str := new(StringType)
|
||||||
t = &str.StructType
|
t = &str.StructType
|
||||||
@ -798,19 +918,22 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
|
|||||||
switch t.ReflectKind {
|
switch t.ReflectKind {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
m := new(MapType)
|
m := new(MapType)
|
||||||
|
typ = m
|
||||||
|
typeCache[off] = typ
|
||||||
m.KeyType = typeOf(e, AttrGoKey)
|
m.KeyType = typeOf(e, AttrGoKey)
|
||||||
m.ElemType = typeOf(e, AttrGoElem)
|
m.ElemType = typeOf(e, AttrGoElem)
|
||||||
t = &m.TypedefType
|
t = &m.TypedefType
|
||||||
typ = m
|
|
||||||
case reflect.Chan:
|
case reflect.Chan:
|
||||||
c := new(ChanType)
|
c := new(ChanType)
|
||||||
|
typ = c
|
||||||
|
typeCache[off] = typ
|
||||||
c.ElemType = typeOf(e, AttrGoElem)
|
c.ElemType = typeOf(e, AttrGoElem)
|
||||||
t = &c.TypedefType
|
t = &c.TypedefType
|
||||||
typ = c
|
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
it := new(InterfaceType)
|
it := new(InterfaceType)
|
||||||
t = &it.TypedefType
|
|
||||||
typ = it
|
typ = it
|
||||||
|
typeCache[off] = it
|
||||||
|
t = &it.TypedefType
|
||||||
default:
|
default:
|
||||||
typ = t
|
typ = t
|
||||||
}
|
}
|
||||||
@ -876,3 +999,13 @@ func zeroArray(t Type) {
|
|||||||
t = at.Type
|
t = at.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveTypedef(typ Type) Type {
|
||||||
|
for {
|
||||||
|
if tt, ok := typ.(*TypedefType); ok {
|
||||||
|
typ = tt.Type
|
||||||
|
} else {
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DebugLinePrologue struct {
|
type DebugLinePrologue struct {
|
@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Opcode byte
|
type Opcode byte
|
2
vendor/github.com/derekparker/delve/pkg/dwarf/reader/reader.go → vendor/github.com/go-delve/delve/pkg/dwarf/reader/reader.go
generated
vendored
Executable file → Normal file
2
vendor/github.com/derekparker/delve/pkg/dwarf/reader/reader.go → vendor/github.com/go-delve/delve/pkg/dwarf/reader/reader.go
generated
vendored
Executable file → Normal file
@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader struct {
|
type Reader struct {
|
227
vendor/github.com/go-delve/delve/pkg/logflags/logflags.go
generated
vendored
Normal file
227
vendor/github.com/go-delve/delve/pkg/logflags/logflags.go
generated
vendored
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package logflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var debugger = false
|
||||||
|
var gdbWire = false
|
||||||
|
var lldbServerOutput = false
|
||||||
|
var debugLineErrors = false
|
||||||
|
var rpc = false
|
||||||
|
var fnCall = false
|
||||||
|
var minidump = false
|
||||||
|
|
||||||
|
var logOut io.WriteCloser
|
||||||
|
|
||||||
|
func makeLogger(flag bool, fields logrus.Fields) *logrus.Entry {
|
||||||
|
logger := logrus.New().WithFields(fields)
|
||||||
|
logger.Logger.Formatter = &textFormatter{}
|
||||||
|
if logOut != nil {
|
||||||
|
logger.Logger.Out = logOut
|
||||||
|
}
|
||||||
|
logger.Logger.Level = logrus.DebugLevel
|
||||||
|
if !flag {
|
||||||
|
logger.Logger.Level = logrus.PanicLevel
|
||||||
|
}
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// GdbWire returns true if the gdbserial package should log all the packets
|
||||||
|
// exchanged with the stub.
|
||||||
|
func GdbWire() bool {
|
||||||
|
return gdbWire
|
||||||
|
}
|
||||||
|
|
||||||
|
// GdbWireLogger returns a configured logger for the gdbserial wire protocol.
|
||||||
|
func GdbWireLogger() *logrus.Entry {
|
||||||
|
return makeLogger(gdbWire, logrus.Fields{"layer": "gdbconn"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugger returns true if the debugger package should log.
|
||||||
|
func Debugger() bool {
|
||||||
|
return debugger
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebuggerLogger returns a logger for the debugger package.
|
||||||
|
func DebuggerLogger() *logrus.Entry {
|
||||||
|
return makeLogger(debugger, logrus.Fields{"layer": "debugger"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLDBServerOutput returns true if the output of the LLDB server should be
|
||||||
|
// redirected to standard output instead of suppressed.
|
||||||
|
func LLDBServerOutput() bool {
|
||||||
|
return lldbServerOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugLineErrors returns true if pkg/dwarf/line should log its recoverable
|
||||||
|
// errors.
|
||||||
|
func DebugLineErrors() bool {
|
||||||
|
return debugLineErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPC returns true if RPC messages should be logged.
|
||||||
|
func RPC() bool {
|
||||||
|
return rpc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCLogger returns a logger for RPC messages.
|
||||||
|
func RPCLogger() *logrus.Entry {
|
||||||
|
return makeLogger(rpc, logrus.Fields{"layer": "rpc"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FnCall returns true if the function call protocol should be logged.
|
||||||
|
func FnCall() bool {
|
||||||
|
return fnCall
|
||||||
|
}
|
||||||
|
|
||||||
|
func FnCallLogger() *logrus.Entry {
|
||||||
|
return makeLogger(fnCall, logrus.Fields{"layer": "proc", "kind": "fncall"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minidump returns true if the minidump loader should be logged.
|
||||||
|
func Minidump() bool {
|
||||||
|
return minidump
|
||||||
|
}
|
||||||
|
|
||||||
|
func MinidumpLogger() *logrus.Entry {
|
||||||
|
return makeLogger(minidump, logrus.Fields{"layer": "core", "kind": "minidump"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteAPIListeningMessage writes the "API server listening" message in headless mode.
|
||||||
|
func WriteAPIListeningMessage(addr string) {
|
||||||
|
if logOut != nil {
|
||||||
|
fmt.Fprintf(logOut, "API server listening at: %s\n", addr)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("API server listening at: %s\n", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
||||||
|
|
||||||
|
// Setup sets debugger flags based on the contents of logstr.
|
||||||
|
// If logDest is not empty logs will be redirected to the file descriptor or
|
||||||
|
// file path specified by logDest.
|
||||||
|
func Setup(logFlag bool, logstr string, logDest string) error {
|
||||||
|
if logDest != "" {
|
||||||
|
n, err := strconv.Atoi(logDest)
|
||||||
|
if err == nil {
|
||||||
|
logOut = os.NewFile(uintptr(n), "delve-logs")
|
||||||
|
} else {
|
||||||
|
fh, err := os.Create(logDest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create log file: %v", err)
|
||||||
|
}
|
||||||
|
logOut = fh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||||
|
if !logFlag {
|
||||||
|
log.SetOutput(ioutil.Discard)
|
||||||
|
if logstr != "" {
|
||||||
|
return errLogstrWithoutLog
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if logstr == "" {
|
||||||
|
logstr = "debugger"
|
||||||
|
}
|
||||||
|
v := strings.Split(logstr, ",")
|
||||||
|
for _, logcmd := range v {
|
||||||
|
switch logcmd {
|
||||||
|
case "debugger":
|
||||||
|
debugger = true
|
||||||
|
case "gdbwire":
|
||||||
|
gdbWire = true
|
||||||
|
case "lldbout":
|
||||||
|
lldbServerOutput = true
|
||||||
|
case "debuglineerr":
|
||||||
|
debugLineErrors = true
|
||||||
|
case "rpc":
|
||||||
|
rpc = true
|
||||||
|
case "fncall":
|
||||||
|
fnCall = true
|
||||||
|
case "minidump":
|
||||||
|
minidump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the logger output.
|
||||||
|
func Close() {
|
||||||
|
if logOut != nil {
|
||||||
|
logOut.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// textFormatter is a simplified version of logrus.TextFormatter that
|
||||||
|
// doesn't make logs unreadable when they are output to a text file or to a
|
||||||
|
// terminal that doesn't support colors.
|
||||||
|
type textFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||||
|
var b *bytes.Buffer
|
||||||
|
if entry.Buffer != nil {
|
||||||
|
b = entry.Buffer
|
||||||
|
} else {
|
||||||
|
b = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(entry.Data))
|
||||||
|
for k := range entry.Data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
b.WriteString(entry.Time.Format(time.RFC3339))
|
||||||
|
b.WriteByte(' ')
|
||||||
|
b.WriteString(entry.Level.String())
|
||||||
|
b.WriteByte(' ')
|
||||||
|
for i, key := range keys {
|
||||||
|
b.WriteString(key)
|
||||||
|
b.WriteByte('=')
|
||||||
|
stringVal, ok := entry.Data[key].(string)
|
||||||
|
if !ok {
|
||||||
|
stringVal = fmt.Sprint(entry.Data[key])
|
||||||
|
}
|
||||||
|
if f.needsQuoting(stringVal) {
|
||||||
|
fmt.Fprintf(b, "%q", stringVal)
|
||||||
|
} else {
|
||||||
|
b.WriteString(stringVal)
|
||||||
|
}
|
||||||
|
if i != len(keys)-1 {
|
||||||
|
b.WriteByte(',')
|
||||||
|
} else {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.WriteString(entry.Message)
|
||||||
|
b.WriteByte('\n')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *textFormatter) needsQuoting(text string) bool {
|
||||||
|
for _, ch := range text {
|
||||||
|
if !((ch >= 'a' && ch <= 'z') ||
|
||||||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') ||
|
||||||
|
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -3,8 +3,8 @@ package proc
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, reg := range regs.Slice() {
|
for _, reg := range regs.Slice(true) {
|
||||||
for dwarfReg, regName := range amd64DwarfToName {
|
for dwarfReg, regName := range amd64DwarfToName {
|
||||||
if regName == reg.Name {
|
if regName == reg.Name {
|
||||||
dregs[dwarfReg] = op.DwarfRegisterFromBytes(reg.Bytes)
|
dregs[dwarfReg] = op.DwarfRegisterFromBytes(reg.Bytes)
|
@ -12,40 +12,30 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/line"
|
"github.com/go-delve/delve/pkg/dwarf/line"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
"github.com/derekparker/delve/pkg/goversion"
|
"github.com/go-delve/delve/pkg/goversion"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BinaryInfo holds information on the binary being executed.
|
// BinaryInfo holds information on the binaries being executed (this
|
||||||
|
// includes both the executable and also any loaded libraries).
|
||||||
type BinaryInfo struct {
|
type BinaryInfo struct {
|
||||||
lastModified time.Time // Time the executable of this process was last modified
|
// Path on disk of the binary being executed.
|
||||||
|
Path string
|
||||||
|
// Architecture of this binary.
|
||||||
|
Arch Arch
|
||||||
|
|
||||||
GOOS string
|
// GOOS operating system this binary is executing on.
|
||||||
closer io.Closer
|
GOOS string
|
||||||
sepDebugCloser io.Closer
|
|
||||||
|
|
||||||
staticBase uint64
|
|
||||||
|
|
||||||
// Maps package names to package paths, needed to lookup types inside DWARF info
|
|
||||||
packageMap map[string]string
|
|
||||||
|
|
||||||
Arch Arch
|
|
||||||
dwarf *dwarf.Data
|
|
||||||
frameEntries frame.FrameDescriptionEntries
|
|
||||||
loclist loclistReader
|
|
||||||
compileUnits []*compileUnit
|
|
||||||
types map[string]dwarf.Offset
|
|
||||||
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
|
|
||||||
gStructOffset uint64
|
|
||||||
|
|
||||||
// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
|
// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
|
||||||
Functions []Function
|
Functions []Function
|
||||||
@ -54,7 +44,32 @@ type BinaryInfo struct {
|
|||||||
// LookupFunc maps function names to a description of the function.
|
// LookupFunc maps function names to a description of the function.
|
||||||
LookupFunc map[string]*Function
|
LookupFunc map[string]*Function
|
||||||
|
|
||||||
typeCache map[dwarf.Offset]godwarf.Type
|
// Images is a list of loaded shared libraries (also known as
|
||||||
|
// shared objects on linux or DLLs on windws).
|
||||||
|
Images []*Image
|
||||||
|
|
||||||
|
ElfDynamicSection ElfDynamicSection
|
||||||
|
|
||||||
|
lastModified time.Time // Time the executable of this process was last modified
|
||||||
|
|
||||||
|
closer io.Closer
|
||||||
|
sepDebugCloser io.Closer
|
||||||
|
|
||||||
|
staticBase uint64
|
||||||
|
|
||||||
|
// Maps package names to package paths, needed to lookup types inside DWARF info
|
||||||
|
packageMap map[string]string
|
||||||
|
|
||||||
|
dwarf *dwarf.Data
|
||||||
|
dwarfReader *dwarf.Reader
|
||||||
|
frameEntries frame.FrameDescriptionEntries
|
||||||
|
loclist loclistReader
|
||||||
|
compileUnits []*compileUnit
|
||||||
|
types map[string]dwarf.Offset
|
||||||
|
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
|
||||||
|
typeCache map[dwarf.Offset]godwarf.Type
|
||||||
|
|
||||||
|
gStructOffset uint64
|
||||||
|
|
||||||
loadModuleDataOnce sync.Once
|
loadModuleDataOnce sync.Once
|
||||||
moduleData []moduleData
|
moduleData []moduleData
|
||||||
@ -71,8 +86,6 @@ type BinaryInfo struct {
|
|||||||
|
|
||||||
loadErrMu sync.Mutex
|
loadErrMu sync.Mutex
|
||||||
loadErr error
|
loadErr error
|
||||||
|
|
||||||
dwarfReader *dwarf.Reader
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture.
|
// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture.
|
||||||
@ -84,14 +97,20 @@ var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/
|
|||||||
// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture.
|
// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture.
|
||||||
var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported")
|
var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported")
|
||||||
|
|
||||||
|
// ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
|
||||||
|
// position independant executable.
|
||||||
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
|
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
|
||||||
|
|
||||||
|
// ErrNoDebugInfoFound is returned when Delve cannot open the debug_info
|
||||||
|
// section or find an external debug info file.
|
||||||
|
var ErrNoDebugInfoFound = errors.New("could not open debug info")
|
||||||
|
|
||||||
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
|
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
|
||||||
|
|
||||||
type compileUnit struct {
|
type compileUnit struct {
|
||||||
Name string // univocal name for non-go compile units
|
name string // univocal name for non-go compile units
|
||||||
LowPC uint64
|
lowPC uint64
|
||||||
Ranges [][2]uint64
|
ranges [][2]uint64
|
||||||
|
|
||||||
entry *dwarf.Entry // debug_info entry describing this compile unit
|
entry *dwarf.Entry // debug_info entry describing this compile unit
|
||||||
isgo bool // true if this is the go compile unit
|
isgo bool // true if this is the go compile unit
|
||||||
@ -277,6 +296,12 @@ type buildIDHeader struct {
|
|||||||
Type uint32
|
Type uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ElfDynamicSection describes the .dynamic section of an ELF executable.
|
||||||
|
type ElfDynamicSection struct {
|
||||||
|
Addr uint64 // relocated address of where the .dynamic section is mapped in memory
|
||||||
|
Size uint64 // size of the .dynamic section of the executable
|
||||||
|
}
|
||||||
|
|
||||||
// NewBinaryInfo returns an initialized but unloaded BinaryInfo struct.
|
// NewBinaryInfo returns an initialized but unloaded BinaryInfo struct.
|
||||||
func NewBinaryInfo(goos, goarch string) *BinaryInfo {
|
func NewBinaryInfo(goos, goarch string) *BinaryInfo {
|
||||||
r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)}
|
r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)}
|
||||||
@ -293,22 +318,24 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo {
|
|||||||
// LoadBinaryInfo will load and store the information from the binary at 'path'.
|
// LoadBinaryInfo will load and store the information from the binary at 'path'.
|
||||||
// It is expected this will be called in parallel with other initialization steps
|
// It is expected this will be called in parallel with other initialization steps
|
||||||
// so a sync.WaitGroup must be provided.
|
// so a sync.WaitGroup must be provided.
|
||||||
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, wg *sync.WaitGroup) error {
|
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string) error {
|
||||||
fi, err := os.Stat(path)
|
fi, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bi.lastModified = fi.ModTime()
|
bi.lastModified = fi.ModTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
bi.Path = path
|
||||||
switch bi.GOOS {
|
switch bi.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
return bi.LoadBinaryInfoElf(path, entryPoint, wg)
|
return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, &wg)
|
||||||
case "windows":
|
case "windows":
|
||||||
return bi.LoadBinaryInfoPE(path, entryPoint, wg)
|
return bi.LoadBinaryInfoPE(path, entryPoint, &wg)
|
||||||
case "darwin":
|
case "darwin":
|
||||||
return bi.LoadBinaryInfoMacho(path, entryPoint, wg)
|
return bi.LoadBinaryInfoMacho(path, entryPoint, &wg)
|
||||||
}
|
}
|
||||||
return errors.New("unsupported operating system")
|
return errors.New("unsupported operating system")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GStructOffset returns the offset of the G
|
// GStructOffset returns the offset of the G
|
||||||
@ -398,12 +425,35 @@ func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Image represents a loaded library file (shared object on linux, DLL on windows).
|
||||||
|
type Image struct {
|
||||||
|
Path string
|
||||||
|
addr uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddImage adds the specified image to bi.
|
||||||
|
func (bi *BinaryInfo) AddImage(path string, addr uint64) {
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, image := range bi.Images {
|
||||||
|
if image.Path == path && image.addr == addr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO(aarzilli): actually load informations about the image here
|
||||||
|
bi.Images = append(bi.Images, &Image{Path: path, addr: addr})
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes all internal readers.
|
// Close closes all internal readers.
|
||||||
func (bi *BinaryInfo) Close() error {
|
func (bi *BinaryInfo) Close() error {
|
||||||
if bi.sepDebugCloser != nil {
|
if bi.sepDebugCloser != nil {
|
||||||
bi.sepDebugCloser.Close()
|
bi.sepDebugCloser.Close()
|
||||||
}
|
}
|
||||||
return bi.closer.Close()
|
if bi.closer != nil {
|
||||||
|
return bi.closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) {
|
func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) {
|
||||||
@ -488,7 +538,7 @@ func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, r
|
|||||||
func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
|
func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
|
||||||
var base uint64
|
var base uint64
|
||||||
if cu := bi.findCompileUnit(pc); cu != nil {
|
if cu := bi.findCompileUnit(pc); cu != nil {
|
||||||
base = cu.LowPC
|
base = cu.lowPC
|
||||||
}
|
}
|
||||||
|
|
||||||
bi.loclist.Seek(int(off))
|
bi.loclist.Seek(int(off))
|
||||||
@ -509,7 +559,7 @@ func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
|
|||||||
// findCompileUnit returns the compile unit containing address pc.
|
// findCompileUnit returns the compile unit containing address pc.
|
||||||
func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit {
|
func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit {
|
||||||
for _, cu := range bi.compileUnits {
|
for _, cu := range bi.compileUnits {
|
||||||
for _, rng := range cu.Ranges {
|
for _, rng := range cu.ranges {
|
||||||
if pc >= rng[0] && pc < rng[1] {
|
if pc >= rng[0] && pc < rng[1] {
|
||||||
return cu
|
return cu
|
||||||
}
|
}
|
||||||
@ -558,35 +608,32 @@ func (e *ErrNoBuildIDNote) Error() string {
|
|||||||
// in GDB's documentation [1], and if found returns two handles, one
|
// in GDB's documentation [1], and if found returns two handles, one
|
||||||
// for the bare file, and another for its corresponding elf.File.
|
// for the bare file, and another for its corresponding elf.File.
|
||||||
// [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
|
// [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
|
||||||
func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, error) {
|
//
|
||||||
buildid := exe.Section(".note.gnu.build-id")
|
// Alternatively, if the debug file cannot be found be the build-id, Delve
|
||||||
if buildid == nil {
|
// will look in directories specified by the debug-info-directories config value.
|
||||||
return nil, nil, &ErrNoBuildIDNote{}
|
func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) {
|
||||||
|
var debugFilePath string
|
||||||
|
for _, dir := range debugInfoDirectories {
|
||||||
|
var potentialDebugFilePath string
|
||||||
|
if strings.Contains(dir, "build-id") {
|
||||||
|
desc1, desc2, err := parseBuildID(exe)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
potentialDebugFilePath = fmt.Sprintf("%s/%s/%s.debug", dir, desc1, desc2)
|
||||||
|
} else {
|
||||||
|
potentialDebugFilePath = fmt.Sprintf("%s/%s.debug", dir, filepath.Base(bi.Path))
|
||||||
|
}
|
||||||
|
_, err := os.Stat(potentialDebugFilePath)
|
||||||
|
if err == nil {
|
||||||
|
debugFilePath = potentialDebugFilePath
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if debugFilePath == "" {
|
||||||
br := buildid.Open()
|
return nil, nil, ErrNoDebugInfoFound
|
||||||
bh := new(buildIDHeader)
|
|
||||||
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
|
|
||||||
return nil, nil, errors.New("can't read build-id header: " + err.Error())
|
|
||||||
}
|
}
|
||||||
|
sepFile, err := os.OpenFile(debugFilePath, 0, os.ModePerm)
|
||||||
name := make([]byte, bh.Namesz)
|
|
||||||
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
|
|
||||||
return nil, nil, errors.New("can't read build-id name: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(string(name)) != "GNU\x00" {
|
|
||||||
return nil, nil, errors.New("invalid build-id signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
descBinary := make([]byte, bh.Descsz)
|
|
||||||
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
|
|
||||||
return nil, nil, errors.New("can't read build-id desc: " + err.Error())
|
|
||||||
}
|
|
||||||
desc := hex.EncodeToString(descBinary)
|
|
||||||
|
|
||||||
debugPath := fmt.Sprintf("/usr/lib/debug/.build-id/%s/%s.debug", desc[:2], desc[2:])
|
|
||||||
sepFile, err := os.OpenFile(debugPath, 0, os.ModePerm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.New("can't open separate debug file: " + err.Error())
|
return nil, nil, errors.New("can't open separate debug file: " + err.Error())
|
||||||
}
|
}
|
||||||
@ -594,19 +641,48 @@ func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File,
|
|||||||
elfFile, err := elf.NewFile(sepFile)
|
elfFile, err := elf.NewFile(sepFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sepFile.Close()
|
sepFile.Close()
|
||||||
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, err.Error())
|
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if elfFile.Machine != elf.EM_X86_64 {
|
if elfFile.Machine != elf.EM_X86_64 {
|
||||||
sepFile.Close()
|
sepFile.Close()
|
||||||
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, ErrUnsupportedLinuxArch.Error())
|
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, ErrUnsupportedLinuxArch.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return sepFile, elfFile, nil
|
return sepFile, elfFile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseBuildID(exe *elf.File) (string, string, error) {
|
||||||
|
buildid := exe.Section(".note.gnu.build-id")
|
||||||
|
if buildid == nil {
|
||||||
|
return "", "", &ErrNoBuildIDNote{}
|
||||||
|
}
|
||||||
|
|
||||||
|
br := buildid.Open()
|
||||||
|
bh := new(buildIDHeader)
|
||||||
|
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
|
||||||
|
return "", "", errors.New("can't read build-id header: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
name := make([]byte, bh.Namesz)
|
||||||
|
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
|
||||||
|
return "", "", errors.New("can't read build-id name: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(string(name)) != "GNU\x00" {
|
||||||
|
return "", "", errors.New("invalid build-id signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
descBinary := make([]byte, bh.Descsz)
|
||||||
|
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
|
||||||
|
return "", "", errors.New("can't read build-id desc: " + err.Error())
|
||||||
|
}
|
||||||
|
desc := hex.EncodeToString(descBinary)
|
||||||
|
return desc[:2], desc[2:], nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadBinaryInfoElf specifically loads information from an ELF binary.
|
// LoadBinaryInfoElf specifically loads information from an ELF binary.
|
||||||
func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync.WaitGroup) error {
|
func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, debugInfoDirectories []string, wg *sync.WaitGroup) error {
|
||||||
exe, err := os.OpenFile(path, 0, os.ModePerm)
|
exe, err := os.OpenFile(path, 0, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -628,17 +704,19 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dynsec := elfFile.Section(".dynamic"); dynsec != nil {
|
||||||
|
bi.ElfDynamicSection.Addr = dynsec.Addr + bi.staticBase
|
||||||
|
bi.ElfDynamicSection.Size = dynsec.Size
|
||||||
|
}
|
||||||
|
|
||||||
dwarfFile := elfFile
|
dwarfFile := elfFile
|
||||||
|
|
||||||
bi.dwarf, err = elfFile.DWARF()
|
bi.dwarf, err = elfFile.DWARF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var sepFile *os.File
|
var sepFile *os.File
|
||||||
var serr error
|
var serr error
|
||||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile)
|
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile, debugInfoDirectories)
|
||||||
if serr != nil {
|
if serr != nil {
|
||||||
if _, ok := serr.(*ErrNoBuildIDNote); ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return serr
|
return serr
|
||||||
}
|
}
|
||||||
bi.sepDebugCloser = sepFile
|
bi.sepDebugCloser = sepFile
|
||||||
@ -714,9 +792,16 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if tls == nil {
|
||||||
|
bi.gStructOffset = ^uint64(8) + 1 // -8
|
||||||
|
return
|
||||||
|
}
|
||||||
|
memsz := tls.Memsz
|
||||||
|
|
||||||
|
memsz = (memsz + uint64(bi.Arch.PtrSize()) - 1) & ^uint64(bi.Arch.PtrSize()-1) // align to pointer-sized-boundary
|
||||||
// The TLS register points to the end of the TLS block, which is
|
// The TLS register points to the end of the TLS block, which is
|
||||||
// tls.Memsz long. runtime.tlsg is an offset from the beginning of that block.
|
// tls.Memsz long. runtime.tlsg is an offset from the beginning of that block.
|
||||||
bi.gStructOffset = ^(tls.Memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value
|
bi.gStructOffset = ^(memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// PE ////////////////////////////////////////////////////////////////
|
// PE ////////////////////////////////////////////////////////////////
|
@ -30,7 +30,8 @@ type Breakpoint struct {
|
|||||||
Kind BreakpointKind
|
Kind BreakpointKind
|
||||||
|
|
||||||
// Breakpoint information
|
// Breakpoint information
|
||||||
Tracepoint bool // Tracepoint flag
|
Tracepoint bool // Tracepoint flag
|
||||||
|
TraceReturn bool
|
||||||
Goroutine bool // Retrieve goroutine information
|
Goroutine bool // Retrieve goroutine information
|
||||||
Stacktrace int // Number of stack frames to retrieve
|
Stacktrace int // Number of stack frames to retrieve
|
||||||
Variables []string // Variables to evaluate
|
Variables []string // Variables to evaluate
|
||||||
@ -45,7 +46,7 @@ type Breakpoint struct {
|
|||||||
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
|
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
|
||||||
// deferred function, DeferReturns is populated with the
|
// deferred function, DeferReturns is populated with the
|
||||||
// addresses of calls to runtime.deferreturn in the current
|
// addresses of calls to runtime.deferreturn in the current
|
||||||
// function. This insures that the breakpoint on the deferred
|
// function. This ensures that the breakpoint on the deferred
|
||||||
// function only triggers on panic or on the defer call to
|
// function only triggers on panic or on the defer call to
|
||||||
// the function, not when the function is called directly
|
// the function, not when the function is called directly
|
||||||
DeferReturns []uint64
|
DeferReturns []uint64
|
||||||
@ -220,13 +221,16 @@ func (bpmap *BreakpointMap) ResetBreakpointIDCounter() {
|
|||||||
bpmap.breakpointIDCounter = 0
|
bpmap.breakpointIDCounter = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type writeBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error)
|
// WriteBreakpointFn is a type that represents a function to be used for
|
||||||
|
// writting breakpoings into the target.
|
||||||
|
type WriteBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error)
|
||||||
|
|
||||||
type clearBreakpointFn func(*Breakpoint) error
|
type clearBreakpointFn func(*Breakpoint) error
|
||||||
|
|
||||||
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
|
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
|
||||||
// function, call proc.Process.SetBreakpoint instead, this function exists
|
// function, call proc.Process.SetBreakpoint instead, this function exists
|
||||||
// to implement proc.Process.SetBreakpoint.
|
// to implement proc.Process.SetBreakpoint.
|
||||||
func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) {
|
func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
|
||||||
if bp, ok := bpmap.M[addr]; ok {
|
if bp, ok := bpmap.M[addr]; ok {
|
||||||
// We can overlap one internal breakpoint with one user breakpoint, we
|
// We can overlap one internal breakpoint with one user breakpoint, we
|
||||||
// need to support this otherwise a conditional breakpoint can mask a
|
// need to support this otherwise a conditional breakpoint can mask a
|
||||||
@ -248,8 +252,13 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fnName := ""
|
||||||
|
if fn != nil {
|
||||||
|
fnName = fn.Name
|
||||||
|
}
|
||||||
|
|
||||||
newBreakpoint := &Breakpoint{
|
newBreakpoint := &Breakpoint{
|
||||||
FunctionName: fn.Name,
|
FunctionName: fnName,
|
||||||
File: f,
|
File: f,
|
||||||
Line: l,
|
Line: l,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
@ -274,7 +283,7 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetWithID creates a breakpoint at addr, with the specified ID.
|
// SetWithID creates a breakpoint at addr, with the specified ID.
|
||||||
func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) {
|
func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
|
||||||
bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint)
|
bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp.ID = id
|
bp.ID = id
|
||||||
@ -421,12 +430,6 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
|
|||||||
return (v.Flags & VariableReturnArgument) != 0
|
return (v.Flags & VariableReturnArgument) != 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// Go saves the return variables in the opposite order that the user
|
|
||||||
// specifies them so here we reverse the slice to make it easier to
|
|
||||||
// understand.
|
|
||||||
for i := 0; i < len(vars)/2; i++ {
|
|
||||||
vars[i], vars[len(vars)-i-1] = vars[len(vars)-i-1], vars[i]
|
|
||||||
}
|
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
|
|
@ -5,9 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A SplicedMemory represents a memory space formed from multiple regions,
|
// A SplicedMemory represents a memory space formed from multiple regions,
|
||||||
@ -144,8 +143,13 @@ func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error)
|
|||||||
|
|
||||||
// Process represents a core file.
|
// Process represents a core file.
|
||||||
type Process struct {
|
type Process struct {
|
||||||
|
mem proc.MemoryReader
|
||||||
|
Threads map[int]*Thread
|
||||||
|
pid int
|
||||||
|
|
||||||
|
entryPoint uint64
|
||||||
|
|
||||||
bi *proc.BinaryInfo
|
bi *proc.BinaryInfo
|
||||||
core *Core
|
|
||||||
breakpoints proc.BreakpointMap
|
breakpoints proc.BreakpointMap
|
||||||
currentThread *Thread
|
currentThread *Thread
|
||||||
selectedGoroutine *proc.G
|
selectedGoroutine *proc.G
|
||||||
@ -154,12 +158,16 @@ type Process struct {
|
|||||||
|
|
||||||
// Thread represents a thread in the core file being debugged.
|
// Thread represents a thread in the core file being debugged.
|
||||||
type Thread struct {
|
type Thread struct {
|
||||||
th *LinuxPrStatus
|
th osThread
|
||||||
fpregs []proc.Register
|
|
||||||
p *Process
|
p *Process
|
||||||
common proc.CommonThread
|
common proc.CommonThread
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type osThread interface {
|
||||||
|
registers(floatingPoint bool) (proc.Registers, error)
|
||||||
|
pid() int
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrWriteCore is returned when attempting to write to the core
|
// ErrWriteCore is returned when attempting to write to the core
|
||||||
// process memory.
|
// process memory.
|
||||||
@ -175,45 +183,66 @@ 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.
|
||||||
func OpenCore(corePath, exePath string) (*Process, error) {
|
// If the DWARF information cannot be found in the binary, Delve will look
|
||||||
core, err := readCore(corePath, exePath)
|
// for external debug files in the directories passed in.
|
||||||
if err != nil {
|
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
|
||||||
return nil, err
|
var p *Process
|
||||||
}
|
var err error
|
||||||
p := &Process{
|
for _, openFn := range openFns {
|
||||||
core: core,
|
p, err = openFn(corePath, exePath)
|
||||||
breakpoints: proc.NewBreakpointMap(),
|
if err != ErrUnrecognizedFormat {
|
||||||
bi: proc.NewBinaryInfo("linux", "amd64"),
|
break
|
||||||
}
|
}
|
||||||
for _, thread := range core.Threads {
|
|
||||||
thread.p = p
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
err = p.bi.LoadBinaryInfo(exePath, core.entryPoint, &wg)
|
|
||||||
wg.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = p.bi.LoadError()
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, th := range p.core.Threads {
|
if err := p.initialize(exePath, debugInfoDirs); err != nil {
|
||||||
p.currentThread = th
|
return nil, err
|
||||||
break
|
|
||||||
}
|
}
|
||||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize for core files doesn't do much
|
||||||
|
// aside from call the post initialization setup.
|
||||||
|
func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||||
|
return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
|
||||||
|
}
|
||||||
|
|
||||||
// BinInfo will return the binary info.
|
// BinInfo will return the binary info.
|
||||||
func (p *Process) BinInfo() *proc.BinaryInfo {
|
func (p *Process) BinInfo() *proc.BinaryInfo {
|
||||||
return p.bi
|
return p.bi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||||
|
// the default for any command executed, the goroutine being actively
|
||||||
|
// followed.
|
||||||
|
func (p *Process) SetSelectedGoroutine(g *proc.G) {
|
||||||
|
p.selectedGoroutine = g
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryPoint will return the entry point address for this core file.
|
||||||
|
func (p *Process) EntryPoint() (uint64, error) {
|
||||||
|
return p.entryPoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBreakpoint is a noop function since you
|
||||||
|
// cannot write breakpoints into core files.
|
||||||
|
func (p *Process) writeBreakpoint(addr uint64) (file string, line int, fn *proc.Function, originalData []byte, err error) {
|
||||||
|
return "", 0, nil, nil, errors.New("cannot write a breakpoint to a core file")
|
||||||
|
}
|
||||||
|
|
||||||
// Recorded returns whether this is a live or recorded process. Always returns true for core files.
|
// Recorded returns whether this is a live or recorded process. Always returns true for core files.
|
||||||
func (p *Process) Recorded() (bool, string) { return true, "" }
|
func (p *Process) Recorded() (bool, string) { return true, "" }
|
||||||
|
|
||||||
@ -239,7 +268,7 @@ func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not
|
|||||||
// read memory into `data`, returning the length read, and returning an error if
|
// read memory into `data`, returning the length read, and returning an error if
|
||||||
// the length read is shorter than the length of the `data` buffer.
|
// the length read is shorter than the length of the `data` buffer.
|
||||||
func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
|
func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
|
||||||
n, err = t.p.core.ReadMemory(data, addr)
|
n, err = t.p.mem.ReadMemory(data, addr)
|
||||||
if err == nil && n != len(data) {
|
if err == nil && n != len(data) {
|
||||||
err = ErrShortRead
|
err = ErrShortRead
|
||||||
}
|
}
|
||||||
@ -255,8 +284,13 @@ func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
|
|||||||
// Location returns the location of this thread based on
|
// Location returns the location of this thread based on
|
||||||
// the value of the instruction pointer register.
|
// the value of the instruction pointer register.
|
||||||
func (t *Thread) Location() (*proc.Location, error) {
|
func (t *Thread) Location() (*proc.Location, error) {
|
||||||
f, l, fn := t.p.bi.PCToLine(t.th.Reg.Rip)
|
regs, err := t.th.registers(false)
|
||||||
return &proc.Location{PC: t.th.Reg.Rip, File: f, Line: l, Fn: fn}, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pc := regs.PC()
|
||||||
|
f, l, fn := t.p.bi.PCToLine(pc)
|
||||||
|
return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Breakpoint returns the current breakpoint this thread is stopped at.
|
// Breakpoint returns the current breakpoint this thread is stopped at.
|
||||||
@ -268,16 +302,12 @@ func (t *Thread) Breakpoint() proc.BreakpointState {
|
|||||||
|
|
||||||
// ThreadID returns the ID for this thread.
|
// ThreadID returns the ID for this thread.
|
||||||
func (t *Thread) ThreadID() int {
|
func (t *Thread) ThreadID() int {
|
||||||
return int(t.th.Pid)
|
return int(t.th.pid())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registers returns the current value of the registers for this thread.
|
// Registers returns the current value of the registers for this thread.
|
||||||
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
|
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
|
||||||
r := &Registers{&t.th.Reg, nil}
|
return t.th.registers(floatingPoint)
|
||||||
if floatingPoint {
|
|
||||||
r.fpregs = t.fpregs
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreRegisters will only return an error for core files,
|
// RestoreRegisters will only return an error for core files,
|
||||||
@ -406,7 +436,7 @@ func (p *Process) Common() *proc.CommonProcess {
|
|||||||
|
|
||||||
// Pid returns the process ID of this process.
|
// Pid returns the process ID of this process.
|
||||||
func (p *Process) Pid() int {
|
func (p *Process) Pid() int {
|
||||||
return p.core.Pid
|
return p.pid
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResumeNotify is a no-op on core files as we cannot
|
// ResumeNotify is a no-op on core files as we cannot
|
||||||
@ -444,7 +474,7 @@ func (p *Process) SwitchGoroutine(gid int) error {
|
|||||||
|
|
||||||
// SwitchThread will change the selected and active thread.
|
// SwitchThread will change the selected and active thread.
|
||||||
func (p *Process) SwitchThread(tid int) error {
|
func (p *Process) SwitchThread(tid int) error {
|
||||||
if th, ok := p.core.Threads[tid]; ok {
|
if th, ok := p.Threads[tid]; ok {
|
||||||
p.currentThread = th
|
p.currentThread = th
|
||||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||||
return nil
|
return nil
|
||||||
@ -454,8 +484,8 @@ func (p *Process) SwitchThread(tid int) error {
|
|||||||
|
|
||||||
// ThreadList will return a list of all threads currently in the process.
|
// ThreadList will return a list of all threads currently in the process.
|
||||||
func (p *Process) ThreadList() []proc.Thread {
|
func (p *Process) ThreadList() []proc.Thread {
|
||||||
r := make([]proc.Thread, 0, len(p.core.Threads))
|
r := make([]proc.Thread, 0, len(p.Threads))
|
||||||
for _, v := range p.core.Threads {
|
for _, v := range p.Threads {
|
||||||
r = append(r, v)
|
r = append(r, v)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
@ -463,64 +493,6 @@ func (p *Process) ThreadList() []proc.Thread {
|
|||||||
|
|
||||||
// FindThread will return the thread with the corresponding thread ID.
|
// FindThread will return the thread with the corresponding thread ID.
|
||||||
func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
|
func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
|
||||||
t, ok := p.core.Threads[threadID]
|
t, ok := p.Threads[threadID]
|
||||||
return t, ok
|
return t, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registers represents the CPU registers.
|
|
||||||
type Registers struct {
|
|
||||||
*LinuxCoreRegisters
|
|
||||||
fpregs []proc.Register
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slice will return a slice containing all registers and their values.
|
|
||||||
func (r *Registers) Slice() []proc.Register {
|
|
||||||
var regs = []struct {
|
|
||||||
k string
|
|
||||||
v uint64
|
|
||||||
}{
|
|
||||||
{"Rip", r.Rip},
|
|
||||||
{"Rsp", r.Rsp},
|
|
||||||
{"Rax", r.Rax},
|
|
||||||
{"Rbx", r.Rbx},
|
|
||||||
{"Rcx", r.Rcx},
|
|
||||||
{"Rdx", r.Rdx},
|
|
||||||
{"Rdi", r.Rdi},
|
|
||||||
{"Rsi", r.Rsi},
|
|
||||||
{"Rbp", r.Rbp},
|
|
||||||
{"R8", r.R8},
|
|
||||||
{"R9", r.R9},
|
|
||||||
{"R10", r.R10},
|
|
||||||
{"R11", r.R11},
|
|
||||||
{"R12", r.R12},
|
|
||||||
{"R13", r.R13},
|
|
||||||
{"R14", r.R14},
|
|
||||||
{"R15", r.R15},
|
|
||||||
{"Orig_rax", r.Orig_rax},
|
|
||||||
{"Cs", r.Cs},
|
|
||||||
{"Eflags", r.Eflags},
|
|
||||||
{"Ss", r.Ss},
|
|
||||||
{"Fs_base", r.Fs_base},
|
|
||||||
{"Gs_base", r.Gs_base},
|
|
||||||
{"Ds", r.Ds},
|
|
||||||
{"Es", r.Es},
|
|
||||||
{"Fs", r.Fs},
|
|
||||||
{"Gs", r.Gs},
|
|
||||||
}
|
|
||||||
out := make([]proc.Register, 0, len(regs))
|
|
||||||
for _, reg := range regs {
|
|
||||||
if reg.k == "Eflags" {
|
|
||||||
out = proc.AppendEflagReg(out, reg.k, reg.v)
|
|
||||||
} else {
|
|
||||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = append(out, r.fpregs...)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy will return a copy of the registers that is guarenteed
|
|
||||||
// not to change.
|
|
||||||
func (r *Registers) Copy() proc.Registers {
|
|
||||||
return r
|
|
||||||
}
|
|
@ -7,45 +7,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
|
||||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Copied from golang.org/x/sys/unix.PtraceRegs since it's not available on
|
|
||||||
// all systems.
|
|
||||||
type LinuxCoreRegisters struct {
|
|
||||||
R15 uint64
|
|
||||||
R14 uint64
|
|
||||||
R13 uint64
|
|
||||||
R12 uint64
|
|
||||||
Rbp uint64
|
|
||||||
Rbx uint64
|
|
||||||
R11 uint64
|
|
||||||
R10 uint64
|
|
||||||
R9 uint64
|
|
||||||
R8 uint64
|
|
||||||
Rax uint64
|
|
||||||
Rcx uint64
|
|
||||||
Rdx uint64
|
|
||||||
Rsi uint64
|
|
||||||
Rdi uint64
|
|
||||||
Orig_rax uint64
|
|
||||||
Rip uint64
|
|
||||||
Cs uint64
|
|
||||||
Eflags uint64
|
|
||||||
Rsp uint64
|
|
||||||
Ss uint64
|
|
||||||
Fs_base uint64
|
|
||||||
Gs_base uint64
|
|
||||||
Ds uint64
|
|
||||||
Es uint64
|
|
||||||
Fs uint64
|
|
||||||
Gs uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from golang.org/x/sys/unix.Timeval since it's not available on all
|
// Copied from golang.org/x/sys/unix.Timeval since it's not available on all
|
||||||
// systems.
|
// systems.
|
||||||
type LinuxCoreTimeval struct {
|
type LinuxCoreTimeval struct {
|
||||||
@ -62,207 +29,21 @@ 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
|
||||||
|
|
||||||
// PC returns the value of RIP.
|
const elfErrorBadMagicNumber = "bad magic number"
|
||||||
func (r *LinuxCoreRegisters) PC() uint64 {
|
|
||||||
return r.Rip
|
|
||||||
}
|
|
||||||
|
|
||||||
// SP returns the value of RSP.
|
// readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
|
||||||
func (r *LinuxCoreRegisters) SP() uint64 {
|
|
||||||
return r.Rsp
|
|
||||||
}
|
|
||||||
|
|
||||||
// BP returns the value of RBP.
|
|
||||||
func (r *LinuxCoreRegisters) BP() uint64 {
|
|
||||||
return r.Rbp
|
|
||||||
}
|
|
||||||
|
|
||||||
// CX returns the value of RCX.
|
|
||||||
func (r *LinuxCoreRegisters) CX() uint64 {
|
|
||||||
return r.Rcx
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLS returns the location of the thread local storate,
|
|
||||||
// which will be the value of Fs_base.
|
|
||||||
func (r *LinuxCoreRegisters) TLS() uint64 {
|
|
||||||
return r.Fs_base
|
|
||||||
}
|
|
||||||
|
|
||||||
// GAddr returns the address of the G struct. Always returns 0
|
|
||||||
// and false for core files.
|
|
||||||
func (r *LinuxCoreRegisters) GAddr() (uint64, bool) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the value of the register requested via the
|
|
||||||
// register number, returning an error if that register
|
|
||||||
// could not be found.
|
|
||||||
func (r *LinuxCoreRegisters) Get(n int) (uint64, error) {
|
|
||||||
reg := x86asm.Reg(n)
|
|
||||||
const (
|
|
||||||
mask8 = 0x000f
|
|
||||||
mask16 = 0x00ff
|
|
||||||
mask32 = 0xffff
|
|
||||||
)
|
|
||||||
|
|
||||||
switch reg {
|
|
||||||
// 8-bit
|
|
||||||
case x86asm.AL:
|
|
||||||
return r.Rax & mask8, nil
|
|
||||||
case x86asm.CL:
|
|
||||||
return r.Rcx & mask8, nil
|
|
||||||
case x86asm.DL:
|
|
||||||
return r.Rdx & mask8, nil
|
|
||||||
case x86asm.BL:
|
|
||||||
return r.Rbx & mask8, nil
|
|
||||||
case x86asm.AH:
|
|
||||||
return (r.Rax >> 8) & mask8, nil
|
|
||||||
case x86asm.CH:
|
|
||||||
return (r.Rcx >> 8) & mask8, nil
|
|
||||||
case x86asm.DH:
|
|
||||||
return (r.Rdx >> 8) & mask8, nil
|
|
||||||
case x86asm.BH:
|
|
||||||
return (r.Rbx >> 8) & mask8, nil
|
|
||||||
case x86asm.SPB:
|
|
||||||
return r.Rsp & mask8, nil
|
|
||||||
case x86asm.BPB:
|
|
||||||
return r.Rbp & mask8, nil
|
|
||||||
case x86asm.SIB:
|
|
||||||
return r.Rsi & mask8, nil
|
|
||||||
case x86asm.DIB:
|
|
||||||
return r.Rdi & mask8, nil
|
|
||||||
case x86asm.R8B:
|
|
||||||
return r.R8 & mask8, nil
|
|
||||||
case x86asm.R9B:
|
|
||||||
return r.R9 & mask8, nil
|
|
||||||
case x86asm.R10B:
|
|
||||||
return r.R10 & mask8, nil
|
|
||||||
case x86asm.R11B:
|
|
||||||
return r.R11 & mask8, nil
|
|
||||||
case x86asm.R12B:
|
|
||||||
return r.R12 & mask8, nil
|
|
||||||
case x86asm.R13B:
|
|
||||||
return r.R13 & mask8, nil
|
|
||||||
case x86asm.R14B:
|
|
||||||
return r.R14 & mask8, nil
|
|
||||||
case x86asm.R15B:
|
|
||||||
return r.R15 & mask8, nil
|
|
||||||
|
|
||||||
// 16-bit
|
|
||||||
case x86asm.AX:
|
|
||||||
return r.Rax & mask16, nil
|
|
||||||
case x86asm.CX:
|
|
||||||
return r.Rcx & mask16, nil
|
|
||||||
case x86asm.DX:
|
|
||||||
return r.Rdx & mask16, nil
|
|
||||||
case x86asm.BX:
|
|
||||||
return r.Rbx & mask16, nil
|
|
||||||
case x86asm.SP:
|
|
||||||
return r.Rsp & mask16, nil
|
|
||||||
case x86asm.BP:
|
|
||||||
return r.Rbp & mask16, nil
|
|
||||||
case x86asm.SI:
|
|
||||||
return r.Rsi & mask16, nil
|
|
||||||
case x86asm.DI:
|
|
||||||
return r.Rdi & mask16, nil
|
|
||||||
case x86asm.R8W:
|
|
||||||
return r.R8 & mask16, nil
|
|
||||||
case x86asm.R9W:
|
|
||||||
return r.R9 & mask16, nil
|
|
||||||
case x86asm.R10W:
|
|
||||||
return r.R10 & mask16, nil
|
|
||||||
case x86asm.R11W:
|
|
||||||
return r.R11 & mask16, nil
|
|
||||||
case x86asm.R12W:
|
|
||||||
return r.R12 & mask16, nil
|
|
||||||
case x86asm.R13W:
|
|
||||||
return r.R13 & mask16, nil
|
|
||||||
case x86asm.R14W:
|
|
||||||
return r.R14 & mask16, nil
|
|
||||||
case x86asm.R15W:
|
|
||||||
return r.R15 & mask16, nil
|
|
||||||
|
|
||||||
// 32-bit
|
|
||||||
case x86asm.EAX:
|
|
||||||
return r.Rax & mask32, nil
|
|
||||||
case x86asm.ECX:
|
|
||||||
return r.Rcx & mask32, nil
|
|
||||||
case x86asm.EDX:
|
|
||||||
return r.Rdx & mask32, nil
|
|
||||||
case x86asm.EBX:
|
|
||||||
return r.Rbx & mask32, nil
|
|
||||||
case x86asm.ESP:
|
|
||||||
return r.Rsp & mask32, nil
|
|
||||||
case x86asm.EBP:
|
|
||||||
return r.Rbp & mask32, nil
|
|
||||||
case x86asm.ESI:
|
|
||||||
return r.Rsi & mask32, nil
|
|
||||||
case x86asm.EDI:
|
|
||||||
return r.Rdi & mask32, nil
|
|
||||||
case x86asm.R8L:
|
|
||||||
return r.R8 & mask32, nil
|
|
||||||
case x86asm.R9L:
|
|
||||||
return r.R9 & mask32, nil
|
|
||||||
case x86asm.R10L:
|
|
||||||
return r.R10 & mask32, nil
|
|
||||||
case x86asm.R11L:
|
|
||||||
return r.R11 & mask32, nil
|
|
||||||
case x86asm.R12L:
|
|
||||||
return r.R12 & mask32, nil
|
|
||||||
case x86asm.R13L:
|
|
||||||
return r.R13 & mask32, nil
|
|
||||||
case x86asm.R14L:
|
|
||||||
return r.R14 & mask32, nil
|
|
||||||
case x86asm.R15L:
|
|
||||||
return r.R15 & mask32, nil
|
|
||||||
|
|
||||||
// 64-bit
|
|
||||||
case x86asm.RAX:
|
|
||||||
return r.Rax, nil
|
|
||||||
case x86asm.RCX:
|
|
||||||
return r.Rcx, nil
|
|
||||||
case x86asm.RDX:
|
|
||||||
return r.Rdx, nil
|
|
||||||
case x86asm.RBX:
|
|
||||||
return r.Rbx, nil
|
|
||||||
case x86asm.RSP:
|
|
||||||
return r.Rsp, nil
|
|
||||||
case x86asm.RBP:
|
|
||||||
return r.Rbp, nil
|
|
||||||
case x86asm.RSI:
|
|
||||||
return r.Rsi, nil
|
|
||||||
case x86asm.RDI:
|
|
||||||
return r.Rdi, nil
|
|
||||||
case x86asm.R8:
|
|
||||||
return r.R8, nil
|
|
||||||
case x86asm.R9:
|
|
||||||
return r.R9, nil
|
|
||||||
case x86asm.R10:
|
|
||||||
return r.R10, nil
|
|
||||||
case x86asm.R11:
|
|
||||||
return r.R11, nil
|
|
||||||
case x86asm.R12:
|
|
||||||
return r.R12, nil
|
|
||||||
case x86asm.R13:
|
|
||||||
return r.R13, nil
|
|
||||||
case x86asm.R14:
|
|
||||||
return r.R14, nil
|
|
||||||
case x86asm.R15:
|
|
||||||
return r.R15, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, proc.ErrUnknownRegister
|
|
||||||
}
|
|
||||||
|
|
||||||
// readCore 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/,
|
||||||
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
|
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
|
||||||
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
|
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
|
||||||
// and, if absolutely desperate, readelf.c from the binutils source.
|
// and, if absolutely desperate, readelf.c from the binutils source.
|
||||||
func readCore(corePath, exePath string) (*Core, 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)
|
||||||
@ -288,37 +69,51 @@ func readCore(corePath, exePath string) (*Core, error) {
|
|||||||
memory := buildMemory(coreFile, exeELF, exe, notes)
|
memory := buildMemory(coreFile, exeELF, exe, notes)
|
||||||
entryPoint := findEntryPoint(notes)
|
entryPoint := findEntryPoint(notes)
|
||||||
|
|
||||||
core := &Core{
|
p := &Process{
|
||||||
MemoryReader: memory,
|
mem: memory,
|
||||||
Threads: map[int]*Thread{},
|
Threads: map[int]*Thread{},
|
||||||
entryPoint: entryPoint,
|
entryPoint: entryPoint,
|
||||||
|
bi: proc.NewBinaryInfo("linux", "amd64"),
|
||||||
|
breakpoints: proc.NewBreakpointMap(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastThread *Thread
|
var lastThread *linuxAMD64Thread
|
||||||
for _, note := range notes {
|
for _, note := range notes {
|
||||||
switch note.Type {
|
switch note.Type {
|
||||||
case elf.NT_PRSTATUS:
|
case elf.NT_PRSTATUS:
|
||||||
t := note.Desc.(*LinuxPrStatus)
|
t := note.Desc.(*LinuxPrStatus)
|
||||||
lastThread = &Thread{t, nil, nil, proc.CommonThread{}}
|
lastThread = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
|
||||||
core.Threads[int(t.Pid)] = lastThread
|
p.Threads[int(t.Pid)] = &Thread{lastThread, p, proc.CommonThread{}}
|
||||||
|
if p.currentThread == nil {
|
||||||
|
p.currentThread = p.Threads[int(t.Pid)]
|
||||||
|
}
|
||||||
case NT_X86_XSTATE:
|
case NT_X86_XSTATE:
|
||||||
if lastThread != nil {
|
if lastThread != nil {
|
||||||
lastThread.fpregs = note.Desc.(*proc.LinuxX86Xstate).Decode()
|
lastThread.regs.Fpregs = note.Desc.(*linutil.AMD64Xstate).Decode()
|
||||||
}
|
}
|
||||||
case elf.NT_PRPSINFO:
|
case elf.NT_PRPSINFO:
|
||||||
core.Pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
|
p.pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return core, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core represents a core file.
|
type linuxAMD64Thread struct {
|
||||||
type Core struct {
|
regs linutil.AMD64Registers
|
||||||
proc.MemoryReader
|
t *LinuxPrStatus
|
||||||
Threads map[int]*Thread
|
}
|
||||||
Pid int
|
|
||||||
|
|
||||||
entryPoint uint64
|
func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error) {
|
||||||
|
var r linutil.AMD64Registers
|
||||||
|
r.Regs = t.regs.Regs
|
||||||
|
if floatingPoint {
|
||||||
|
r.Fpregs = t.regs.Fpregs
|
||||||
|
}
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *linuxAMD64Thread) pid() int {
|
||||||
|
return int(t.t.Pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note is a note from the PT_NOTE prog.
|
// Note is a note from the PT_NOTE prog.
|
||||||
@ -415,8 +210,8 @@ func readNote(r io.ReadSeeker) (*Note, error) {
|
|||||||
}
|
}
|
||||||
note.Desc = data
|
note.Desc = data
|
||||||
case NT_X86_XSTATE:
|
case NT_X86_XSTATE:
|
||||||
var fpregs proc.LinuxX86Xstate
|
var fpregs linutil.AMD64Xstate
|
||||||
if err := proc.LinuxX86XstateRead(desc, true, &fpregs); err != nil {
|
if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
note.Desc = &fpregs
|
note.Desc = &fpregs
|
||||||
@ -516,7 +311,7 @@ type LinuxPrStatus struct {
|
|||||||
Sighold uint64
|
Sighold uint64
|
||||||
Pid, Ppid, Pgrp, Sid int32
|
Pid, Ppid, Pgrp, Sid int32
|
||||||
Utime, Stime, CUtime, CStime LinuxCoreTimeval
|
Utime, Stime, CUtime, CStime LinuxCoreTimeval
|
||||||
Reg LinuxCoreRegisters
|
Reg linutil.AMD64PtraceRegs
|
||||||
Fpvalid int32
|
Fpvalid int32
|
||||||
}
|
}
|
||||||
|
|
154
vendor/github.com/go-delve/delve/pkg/proc/core/minidump/fileflags_string.go
generated
vendored
Normal file
154
vendor/github.com/go-delve/delve/pkg/proc/core/minidump/fileflags_string.go
generated
vendored
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
vendor/github.com/go-delve/delve/pkg/proc/core/minidump/minidump.go
generated
vendored
Normal file
686
vendor/github.com/go-delve/delve/pkg/proc/core/minidump/minidump.go
generated
vendored
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/go-delve/delve/pkg/proc/winutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type minidumpBuf struct {
|
||||||
|
buf []byte
|
||||||
|
kind string
|
||||||
|
off int
|
||||||
|
err error
|
||||||
|
ctx string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf *minidumpBuf) u16() uint16 {
|
||||||
|
const stride = 2
|
||||||
|
if buf.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if buf.off+stride >= len(buf.buf) {
|
||||||
|
buf.err = fmt.Errorf("minidump %s truncated at offset %#x while %s", buf.kind, buf.off, buf.ctx)
|
||||||
|
}
|
||||||
|
r := binary.LittleEndian.Uint16(buf.buf[buf.off : buf.off+stride])
|
||||||
|
buf.off += stride
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf *minidumpBuf) u32() uint32 {
|
||||||
|
const stride = 4
|
||||||
|
if buf.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if buf.off+stride >= len(buf.buf) {
|
||||||
|
buf.err = fmt.Errorf("minidump %s truncated at offset %#x while %s", buf.kind, buf.off, buf.ctx)
|
||||||
|
}
|
||||||
|
r := binary.LittleEndian.Uint32(buf.buf[buf.off : buf.off+stride])
|
||||||
|
buf.off += stride
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf *minidumpBuf) u64() uint64 {
|
||||||
|
const stride = 8
|
||||||
|
if buf.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if buf.off+stride >= len(buf.buf) {
|
||||||
|
buf.err = fmt.Errorf("minidump %s truncated at offset %#x while %s", buf.kind, buf.off, buf.ctx)
|
||||||
|
}
|
||||||
|
r := binary.LittleEndian.Uint64(buf.buf[buf.off : buf.off+stride])
|
||||||
|
buf.off += stride
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamBuf(stream *Stream, buf *minidumpBuf, name string) *minidumpBuf {
|
||||||
|
return &minidumpBuf{
|
||||||
|
buf: buf.buf,
|
||||||
|
kind: "stream",
|
||||||
|
off: stream.Offset,
|
||||||
|
err: nil,
|
||||||
|
ctx: fmt.Sprintf("reading %s stream at %#x", name, stream.Offset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNotAMinidump is the error returned when the file being loaded is not a
|
||||||
|
// minidump file.
|
||||||
|
type ErrNotAMinidump struct {
|
||||||
|
what string
|
||||||
|
got uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrNotAMinidump) Error() string {
|
||||||
|
return fmt.Sprintf("not a minidump, invalid %s %#x", err.what, err.got)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
minidumpSignature = 0x504d444d // 'MDMP'
|
||||||
|
minidumpVersion = 0xa793
|
||||||
|
)
|
||||||
|
|
||||||
|
// Minidump represents a minidump file
|
||||||
|
type Minidump struct {
|
||||||
|
Timestamp uint32
|
||||||
|
Flags FileFlags
|
||||||
|
|
||||||
|
Streams []Stream
|
||||||
|
|
||||||
|
Threads []Thread
|
||||||
|
Modules []Module
|
||||||
|
|
||||||
|
Pid uint32
|
||||||
|
|
||||||
|
MemoryRanges []MemoryRange
|
||||||
|
MemoryInfo []MemoryInfo
|
||||||
|
|
||||||
|
streamNum uint32
|
||||||
|
streamOff uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream represents one (uninterpreted) stream in a minidump file.
|
||||||
|
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_directory
|
||||||
|
type Stream struct {
|
||||||
|
Type StreamType
|
||||||
|
Offset int
|
||||||
|
RawData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread represents an entry in the ThreadList stream.
|
||||||
|
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_thread
|
||||||
|
type Thread struct {
|
||||||
|
ID uint32
|
||||||
|
SuspendCount uint32
|
||||||
|
PriorityClass uint32
|
||||||
|
Priority uint32
|
||||||
|
TEB uint64
|
||||||
|
Context winutil.CONTEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module represents an entry in the ModuleList stream.
|
||||||
|
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_module
|
||||||
|
type Module struct {
|
||||||
|
BaseOfImage uint64
|
||||||
|
SizeOfImage uint32
|
||||||
|
Checksum uint32
|
||||||
|
TimeDateStamp uint32
|
||||||
|
Name string
|
||||||
|
VersionInfo VSFixedFileInfo
|
||||||
|
|
||||||
|
// CVRecord stores a CodeView record and is populated when a module's debug information resides in a PDB file. It identifies the PDB file.
|
||||||
|
CVRecord []byte
|
||||||
|
|
||||||
|
// MiscRecord is populated when a module's debug information resides in a DBG file. It identifies the DBG file. This field is effectively obsolete with modules built by recent toolchains.
|
||||||
|
MiscRecord []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// VSFixedFileInfo: Visual Studio Fixed File Info.
|
||||||
|
// See: https://docs.microsoft.com/en-us/windows/desktop/api/verrsrc/ns-verrsrc-tagvs_fixedfileinfo
|
||||||
|
type VSFixedFileInfo struct {
|
||||||
|
Signature uint32
|
||||||
|
StructVersion uint32
|
||||||
|
FileVersionHi uint32
|
||||||
|
FileVersionLo uint32
|
||||||
|
ProductVersionHi uint32
|
||||||
|
ProductVersionLo uint32
|
||||||
|
FileFlagsMask uint32
|
||||||
|
FileFlags uint32
|
||||||
|
FileOS uint32
|
||||||
|
FileType uint32
|
||||||
|
FileSubtype uint32
|
||||||
|
FileDateHi uint32
|
||||||
|
FileDateLo uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryRange represents a region of memory saved to the core file, it's constructed after either:
|
||||||
|
// 1. parsing an entry in the Memory64List stream.
|
||||||
|
// 2. parsing the stack field of an entry in the ThreadList stream.
|
||||||
|
type MemoryRange struct {
|
||||||
|
Addr uint64
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMemory reads len(buf) bytes of memory starting at addr into buf from this memory region.
|
||||||
|
func (m *MemoryRange) ReadMemory(buf []byte, addr uintptr) (int, error) {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if (uint64(addr) < m.Addr) || (uint64(addr)+uint64(len(buf)) > m.Addr+uint64(len(m.Data))) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
copy(buf, m.Data[uint64(addr)-m.Addr:])
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryInfo reprents an entry in the MemoryInfoList stream.
|
||||||
|
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory_info
|
||||||
|
type MemoryInfo struct {
|
||||||
|
Addr uint64
|
||||||
|
Size uint64
|
||||||
|
State MemoryState
|
||||||
|
Protection MemoryProtection
|
||||||
|
Type MemoryType
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate stringer -type FileFlags,StreamType,Arch,MemoryState,MemoryType,MemoryProtection
|
||||||
|
|
||||||
|
// MemoryState is the type of the State field of MINIDUMP_MEMORY_INFO
|
||||||
|
type MemoryState uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
MemoryStateCommit MemoryState = 0x1000
|
||||||
|
MemoryStateReserve MemoryState = 0x2000
|
||||||
|
MemoryStateFree MemoryState = 0x10000
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryType is the type of the Type field of MINIDUMP_MEMORY_INFO
|
||||||
|
type MemoryType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
MemoryTypePrivate MemoryType = 0x20000
|
||||||
|
MemoryTypeMapped MemoryType = 0x40000
|
||||||
|
MemoryTypeImage MemoryType = 0x1000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryProtection is the type of the Protection field of MINIDUMP_MEMORY_INFO
|
||||||
|
type MemoryProtection uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
MemoryProtectNoAccess MemoryProtection = 0x01 // PAGE_NOACCESS
|
||||||
|
MemoryProtectReadOnly MemoryProtection = 0x02 // PAGE_READONLY
|
||||||
|
MemoryProtectReadWrite MemoryProtection = 0x04 // PAGE_READWRITE
|
||||||
|
MemoryProtectWriteCopy MemoryProtection = 0x08 // PAGE_WRITECOPY
|
||||||
|
MemoryProtectExecute MemoryProtection = 0x10 // PAGE_EXECUTE
|
||||||
|
MemoryProtectExecuteRead MemoryProtection = 0x20 // PAGE_EXECUTE_READ
|
||||||
|
MemoryProtectExecuteReadWrite MemoryProtection = 0x40 // PAGE_EXECUTE_READWRITE
|
||||||
|
MemoryProtectExecuteWriteCopy MemoryProtection = 0x80 // PAGE_EXECUTE_WRITECOPY
|
||||||
|
// These options can be combined with the previous flags
|
||||||
|
MemoryProtectPageGuard MemoryProtection = 0x100 // PAGE_GUARD
|
||||||
|
MemoryProtectNoCache MemoryProtection = 0x200 // PAGE_NOCACHE
|
||||||
|
MemoryProtectWriteCombine MemoryProtection = 0x400 // PAGE_WRITECOMBINE
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileFlags is the type of the Flags field of MINIDUMP_HEADER
|
||||||
|
type FileFlags uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileNormal FileFlags = 0x00000000
|
||||||
|
FileWithDataSegs FileFlags = 0x00000001
|
||||||
|
FileWithFullMemory FileFlags = 0x00000002
|
||||||
|
FileWithHandleData FileFlags = 0x00000004
|
||||||
|
FileFilterMemory FileFlags = 0x00000008
|
||||||
|
FileScanMemory FileFlags = 0x00000010
|
||||||
|
FileWithUnloadedModules FileFlags = 0x00000020
|
||||||
|
FileWithIncorrectlyReferencedMemory FileFlags = 0x00000040
|
||||||
|
FileFilterModulePaths FileFlags = 0x00000080
|
||||||
|
FileWithProcessThreadData FileFlags = 0x00000100
|
||||||
|
FileWithPrivateReadWriteMemory FileFlags = 0x00000200
|
||||||
|
FileWithoutOptionalData FileFlags = 0x00000400
|
||||||
|
FileWithFullMemoryInfo FileFlags = 0x00000800
|
||||||
|
FileWithThreadInfo FileFlags = 0x00001000
|
||||||
|
FileWithCodeSegs FileFlags = 0x00002000
|
||||||
|
FileWithoutAuxilliarySegs FileFlags = 0x00004000
|
||||||
|
FileWithFullAuxilliaryState FileFlags = 0x00008000
|
||||||
|
FileWithPrivateCopyMemory FileFlags = 0x00010000
|
||||||
|
FileIgnoreInaccessibleMemory FileFlags = 0x00020000
|
||||||
|
FileWithTokenInformation FileFlags = 0x00040000
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamType is the type of the StreamType field of MINIDUMP_DIRECTORY
|
||||||
|
type StreamType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnusedStream StreamType = 0
|
||||||
|
ReservedStream0 StreamType = 1
|
||||||
|
ReservedStream1 StreamType = 2
|
||||||
|
ThreadListStream StreamType = 3
|
||||||
|
ModuleListStream StreamType = 4
|
||||||
|
MemoryListStream StreamType = 5
|
||||||
|
ExceptionStream StreamType = 6
|
||||||
|
SystemInfoStream StreamType = 7
|
||||||
|
ThreadExListStream StreamType = 8
|
||||||
|
Memory64ListStream StreamType = 9
|
||||||
|
CommentStreamA StreamType = 10
|
||||||
|
CommentStreamW StreamType = 11
|
||||||
|
HandleDataStream StreamType = 12
|
||||||
|
FunctionTableStream StreamType = 13
|
||||||
|
UnloadedModuleStream StreamType = 14
|
||||||
|
MiscInfoStream StreamType = 15
|
||||||
|
MemoryInfoListStream StreamType = 16
|
||||||
|
ThreadInfoListStream StreamType = 17
|
||||||
|
HandleOperationListStream StreamType = 18
|
||||||
|
TokenStream StreamType = 19
|
||||||
|
JavascriptDataStream StreamType = 20
|
||||||
|
SystemMemoryInfoStream StreamType = 21
|
||||||
|
ProcessVMCounterStream StreamType = 22
|
||||||
|
)
|
||||||
|
|
||||||
|
// Arch is the type of the ProcessorArchitecture field of MINIDUMP_SYSTEM_INFO.
|
||||||
|
type Arch uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
CpuArchitectureX86 Arch = 0
|
||||||
|
CpuArchitectureMips Arch = 1
|
||||||
|
CpuArchitectureAlpha Arch = 2
|
||||||
|
CpuArchitecturePPC Arch = 3
|
||||||
|
CpuArchitectureSHX Arch = 4 // Super-H
|
||||||
|
CpuArchitectureARM Arch = 5
|
||||||
|
CpuArchitectureIA64 Arch = 6
|
||||||
|
CpuArchitectureAlpha64 Arch = 7
|
||||||
|
CpuArchitectureMSIL Arch = 8 // Microsoft Intermediate Language
|
||||||
|
CpuArchitectureAMD64 Arch = 9
|
||||||
|
CpuArchitectureWoW64 Arch = 10
|
||||||
|
CpuArchitectureARM64 Arch = 12
|
||||||
|
CpuArchitectureUnknown Arch = 0xffff
|
||||||
|
)
|
||||||
|
|
||||||
|
// Open reads the minidump file at path and returns it as a Minidump structure.
|
||||||
|
func Open(path string, logfn func(fmt string, args ...interface{})) (*Minidump, error) {
|
||||||
|
rawbuf, err := ioutil.ReadFile(path) //TODO(aarzilli): mmap?
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &minidumpBuf{buf: rawbuf, kind: "file"}
|
||||||
|
|
||||||
|
var mdmp Minidump
|
||||||
|
|
||||||
|
readMinidumpHeader(&mdmp, buf)
|
||||||
|
if buf.err != nil {
|
||||||
|
return nil, buf.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("Minidump Header\n")
|
||||||
|
logfn("Num Streams: %d\n", mdmp.streamNum)
|
||||||
|
logfn("Streams offset: %#x\n", mdmp.streamOff)
|
||||||
|
logfn("File flags: %s\n", fileFlagsToString(mdmp.Flags))
|
||||||
|
logfn("Offset after header %#x\n", buf.off)
|
||||||
|
}
|
||||||
|
|
||||||
|
readDirectory(&mdmp, buf)
|
||||||
|
if buf.err != nil {
|
||||||
|
return nil, buf.err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range mdmp.Streams {
|
||||||
|
stream := &mdmp.Streams[i]
|
||||||
|
if stream.Type != SystemInfoStream {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sb := streamBuf(stream, buf, "system info")
|
||||||
|
if buf.err != nil {
|
||||||
|
return nil, buf.err
|
||||||
|
}
|
||||||
|
|
||||||
|
arch := Arch(sb.u16())
|
||||||
|
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("Found processor architecture %s\n", arch.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if arch != CpuArchitectureAMD64 {
|
||||||
|
return nil, fmt.Errorf("unsupported architecture %s", arch.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range mdmp.Streams {
|
||||||
|
stream := &mdmp.Streams[i]
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("Stream %d: type:%s off:%#x size:%#x\n", i, stream.Type, stream.Offset, len(stream.RawData))
|
||||||
|
}
|
||||||
|
switch stream.Type {
|
||||||
|
case ThreadListStream:
|
||||||
|
readThreadList(&mdmp, streamBuf(stream, buf, "thread list"))
|
||||||
|
if logfn != nil {
|
||||||
|
for i := range mdmp.Threads {
|
||||||
|
logfn("\tID:%#x TEB:%#x\n", mdmp.Threads[i].ID, mdmp.Threads[i].TEB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ModuleListStream:
|
||||||
|
readModuleList(&mdmp, streamBuf(stream, buf, "module list"))
|
||||||
|
if logfn != nil {
|
||||||
|
for i := range mdmp.Modules {
|
||||||
|
logfn("\tName:%q BaseOfImage:%#x SizeOfImage:%#x\n", mdmp.Modules[i].Name, mdmp.Modules[i].BaseOfImage, mdmp.Modules[i].SizeOfImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ExceptionStream:
|
||||||
|
//TODO(aarzilli): this stream contains the exception that made the
|
||||||
|
//process stop and caused the minidump to be taken. If we ever start
|
||||||
|
//caring about this we should parse this.
|
||||||
|
case Memory64ListStream:
|
||||||
|
readMemory64List(&mdmp, streamBuf(stream, buf, "memory64 list"), logfn)
|
||||||
|
case MemoryInfoListStream:
|
||||||
|
readMemoryInfoList(&mdmp, streamBuf(stream, buf, "memory info list"), logfn)
|
||||||
|
case MiscInfoStream:
|
||||||
|
readMiscInfo(&mdmp, streamBuf(stream, buf, "misc info"))
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("\tPid: %#x\n", mdmp.Pid)
|
||||||
|
}
|
||||||
|
case CommentStreamW:
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("\t%q\n", decodeUTF16(stream.RawData))
|
||||||
|
}
|
||||||
|
case CommentStreamA:
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("\t%s\n", string(stream.RawData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf.err != nil {
|
||||||
|
return nil, buf.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mdmp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeUTF16 converts a NUL-terminated UTF16LE string to (non NUL-terminated) UTF8.
|
||||||
|
func decodeUTF16(in []byte) string {
|
||||||
|
utf16encoded := []uint16{}
|
||||||
|
for i := 0; i+1 < len(in); i += 2 {
|
||||||
|
var ch uint16
|
||||||
|
ch = uint16(in[i]) + uint16(in[i+1])<<8
|
||||||
|
utf16encoded = append(utf16encoded, ch)
|
||||||
|
}
|
||||||
|
s := string(utf16.Decode(utf16encoded))
|
||||||
|
if len(s) > 0 && s[len(s)-1] == 0 {
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileFlagsToString(flags FileFlags) string {
|
||||||
|
out := []byte{}
|
||||||
|
for i, name := range _FileFlags_map {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if flags&i != 0 {
|
||||||
|
if len(out) > 0 {
|
||||||
|
out = append(out, '|')
|
||||||
|
}
|
||||||
|
out = append(out, name...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return flags.String()
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMinidumpHeader reads the minidump file header
|
||||||
|
func readMinidumpHeader(mdmp *Minidump, buf *minidumpBuf) {
|
||||||
|
buf.ctx = "reading minidump header"
|
||||||
|
|
||||||
|
if sig := buf.u32(); sig != minidumpSignature {
|
||||||
|
buf.err = ErrNotAMinidump{"signature", sig}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ver := buf.u16(); ver != minidumpVersion {
|
||||||
|
buf.err = ErrNotAMinidump{"version", uint32(ver)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.u16() // implementation specific version
|
||||||
|
mdmp.streamNum = buf.u32()
|
||||||
|
mdmp.streamOff = buf.u32()
|
||||||
|
buf.u32() // checksum, but it's always 0
|
||||||
|
mdmp.Timestamp = buf.u32()
|
||||||
|
mdmp.Flags = FileFlags(buf.u64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDirectory reads the list of streams (i.e. the minidum "directory")
|
||||||
|
func readDirectory(mdmp *Minidump, buf *minidumpBuf) {
|
||||||
|
buf.off = int(mdmp.streamOff)
|
||||||
|
|
||||||
|
mdmp.Streams = make([]Stream, mdmp.streamNum)
|
||||||
|
for i := range mdmp.Streams {
|
||||||
|
buf.ctx = fmt.Sprintf("reading stream directory entry %d", i)
|
||||||
|
stream := &mdmp.Streams[i]
|
||||||
|
stream.Type = StreamType(buf.u32())
|
||||||
|
stream.Offset, stream.RawData = readLocationDescriptor(buf)
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLocationDescriptor reads a location descriptor structure (a structure
|
||||||
|
// which describes a subregion of the file), and returns the destination
|
||||||
|
// offset and a slice into the minidump file's buffer.
|
||||||
|
func readLocationDescriptor(buf *minidumpBuf) (off int, rawData []byte) {
|
||||||
|
sz := buf.u32()
|
||||||
|
off = int(buf.u32())
|
||||||
|
if buf.err != nil {
|
||||||
|
return off, nil
|
||||||
|
}
|
||||||
|
end := off + int(sz)
|
||||||
|
if off >= len(buf.buf) || end > len(buf.buf) {
|
||||||
|
buf.err = fmt.Errorf("location starting at %#x of size %#x is past the end of file, while %s", off, sz, buf.ctx)
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
rawData = buf.buf[off:end]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readString(buf *minidumpBuf) string {
|
||||||
|
startOff := buf.off
|
||||||
|
sz := buf.u32()
|
||||||
|
if buf.err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
end := buf.off + int(sz)
|
||||||
|
if buf.off >= len(buf.buf) || end > len(buf.buf) {
|
||||||
|
buf.err = fmt.Errorf("string starting at %#x of size %#x is past the end of file, while %s", startOff, sz, buf.ctx)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return decodeUTF16(buf.buf[buf.off:end])
|
||||||
|
}
|
||||||
|
|
||||||
|
// readThreadList reads a thread list stream and adds the threads to the minidump.
|
||||||
|
func readThreadList(mdmp *Minidump, buf *minidumpBuf) {
|
||||||
|
threadNum := buf.u32()
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mdmp.Threads = make([]Thread, threadNum)
|
||||||
|
|
||||||
|
for i := range mdmp.Threads {
|
||||||
|
buf.ctx = fmt.Sprintf("reading thread list entry %d", i)
|
||||||
|
thread := &mdmp.Threads[i]
|
||||||
|
|
||||||
|
thread.ID = buf.u32()
|
||||||
|
thread.SuspendCount = buf.u32()
|
||||||
|
thread.PriorityClass = buf.u32()
|
||||||
|
thread.Priority = buf.u32()
|
||||||
|
thread.TEB = buf.u64()
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readMemoryDescriptor(mdmp, buf) // thread stack
|
||||||
|
_, rawThreadContext := readLocationDescriptor(buf) // thread context
|
||||||
|
thread.Context = *((*winutil.CONTEXT)(unsafe.Pointer(&rawThreadContext[0])))
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readModuleList reads a module list stream and adds the modules to the minidump.
|
||||||
|
func readModuleList(mdmp *Minidump, buf *minidumpBuf) {
|
||||||
|
moduleNum := buf.u32()
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mdmp.Modules = make([]Module, moduleNum)
|
||||||
|
|
||||||
|
for i := range mdmp.Modules {
|
||||||
|
buf.ctx = fmt.Sprintf("reading module list entry %d", i)
|
||||||
|
module := &mdmp.Modules[i]
|
||||||
|
|
||||||
|
module.BaseOfImage = buf.u64()
|
||||||
|
module.SizeOfImage = buf.u32()
|
||||||
|
module.Checksum = buf.u32()
|
||||||
|
module.TimeDateStamp = buf.u32()
|
||||||
|
nameOff := int(buf.u32())
|
||||||
|
|
||||||
|
versionInfoVec := make([]uint32, unsafe.Sizeof(VSFixedFileInfo{})/unsafe.Sizeof(uint32(0)))
|
||||||
|
for j := range versionInfoVec {
|
||||||
|
versionInfoVec[j] = buf.u32()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.VersionInfo = *(*VSFixedFileInfo)(unsafe.Pointer(&versionInfoVec[0]))
|
||||||
|
|
||||||
|
_, module.CVRecord = readLocationDescriptor(buf)
|
||||||
|
_, module.MiscRecord = readLocationDescriptor(buf)
|
||||||
|
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nameBuf := minidumpBuf{buf: buf.buf, kind: "file", off: nameOff, err: nil, ctx: buf.ctx}
|
||||||
|
module.Name = readString(&nameBuf)
|
||||||
|
if nameBuf.err != nil {
|
||||||
|
buf.err = nameBuf.err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMemory64List reads a _MINIDUMP_MEMORY64_LIST structure, containing
|
||||||
|
// the description of the process memory.
|
||||||
|
// See: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory64_list
|
||||||
|
// And: https://docs.microsoft.com/en-us/windows/desktop/api/minidumpapiset/ns-minidumpapiset-_minidump_memory_descriptor
|
||||||
|
func readMemory64List(mdmp *Minidump, buf *minidumpBuf, logfn func(fmt string, args ...interface{})) {
|
||||||
|
rangesNum := buf.u64()
|
||||||
|
baseOff := int(buf.u64())
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < rangesNum; i++ {
|
||||||
|
addr := buf.u64()
|
||||||
|
sz := buf.u64()
|
||||||
|
|
||||||
|
end := baseOff + int(sz)
|
||||||
|
if baseOff >= len(buf.buf) || end > len(buf.buf) {
|
||||||
|
buf.err = fmt.Errorf("memory range at %#x of size %#x is past the end of file, while %s", baseOff, sz, buf.ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mdmp.addMemory(addr, buf.buf[baseOff:end])
|
||||||
|
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("\tMemory %d addr:%#x size:%#x FileOffset:%#x\n", i, addr, sz, baseOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseOff = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMemoryInfoList(mdmp *Minidump, buf *minidumpBuf, logfn func(fmt string, args ...interface{})) {
|
||||||
|
startOff := buf.off
|
||||||
|
sizeOfHeader := int(buf.u32())
|
||||||
|
sizeOfEntry := int(buf.u32())
|
||||||
|
numEntries := buf.u64()
|
||||||
|
|
||||||
|
buf.off = startOff + sizeOfHeader
|
||||||
|
|
||||||
|
mdmp.MemoryInfo = make([]MemoryInfo, numEntries)
|
||||||
|
|
||||||
|
for i := range mdmp.MemoryInfo {
|
||||||
|
memInfo := &mdmp.MemoryInfo[i]
|
||||||
|
startOff := buf.off
|
||||||
|
|
||||||
|
memInfo.Addr = buf.u64()
|
||||||
|
buf.u64() // allocation_base
|
||||||
|
|
||||||
|
buf.u32() // allocation_protection
|
||||||
|
buf.u32() // alignment
|
||||||
|
|
||||||
|
memInfo.Size = buf.u64()
|
||||||
|
|
||||||
|
memInfo.State = MemoryState(buf.u32())
|
||||||
|
memInfo.Protection = MemoryProtection(buf.u32())
|
||||||
|
memInfo.Type = MemoryType(buf.u32())
|
||||||
|
|
||||||
|
if logfn != nil {
|
||||||
|
logfn("\tMemoryInfo %d Addr:%#x Size:%#x %s %s %s\n", i, memInfo.Addr, memInfo.Size, memInfo.State, memInfo.Protection, memInfo.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.off = startOff + sizeOfEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMiscInfo reads the process_id from a MiscInfo stream.
|
||||||
|
func readMiscInfo(mdmp *Minidump, buf *minidumpBuf) {
|
||||||
|
buf.u32() // size of info
|
||||||
|
buf.u32() // flags1
|
||||||
|
|
||||||
|
mdmp.Pid = buf.u32() // process_id
|
||||||
|
// there are more fields here, but we don't care about them
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMemoryDescriptor reads a memory descriptor struct and adds it to the memory map of the minidump.
|
||||||
|
func readMemoryDescriptor(mdmp *Minidump, buf *minidumpBuf) {
|
||||||
|
addr := buf.u64()
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, rawData := readLocationDescriptor(buf)
|
||||||
|
if buf.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mdmp.addMemory(addr, rawData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdmp *Minidump) addMemory(addr uint64, data []byte) {
|
||||||
|
mdmp.MemoryRanges = append(mdmp.MemoryRanges, MemoryRange{addr, data})
|
||||||
|
}
|
59
vendor/github.com/go-delve/delve/pkg/proc/core/windows_amd64_minidump.go
generated
vendored
Normal file
59
vendor/github.com/go-delve/delve/pkg/proc/core/windows_amd64_minidump.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/core/minidump"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/winutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readAMD64Minidump(minidumpPath, exePath string) (*Process, error) {
|
||||||
|
var logfn func(string, ...interface{})
|
||||||
|
if logflags.Minidump() {
|
||||||
|
logfn = logflags.MinidumpLogger().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
|
||||||
|
}
|
@ -65,6 +65,14 @@ func (inst *AsmInstruction) IsCall() bool {
|
|||||||
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
|
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRet returns true if the instruction is a RET or LRET instruction.
|
||||||
|
func (inst *AsmInstruction) IsRet() bool {
|
||||||
|
if inst.Inst == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return inst.Inst.Op == x86asm.RET || inst.Inst.Op == x86asm.LRET
|
||||||
|
}
|
||||||
|
|
||||||
func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
|
func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
|
||||||
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
|
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
|
||||||
return nil
|
return nil
|
||||||
@ -110,7 +118,7 @@ func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem M
|
|||||||
|
|
||||||
file, line, fn := bininfo.PCToLine(pc)
|
file, line, fn := bininfo.PCToLine(pc)
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
return nil
|
return &Location{PC: pc}
|
||||||
}
|
}
|
||||||
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
||||||
}
|
}
|
@ -14,9 +14,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
"github.com/derekparker/delve/pkg/goversion"
|
"github.com/go-delve/delve/pkg/goversion"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
|
var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
|
||||||
@ -114,15 +114,25 @@ func (scope *EvalScope) evalToplevelTypeCast(t ast.Expr, cfg LoadConfig) (*Varia
|
|||||||
|
|
||||||
case "string":
|
case "string":
|
||||||
switch argv.Kind {
|
switch argv.Kind {
|
||||||
|
case reflect.String:
|
||||||
|
s := constant.StringVal(argv.Value)
|
||||||
|
v.Value = constant.MakeString(s)
|
||||||
|
v.Len = int64(len(s))
|
||||||
|
return v, nil
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
|
||||||
b, _ := constant.Int64Val(argv.Value)
|
b, _ := constant.Int64Val(argv.Value)
|
||||||
s := string(b)
|
s := string(b)
|
||||||
v.Value = constant.MakeString(s)
|
v.Value = constant.MakeString(s)
|
||||||
v.Len = int64(len(s))
|
v.Len = int64(len(s))
|
||||||
return v, nil
|
return v, nil
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
case reflect.Slice:
|
var elem godwarf.Type
|
||||||
switch elemType := argv.RealType.(*godwarf.SliceType).ElemType.(type) {
|
if argv.Kind == reflect.Slice {
|
||||||
|
elem = argv.RealType.(*godwarf.SliceType).ElemType
|
||||||
|
} else {
|
||||||
|
elem = argv.RealType.(*godwarf.ArrayType).Type
|
||||||
|
}
|
||||||
|
switch elemType := elem.(type) {
|
||||||
case *godwarf.UintType:
|
case *godwarf.UintType:
|
||||||
if elemType.Name != "uint8" && elemType.Name != "byte" {
|
if elemType.Name != "uint8" && elemType.Name != "byte" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -1198,7 +1208,7 @@ func (err *typeConvErr) Error() string {
|
|||||||
|
|
||||||
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
|
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
|
||||||
if v.DwarfType != nil {
|
if v.DwarfType != nil {
|
||||||
if typ != nil && typ.String() != v.RealType.String() {
|
if typ == nil || !sameType(typ, v.RealType) {
|
||||||
return &typeConvErr{v.DwarfType, typ}
|
return &typeConvErr{v.DwarfType, typ}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -1255,6 +1265,34 @@ func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sameType(t1, t2 godwarf.Type) bool {
|
||||||
|
// Because of a bug in the go linker a type that refers to another type
|
||||||
|
// (for example a pointer type) will usually use the typedef but rarely use
|
||||||
|
// the non-typedef entry directly.
|
||||||
|
// For types that we read directly from go this is fine because it's
|
||||||
|
// consistent, however we also synthesize some types ourselves
|
||||||
|
// (specifically pointers and slices) and we always use a reference through
|
||||||
|
// a typedef.
|
||||||
|
t1 = resolveTypedef(t1)
|
||||||
|
t2 = resolveTypedef(t2)
|
||||||
|
|
||||||
|
if tt1, isptr1 := t1.(*godwarf.PtrType); isptr1 {
|
||||||
|
tt2, isptr2 := t2.(*godwarf.PtrType)
|
||||||
|
if !isptr2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sameType(tt1.Type, tt2.Type)
|
||||||
|
}
|
||||||
|
if tt1, isslice1 := t1.(*godwarf.SliceType); isslice1 {
|
||||||
|
tt2, isslice2 := t2.(*godwarf.SliceType)
|
||||||
|
if !isslice2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sameType(tt1.ElemType, tt2.ElemType)
|
||||||
|
}
|
||||||
|
return t1.String() == t2.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
||||||
if idx < 0 || int64(idx) >= v.Len {
|
if idx < 0 || int64(idx) >= v.Len {
|
||||||
return nil, fmt.Errorf("index out of bounds")
|
return nil, fmt.Errorf("index out of bounds")
|
@ -11,11 +11,10 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
"github.com/derekparker/delve/pkg/logflags"
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,7 +80,7 @@ type functionCallState struct {
|
|||||||
// CallFunction starts a debugger injected function call on the current thread of p.
|
// CallFunction starts a debugger injected function call on the current thread of p.
|
||||||
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
|
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
|
||||||
// description of the protocol.
|
// description of the protocol.
|
||||||
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
|
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig, checkEscape bool) error {
|
||||||
bi := p.BinInfo()
|
bi := p.BinInfo()
|
||||||
if !p.Common().fncallEnabled {
|
if !p.Common().fncallEnabled {
|
||||||
return errFuncCallUnsupportedBackend
|
return errFuncCallUnsupportedBackend
|
||||||
@ -126,7 +125,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
argmem, err := funcCallArgFrame(fn, argvars, g, bi)
|
argmem, err := funcCallArgFrame(fn, argvars, g, bi, checkEscape)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -153,10 +152,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fncallLog(fmtstr string, args ...interface{}) {
|
func fncallLog(fmtstr string, args ...interface{}) {
|
||||||
if !logflags.FnCall() {
|
logflags.FnCallLogger().Infof(fmtstr, args...)
|
||||||
return
|
|
||||||
}
|
|
||||||
logrus.WithFields(logrus.Fields{"layer": "proc", "kind": "fncall"}).Infof(fmtstr, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writePointer writes val as an architecture pointer at addr in mem.
|
// writePointer writes val as an architecture pointer at addr in mem.
|
||||||
@ -218,7 +214,7 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, closureAddr uint64,
|
|||||||
if fnvar.Kind != reflect.Func {
|
if fnvar.Kind != reflect.Func {
|
||||||
return nil, 0, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun))
|
return nil, 0, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun))
|
||||||
}
|
}
|
||||||
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0})
|
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0, 0})
|
||||||
if fnvar.Unreadable != nil {
|
if fnvar.Unreadable != nil {
|
||||||
return nil, 0, nil, fnvar.Unreadable
|
return nil, 0, nil, fnvar.Unreadable
|
||||||
}
|
}
|
||||||
@ -259,7 +255,7 @@ type funcCallArg struct {
|
|||||||
|
|
||||||
// funcCallArgFrame checks type and pointer escaping for the arguments and
|
// funcCallArgFrame checks type and pointer escaping for the arguments and
|
||||||
// returns the argument frame.
|
// returns the argument frame.
|
||||||
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo) (argmem []byte, err error) {
|
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo, checkEscape bool) (argmem []byte, err error) {
|
||||||
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
|
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -278,9 +274,11 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
|
|||||||
formalArg := &formalArgs[i]
|
formalArg := &formalArgs[i]
|
||||||
actualArg := actualArgs[i]
|
actualArg := actualArgs[i]
|
||||||
|
|
||||||
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
|
if checkEscape {
|
||||||
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
|
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
|
||||||
return nil, fmt.Errorf("can not pass %s to %s: %v", actualArg.Name, formalArg.name, err)
|
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fn.Name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
|
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
|
||||||
@ -343,7 +341,13 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
|
|||||||
func escapeCheck(v *Variable, name string, g *G) error {
|
func escapeCheck(v *Variable, name string, g *G) error {
|
||||||
switch v.Kind {
|
switch v.Kind {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
w := v.maybeDereference()
|
var w *Variable
|
||||||
|
if len(v.Children) == 1 {
|
||||||
|
// this branch is here to support pointers constructed with typecasts from ints or the '&' operator
|
||||||
|
w = &v.Children[0]
|
||||||
|
} else {
|
||||||
|
w = v.maybeDereference()
|
||||||
|
}
|
||||||
return escapeCheckPointer(w.Addr, name, g)
|
return escapeCheckPointer(w.Addr, name, g)
|
||||||
case reflect.Chan, reflect.String, reflect.Slice:
|
case reflect.Chan, reflect.String, reflect.Slice:
|
||||||
return escapeCheckPointer(v.Base, name, g)
|
return escapeCheckPointer(v.Base, name, g)
|
@ -68,23 +68,20 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/logflags"
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
"github.com/mattn/go-isatty"
|
isatty "github.com/mattn/go-isatty"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -130,7 +127,7 @@ type Process struct {
|
|||||||
common proc.CommonProcess
|
common proc.CommonProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread is a thread.
|
// Thread represents an operating system thread.
|
||||||
type Thread struct {
|
type Thread struct {
|
||||||
ID int
|
ID int
|
||||||
strID string
|
strID string
|
||||||
@ -142,8 +139,7 @@ type Thread struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ErrBackendUnavailable is returned when the stub program can not be found.
|
// ErrBackendUnavailable is returned when the stub program can not be found.
|
||||||
type ErrBackendUnavailable struct {
|
type ErrBackendUnavailable struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (err *ErrBackendUnavailable) Error() string {
|
func (err *ErrBackendUnavailable) Error() string {
|
||||||
return "backend unavailable"
|
return "backend unavailable"
|
||||||
@ -172,11 +168,7 @@ type gdbRegister struct {
|
|||||||
// Detach.
|
// Detach.
|
||||||
// Use Listen, Dial or Connect to complete connection.
|
// Use Listen, Dial or Connect to complete connection.
|
||||||
func New(process *os.Process) *Process {
|
func New(process *os.Process) *Process {
|
||||||
logger := logrus.New().WithFields(logrus.Fields{"layer": "gdbconn"})
|
logger := logflags.GdbWireLogger()
|
||||||
logger.Logger.Level = logrus.DebugLevel
|
|
||||||
if !logflags.GdbWire() {
|
|
||||||
logger.Logger.Out = ioutil.Discard
|
|
||||||
}
|
|
||||||
p := &Process{
|
p := &Process{
|
||||||
conn: gdbConn{
|
conn: gdbConn{
|
||||||
maxTransmitAttempts: maxTransmitAttempts,
|
maxTransmitAttempts: maxTransmitAttempts,
|
||||||
@ -205,7 +197,7 @@ func New(process *os.Process) *Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Listen waits for a connection from the stub.
|
// Listen waits for a connection from the stub.
|
||||||
func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string) error {
|
||||||
acceptChan := make(chan net.Conn)
|
acceptChan := make(chan net.Conn)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -219,7 +211,7 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
|||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New("could not connect")
|
return errors.New("could not connect")
|
||||||
}
|
}
|
||||||
return p.Connect(conn, path, pid)
|
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||||
case status := <-p.waitChan:
|
case status := <-p.waitChan:
|
||||||
listener.Close()
|
listener.Close()
|
||||||
return fmt.Errorf("stub exited while waiting for connection: %v", status)
|
return fmt.Errorf("stub exited while waiting for connection: %v", status)
|
||||||
@ -227,11 +219,11 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dial attempts to connect to the stub.
|
// Dial attempts to connect to the stub.
|
||||||
func (p *Process) Dial(addr string, path string, pid int) error {
|
func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string) error {
|
||||||
for {
|
for {
|
||||||
conn, err := net.Dial("tcp", addr)
|
conn, err := net.Dial("tcp", addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return p.Connect(conn, path, pid)
|
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case status := <-p.waitChan:
|
case status := <-p.waitChan:
|
||||||
@ -248,9 +240,8 @@ func (p *Process) Dial(addr string, path string, pid int) error {
|
|||||||
// program and the PID of the target process, both are optional, however
|
// program and the PID of the target process, both are optional, however
|
||||||
// some stubs do not provide ways to determine path and pid automatically
|
// some stubs do not provide ways to determine path and pid automatically
|
||||||
// and Connect will be unable to function without knowing them.
|
// and Connect will be unable to function without knowing them.
|
||||||
func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
|
||||||
p.conn.conn = conn
|
p.conn.conn = conn
|
||||||
|
|
||||||
p.conn.pid = pid
|
p.conn.pid = pid
|
||||||
err := p.conn.handshake()
|
err := p.conn.handshake()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -270,55 +261,7 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
if err := p.initialize(path, debugInfoDirs); err != nil {
|
||||||
// If we are attaching to a running process and the user didn't specify
|
|
||||||
// the executable file manually we must ask the stub for it.
|
|
||||||
// We support both qXfer:exec-file:read:: (the gdb way) and calling
|
|
||||||
// qProcessInfo (the lldb way).
|
|
||||||
// Unfortunately debugserver on macOS supports neither.
|
|
||||||
path, err = p.conn.readExecFile()
|
|
||||||
if err != nil {
|
|
||||||
if isProtocolErrorUnsupported(err) {
|
|
||||||
_, path, err = p.loadProcessInfo(pid)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conn.Close()
|
|
||||||
return fmt.Errorf("could not determine executable path: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if path == "" {
|
|
||||||
// try using jGetLoadedDynamicLibrariesInfos which is the only way to do
|
|
||||||
// this supported on debugserver (but only on macOS >= 12.10)
|
|
||||||
images, _ := p.conn.getLoadedDynamicLibraries()
|
|
||||||
for _, image := range images {
|
|
||||||
if image.MachHeader.FileType == macho.TypeExec {
|
|
||||||
path = image.Pathname
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var entryPoint uint64
|
|
||||||
if auxv, err := p.conn.readAuxv(); err == nil {
|
|
||||||
// If we can't read the auxiliary vector it just means it's not supported
|
|
||||||
// by the OS or by the stub. If we are debugging a PIE and the entry point
|
|
||||||
// is needed proc.LoadBinaryInfo will complain about it.
|
|
||||||
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
err = p.bi.LoadBinaryInfo(path, entryPoint, &wg)
|
|
||||||
wg.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = p.bi.LoadError()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,36 +278,6 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
|||||||
p.loadGInstrAddr = addr
|
p.loadGInstrAddr = addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.updateThreadList(&threadUpdater{p: p})
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
p.bi.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.conn.pid <= 0 {
|
|
||||||
p.conn.pid, _, err = p.loadProcessInfo(0)
|
|
||||||
if err != nil && !isProtocolErrorUnsupported(err) {
|
|
||||||
conn.Close()
|
|
||||||
p.bi.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
|
||||||
|
|
||||||
proc.CreateUnrecoveredPanicBreakpoint(p, p.writeBreakpoint, &p.breakpoints)
|
|
||||||
|
|
||||||
panicpc, err := proc.FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
|
||||||
if err == nil {
|
|
||||||
bp, err := p.breakpoints.SetWithID(-1, panicpc, p.writeBreakpoint)
|
|
||||||
if err == nil {
|
|
||||||
bp.Name = proc.UnrecoveredPanic
|
|
||||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +318,7 @@ func getLdEnvVars() []string {
|
|||||||
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
||||||
// it to launch the specified target program with the specified arguments
|
// it to launch the specified target program with the specified arguments
|
||||||
// (cmd) on the specified directory wd.
|
// (cmd) on the specified directory wd.
|
||||||
func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
return nil, ErrUnsupportedOS
|
return nil, ErrUnsupportedOS
|
||||||
@ -482,9 +395,9 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
|||||||
p.conn.isDebugserver = isDebugserver
|
p.conn.isDebugserver = isDebugserver
|
||||||
|
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
err = p.Listen(listener, cmd[0], 0)
|
err = p.Listen(listener, cmd[0], 0, debugInfoDirs)
|
||||||
} else {
|
} else {
|
||||||
err = p.Dial(port, cmd[0], 0)
|
err = p.Dial(port, cmd[0], 0, debugInfoDirs)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -497,13 +410,13 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
|||||||
// Path is path to the target's executable, path only needs to be specified
|
// Path is path to the target's executable, path only needs to be specified
|
||||||
// for some stubs that do not provide an automated way of determining it
|
// for some stubs that do not provide an automated way of determining it
|
||||||
// (for example debugserver).
|
// (for example debugserver).
|
||||||
func LLDBAttach(pid int, path string) (*Process, error) {
|
func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return nil, ErrUnsupportedOS
|
return nil, ErrUnsupportedOS
|
||||||
}
|
}
|
||||||
|
|
||||||
isDebugserver := false
|
isDebugserver := false
|
||||||
var proc *exec.Cmd
|
var process *exec.Cmd
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
var port string
|
var port string
|
||||||
if _, err := os.Stat(debugserverExecutable); err == nil {
|
if _, err := os.Stat(debugserverExecutable); err == nil {
|
||||||
@ -512,32 +425,31 @@ func LLDBAttach(pid int, path string) (*Process, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
proc = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid))
|
process = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid))
|
||||||
} else {
|
} else {
|
||||||
if _, err := exec.LookPath("lldb-server"); err != nil {
|
if _, err := exec.LookPath("lldb-server"); err != nil {
|
||||||
return nil, &ErrBackendUnavailable{}
|
return nil, &ErrBackendUnavailable{}
|
||||||
}
|
}
|
||||||
port = unusedPort()
|
port = unusedPort()
|
||||||
proc = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port)
|
process = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port)
|
||||||
}
|
}
|
||||||
|
|
||||||
proc.Stdout = os.Stdout
|
process.Stdout = os.Stdout
|
||||||
proc.Stderr = os.Stderr
|
process.Stderr = os.Stderr
|
||||||
|
process.SysProcAttr = sysProcAttr(false)
|
||||||
|
|
||||||
proc.SysProcAttr = sysProcAttr(false)
|
err := process.Start()
|
||||||
|
|
||||||
err := proc.Start()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p := New(proc.Process)
|
p := New(process.Process)
|
||||||
p.conn.isDebugserver = isDebugserver
|
p.conn.isDebugserver = isDebugserver
|
||||||
|
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
err = p.Listen(listener, path, pid)
|
err = p.Listen(listener, path, pid, debugInfoDirs)
|
||||||
} else {
|
} else {
|
||||||
err = p.Dial(port, path, pid)
|
err = p.Dial(port, path, pid, debugInfoDirs)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -545,10 +457,80 @@ func LLDBAttach(pid int, path string) (*Process, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadProcessInfo uses qProcessInfo to load the inferior's PID and
|
// EntryPoint will return the process entry point address, useful for
|
||||||
|
// debugging PIEs.
|
||||||
|
func (p *Process) EntryPoint() (uint64, error) {
|
||||||
|
var entryPoint uint64
|
||||||
|
if auxv, err := p.conn.readAuxv(); err == nil {
|
||||||
|
// If we can't read the auxiliary vector it just means it's not supported
|
||||||
|
// by the OS or by the stub. If we are debugging a PIE and the entry point
|
||||||
|
// is needed proc.LoadBinaryInfo will complain about it.
|
||||||
|
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
|
||||||
|
}
|
||||||
|
return entryPoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize uses qProcessInfo to load the inferior's PID and
|
||||||
// executable path. This command is not supported by all stubs and not all
|
// executable path. This command is not supported by all stubs and not all
|
||||||
// stubs will report both the PID and executable path.
|
// stubs will report both the PID and executable path.
|
||||||
func (p *Process) loadProcessInfo(pid int) (int, string, error) {
|
func (p *Process) initialize(path string, debugInfoDirs []string) error {
|
||||||
|
var err error
|
||||||
|
if path == "" {
|
||||||
|
// If we are attaching to a running process and the user didn't specify
|
||||||
|
// the executable file manually we must ask the stub for it.
|
||||||
|
// We support both qXfer:exec-file:read:: (the gdb way) and calling
|
||||||
|
// qProcessInfo (the lldb way).
|
||||||
|
// Unfortunately debugserver on macOS supports neither.
|
||||||
|
path, err = p.conn.readExecFile()
|
||||||
|
if err != nil {
|
||||||
|
if isProtocolErrorUnsupported(err) {
|
||||||
|
_, path, err = queryProcessInfo(p, p.Pid())
|
||||||
|
if err != nil {
|
||||||
|
p.conn.conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.conn.conn.Close()
|
||||||
|
return fmt.Errorf("could not determine executable path: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
// try using jGetLoadedDynamicLibrariesInfos which is the only way to do
|
||||||
|
// this supported on debugserver (but only on macOS >= 12.10)
|
||||||
|
images, _ := p.conn.getLoadedDynamicLibraries()
|
||||||
|
for _, image := range images {
|
||||||
|
if image.MachHeader.FileType == macho.TypeExec {
|
||||||
|
path = image.Pathname
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.updateThreadList(&threadUpdater{p: p})
|
||||||
|
if err != nil {
|
||||||
|
p.conn.conn.Close()
|
||||||
|
p.bi.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.conn.pid <= 0 {
|
||||||
|
p.conn.pid, _, err = queryProcessInfo(p, 0)
|
||||||
|
if err != nil && !isProtocolErrorUnsupported(err) {
|
||||||
|
p.conn.conn.Close()
|
||||||
|
p.bi.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil {
|
||||||
|
p.conn.conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryProcessInfo(p *Process, pid int) (int, string, error) {
|
||||||
pi, err := p.conn.queryProcessInfo(pid)
|
pi, err := p.conn.queryProcessInfo(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
@ -659,7 +641,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
|
|||||||
|
|
||||||
// resume all threads
|
// resume all threads
|
||||||
var threadID string
|
var threadID string
|
||||||
var sig uint8 = 0
|
var sig uint8
|
||||||
var tu = threadUpdater{p: p}
|
var tu = threadUpdater{p: p}
|
||||||
var err error
|
var err error
|
||||||
continueLoop:
|
continueLoop:
|
||||||
@ -697,6 +679,14 @@ continueLoop:
|
|||||||
// process so we must stop here.
|
// process so we must stop here.
|
||||||
case 0x91, 0x92, 0x93, 0x94, 0x95, 0x96: /* TARGET_EXC_BAD_ACCESS */
|
case 0x91, 0x92, 0x93, 0x94, 0x95, 0x96: /* TARGET_EXC_BAD_ACCESS */
|
||||||
break continueLoop
|
break continueLoop
|
||||||
|
|
||||||
|
// Signal 0 is returned by rr when it reaches the start of the process
|
||||||
|
// in backward continue mode.
|
||||||
|
case 0:
|
||||||
|
if p.conn.direction == proc.Backward {
|
||||||
|
break continueLoop
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// any other signal is always propagated to inferior
|
// any other signal is always propagated to inferior
|
||||||
}
|
}
|
||||||
@ -706,13 +696,19 @@ continueLoop:
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.BinInfo().GOOS == "linux" {
|
||||||
|
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := p.setCurrentBreakpoints(); err != nil {
|
if err := p.setCurrentBreakpoints(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, thread := range p.threads {
|
for _, thread := range p.threads {
|
||||||
if thread.strID == threadID {
|
if thread.strID == threadID {
|
||||||
var err error = nil
|
var err error
|
||||||
switch sig {
|
switch sig {
|
||||||
case 0x91:
|
case 0x91:
|
||||||
err = errors.New("bad access")
|
err = errors.New("bad access")
|
||||||
@ -734,6 +730,13 @@ continueLoop:
|
|||||||
return nil, fmt.Errorf("could not find thread %s", threadID)
|
return nil, fmt.Errorf("could not find thread %s", threadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||||
|
// the default for any command executed, the goroutine being actively
|
||||||
|
// followed.
|
||||||
|
func (p *Process) SetSelectedGoroutine(g *proc.G) {
|
||||||
|
p.selectedGoroutine = g
|
||||||
|
}
|
||||||
|
|
||||||
// StepInstruction will step exactly one CPU instruction.
|
// StepInstruction will step exactly one CPU instruction.
|
||||||
func (p *Process) StepInstruction() error {
|
func (p *Process) StepInstruction() error {
|
||||||
thread := p.currentThread
|
thread := p.currentThread
|
||||||
@ -1025,9 +1028,6 @@ func (p *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
|||||||
|
|
||||||
func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
|
func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
|
||||||
f, l, fn := p.bi.PCToLine(uint64(addr))
|
f, l, fn := p.bi.PCToLine(uint64(addr))
|
||||||
if fn == nil {
|
|
||||||
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.conn.setBreakpoint(addr); err != nil {
|
if err := p.conn.setBreakpoint(addr); err != nil {
|
||||||
return "", 0, nil, nil, err
|
return "", 0, nil, nil, err
|
||||||
@ -1310,7 +1310,6 @@ func (t *Thread) Blocked() bool {
|
|||||||
default:
|
default:
|
||||||
return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall")
|
return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall")
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadGInstr returns the correct MOV instruction for the current
|
// loadGInstr returns the correct MOV instruction for the current
|
||||||
@ -1569,25 +1568,25 @@ func (t *Thread) clearBreakpointState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrentBreakpoint will find and set the threads current breakpoint.
|
// SetCurrentBreakpoint will find and set the threads current breakpoint.
|
||||||
func (thread *Thread) SetCurrentBreakpoint() error {
|
func (t *Thread) SetCurrentBreakpoint() error {
|
||||||
thread.clearBreakpointState()
|
t.clearBreakpointState()
|
||||||
regs, err := thread.Registers(false)
|
regs, err := t.Registers(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pc := regs.PC()
|
pc := regs.PC()
|
||||||
if bp, ok := thread.p.FindBreakpoint(pc); ok {
|
if bp, ok := t.p.FindBreakpoint(pc); ok {
|
||||||
if thread.regs.PC() != bp.Addr {
|
if t.regs.PC() != bp.Addr {
|
||||||
if err := thread.SetPC(bp.Addr); err != nil {
|
if err := t.SetPC(bp.Addr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thread.CurrentBreakpoint = bp.CheckCondition(thread)
|
t.CurrentBreakpoint = bp.CheckCondition(t)
|
||||||
if thread.CurrentBreakpoint.Breakpoint != nil && thread.CurrentBreakpoint.Active {
|
if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
|
||||||
if g, err := proc.GetG(thread); err == nil {
|
if g, err := proc.GetG(t); err == nil {
|
||||||
thread.CurrentBreakpoint.HitCount[g.ID]++
|
t.CurrentBreakpoint.HitCount[g.ID]++
|
||||||
}
|
}
|
||||||
thread.CurrentBreakpoint.TotalHitCount++
|
t.CurrentBreakpoint.TotalHitCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -1827,9 +1826,12 @@ func (t *Thread) SetDX(dx uint64) error {
|
|||||||
return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
|
return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (regs *gdbRegisters) Slice() []proc.Register {
|
func (regs *gdbRegisters) Slice(floatingPoint bool) []proc.Register {
|
||||||
r := make([]proc.Register, 0, len(regs.regsInfo))
|
r := make([]proc.Register, 0, len(regs.regsInfo))
|
||||||
for _, reginfo := range regs.regsInfo {
|
for _, reginfo := range regs.regsInfo {
|
||||||
|
if reginfo.Group == "float" && !floatingPoint {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case reginfo.Name == "eflags":
|
case reginfo.Name == "eflags":
|
||||||
r = proc.AppendEflagReg(r, reginfo.Name, uint64(binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value)))
|
r = proc.AppendEflagReg(r, reginfo.Name, uint64(binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value)))
|
||||||
@ -1842,6 +1844,9 @@ func (regs *gdbRegisters) Slice() []proc.Register {
|
|||||||
case reginfo.Bitsize == 64:
|
case reginfo.Bitsize == 64:
|
||||||
r = proc.AppendQwordReg(r, reginfo.Name, binary.LittleEndian.Uint64(regs.regs[reginfo.Name].value))
|
r = proc.AppendQwordReg(r, reginfo.Name, binary.LittleEndian.Uint64(regs.regs[reginfo.Name].value))
|
||||||
case reginfo.Bitsize == 80:
|
case reginfo.Bitsize == 80:
|
||||||
|
if !floatingPoint {
|
||||||
|
continue
|
||||||
|
}
|
||||||
idx := 0
|
idx := 0
|
||||||
for _, stprefix := range []string{"stmm", "st"} {
|
for _, stprefix := range []string{"stmm", "st"} {
|
||||||
if strings.HasPrefix(reginfo.Name, stprefix) {
|
if strings.HasPrefix(reginfo.Name, stprefix) {
|
||||||
@ -1853,10 +1858,12 @@ func (regs *gdbRegisters) Slice() []proc.Register {
|
|||||||
r = proc.AppendX87Reg(r, idx, binary.LittleEndian.Uint16(value[8:]), binary.LittleEndian.Uint64(value[:8]))
|
r = proc.AppendX87Reg(r, idx, binary.LittleEndian.Uint16(value[8:]), binary.LittleEndian.Uint64(value[:8]))
|
||||||
|
|
||||||
case reginfo.Bitsize == 128:
|
case reginfo.Bitsize == 128:
|
||||||
r = proc.AppendSSEReg(r, strings.ToUpper(reginfo.Name), regs.regs[reginfo.Name].value)
|
if floatingPoint {
|
||||||
|
r = proc.AppendSSEReg(r, strings.ToUpper(reginfo.Name), regs.regs[reginfo.Name].value)
|
||||||
|
}
|
||||||
|
|
||||||
case reginfo.Bitsize == 256:
|
case reginfo.Bitsize == 256:
|
||||||
if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") {
|
if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") || !floatingPoint {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -16,8 +16,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/logflags"
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -227,7 +227,8 @@ type gdbRegisterInfo struct {
|
|||||||
Name string `xml:"name,attr"`
|
Name string `xml:"name,attr"`
|
||||||
Bitsize int `xml:"bitsize,attr"`
|
Bitsize int `xml:"bitsize,attr"`
|
||||||
Offset int
|
Offset int
|
||||||
Regnum int `xml:"regnum,attr"`
|
Regnum int `xml:"regnum,attr"`
|
||||||
|
Group string `xml:"group,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// readTargetXml reads target.xml file from stub using qXfer:features:read,
|
// readTargetXml reads target.xml file from stub using qXfer:features:read,
|
@ -53,7 +53,7 @@ func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
|
|||||||
|
|
||||||
// Replay starts an instance of rr in replay mode, with the specified trace
|
// Replay starts an instance of rr in replay mode, with the specified trace
|
||||||
// directory, and connects to it.
|
// directory, and connects to it.
|
||||||
func Replay(tracedir string, quiet bool) (*Process, error) {
|
func Replay(tracedir string, quiet bool, debugInfoDirs []string) (*Process, error) {
|
||||||
if err := checkRRAvailabe(); err != nil {
|
if err := checkRRAvailabe(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ func Replay(tracedir string, quiet bool) (*Process, error) {
|
|||||||
|
|
||||||
p := New(rrcmd.Process)
|
p := New(rrcmd.Process)
|
||||||
p.tracedir = tracedir
|
p.tracedir = tracedir
|
||||||
err = p.Dial(init.port, init.exe, 0)
|
err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rrcmd.Process.Kill()
|
rrcmd.Process.Kill()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -257,11 +257,11 @@ func splitQuotedFields(in string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordAndReplay acts like calling Record and then Replay.
|
// RecordAndReplay acts like calling Record and then Replay.
|
||||||
func RecordAndReplay(cmd []string, wd string, quiet bool) (p *Process, tracedir string, err error) {
|
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (p *Process, tracedir string, err error) {
|
||||||
tracedir, err = Record(cmd, wd, quiet)
|
tracedir, err = Record(cmd, wd, quiet)
|
||||||
if tracedir == "" {
|
if tracedir == "" {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
p, err = Replay(tracedir, quiet)
|
p, err = Replay(tracedir, quiet, debugInfoDirs)
|
||||||
return p, tracedir, err
|
return p, tracedir, err
|
||||||
}
|
}
|
@ -68,6 +68,7 @@ type Info interface {
|
|||||||
// ErrProcessExited or ProcessDetachedError).
|
// ErrProcessExited or ProcessDetachedError).
|
||||||
Valid() (bool, error)
|
Valid() (bool, error)
|
||||||
BinInfo() *BinaryInfo
|
BinInfo() *BinaryInfo
|
||||||
|
EntryPoint() (uint64, error)
|
||||||
// Common returns a struct with fields common to all backends
|
// Common returns a struct with fields common to all backends
|
||||||
Common() *CommonProcess
|
Common() *CommonProcess
|
||||||
|
|
||||||
@ -86,6 +87,7 @@ type ThreadInfo interface {
|
|||||||
// GoroutineInfo is an interface for getting information on running goroutines.
|
// GoroutineInfo is an interface for getting information on running goroutines.
|
||||||
type GoroutineInfo interface {
|
type GoroutineInfo interface {
|
||||||
SelectedGoroutine() *G
|
SelectedGoroutine() *G
|
||||||
|
SetSelectedGoroutine(*G)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessManipulation is an interface for changing the execution state of a process.
|
// ProcessManipulation is an interface for changing the execution state of a process.
|
172
vendor/github.com/go-delve/delve/pkg/proc/linutil/dynamic.go
generated
vendored
Normal file
172
vendor/github.com/go-delve/delve/pkg/proc/linutil/dynamic.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package linutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxNumLibraries = 1000000 // maximum number of loaded libraries, to avoid loading forever on corrupted memory
|
||||||
|
maxLibraryPathLength = 1000000 // maximum length for the path of a library, to avoid loading forever on corrupted memory
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrTooManyLibraries = errors.New("number of loaded libraries exceeds maximum")
|
||||||
|
|
||||||
|
const (
|
||||||
|
_DT_NULL = 0 // DT_NULL as defined by SysV ABI specification
|
||||||
|
_DT_DEBUG = 21 // DT_DEBUG as defined by SysV ABI specification
|
||||||
|
)
|
||||||
|
|
||||||
|
// dynamicSearchDebug searches for the DT_DEBUG entry in the .dynamic section
|
||||||
|
func dynamicSearchDebug(p proc.Process) (uint64, error) {
|
||||||
|
bi := p.BinInfo()
|
||||||
|
mem := p.CurrentThread()
|
||||||
|
|
||||||
|
dynbuf := make([]byte, bi.ElfDynamicSection.Size)
|
||||||
|
_, err := mem.ReadMemory(dynbuf, uintptr(bi.ElfDynamicSection.Addr))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := bytes.NewReader(dynbuf)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var tag, val uint64
|
||||||
|
if err := binary.Read(rd, binary.LittleEndian, &tag); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(rd, binary.LittleEndian, &val); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch tag {
|
||||||
|
case _DT_NULL:
|
||||||
|
return 0, nil
|
||||||
|
case _DT_DEBUG:
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hard-coded offsets of the fields of the r_debug and link_map structs, see
|
||||||
|
// /usr/include/elf/link.h for a full description of those structs.
|
||||||
|
const (
|
||||||
|
_R_DEBUG_MAP_OFFSET = 8
|
||||||
|
_LINK_MAP_ADDR_OFFSET = 0 // offset of link_map.l_addr field (base address shared object is loaded at)
|
||||||
|
_LINK_MAP_NAME_OFFSET = 8 // offset of link_map.l_name field (absolute file name object was found in)
|
||||||
|
_LINK_MAP_LD = 16 // offset of link_map.l_ld field (dynamic section of the shared object)
|
||||||
|
_LINK_MAP_NEXT = 24 // offset of link_map.l_next field
|
||||||
|
_LINK_MAP_PREV = 32 // offset of link_map.l_prev field
|
||||||
|
)
|
||||||
|
|
||||||
|
func readPtr(p proc.Process, addr uint64) (uint64, error) {
|
||||||
|
ptrbuf := make([]byte, p.BinInfo().Arch.PtrSize())
|
||||||
|
_, err := p.CurrentThread().ReadMemory(ptrbuf, uintptr(addr))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.LittleEndian.Uint64(ptrbuf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkMap struct {
|
||||||
|
addr uint64
|
||||||
|
name string
|
||||||
|
ld uint64
|
||||||
|
next, prev uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLinkMapNode(p proc.Process, r_map uint64) (*linkMap, error) {
|
||||||
|
bi := p.BinInfo()
|
||||||
|
|
||||||
|
var lm linkMap
|
||||||
|
var ptrs [5]uint64
|
||||||
|
for i := range ptrs {
|
||||||
|
var err error
|
||||||
|
ptrs[i], err = readPtr(p, r_map+uint64(bi.Arch.PtrSize()*i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lm.addr = ptrs[0]
|
||||||
|
var err error
|
||||||
|
lm.name, err = readCString(p, ptrs[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lm.ld = ptrs[2]
|
||||||
|
lm.next = ptrs[3]
|
||||||
|
lm.prev = ptrs[4]
|
||||||
|
return &lm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readCString(p proc.Process, addr uint64) (string, error) {
|
||||||
|
if addr == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
mem := p.CurrentThread()
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
r := []byte{}
|
||||||
|
for {
|
||||||
|
if len(r) > maxLibraryPathLength {
|
||||||
|
return "", fmt.Errorf("error reading libraries: string too long (%d)", len(r))
|
||||||
|
}
|
||||||
|
_, err := mem.ReadMemory(buf, uintptr(addr))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if buf[0] == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r = append(r, buf[0])
|
||||||
|
addr++
|
||||||
|
}
|
||||||
|
return string(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElfUpdateSharedObjects reads the list of dynamic libraries loaded by the
|
||||||
|
// dynamic linker from the .dynamic section and uses it to update p.BinInfo().
|
||||||
|
// See the SysV ABI for a description of how the .dynamic section works:
|
||||||
|
// http://www.sco.com/developers/gabi/latest/contents.html
|
||||||
|
func ElfUpdateSharedObjects(p proc.Process) error {
|
||||||
|
bi := p.BinInfo()
|
||||||
|
if bi.ElfDynamicSection.Addr == 0 {
|
||||||
|
// no dynamic section, therefore nothing to do here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
debugAddr, err := dynamicSearchDebug(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if debugAddr == 0 {
|
||||||
|
// no DT_DEBUG entry
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r_map, err := readPtr(p, debugAddr+_R_DEBUG_MAP_OFFSET)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
libs := []string{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if r_map == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(libs) > maxNumLibraries {
|
||||||
|
return ErrTooManyLibraries
|
||||||
|
}
|
||||||
|
lm, err := readLinkMapNode(p, r_map)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bi.AddImage(lm.name, lm.addr)
|
||||||
|
libs = append(libs, lm.name)
|
||||||
|
r_map = lm.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
399
vendor/github.com/go-delve/delve/pkg/proc/linutil/regs.go
generated
vendored
Normal file
399
vendor/github.com/go-delve/delve/pkg/proc/linutil/regs.go
generated
vendored
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
package linutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AMD64Registers implements the proc.Registers interface for the native/linux
|
||||||
|
// backend and core/linux backends, on AMD64.
|
||||||
|
type AMD64Registers struct {
|
||||||
|
Regs *AMD64PtraceRegs
|
||||||
|
Fpregs []proc.Register
|
||||||
|
Fpregset *AMD64Xstate
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMD64PtraceRegs is the struct used by the linux kernel to return the
|
||||||
|
// general purpose registers for AMD64 CPUs.
|
||||||
|
type AMD64PtraceRegs struct {
|
||||||
|
R15 uint64
|
||||||
|
R14 uint64
|
||||||
|
R13 uint64
|
||||||
|
R12 uint64
|
||||||
|
Rbp uint64
|
||||||
|
Rbx uint64
|
||||||
|
R11 uint64
|
||||||
|
R10 uint64
|
||||||
|
R9 uint64
|
||||||
|
R8 uint64
|
||||||
|
Rax uint64
|
||||||
|
Rcx uint64
|
||||||
|
Rdx uint64
|
||||||
|
Rsi uint64
|
||||||
|
Rdi uint64
|
||||||
|
Orig_rax uint64
|
||||||
|
Rip uint64
|
||||||
|
Cs uint64
|
||||||
|
Eflags uint64
|
||||||
|
Rsp uint64
|
||||||
|
Ss uint64
|
||||||
|
Fs_base uint64
|
||||||
|
Gs_base uint64
|
||||||
|
Ds uint64
|
||||||
|
Es uint64
|
||||||
|
Fs uint64
|
||||||
|
Gs uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice returns the registers as a list of (name, value) pairs.
|
||||||
|
func (r *AMD64Registers) Slice(floatingPoint bool) []proc.Register {
|
||||||
|
var regs = []struct {
|
||||||
|
k string
|
||||||
|
v uint64
|
||||||
|
}{
|
||||||
|
{"Rip", r.Regs.Rip},
|
||||||
|
{"Rsp", r.Regs.Rsp},
|
||||||
|
{"Rax", r.Regs.Rax},
|
||||||
|
{"Rbx", r.Regs.Rbx},
|
||||||
|
{"Rcx", r.Regs.Rcx},
|
||||||
|
{"Rdx", r.Regs.Rdx},
|
||||||
|
{"Rdi", r.Regs.Rdi},
|
||||||
|
{"Rsi", r.Regs.Rsi},
|
||||||
|
{"Rbp", r.Regs.Rbp},
|
||||||
|
{"R8", r.Regs.R8},
|
||||||
|
{"R9", r.Regs.R9},
|
||||||
|
{"R10", r.Regs.R10},
|
||||||
|
{"R11", r.Regs.R11},
|
||||||
|
{"R12", r.Regs.R12},
|
||||||
|
{"R13", r.Regs.R13},
|
||||||
|
{"R14", r.Regs.R14},
|
||||||
|
{"R15", r.Regs.R15},
|
||||||
|
{"Orig_rax", r.Regs.Orig_rax},
|
||||||
|
{"Cs", r.Regs.Cs},
|
||||||
|
{"Eflags", r.Regs.Eflags},
|
||||||
|
{"Ss", r.Regs.Ss},
|
||||||
|
{"Fs_base", r.Regs.Fs_base},
|
||||||
|
{"Gs_base", r.Regs.Gs_base},
|
||||||
|
{"Ds", r.Regs.Ds},
|
||||||
|
{"Es", r.Regs.Es},
|
||||||
|
{"Fs", r.Regs.Fs},
|
||||||
|
{"Gs", r.Regs.Gs},
|
||||||
|
}
|
||||||
|
out := make([]proc.Register, 0, len(regs)+len(r.Fpregs))
|
||||||
|
for _, reg := range regs {
|
||||||
|
if reg.k == "Eflags" {
|
||||||
|
out = proc.AppendEflagReg(out, reg.k, reg.v)
|
||||||
|
} else {
|
||||||
|
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if floatingPoint {
|
||||||
|
out = append(out, r.Fpregs...)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC returns the value of RIP register.
|
||||||
|
func (r *AMD64Registers) PC() uint64 {
|
||||||
|
return r.Regs.Rip
|
||||||
|
}
|
||||||
|
|
||||||
|
// SP returns the value of RSP register.
|
||||||
|
func (r *AMD64Registers) SP() uint64 {
|
||||||
|
return r.Regs.Rsp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AMD64Registers) BP() uint64 {
|
||||||
|
return r.Regs.Rbp
|
||||||
|
}
|
||||||
|
|
||||||
|
// CX returns the value of RCX register.
|
||||||
|
func (r *AMD64Registers) CX() uint64 {
|
||||||
|
return r.Regs.Rcx
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS returns the address of the thread local storage memory segment.
|
||||||
|
func (r *AMD64Registers) TLS() uint64 {
|
||||||
|
return r.Regs.Fs_base
|
||||||
|
}
|
||||||
|
|
||||||
|
// GAddr returns the address of the G variable if it is known, 0 and false
|
||||||
|
// otherwise.
|
||||||
|
func (r *AMD64Registers) GAddr() (uint64, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value of the n-th register (in x86asm order).
|
||||||
|
func (r *AMD64Registers) Get(n int) (uint64, error) {
|
||||||
|
reg := x86asm.Reg(n)
|
||||||
|
const (
|
||||||
|
mask8 = 0x000f
|
||||||
|
mask16 = 0x00ff
|
||||||
|
mask32 = 0xffff
|
||||||
|
)
|
||||||
|
|
||||||
|
switch reg {
|
||||||
|
// 8-bit
|
||||||
|
case x86asm.AL:
|
||||||
|
return r.Regs.Rax & mask8, nil
|
||||||
|
case x86asm.CL:
|
||||||
|
return r.Regs.Rcx & mask8, nil
|
||||||
|
case x86asm.DL:
|
||||||
|
return r.Regs.Rdx & mask8, nil
|
||||||
|
case x86asm.BL:
|
||||||
|
return r.Regs.Rbx & mask8, nil
|
||||||
|
case x86asm.AH:
|
||||||
|
return (r.Regs.Rax >> 8) & mask8, nil
|
||||||
|
case x86asm.CH:
|
||||||
|
return (r.Regs.Rcx >> 8) & mask8, nil
|
||||||
|
case x86asm.DH:
|
||||||
|
return (r.Regs.Rdx >> 8) & mask8, nil
|
||||||
|
case x86asm.BH:
|
||||||
|
return (r.Regs.Rbx >> 8) & mask8, nil
|
||||||
|
case x86asm.SPB:
|
||||||
|
return r.Regs.Rsp & mask8, nil
|
||||||
|
case x86asm.BPB:
|
||||||
|
return r.Regs.Rbp & mask8, nil
|
||||||
|
case x86asm.SIB:
|
||||||
|
return r.Regs.Rsi & mask8, nil
|
||||||
|
case x86asm.DIB:
|
||||||
|
return r.Regs.Rdi & mask8, nil
|
||||||
|
case x86asm.R8B:
|
||||||
|
return r.Regs.R8 & mask8, nil
|
||||||
|
case x86asm.R9B:
|
||||||
|
return r.Regs.R9 & mask8, nil
|
||||||
|
case x86asm.R10B:
|
||||||
|
return r.Regs.R10 & mask8, nil
|
||||||
|
case x86asm.R11B:
|
||||||
|
return r.Regs.R11 & mask8, nil
|
||||||
|
case x86asm.R12B:
|
||||||
|
return r.Regs.R12 & mask8, nil
|
||||||
|
case x86asm.R13B:
|
||||||
|
return r.Regs.R13 & mask8, nil
|
||||||
|
case x86asm.R14B:
|
||||||
|
return r.Regs.R14 & mask8, nil
|
||||||
|
case x86asm.R15B:
|
||||||
|
return r.Regs.R15 & mask8, nil
|
||||||
|
|
||||||
|
// 16-bit
|
||||||
|
case x86asm.AX:
|
||||||
|
return r.Regs.Rax & mask16, nil
|
||||||
|
case x86asm.CX:
|
||||||
|
return r.Regs.Rcx & mask16, nil
|
||||||
|
case x86asm.DX:
|
||||||
|
return r.Regs.Rdx & mask16, nil
|
||||||
|
case x86asm.BX:
|
||||||
|
return r.Regs.Rbx & mask16, nil
|
||||||
|
case x86asm.SP:
|
||||||
|
return r.Regs.Rsp & mask16, nil
|
||||||
|
case x86asm.BP:
|
||||||
|
return r.Regs.Rbp & mask16, nil
|
||||||
|
case x86asm.SI:
|
||||||
|
return r.Regs.Rsi & mask16, nil
|
||||||
|
case x86asm.DI:
|
||||||
|
return r.Regs.Rdi & mask16, nil
|
||||||
|
case x86asm.R8W:
|
||||||
|
return r.Regs.R8 & mask16, nil
|
||||||
|
case x86asm.R9W:
|
||||||
|
return r.Regs.R9 & mask16, nil
|
||||||
|
case x86asm.R10W:
|
||||||
|
return r.Regs.R10 & mask16, nil
|
||||||
|
case x86asm.R11W:
|
||||||
|
return r.Regs.R11 & mask16, nil
|
||||||
|
case x86asm.R12W:
|
||||||
|
return r.Regs.R12 & mask16, nil
|
||||||
|
case x86asm.R13W:
|
||||||
|
return r.Regs.R13 & mask16, nil
|
||||||
|
case x86asm.R14W:
|
||||||
|
return r.Regs.R14 & mask16, nil
|
||||||
|
case x86asm.R15W:
|
||||||
|
return r.Regs.R15 & mask16, nil
|
||||||
|
|
||||||
|
// 32-bit
|
||||||
|
case x86asm.EAX:
|
||||||
|
return r.Regs.Rax & mask32, nil
|
||||||
|
case x86asm.ECX:
|
||||||
|
return r.Regs.Rcx & mask32, nil
|
||||||
|
case x86asm.EDX:
|
||||||
|
return r.Regs.Rdx & mask32, nil
|
||||||
|
case x86asm.EBX:
|
||||||
|
return r.Regs.Rbx & mask32, nil
|
||||||
|
case x86asm.ESP:
|
||||||
|
return r.Regs.Rsp & mask32, nil
|
||||||
|
case x86asm.EBP:
|
||||||
|
return r.Regs.Rbp & mask32, nil
|
||||||
|
case x86asm.ESI:
|
||||||
|
return r.Regs.Rsi & mask32, nil
|
||||||
|
case x86asm.EDI:
|
||||||
|
return r.Regs.Rdi & mask32, nil
|
||||||
|
case x86asm.R8L:
|
||||||
|
return r.Regs.R8 & mask32, nil
|
||||||
|
case x86asm.R9L:
|
||||||
|
return r.Regs.R9 & mask32, nil
|
||||||
|
case x86asm.R10L:
|
||||||
|
return r.Regs.R10 & mask32, nil
|
||||||
|
case x86asm.R11L:
|
||||||
|
return r.Regs.R11 & mask32, nil
|
||||||
|
case x86asm.R12L:
|
||||||
|
return r.Regs.R12 & mask32, nil
|
||||||
|
case x86asm.R13L:
|
||||||
|
return r.Regs.R13 & mask32, nil
|
||||||
|
case x86asm.R14L:
|
||||||
|
return r.Regs.R14 & mask32, nil
|
||||||
|
case x86asm.R15L:
|
||||||
|
return r.Regs.R15 & mask32, nil
|
||||||
|
|
||||||
|
// 64-bit
|
||||||
|
case x86asm.RAX:
|
||||||
|
return r.Regs.Rax, nil
|
||||||
|
case x86asm.RCX:
|
||||||
|
return r.Regs.Rcx, nil
|
||||||
|
case x86asm.RDX:
|
||||||
|
return r.Regs.Rdx, nil
|
||||||
|
case x86asm.RBX:
|
||||||
|
return r.Regs.Rbx, nil
|
||||||
|
case x86asm.RSP:
|
||||||
|
return r.Regs.Rsp, nil
|
||||||
|
case x86asm.RBP:
|
||||||
|
return r.Regs.Rbp, nil
|
||||||
|
case x86asm.RSI:
|
||||||
|
return r.Regs.Rsi, nil
|
||||||
|
case x86asm.RDI:
|
||||||
|
return r.Regs.Rdi, nil
|
||||||
|
case x86asm.R8:
|
||||||
|
return r.Regs.R8, nil
|
||||||
|
case x86asm.R9:
|
||||||
|
return r.Regs.R9, nil
|
||||||
|
case x86asm.R10:
|
||||||
|
return r.Regs.R10, nil
|
||||||
|
case x86asm.R11:
|
||||||
|
return r.Regs.R11, nil
|
||||||
|
case x86asm.R12:
|
||||||
|
return r.Regs.R12, nil
|
||||||
|
case x86asm.R13:
|
||||||
|
return r.Regs.R13, nil
|
||||||
|
case x86asm.R14:
|
||||||
|
return r.Regs.R14, nil
|
||||||
|
case x86asm.R15:
|
||||||
|
return r.Regs.R15, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, proc.ErrUnknownRegister
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a copy of these registers that is guarenteed not to change.
|
||||||
|
func (r *AMD64Registers) Copy() proc.Registers {
|
||||||
|
var rr AMD64Registers
|
||||||
|
rr.Regs = &AMD64PtraceRegs{}
|
||||||
|
rr.Fpregset = &AMD64Xstate{}
|
||||||
|
*(rr.Regs) = *(r.Regs)
|
||||||
|
if r.Fpregset != nil {
|
||||||
|
*(rr.Fpregset) = *(r.Fpregset)
|
||||||
|
}
|
||||||
|
if r.Fpregs != nil {
|
||||||
|
rr.Fpregs = make([]proc.Register, len(r.Fpregs))
|
||||||
|
copy(rr.Fpregs, r.Fpregs)
|
||||||
|
}
|
||||||
|
return &rr
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMD64PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
|
||||||
|
type AMD64PtraceFpRegs struct {
|
||||||
|
Cwd uint16
|
||||||
|
Swd uint16
|
||||||
|
Ftw uint16
|
||||||
|
Fop uint16
|
||||||
|
Rip uint64
|
||||||
|
Rdp uint64
|
||||||
|
Mxcsr uint32
|
||||||
|
MxcrMask uint32
|
||||||
|
StSpace [32]uint32
|
||||||
|
XmmSpace [256]byte
|
||||||
|
Padding [24]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// AMD64Xstate represents amd64 XSAVE area. See Section 13.1 (and
|
||||||
|
// following) of Intel® 64 and IA-32 Architectures Software Developer’s
|
||||||
|
// Manual, Volume 1: Basic Architecture.
|
||||||
|
type AMD64Xstate struct {
|
||||||
|
AMD64PtraceFpRegs
|
||||||
|
Xsave []byte // raw xsave area
|
||||||
|
AvxState bool // contains AVX state
|
||||||
|
YmmSpace [256]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes an XSAVE area to a list of name/value pairs of registers.
|
||||||
|
func (xsave *AMD64Xstate) Decode() (regs []proc.Register) {
|
||||||
|
// x87 registers
|
||||||
|
regs = proc.AppendWordReg(regs, "CW", xsave.Cwd)
|
||||||
|
regs = proc.AppendWordReg(regs, "SW", xsave.Swd)
|
||||||
|
regs = proc.AppendWordReg(regs, "TW", xsave.Ftw)
|
||||||
|
regs = proc.AppendWordReg(regs, "FOP", xsave.Fop)
|
||||||
|
regs = proc.AppendQwordReg(regs, "FIP", xsave.Rip)
|
||||||
|
regs = proc.AppendQwordReg(regs, "FDP", xsave.Rdp)
|
||||||
|
|
||||||
|
for i := 0; i < len(xsave.StSpace); i += 4 {
|
||||||
|
regs = proc.AppendX87Reg(regs, i/4, uint16(xsave.StSpace[i+2]), uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSE registers
|
||||||
|
regs = proc.AppendMxcsrReg(regs, "MXCSR", uint64(xsave.Mxcsr))
|
||||||
|
regs = proc.AppendDwordReg(regs, "MXCSR_MASK", xsave.MxcrMask)
|
||||||
|
|
||||||
|
for i := 0; i < len(xsave.XmmSpace); i += 16 {
|
||||||
|
regs = proc.AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16])
|
||||||
|
if xsave.AvxState {
|
||||||
|
regs = proc.AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_XSAVE_HEADER_START = 512
|
||||||
|
_XSAVE_HEADER_LEN = 64
|
||||||
|
_XSAVE_EXTENDED_REGION_START = 576
|
||||||
|
_XSAVE_SSE_REGION_LEN = 416
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset.
|
||||||
|
// If readLegacy is true regset.PtraceFpRegs will be filled with the
|
||||||
|
// contents of the legacy region of the XSAVE area.
|
||||||
|
// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures
|
||||||
|
// Software Developer’s Manual, Volume 1: Basic Architecture.
|
||||||
|
func AMD64XstateRead(xstateargs []byte, readLegacy bool, regset *AMD64Xstate) error {
|
||||||
|
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if readLegacy {
|
||||||
|
rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START])
|
||||||
|
if err := binary.Read(rdr, binary.LittleEndian, ®set.AMD64PtraceFpRegs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
|
||||||
|
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
|
||||||
|
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
|
||||||
|
|
||||||
|
if xcomp_bv&(1<<63) != 0 {
|
||||||
|
// compact format not supported
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if xstate_bv&(1<<2) == 0 {
|
||||||
|
// AVX state not present
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:]
|
||||||
|
regset.AvxState = true
|
||||||
|
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,8 +2,9 @@ package proc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cacheEnabled = true
|
const cacheEnabled = true
|
||||||
@ -93,7 +94,7 @@ type compositeMemory struct {
|
|||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece) *compositeMemory {
|
func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece) (*compositeMemory, error) {
|
||||||
cmem := &compositeMemory{realmem: mem, regs: regs, pieces: pieces, data: []byte{}}
|
cmem := &compositeMemory{realmem: mem, regs: regs, pieces: pieces, data: []byte{}}
|
||||||
for _, piece := range pieces {
|
for _, piece := range pieces {
|
||||||
if piece.IsRegister {
|
if piece.IsRegister {
|
||||||
@ -102,6 +103,9 @@ func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []o
|
|||||||
if sz == 0 && len(pieces) == 1 {
|
if sz == 0 && len(pieces) == 1 {
|
||||||
sz = len(reg)
|
sz = len(reg)
|
||||||
}
|
}
|
||||||
|
if sz > len(reg) {
|
||||||
|
return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", sz, piece.RegNum, len(reg))
|
||||||
|
}
|
||||||
cmem.data = append(cmem.data, reg[:sz]...)
|
cmem.data = append(cmem.data, reg[:sz]...)
|
||||||
} else {
|
} else {
|
||||||
buf := make([]byte, piece.Size)
|
buf := make([]byte, piece.Size)
|
||||||
@ -109,7 +113,7 @@ func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []o
|
|||||||
cmem.data = append(cmem.data, buf...)
|
cmem.data = append(cmem.data, buf...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cmem
|
return cmem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mem *compositeMemory) ReadMemory(data []byte, addr uintptr) (int, error) {
|
func (mem *compositeMemory) ReadMemory(data []byte, addr uintptr) (int, error) {
|
@ -88,7 +88,7 @@ func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
v.loadValue(LoadConfig{false, 1, 0, 0, -1})
|
v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
|
||||||
addr, _ := constant.Int64Val(v.Value)
|
addr, _ := constant.Int64Val(v.Value)
|
||||||
return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil
|
return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil
|
||||||
}
|
}
|
@ -6,18 +6,18 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
||||||
|
|
||||||
// Launch returns ErrNativeBackendDisabled.
|
// Launch returns ErrNativeBackendDisabled.
|
||||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
|
||||||
return nil, ErrNativeBackendDisabled
|
return nil, ErrNativeBackendDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach returns ErrNativeBackendDisabled.
|
// Attach returns ErrNativeBackendDisabled.
|
||||||
func Attach(pid int) (*Process, error) {
|
func Attach(pid int, _ []string) (*Process, error) {
|
||||||
return nil, ErrNativeBackendDisabled
|
return nil, ErrNativeBackendDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,9 @@ func (dbp *Process) detach(kill bool) error {
|
|||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) entryPoint() (uint64, error) {
|
// EntryPoint returns the entry point for the process,
|
||||||
|
// useful for PIEs.
|
||||||
|
func (dbp *Process) EntryPoint() (uint64, error) {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,17 +87,17 @@ func (t *Thread) Blocked() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPC sets the value of the PC register.
|
// SetPC sets the value of the PC register.
|
||||||
func (thread *Thread) SetPC(pc uint64) error {
|
func (t *Thread) SetPC(pc uint64) error {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSP sets the value of the SP register.
|
// SetSP sets the value of the SP register.
|
||||||
func (thread *Thread) SetSP(sp uint64) error {
|
func (t *Thread) SetSP(sp uint64) error {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDX sets the value of the DX register.
|
// SetDX sets the value of the DX register.
|
||||||
func (thread *Thread) SetDX(dx uint64) error {
|
func (t *Thread) SetDX(dx uint64) error {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,3 +128,5 @@ func (t *Thread) restoreRegisters(sr proc.Registers) error {
|
|||||||
func (t *Thread) Stopped() bool {
|
func (t *Thread) Stopped() bool {
|
||||||
panic(ErrNativeBackendDisabled)
|
panic(ErrNativeBackendDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initialize(dbp *Process) error { return nil }
|
@ -6,13 +6,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Process represents all of the information the debugger
|
// Process represents all of the information the debugger
|
||||||
// is holding onto regarding the process we are debugging.
|
// is holding onto regarding the process we are debugging.
|
||||||
type Process struct {
|
type Process struct {
|
||||||
bi *proc.BinaryInfo
|
bi *proc.BinaryInfo
|
||||||
|
|
||||||
pid int // Process Pid
|
pid int // Process Pid
|
||||||
|
|
||||||
// Breakpoint table, holds information on breakpoints.
|
// Breakpoint table, holds information on breakpoints.
|
||||||
@ -184,32 +185,6 @@ func (dbp *Process) Breakpoints() *proc.BreakpointMap {
|
|||||||
return &dbp.breakpoints
|
return &dbp.breakpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadInformation finds the executable and then uses it
|
|
||||||
// to parse the following information:
|
|
||||||
// * Dwarf .debug_frame section
|
|
||||||
// * Dwarf .debug_line section
|
|
||||||
// * Go symbol table.
|
|
||||||
func (dbp *Process) LoadInformation(path string) error {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
path = findExecutable(path, dbp.pid)
|
|
||||||
|
|
||||||
entryPoint, err := dbp.entryPoint()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go dbp.loadProcessInformation(&wg)
|
|
||||||
err = dbp.bi.LoadBinaryInfo(path, entryPoint, &wg)
|
|
||||||
wg.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = dbp.bi.LoadError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestManualStop sets the `halt` flag and
|
// RequestManualStop sets the `halt` flag and
|
||||||
// sends SIGSTOP to all threads.
|
// sends SIGSTOP to all threads.
|
||||||
func (dbp *Process) RequestManualStop() error {
|
func (dbp *Process) RequestManualStop() error {
|
||||||
@ -236,9 +211,6 @@ func (dbp *Process) CheckAndClearManualStopRequest() bool {
|
|||||||
|
|
||||||
func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
|
func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
|
||||||
f, l, fn := dbp.bi.PCToLine(uint64(addr))
|
f, l, fn := dbp.bi.PCToLine(uint64(addr))
|
||||||
if fn == nil {
|
|
||||||
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
|
|
||||||
}
|
|
||||||
|
|
||||||
originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
|
originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
|
||||||
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
|
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
|
||||||
@ -379,26 +351,23 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new Process struct.
|
// initialize will ensure that all relevant information is loaded
|
||||||
func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
|
// so the process is ready to be debugged.
|
||||||
err := dbp.LoadInformation(path)
|
func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
|
||||||
if err != nil {
|
if err := initialize(dbp); err != nil {
|
||||||
return dbp, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbp.updateThreadList(); err != nil {
|
if err := dbp.updateThreadList(); err != nil {
|
||||||
return dbp, err
|
return err
|
||||||
}
|
}
|
||||||
|
return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint)
|
||||||
|
}
|
||||||
|
|
||||||
// selectedGoroutine can not be set correctly by the call to updateThreadList
|
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||||
// because without calling SetGStructOffset we can not read the G struct of currentThread
|
// the default for any command executed, the goroutine being actively
|
||||||
// but without calling updateThreadList we can not examine memory to determine
|
// followed.
|
||||||
// the offset of g struct inside TLS
|
func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
|
||||||
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
|
dbp.selectedGoroutine = g
|
||||||
|
|
||||||
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
|
|
||||||
|
|
||||||
return dbp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearInternalBreakpoints will clear all non-user set breakpoints. These
|
// ClearInternalBreakpoints will clear all non-user set breakpoints. These
|
@ -13,12 +13,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OSProcessDetails holds Darwin specific information.
|
// OSProcessDetails holds Darwin specific information.
|
||||||
@ -38,7 +37,7 @@ type OSProcessDetails struct {
|
|||||||
// custom fork/exec process in order to take advantage of
|
// custom fork/exec process in order to take advantage of
|
||||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||||
// Mach exceptions.
|
// Mach exceptions.
|
||||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
|
||||||
// check that the argument to Launch is an executable file
|
// check that the argument to Launch is an executable file
|
||||||
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
|
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
|
||||||
return nil, proc.ErrNotExecutable
|
return nil, proc.ErrNotExecutable
|
||||||
@ -119,7 +118,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbp.os.initialized = true
|
dbp.os.initialized = true
|
||||||
dbp, err = initializeDebugProcess(dbp, argv0Go)
|
err = dbp.initialize(argv0Go, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -132,7 +131,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach to an existing process with the given PID.
|
// Attach to an existing process with the given PID.
|
||||||
func Attach(pid int) (*Process, error) {
|
func Attach(pid int, _ []string) (*Process, error) {
|
||||||
dbp := New(pid)
|
dbp := New(pid)
|
||||||
|
|
||||||
kret := C.acquire_mach_task(C.int(pid),
|
kret := C.acquire_mach_task(C.int(pid),
|
||||||
@ -155,7 +154,7 @@ func Attach(pid int) (*Process, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dbp, err = initializeDebugProcess(dbp, "")
|
err = dbp.initialize("", []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dbp.Detach(false)
|
dbp.Detach(false)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -383,10 +382,6 @@ func (dbp *Process) waitForStop() ([]int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
||||||
var status sys.WaitStatus
|
var status sys.WaitStatus
|
||||||
wpid, err := sys.Wait4(pid, &status, options, nil)
|
wpid, err := sys.Wait4(pid, &status, options, nil)
|
||||||
@ -464,7 +459,9 @@ func (dbp *Process) detach(kill bool) error {
|
|||||||
return PtraceDetach(dbp.pid, 0)
|
return PtraceDetach(dbp.pid, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) entryPoint() (uint64, error) {
|
func (dbp *Process) EntryPoint() (uint64, error) {
|
||||||
//TODO(aarzilli): implement this
|
//TODO(aarzilli): implement this
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initialize(dbp *Process) error { return nil }
|
@ -12,15 +12,15 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
|
isatty "github.com/mattn/go-isatty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Process statuses
|
// Process statuses
|
||||||
@ -46,7 +46,9 @@ type OSProcessDetails struct {
|
|||||||
// Launch creates and begins debugging a new process. First entry in
|
// Launch creates and begins debugging a new process. First entry in
|
||||||
// `cmd` is the program to run, and then rest are the arguments
|
// `cmd` is the program to run, and then rest are the arguments
|
||||||
// to be supplied to that process. `wd` is working directory of the program.
|
// to be supplied to that process. `wd` is working directory of the program.
|
||||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
// If the DWARF information cannot be found in the binary, Delve will look
|
||||||
|
// for external debug files in the directories passed in.
|
||||||
|
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) {
|
||||||
var (
|
var (
|
||||||
process *exec.Cmd
|
process *exec.Cmd
|
||||||
err error
|
err error
|
||||||
@ -88,11 +90,16 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||||
}
|
}
|
||||||
return initializeDebugProcess(dbp, process.Path)
|
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dbp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach to an existing process with the given PID.
|
// Attach to an existing process with the given PID. Once attached, if
|
||||||
func Attach(pid int) (*Process, error) {
|
// the DWARF information cannot be found in the binary, Delve will look
|
||||||
|
// for external debug files in the directories passed in.
|
||||||
|
func Attach(pid int, debugInfoDirs []string) (*Process, error) {
|
||||||
dbp := New(pid)
|
dbp := New(pid)
|
||||||
dbp.common = proc.NewCommonProcess(true)
|
dbp.common = proc.NewCommonProcess(true)
|
||||||
|
|
||||||
@ -106,7 +113,7 @@ func Attach(pid int) (*Process, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dbp, err = initializeDebugProcess(dbp, "")
|
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dbp.Detach(false)
|
dbp.Detach(false)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -114,6 +121,33 @@ func Attach(pid int) (*Process, error) {
|
|||||||
return dbp, nil
|
return dbp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initialize(dbp *Process) error {
|
||||||
|
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
|
||||||
|
if err == nil {
|
||||||
|
// removes newline character
|
||||||
|
comm = bytes.TrimSuffix(comm, []byte("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if comm == nil || len(comm) <= 0 {
|
||||||
|
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read proc stat: %v", err)
|
||||||
|
}
|
||||||
|
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
|
||||||
|
rexp, err := regexp.Compile(expr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("regexp compile error: %v", err)
|
||||||
|
}
|
||||||
|
match := rexp.FindSubmatch(stat)
|
||||||
|
if match == nil {
|
||||||
|
return fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
|
||||||
|
}
|
||||||
|
comm = match[1]
|
||||||
|
}
|
||||||
|
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// kill kills the target process.
|
// kill kills the target process.
|
||||||
func (dbp *Process) kill() (err error) {
|
func (dbp *Process) kill() (err error) {
|
||||||
if dbp.exited {
|
if dbp.exited {
|
||||||
@ -199,7 +233,7 @@ func (dbp *Process) updateThreadList() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return linutil.ElfUpdateSharedObjects(dbp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findExecutable(path string, pid int) string {
|
func findExecutable(path string, pid int) string {
|
||||||
@ -295,35 +329,7 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
func (dbp *Process) loadProcessInformation() {
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
|
|
||||||
if err == nil {
|
|
||||||
// removes newline character
|
|
||||||
comm = bytes.TrimSuffix(comm, []byte("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if comm == nil || len(comm) <= 0 {
|
|
||||||
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not read proc stat: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
|
|
||||||
rexp, err := regexp.Compile(expr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Regexp compile error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
match := rexp.FindSubmatch(stat)
|
|
||||||
if match == nil {
|
|
||||||
fmt.Printf("No match found using regexp '%s' in /proc/%d/stat\n", expr, dbp.pid)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
comm = match[1]
|
|
||||||
}
|
|
||||||
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func status(pid int, comm string) rune {
|
func status(pid int, comm string) rune {
|
||||||
@ -447,6 +453,10 @@ func (dbp *Process) stop(trapthread *Thread) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// set breakpoints on all threads
|
// set breakpoints on all threads
|
||||||
for _, th := range dbp.threads {
|
for _, th := range dbp.threads {
|
||||||
if th.CurrentBreakpoint.Breakpoint == nil {
|
if th.CurrentBreakpoint.Breakpoint == nil {
|
||||||
@ -479,7 +489,9 @@ func (dbp *Process) detach(kill bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) entryPoint() (uint64, error) {
|
// EntryPoint will return the process entry point address, useful for
|
||||||
|
// debugging PIEs.
|
||||||
|
func (dbp *Process) EntryPoint() (uint64, error) {
|
||||||
auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
|
auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
|
return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
|
@ -7,13 +7,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
sys "golang.org/x/sys/windows"
|
sys "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OSProcessDetails holds Windows specific information.
|
// OSProcessDetails holds Windows specific information.
|
||||||
@ -37,7 +36,7 @@ func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch creates and begins debugging a new process.
|
// Launch creates and begins debugging a new process.
|
||||||
func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
|
||||||
argv0Go, err := filepath.Abs(cmd[0])
|
argv0Go, err := filepath.Abs(cmd[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -77,11 +76,14 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
|||||||
dbp.pid = p.Pid
|
dbp.pid = p.Pid
|
||||||
dbp.childProcess = true
|
dbp.childProcess = true
|
||||||
|
|
||||||
return newDebugProcess(dbp, argv0Go)
|
if err = dbp.initialize(argv0Go, []string{}); err != nil {
|
||||||
|
dbp.Detach(true)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dbp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDebugProcess prepares process pid for debugging.
|
func initialize(dbp *Process) error {
|
||||||
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
|
|
||||||
// It should not actually be possible for the
|
// It should not actually be possible for the
|
||||||
// call to waitForDebugEvent to fail, since Windows
|
// call to waitForDebugEvent to fail, since Windows
|
||||||
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
|
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
|
||||||
@ -93,29 +95,25 @@ func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
|
|||||||
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
|
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
if tid == 0 {
|
if tid == 0 {
|
||||||
dbp.postExit()
|
dbp.postExit()
|
||||||
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
|
return proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
|
||||||
}
|
}
|
||||||
// Suspend all threads so that the call to _ContinueDebugEvent will
|
// Suspend all threads so that the call to _ContinueDebugEvent will
|
||||||
// not resume the target.
|
// not resume the target.
|
||||||
for _, thread := range dbp.threads {
|
for _, thread := range dbp.threads {
|
||||||
_, err := _SuspendThread(thread.os.hThread)
|
_, err := _SuspendThread(thread.os.hThread)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dbp.execPtraceFunc(func() {
|
dbp.execPtraceFunc(func() {
|
||||||
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
|
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
|
||||||
})
|
})
|
||||||
if err != nil {
|
return err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return initializeDebugProcess(dbp, exepath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findExePath searches for process pid, and returns its executable path.
|
// findExePath searches for process pid, and returns its executable path.
|
||||||
@ -153,7 +151,7 @@ func findExePath(pid int) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach to an existing process with the given PID.
|
// Attach to an existing process with the given PID.
|
||||||
func Attach(pid int) (*Process, error) {
|
func Attach(pid int, _ []string) (*Process, error) {
|
||||||
// TODO: Probably should have SeDebugPrivilege before starting here.
|
// TODO: Probably should have SeDebugPrivilege before starting here.
|
||||||
err := _DebugActiveProcess(uint32(pid))
|
err := _DebugActiveProcess(uint32(pid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -163,11 +161,9 @@ func Attach(pid int) (*Process, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dbp, err := newDebugProcess(New(pid), exepath)
|
dbp := New(pid)
|
||||||
if err != nil {
|
if err = dbp.initialize(exepath, []string{}); err != nil {
|
||||||
if dbp != nil {
|
dbp.Detach(true)
|
||||||
dbp.Detach(false)
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return dbp, nil
|
return dbp, nil
|
||||||
@ -245,6 +241,8 @@ const (
|
|||||||
waitDontHandleExceptions
|
waitDontHandleExceptions
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const _MS_VC_EXCEPTION = 0x406D1388 // part of VisualC protocol to set thread names
|
||||||
|
|
||||||
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
|
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
|
||||||
var debugEvent _DEBUG_EVENT
|
var debugEvent _DEBUG_EVENT
|
||||||
shouldExit := false
|
shouldExit := false
|
||||||
@ -346,6 +344,10 @@ func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, e
|
|||||||
case _EXCEPTION_SINGLE_STEP:
|
case _EXCEPTION_SINGLE_STEP:
|
||||||
dbp.os.breakThread = tid
|
dbp.os.breakThread = tid
|
||||||
return tid, 0, nil
|
return tid, 0, nil
|
||||||
|
case _MS_VC_EXCEPTION:
|
||||||
|
// This exception is sent to set the thread name in VisualC, we should
|
||||||
|
// mask it or it might crash the program.
|
||||||
|
continueStatus = _DBG_CONTINUE
|
||||||
default:
|
default:
|
||||||
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
|
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
|
||||||
}
|
}
|
||||||
@ -386,10 +388,6 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
|||||||
return th, nil
|
return th, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
||||||
return 0, nil, fmt.Errorf("not implemented: wait")
|
return 0, nil, fmt.Errorf("not implemented: wait")
|
||||||
}
|
}
|
||||||
@ -482,7 +480,7 @@ func (dbp *Process) detach(kill bool) error {
|
|||||||
return _DebugActiveProcessStop(uint32(dbp.pid))
|
return _DebugActiveProcessStop(uint32(dbp.pid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) entryPoint() (uint64, error) {
|
func (dbp *Process) EntryPoint() (uint64, error) {
|
||||||
return dbp.os.entryPoint, nil
|
return dbp.os.entryPoint, nil
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PtraceAttach executes the sys.PtraceAttach call.
|
// PtraceAttach executes the sys.PtraceAttach call.
|
||||||
@ -57,8 +57,8 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
|
|||||||
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
|
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
|
||||||
// and amd64_supply_xsave in gdb/amd64-tdep.c.html
|
// and amd64_supply_xsave in gdb/amd64-tdep.c.html
|
||||||
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture
|
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture
|
||||||
func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
|
func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) {
|
||||||
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(®set.PtraceFpRegs)), 0, 0)
|
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(®set.AMD64PtraceFpRegs)), 0, 0)
|
||||||
if err == syscall.Errno(0) || err == syscall.ENODEV {
|
if err == syscall.Errno(0) || err == syscall.ENODEV {
|
||||||
// ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
|
// ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
|
||||||
err = nil
|
err = nil
|
||||||
@ -68,8 +68,9 @@ func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
|
|||||||
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
|
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
|
||||||
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
||||||
if err != syscall.Errno(0) {
|
if err != syscall.Errno(0) {
|
||||||
if err == syscall.ENODEV {
|
if err == syscall.ENODEV || err == syscall.EIO {
|
||||||
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/derekparker/delve/issues/1022
|
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/go-delve/delve/issues/1022
|
||||||
|
// also ignore EIO, it means that we are running on an old kernel (pre 2.6.34) and PTRACE_GETREGSET is not implemented
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -78,6 +79,6 @@ func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
regset.Xsave = xstateargs[:iov.Len]
|
regset.Xsave = xstateargs[:iov.Len]
|
||||||
err = proc.LinuxX86XstateRead(regset.Xsave, false, ®set)
|
err = linutil.AMD64XstateRead(regset.Xsave, false, ®set)
|
||||||
return regset, err
|
return regset, err
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regs represents CPU registers on an AMD64 processor.
|
// Regs represents CPU registers on an AMD64 processor.
|
||||||
@ -42,7 +42,7 @@ type Regs struct {
|
|||||||
fpregs []proc.Register
|
fpregs []proc.Register
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Regs) Slice() []proc.Register {
|
func (r *Regs) Slice(floatingPoint bool) []proc.Register {
|
||||||
var regs = []struct {
|
var regs = []struct {
|
||||||
k string
|
k string
|
||||||
v uint64
|
v uint64
|
||||||
@ -78,7 +78,9 @@ func (r *Regs) Slice() []proc.Register {
|
|||||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out = append(out, r.fpregs...)
|
if floatingPoint {
|
||||||
|
out = append(out, r.fpregs...)
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
87
vendor/github.com/go-delve/delve/pkg/proc/native/registers_linux_amd64.go
generated
vendored
Normal file
87
vendor/github.com/go-delve/delve/pkg/proc/native/registers_linux_amd64.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetPC sets RIP to the value specified by 'pc'.
|
||||||
|
func (thread *Thread) SetPC(pc uint64) error {
|
||||||
|
ir, err := registers(thread, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := ir.(*linutil.AMD64Registers)
|
||||||
|
r.Regs.Rip = pc
|
||||||
|
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSP sets RSP to the value specified by 'sp'
|
||||||
|
func (thread *Thread) SetSP(sp uint64) (err error) {
|
||||||
|
var ir proc.Registers
|
||||||
|
ir, err = registers(thread, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := ir.(*linutil.AMD64Registers)
|
||||||
|
r.Regs.Rsp = sp
|
||||||
|
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (thread *Thread) SetDX(dx uint64) (err error) {
|
||||||
|
var ir proc.Registers
|
||||||
|
ir, err = registers(thread, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := ir.(*linutil.AMD64Registers)
|
||||||
|
r.Regs.Rdx = dx
|
||||||
|
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
||||||
|
var (
|
||||||
|
regs linutil.AMD64PtraceRegs
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, (*sys.PtraceRegs)(®s)) })
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := &linutil.AMD64Registers{®s, nil, nil}
|
||||||
|
if floatingPoint {
|
||||||
|
var fpregset linutil.AMD64Xstate
|
||||||
|
r.Fpregs, fpregset, err = thread.fpRegisters()
|
||||||
|
r.Fpregset = &fpregset
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_X86_XSTATE_MAX_SIZE = 2688
|
||||||
|
_NT_X86_XSTATE = 0x202
|
||||||
|
|
||||||
|
_XSAVE_HEADER_START = 512
|
||||||
|
_XSAVE_HEADER_LEN = 64
|
||||||
|
_XSAVE_EXTENDED_REGION_START = 576
|
||||||
|
_XSAVE_SSE_REGION_LEN = 416
|
||||||
|
)
|
||||||
|
|
||||||
|
func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs linutil.AMD64Xstate, err error) {
|
||||||
|
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
|
||||||
|
regs = fpregs.Decode()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
71
vendor/github.com/go-delve/delve/pkg/proc/native/registers_windows_amd64.go
generated
vendored
Normal file
71
vendor/github.com/go-delve/delve/pkg/proc/native/registers_windows_amd64.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/winutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetPC sets the RIP register to the value specified by `pc`.
|
||||||
|
func (thread *Thread) SetPC(pc uint64) error {
|
||||||
|
context := winutil.NewCONTEXT()
|
||||||
|
context.ContextFlags = _CONTEXT_ALL
|
||||||
|
|
||||||
|
err := _GetThreadContext(thread.os.hThread, context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Rip = pc
|
||||||
|
|
||||||
|
return _SetThreadContext(thread.os.hThread, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSP sets the RSP register to the value specified by `sp`.
|
||||||
|
func (thread *Thread) SetSP(sp uint64) error {
|
||||||
|
context := winutil.NewCONTEXT()
|
||||||
|
context.ContextFlags = _CONTEXT_ALL
|
||||||
|
|
||||||
|
err := _GetThreadContext(thread.os.hThread, context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Rsp = sp
|
||||||
|
|
||||||
|
return _SetThreadContext(thread.os.hThread, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (thread *Thread) SetDX(dx uint64) error {
|
||||||
|
context := winutil.NewCONTEXT()
|
||||||
|
context.ContextFlags = _CONTEXT_ALL
|
||||||
|
|
||||||
|
err := _GetThreadContext(thread.os.hThread, context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Rdx = dx
|
||||||
|
|
||||||
|
return _SetThreadContext(thread.os.hThread, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
||||||
|
context := winutil.NewCONTEXT()
|
||||||
|
|
||||||
|
context.ContextFlags = _CONTEXT_ALL
|
||||||
|
err := _GetThreadContext(thread.os.hThread, context)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var threadInfo _THREAD_BASIC_INFORMATION
|
||||||
|
status := _NtQueryInformationThread(thread.os.hThread, _ThreadBasicInformation, uintptr(unsafe.Pointer(&threadInfo)), uint32(unsafe.Sizeof(threadInfo)), nil)
|
||||||
|
if !_NT_SUCCESS(status) {
|
||||||
|
return nil, fmt.Errorf("NtQueryInformationThread failed: it returns 0x%x", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return winutil.NewAMD64Registers(context, uint64(threadInfo.TebBaseAddress), floatingPoint), nil
|
||||||
|
}
|
@ -4,6 +4,8 @@ package native
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/proc/winutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type _NTSTATUS int32
|
type _NTSTATUS int32
|
||||||
@ -97,6 +99,11 @@ func _NT_SUCCESS(x _NTSTATUS) bool {
|
|||||||
return x >= 0
|
return x >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// zsyscall_windows.go, an autogenerated file, wants to refer to the context
|
||||||
|
// structure as _CONTEXT, but we need to have it in pkg/proc/winutil.CONTEXT
|
||||||
|
// because it's also used on non-windows operating systems.
|
||||||
|
type _CONTEXT = winutil.CONTEXT
|
||||||
|
|
||||||
//sys _NtQueryInformationThread(threadHandle syscall.Handle, infoclass int32, info uintptr, infolen uint32, retlen *uint32) (status _NTSTATUS) = ntdll.NtQueryInformationThread
|
//sys _NtQueryInformationThread(threadHandle syscall.Handle, infoclass int32, info uintptr, infolen uint32, retlen *uint32) (status _NTSTATUS) = ntdll.NtQueryInformationThread
|
||||||
//sys _GetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.GetThreadContext
|
//sys _GetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.GetThreadContext
|
||||||
//sys _SetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.SetThreadContext
|
//sys _SetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.SetThreadContext
|
24
vendor/github.com/go-delve/delve/pkg/proc/native/syscall_windows_amd64.go
generated
vendored
Normal file
24
vendor/github.com/go-delve/delve/pkg/proc/native/syscall_windows_amd64.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package native
|
||||||
|
|
||||||
|
const (
|
||||||
|
_CONTEXT_AMD64 = 0x100000
|
||||||
|
_CONTEXT_CONTROL = (_CONTEXT_AMD64 | 0x1)
|
||||||
|
_CONTEXT_INTEGER = (_CONTEXT_AMD64 | 0x2)
|
||||||
|
_CONTEXT_SEGMENTS = (_CONTEXT_AMD64 | 0x4)
|
||||||
|
_CONTEXT_FLOATING_POINT = (_CONTEXT_AMD64 | 0x8)
|
||||||
|
_CONTEXT_DEBUG_REGISTERS = (_CONTEXT_AMD64 | 0x10)
|
||||||
|
_CONTEXT_FULL = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_FLOATING_POINT)
|
||||||
|
_CONTEXT_ALL = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_SEGMENTS | _CONTEXT_FLOATING_POINT | _CONTEXT_DEBUG_REGISTERS)
|
||||||
|
_CONTEXT_EXCEPTION_ACTIVE = 0x8000000
|
||||||
|
_CONTEXT_SERVICE_ACTIVE = 0x10000000
|
||||||
|
_CONTEXT_EXCEPTION_REQUEST = 0x40000000
|
||||||
|
_CONTEXT_EXCEPTION_REPORTING = 0x80000000
|
||||||
|
)
|
||||||
|
|
||||||
|
type _DEBUG_EVENT struct {
|
||||||
|
DebugEventCode uint32
|
||||||
|
ProcessId uint32
|
||||||
|
ThreadId uint32
|
||||||
|
_ uint32 // to align Union properly
|
||||||
|
U [160]byte
|
||||||
|
}
|
@ -3,7 +3,7 @@ package native
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Thread represents a single thread in the traced process
|
// Thread represents a single thread in the traced process
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WaitStatus is a synonym for the platform-specific WaitStatus
|
// WaitStatus is a synonym for the platform-specific WaitStatus
|
@ -7,7 +7,8 @@ import (
|
|||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WaitStatus sys.WaitStatus
|
type WaitStatus sys.WaitStatus
|
||||||
@ -83,21 +84,21 @@ func (t *Thread) Blocked() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
||||||
sr := savedRegs.(*Regs)
|
sr := savedRegs.(*linutil.AMD64Registers)
|
||||||
|
|
||||||
var restoreRegistersErr error
|
var restoreRegistersErr error
|
||||||
t.dbp.execPtraceFunc(func() {
|
t.dbp.execPtraceFunc(func() {
|
||||||
restoreRegistersErr = sys.PtraceSetRegs(t.ID, sr.regs)
|
restoreRegistersErr = sys.PtraceSetRegs(t.ID, (*sys.PtraceRegs)(sr.Regs))
|
||||||
if restoreRegistersErr != nil {
|
if restoreRegistersErr != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if sr.fpregset.Xsave != nil {
|
if sr.Fpregset.Xsave != nil {
|
||||||
iov := sys.Iovec{Base: &sr.fpregset.Xsave[0], Len: uint64(len(sr.fpregset.Xsave))}
|
iov := sys.Iovec{Base: &sr.Fpregset.Xsave[0], Len: uint64(len(sr.Fpregset.Xsave))}
|
||||||
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(0), uintptr(unsafe.Pointer(&sr.fpregset.PtraceFpRegs)), 0, 0)
|
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(0), uintptr(unsafe.Pointer(&sr.Fpregset.AMD64PtraceFpRegs)), 0, 0)
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
if restoreRegistersErr == syscall.Errno(0) {
|
if restoreRegistersErr == syscall.Errno(0) {
|
@ -6,7 +6,8 @@ import (
|
|||||||
|
|
||||||
sys "golang.org/x/sys/windows"
|
sys "golang.org/x/sys/windows"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/winutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WaitStatus is a synonym for the platform-specific WaitStatus
|
// WaitStatus is a synonym for the platform-specific WaitStatus
|
||||||
@ -19,7 +20,7 @@ type OSSpecificDetails struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thread) singleStep() error {
|
func (t *Thread) singleStep() error {
|
||||||
context := newCONTEXT()
|
context := winutil.NewCONTEXT()
|
||||||
context.ContextFlags = _CONTEXT_ALL
|
context.ContextFlags = _CONTEXT_ALL
|
||||||
|
|
||||||
// Set the processor TRAP flag
|
// Set the processor TRAP flag
|
||||||
@ -154,5 +155,5 @@ func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
||||||
return _SetThreadContext(t.os.hThread, savedRegs.(*Regs).context)
|
return _SetThreadContext(t.os.hThread, savedRegs.(*winutil.AMD64Registers).Context)
|
||||||
}
|
}
|
@ -19,8 +19,16 @@ var ErrNotExecutable = errors.New("not an executable file")
|
|||||||
// only possible on recorded (traced) programs.
|
// only possible on recorded (traced) programs.
|
||||||
var ErrNotRecorded = errors.New("not a recording")
|
var ErrNotRecorded = errors.New("not a recording")
|
||||||
|
|
||||||
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
|
const (
|
||||||
const UnrecoveredPanic = "unrecovered-panic"
|
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
|
||||||
|
UnrecoveredPanic = "unrecovered-panic"
|
||||||
|
|
||||||
|
// FatalThrow is the name given to the breakpoint triggered when the target process dies because of a fatal runtime error
|
||||||
|
FatalThrow = "runtime-fatal-throw"
|
||||||
|
|
||||||
|
unrecoveredPanicID = -1
|
||||||
|
fatalThrowID = -2
|
||||||
|
)
|
||||||
|
|
||||||
// ErrProcessExited indicates that the process has exited and contains both
|
// ErrProcessExited indicates that the process has exited and contains both
|
||||||
// process id and exit status.
|
// process id and exit status.
|
||||||
@ -41,6 +49,31 @@ func (pe ProcessDetachedError) Error() string {
|
|||||||
return "detached from the process"
|
return "detached from the process"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostInitializationSetup handles all of the initialization procedures
|
||||||
|
// that must happen after Delve creates or attaches to a process.
|
||||||
|
func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
|
||||||
|
entryPoint, err := p.EntryPoint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
|
||||||
|
if err == nil {
|
||||||
|
err = p.BinInfo().LoadError()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g, _ := GetG(p.CurrentThread())
|
||||||
|
p.SetSelectedGoroutine(g)
|
||||||
|
|
||||||
|
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
|
||||||
|
createFatalThrowBreakpoint(p, writeBreakpoint)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FindFileLocation returns the PC for a given file:line.
|
// FindFileLocation returns the PC for a given file:line.
|
||||||
// Assumes that `file` is normalized to lower case and '/' on Windows.
|
// Assumes that `file` is normalized to lower case and '/' on Windows.
|
||||||
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
|
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
|
||||||
@ -69,7 +102,7 @@ func (err *ErrFunctionNotFound) Error() string {
|
|||||||
// If lineOffset is passed FindFunctionLocation will return the address of that line
|
// If lineOffset is passed FindFunctionLocation will return the address of that line
|
||||||
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
|
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
|
||||||
// Note that setting breakpoints at that address will cause surprising behavior:
|
// Note that setting breakpoints at that address will cause surprising behavior:
|
||||||
// https://github.com/derekparker/delve/issues/170
|
// https://github.com/go-delve/delve/issues/170
|
||||||
func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) {
|
func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) {
|
||||||
bi := p.BinInfo()
|
bi := p.BinInfo()
|
||||||
origfn := bi.LookupFunc[funcName]
|
origfn := bi.LookupFunc[funcName]
|
||||||
@ -88,6 +121,33 @@ func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset
|
|||||||
return origfn.Entry, nil
|
return origfn.Entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FunctionReturnLocations will return a list of addresses corresponding
|
||||||
|
// to 'ret' or 'call runtime.deferreturn'.
|
||||||
|
func FunctionReturnLocations(p Process, funcName string) ([]uint64, error) {
|
||||||
|
const deferReturn = "runtime.deferreturn"
|
||||||
|
|
||||||
|
g := p.SelectedGoroutine()
|
||||||
|
fn, ok := p.BinInfo().LookupFunc[funcName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unable to find function %s", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
instructions, err := Disassemble(p, g, fn.Entry, fn.End)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrs []uint64
|
||||||
|
for _, instruction := range instructions {
|
||||||
|
if instruction.IsRet() {
|
||||||
|
addrs = append(addrs, instruction.Loc.PC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrs = append(addrs, findDeferReturnCalls(instructions)...)
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Next continues execution until the next source line.
|
// Next continues execution until the next source line.
|
||||||
func Next(dbp Process) (err error) {
|
func Next(dbp Process) (err error) {
|
||||||
if _, err := dbp.Valid(); err != nil {
|
if _, err := dbp.Valid(); err != nil {
|
||||||
@ -184,7 +244,8 @@ func Continue(dbp Process) error {
|
|||||||
return conditionErrors(threads)
|
return conditionErrors(threads)
|
||||||
}
|
}
|
||||||
case curbp.Active && curbp.Internal:
|
case curbp.Active && curbp.Internal:
|
||||||
if curbp.Kind == StepBreakpoint {
|
switch curbp.Kind {
|
||||||
|
case StepBreakpoint:
|
||||||
// See description of proc.(*Process).next for the meaning of StepBreakpoints
|
// See description of proc.(*Process).next for the meaning of StepBreakpoints
|
||||||
if err := conditionErrors(threads); err != nil {
|
if err := conditionErrors(threads); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -204,7 +265,7 @@ func Continue(dbp Process) error {
|
|||||||
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
|
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
|
||||||
if err := dbp.ClearInternalBreakpoints(); err != nil {
|
if err := dbp.ClearInternalBreakpoints(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -281,7 +342,7 @@ func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string)
|
|||||||
if g := dbp.SelectedGoroutine(); g != nil {
|
if g := dbp.SelectedGoroutine(); g != nil {
|
||||||
g.CurrentLoc = *loc
|
g.CurrentLoc = *loc
|
||||||
}
|
}
|
||||||
return nil
|
return curthread.SetCurrentBreakpoint()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -436,14 +497,22 @@ func StepOut(dbp Process) error {
|
|||||||
return Continue(dbp)
|
return Continue(dbp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoroutinesInfo returns an array of G structures representing the information
|
// GoroutinesInfo searches for goroutines starting at index 'start', and
|
||||||
// Delve cares about from the internal runtime G structure.
|
// returns an array of up to 'count' (or all found elements, if 'count' is 0)
|
||||||
func GoroutinesInfo(dbp Process) ([]*G, error) {
|
// G structures representing the information Delve care about from the internal
|
||||||
|
// runtime G structure.
|
||||||
|
// GoroutinesInfo also returns the next index to be used as 'start' argument
|
||||||
|
// while scanning for all available goroutines, or -1 if there was an error
|
||||||
|
// or if the index already reached the last possible value.
|
||||||
|
func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
||||||
if _, err := dbp.Valid(); err != nil {
|
if _, err := dbp.Valid(); err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
if dbp.Common().allGCache != nil {
|
if dbp.Common().allGCache != nil {
|
||||||
return dbp.Common().allGCache, nil
|
// We can't use the cached array to fulfill a subrange request
|
||||||
|
if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) {
|
||||||
|
return dbp.Common().allGCache, -1, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -465,12 +534,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
|||||||
|
|
||||||
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
|
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
allglenBytes := make([]byte, 8)
|
allglenBytes := make([]byte, 8)
|
||||||
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
|
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
||||||
|
|
||||||
@ -480,17 +549,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
|||||||
// try old name (pre Go 1.6)
|
// try old name (pre Go 1.6)
|
||||||
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
|
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
|
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
|
||||||
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
|
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
allgptr := binary.LittleEndian.Uint64(faddr)
|
allgptr := binary.LittleEndian.Uint64(faddr)
|
||||||
|
|
||||||
for i := uint64(0); i < allglen; i++ {
|
for i := uint64(start); i < allglen; i++ {
|
||||||
|
if count != 0 && len(allg) >= count {
|
||||||
|
return allg, int(i), nil
|
||||||
|
}
|
||||||
gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
|
gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
allg = append(allg, &G{Unreadable: err})
|
allg = append(allg, &G{Unreadable: err})
|
||||||
@ -504,7 +576,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
|||||||
if thg, allocated := threadg[g.ID]; allocated {
|
if thg, allocated := threadg[g.ID]; allocated {
|
||||||
loc, err := thg.Thread.Location()
|
loc, err := thg.Thread.Location()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
g.Thread = thg.Thread
|
g.Thread = thg.Thread
|
||||||
// Prefer actual thread location information.
|
// Prefer actual thread location information.
|
||||||
@ -515,36 +587,78 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
|||||||
allg = append(allg, g)
|
allg = append(allg, g)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbp.Common().allGCache = allg
|
if start == 0 {
|
||||||
|
dbp.Common().allGCache = allg
|
||||||
|
}
|
||||||
|
|
||||||
return allg, nil
|
return allg, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindGoroutine returns a G struct representing the goroutine
|
// FindGoroutine returns a G struct representing the goroutine
|
||||||
// specified by `gid`.
|
// specified by `gid`.
|
||||||
func FindGoroutine(dbp Process, gid int) (*G, error) {
|
func FindGoroutine(dbp Process, gid int) (*G, error) {
|
||||||
if gid == -1 {
|
if selg := dbp.SelectedGoroutine(); (gid == -1) || (selg != nil && selg.ID == gid) || (selg == nil && gid == 0) {
|
||||||
return dbp.SelectedGoroutine(), nil
|
// Return the currently selected goroutine in the following circumstances:
|
||||||
|
//
|
||||||
|
// 1. if the caller asks for gid == -1 (because that's what a goroutine ID of -1 means in our API).
|
||||||
|
// 2. if gid == selg.ID.
|
||||||
|
// this serves two purposes: (a) it's an optimizations that allows us
|
||||||
|
// to avoid reading any other goroutine and, more importantly, (b) we
|
||||||
|
// could be reading an incorrect value for the goroutine ID of a thread.
|
||||||
|
// This condition usually happens when a goroutine calls runtime.clone
|
||||||
|
// and for a short period of time two threads will appear to be running
|
||||||
|
// the same goroutine.
|
||||||
|
// 3. if the caller asks for gid == 0 and the selected goroutine is
|
||||||
|
// either 0 or nil.
|
||||||
|
// Goroutine 0 is special, it either means we have no current goroutine
|
||||||
|
// (for example, running C code), or that we are running on a speical
|
||||||
|
// stack (system stack, signal handling stack) and we didn't properly
|
||||||
|
// detect it.
|
||||||
|
// Since there could be multiple goroutines '0' running simultaneously
|
||||||
|
// if the user requests it return the one that's already selected or
|
||||||
|
// nil if there isn't a selected goroutine.
|
||||||
|
return selg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gs, err := GoroutinesInfo(dbp)
|
if gid == 0 {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
for i := range gs {
|
|
||||||
if gs[i].ID == gid {
|
// Calling GoroutinesInfo could be slow if there are many goroutines
|
||||||
if gs[i].Unreadable != nil {
|
// running, check if a running goroutine has been requested first.
|
||||||
return nil, gs[i].Unreadable
|
for _, thread := range dbp.ThreadList() {
|
||||||
}
|
g, _ := GetG(thread)
|
||||||
return gs[i], nil
|
if g != nil && g.ID == gid {
|
||||||
|
return g, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goroutinesInfoLimit = 10
|
||||||
|
nextg := 0
|
||||||
|
for nextg >= 0 {
|
||||||
|
var gs []*G
|
||||||
|
var err error
|
||||||
|
gs, nextg, err = GoroutinesInfo(dbp, nextg, goroutinesInfoLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range gs {
|
||||||
|
if gs[i].ID == gid {
|
||||||
|
if gs[i].Unreadable != nil {
|
||||||
|
return nil, gs[i].Unreadable
|
||||||
|
}
|
||||||
|
return gs[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertEvalScope returns a new EvalScope in the context of the
|
// ConvertEvalScope returns a new EvalScope in the context of the
|
||||||
// specified goroutine ID and stack frame.
|
// specified goroutine ID and stack frame.
|
||||||
func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
|
// If deferCall is > 0 the eval scope will be relative to the specified deferred call.
|
||||||
|
func ConvertEvalScope(dbp Process, gid, frame, deferCall int) (*EvalScope, error) {
|
||||||
if _, err := dbp.Valid(); err != nil {
|
if _, err := dbp.Valid(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -564,7 +678,7 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
|
|||||||
thread = g.Thread
|
thread = g.Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
locs, err := g.Stacktrace(frame+1, false)
|
locs, err := g.Stacktrace(frame+1, deferCall > 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -573,6 +687,19 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
|
|||||||
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
|
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if deferCall > 0 {
|
||||||
|
if deferCall-1 >= len(locs[frame].Defers) {
|
||||||
|
return nil, fmt.Errorf("Frame %d only has %d deferred calls", frame, len(locs[frame].Defers))
|
||||||
|
}
|
||||||
|
|
||||||
|
d := locs[frame].Defers[deferCall-1]
|
||||||
|
if d.Unreadable != nil {
|
||||||
|
return nil, d.Unreadable
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.EvalScope(ct)
|
||||||
|
}
|
||||||
|
|
||||||
return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
|
return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,21 +733,30 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
|
// createUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
|
||||||
// This function is meant to be called by implementations of the Process interface.
|
// This function is meant to be called by implementations of the Process interface.
|
||||||
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) {
|
func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
||||||
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
||||||
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
|
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
|
||||||
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
|
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint)
|
bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpc, writeBreakpoint)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp.Name = UnrecoveredPanic
|
bp.Name = UnrecoveredPanic
|
||||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
bp.Variables = []string{"runtime.curg._panic.arg"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFatalThrowBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
||||||
|
fatalpc, err := FindFunctionLocation(p, "runtime.fatalthrow", true, 0)
|
||||||
|
if err == nil {
|
||||||
|
bp, err := p.Breakpoints().SetWithID(fatalThrowID, fatalpc, writeBreakpoint)
|
||||||
|
if err == nil {
|
||||||
|
bp.Name = FatalThrow
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FirstPCAfterPrologue returns the address of the first
|
// FirstPCAfterPrologue returns the address of the first
|
@ -23,7 +23,7 @@ type Registers interface {
|
|||||||
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
|
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
|
||||||
GAddr() (uint64, bool)
|
GAddr() (uint64, bool)
|
||||||
Get(int) (uint64, error)
|
Get(int) (uint64, error)
|
||||||
Slice() []Register
|
Slice(floatingPoint bool) []Register
|
||||||
// Copy returns a copy of the registers that is guaranteed not to change
|
// Copy returns a copy of the registers that is guaranteed not to change
|
||||||
// when the registers of the associated thread change.
|
// when the registers of the associated thread change.
|
||||||
Copy() Registers
|
Copy() Registers
|
||||||
@ -250,99 +250,3 @@ func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
|
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
|
|
||||||
type PtraceFpRegs struct {
|
|
||||||
Cwd uint16
|
|
||||||
Swd uint16
|
|
||||||
Ftw uint16
|
|
||||||
Fop uint16
|
|
||||||
Rip uint64
|
|
||||||
Rdp uint64
|
|
||||||
Mxcsr uint32
|
|
||||||
MxcrMask uint32
|
|
||||||
StSpace [32]uint32
|
|
||||||
XmmSpace [256]byte
|
|
||||||
Padding [24]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinuxX86Xstate represents amd64 XSAVE area. See Section 13.1 (and
|
|
||||||
// following) of Intel® 64 and IA-32 Architectures Software Developer’s
|
|
||||||
// Manual, Volume 1: Basic Architecture.
|
|
||||||
type LinuxX86Xstate struct {
|
|
||||||
PtraceFpRegs
|
|
||||||
Xsave []byte // raw xsave area
|
|
||||||
AvxState bool // contains AVX state
|
|
||||||
YmmSpace [256]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode decodes an XSAVE area to a list of name/value pairs of registers.
|
|
||||||
func (xsave *LinuxX86Xstate) Decode() (regs []Register) {
|
|
||||||
// x87 registers
|
|
||||||
regs = AppendWordReg(regs, "CW", xsave.Cwd)
|
|
||||||
regs = AppendWordReg(regs, "SW", xsave.Swd)
|
|
||||||
regs = AppendWordReg(regs, "TW", xsave.Ftw)
|
|
||||||
regs = AppendWordReg(regs, "FOP", xsave.Fop)
|
|
||||||
regs = AppendQwordReg(regs, "FIP", xsave.Rip)
|
|
||||||
regs = AppendQwordReg(regs, "FDP", xsave.Rdp)
|
|
||||||
|
|
||||||
for i := 0; i < len(xsave.StSpace); i += 4 {
|
|
||||||
regs = AppendX87Reg(regs, i/4, uint16(xsave.StSpace[i+2]), uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSE registers
|
|
||||||
regs = AppendMxcsrReg(regs, "MXCSR", uint64(xsave.Mxcsr))
|
|
||||||
regs = AppendDwordReg(regs, "MXCSR_MASK", xsave.MxcrMask)
|
|
||||||
|
|
||||||
for i := 0; i < len(xsave.XmmSpace); i += 16 {
|
|
||||||
regs = AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16])
|
|
||||||
if xsave.AvxState {
|
|
||||||
regs = AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
_XSAVE_HEADER_START = 512
|
|
||||||
_XSAVE_HEADER_LEN = 64
|
|
||||||
_XSAVE_EXTENDED_REGION_START = 576
|
|
||||||
_XSAVE_SSE_REGION_LEN = 416
|
|
||||||
)
|
|
||||||
|
|
||||||
// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset.
|
|
||||||
// If readLegacy is true regset.PtraceFpRegs will be filled with the
|
|
||||||
// contents of the legacy region of the XSAVE area.
|
|
||||||
// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures
|
|
||||||
// Software Developer’s Manual, Volume 1: Basic Architecture.
|
|
||||||
func LinuxX86XstateRead(xstateargs []byte, readLegacy bool, regset *LinuxX86Xstate) error {
|
|
||||||
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if readLegacy {
|
|
||||||
rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START])
|
|
||||||
if err := binary.Read(rdr, binary.LittleEndian, ®set.PtraceFpRegs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
|
|
||||||
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
|
|
||||||
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
|
|
||||||
|
|
||||||
if xcomp_bv&(1<<63) != 0 {
|
|
||||||
// compact format not supported
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if xstate_bv&(1<<2) == 0 {
|
|
||||||
// AVX state not present
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:]
|
|
||||||
regset.AvxState = true
|
|
||||||
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -79,7 +79,7 @@ func GetDwarfRegister(regs Registers, i int) []byte {
|
|||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
if regname, ok := dwarfToName[i]; ok {
|
if regname, ok := dwarfToName[i]; ok {
|
||||||
regslice := regs.Slice()
|
regslice := regs.Slice(true)
|
||||||
for _, reg := range regslice {
|
for _, reg := range regslice {
|
||||||
if reg.Name == regname {
|
if reg.Name == regname {
|
||||||
return reg.Bytes
|
return reg.Bytes
|
@ -7,9 +7,9 @@ import (
|
|||||||
"go/constant"
|
"go/constant"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This code is partly adapted from runtime.gentraceback in
|
// This code is partly adapted from runtime.gentraceback in
|
||||||
@ -375,36 +375,21 @@ func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe {
|
|||||||
it.regs.FrameBase = it.frameBase(fn)
|
it.regs.FrameBase = it.frameBase(fn)
|
||||||
}
|
}
|
||||||
r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack, lastpc: it.pc}
|
r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack, lastpc: it.pc}
|
||||||
if !it.top {
|
r.Call = r.Current
|
||||||
fnname := ""
|
if !it.top && r.Current.Fn != nil && it.pc != r.Current.Fn.Entry {
|
||||||
if r.Current.Fn != nil {
|
// if the return address is the entry point of the function that
|
||||||
fnname = r.Current.Fn.Name
|
// contains it then this is some kind of fake return frame (for example
|
||||||
}
|
// runtime.sigreturn) that didn't actually call the current frame,
|
||||||
switch fnname {
|
// attempting to get the location of the CALL instruction would just
|
||||||
|
// obfuscate what's going on, since there is no CALL instruction.
|
||||||
|
switch r.Current.Fn.Name {
|
||||||
case "runtime.mstart", "runtime.systemstack_switch":
|
case "runtime.mstart", "runtime.systemstack_switch":
|
||||||
// these frames are inserted by runtime.systemstack and there is no CALL
|
// these frames are inserted by runtime.systemstack and there is no CALL
|
||||||
// instruction to look for at pc - 1
|
// instruction to look for at pc - 1
|
||||||
r.Call = r.Current
|
|
||||||
default:
|
default:
|
||||||
if r.Current.Fn != nil && it.pc == r.Current.Fn.Entry {
|
r.lastpc = it.pc - 1
|
||||||
// if the return address is the entry point of the function that
|
r.Call.File, r.Call.Line = r.Current.Fn.cu.lineInfo.PCToLine(r.Current.Fn.Entry, it.pc-1)
|
||||||
// contains it then this is some kind of fake return frame (for example
|
|
||||||
// runtime.sigreturn) that didn't actually call the current frame,
|
|
||||||
// attempting to get the location of the CALL instruction would just
|
|
||||||
// obfuscate what's going on, since there is no CALL instruction.
|
|
||||||
r.Call = r.Current
|
|
||||||
} else {
|
|
||||||
r.lastpc = it.pc - 1
|
|
||||||
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1)
|
|
||||||
if r.Call.Fn == nil {
|
|
||||||
r.Call.File = "?"
|
|
||||||
r.Call.Line = -1
|
|
||||||
}
|
|
||||||
r.Call.PC = r.Current.PC
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
r.Call = r.Current
|
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@ -594,6 +579,7 @@ type Defer struct {
|
|||||||
DeferPC uint64 // PC address of instruction that added this defer
|
DeferPC uint64 // PC address of instruction that added this defer
|
||||||
SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space)
|
SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space)
|
||||||
link *Defer // Next deferred function
|
link *Defer // Next deferred function
|
||||||
|
argSz int64
|
||||||
|
|
||||||
variable *Variable
|
variable *Variable
|
||||||
Unreadable error
|
Unreadable error
|
||||||
@ -644,7 +630,7 @@ func (g *G) readDefers(frames []Stackframe) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Defer) load() {
|
func (d *Defer) load() {
|
||||||
d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1})
|
d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
|
||||||
if d.variable.Unreadable != nil {
|
if d.variable.Unreadable != nil {
|
||||||
d.Unreadable = d.variable.Unreadable
|
d.Unreadable = d.variable.Unreadable
|
||||||
return
|
return
|
||||||
@ -660,6 +646,7 @@ func (d *Defer) load() {
|
|||||||
|
|
||||||
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value)
|
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value)
|
||||||
d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value)
|
d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value)
|
||||||
|
d.argSz, _ = constant.Int64Val(d.variable.fieldVariable("siz").Value)
|
||||||
|
|
||||||
linkvar := d.variable.fieldVariable("link").maybeDereference()
|
linkvar := d.variable.fieldVariable("link").maybeDereference()
|
||||||
if linkvar.Addr != 0 {
|
if linkvar.Addr != 0 {
|
||||||
@ -685,3 +672,40 @@ func (d *Defer) Next() *Defer {
|
|||||||
}
|
}
|
||||||
return d.link
|
return d.link
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EvalScope returns an EvalScope relative to the argument frame of this deferred call.
|
||||||
|
// The argument frame of a deferred call is stored in memory immediately
|
||||||
|
// after the deferred header.
|
||||||
|
func (d *Defer) EvalScope(thread Thread) (*EvalScope, error) {
|
||||||
|
scope, err := GoroutineScope(thread)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get scope: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bi := thread.BinInfo()
|
||||||
|
scope.PC = d.DeferredPC
|
||||||
|
scope.File, scope.Line, scope.Fn = bi.PCToLine(d.DeferredPC)
|
||||||
|
|
||||||
|
if scope.Fn == nil {
|
||||||
|
return nil, fmt.Errorf("could not find function at %#x", d.DeferredPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The arguments are stored immediately after the defer header struct, i.e.
|
||||||
|
// addr+sizeof(_defer). Since CFA in go is always the address of the first
|
||||||
|
// argument, that's what we use for the value of CFA.
|
||||||
|
// For SP we use CFA minus the size of one pointer because that would be
|
||||||
|
// the space occupied by pushing the return address on the stack during the
|
||||||
|
// CALL.
|
||||||
|
scope.Regs.CFA = (int64(d.variable.Addr) + d.variable.RealType.Common().ByteSize)
|
||||||
|
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = uint64(scope.Regs.CFA - int64(bi.Arch.PtrSize()))
|
||||||
|
|
||||||
|
bi.dwarfReader.Seek(scope.Fn.offset)
|
||||||
|
e, err := bi.dwarfReader.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read DWARF function entry: %v", err)
|
||||||
|
}
|
||||||
|
scope.Regs.FrameBase, _, _, _ = bi.Location(e, dwarf.AttrFrameBase, scope.PC, scope.Regs)
|
||||||
|
scope.Mem = cacheMemory(scope.Mem, uintptr(scope.Regs.CFA), int(d.argSz))
|
||||||
|
|
||||||
|
return scope, nil
|
||||||
|
}
|
@ -10,8 +10,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Thread represents a thread.
|
// Thread represents a thread.
|
||||||
@ -225,15 +225,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !csource {
|
if !csource {
|
||||||
deferreturns := []uint64{}
|
deferreturns := findDeferReturnCalls(text)
|
||||||
|
|
||||||
// Find all runtime.deferreturn locations in the function
|
|
||||||
// See documentation of Breakpoint.DeferCond for why this is necessary
|
|
||||||
for _, instr := range text {
|
|
||||||
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == "runtime.deferreturn" {
|
|
||||||
deferreturns = append(deferreturns, instr.Loc.PC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set breakpoint on the most recently deferred function (if any)
|
// Set breakpoint on the most recently deferred function (if any)
|
||||||
var deferpc uint64
|
var deferpc uint64
|
||||||
@ -333,6 +325,20 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findDeferReturnCalls(text []AsmInstruction) []uint64 {
|
||||||
|
const deferreturn = "runtime.deferreturn"
|
||||||
|
deferreturns := []uint64{}
|
||||||
|
|
||||||
|
// Find all runtime.deferreturn locations in the function
|
||||||
|
// See documentation of Breakpoint.DeferCond for why this is necessary
|
||||||
|
for _, instr := range text {
|
||||||
|
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == deferreturn {
|
||||||
|
deferreturns = append(deferreturns, instr.Loc.PC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deferreturns
|
||||||
|
}
|
||||||
|
|
||||||
// Removes instructions belonging to inlined calls of topframe from pcs.
|
// Removes instructions belonging to inlined calls of topframe from pcs.
|
||||||
// If includeCurrentFn is true it will also remove all instructions
|
// If includeCurrentFn is true it will also remove all instructions
|
||||||
// belonging to the current function.
|
// belonging to the current function.
|
||||||
@ -373,21 +379,16 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
|
|||||||
|
|
||||||
instr := text[0]
|
instr := text[0]
|
||||||
|
|
||||||
if instr.DestLoc == nil || instr.DestLoc.Fn == nil {
|
if instr.DestLoc == nil {
|
||||||
|
// Call destination couldn't be resolved because this was not the
|
||||||
|
// current instruction, therefore the step-into breakpoint can not be set.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := instr.DestLoc.Fn
|
fn := instr.DestLoc.Fn
|
||||||
|
|
||||||
// Ensure PC and Entry match, otherwise StepInto is likely to set
|
|
||||||
// its breakpoint before DestLoc.PC and hence run too far ahead.
|
|
||||||
// Calls to runtime.duffzero and duffcopy have this problem.
|
|
||||||
if fn.Entry != instr.DestLoc.PC {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip unexported runtime functions
|
// Skip unexported runtime functions
|
||||||
if strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
|
if fn != nil && strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,8 +396,19 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
|
|||||||
// or entire packages from being stepped into with 'step'
|
// or entire packages from being stepped into with 'step'
|
||||||
// those extra checks should be done here.
|
// those extra checks should be done here.
|
||||||
|
|
||||||
|
pc := instr.DestLoc.PC
|
||||||
|
|
||||||
|
// We want to skip the function prologue but we should only do it if the
|
||||||
|
// destination address of the CALL instruction is the entry point of the
|
||||||
|
// function.
|
||||||
|
// Calls to runtime.duffzero and duffcopy inserted by the compiler can
|
||||||
|
// sometimes point inside the body of those functions, well after the
|
||||||
|
// prologue.
|
||||||
|
if fn != nil && fn.Entry == instr.DestLoc.PC {
|
||||||
|
pc, _ = FirstPCAfterPrologue(dbp, fn, false)
|
||||||
|
}
|
||||||
|
|
||||||
// Set a breakpoint after the function's prologue
|
// Set a breakpoint after the function's prologue
|
||||||
pc, _ := FirstPCAfterPrologue(dbp, fn, false)
|
|
||||||
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
|
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
|
||||||
if _, ok := err.(BreakpointExistsError); !ok {
|
if _, ok := err.(BreakpointExistsError); !ok {
|
||||||
return err
|
return err
|
||||||
@ -465,6 +477,10 @@ func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
|
|||||||
// In order to get around all this craziness, we read the address of the G structure for
|
// In order to get around all this craziness, we read the address of the G structure for
|
||||||
// the current thread from the thread local storage area.
|
// the current thread from the thread local storage area.
|
||||||
func GetG(thread Thread) (*G, error) {
|
func GetG(thread Thread) (*G, error) {
|
||||||
|
if loc, _ := thread.Location(); loc.Fn != nil && loc.Fn.Name == "runtime.clone" {
|
||||||
|
// When threads are executing runtime.clone the value of TLS is unreliable.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
gaddr, err := getGVariable(thread)
|
gaddr, err := getGVariable(thread)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -18,12 +17,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/line"
|
"github.com/go-delve/delve/pkg/dwarf/line"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
"github.com/derekparker/delve/pkg/goversion"
|
"github.com/go-delve/delve/pkg/goversion"
|
||||||
"github.com/derekparker/delve/pkg/logflags"
|
"github.com/go-delve/delve/pkg/logflags"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -180,7 +179,7 @@ func (v functionsDebugInfoByEntry) Swap(i, j int) { v[i], v[j] = v[j], v[i]
|
|||||||
type compileUnitsByLowpc []*compileUnit
|
type compileUnitsByLowpc []*compileUnit
|
||||||
|
|
||||||
func (v compileUnitsByLowpc) Len() int { return len(v) }
|
func (v compileUnitsByLowpc) Len() int { return len(v) }
|
||||||
func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].LowPC < v[j].LowPC }
|
func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].lowPC < v[j].lowPC }
|
||||||
func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
|
func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
|
||||||
|
|
||||||
type packageVarsByAddr []packageVar
|
type packageVarsByAddr []packageVar
|
||||||
@ -230,18 +229,18 @@ outer:
|
|||||||
if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
|
if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
|
||||||
cu.isgo = true
|
cu.isgo = true
|
||||||
}
|
}
|
||||||
cu.Name, _ = entry.Val(dwarf.AttrName).(string)
|
cu.name, _ = entry.Val(dwarf.AttrName).(string)
|
||||||
compdir, _ := entry.Val(dwarf.AttrCompDir).(string)
|
compdir, _ := entry.Val(dwarf.AttrCompDir).(string)
|
||||||
if compdir != "" {
|
if compdir != "" {
|
||||||
cu.Name = filepath.Join(compdir, cu.Name)
|
cu.name = filepath.Join(compdir, cu.name)
|
||||||
}
|
}
|
||||||
cu.Ranges, _ = bi.dwarf.Ranges(entry)
|
cu.ranges, _ = bi.dwarf.Ranges(entry)
|
||||||
for i := range cu.Ranges {
|
for i := range cu.ranges {
|
||||||
cu.Ranges[i][0] += bi.staticBase
|
cu.ranges[i][0] += bi.staticBase
|
||||||
cu.Ranges[i][1] += bi.staticBase
|
cu.ranges[i][1] += bi.staticBase
|
||||||
}
|
}
|
||||||
if len(cu.Ranges) >= 1 {
|
if len(cu.ranges) >= 1 {
|
||||||
cu.LowPC = cu.Ranges[0][0]
|
cu.lowPC = cu.ranges[0][0]
|
||||||
}
|
}
|
||||||
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
|
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
|
||||||
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
|
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
|
||||||
@ -398,6 +397,13 @@ outer:
|
|||||||
highpc = ranges[0][1] + bi.staticBase
|
highpc = ranges[0][1] + bi.staticBase
|
||||||
}
|
}
|
||||||
name, ok2 := entry.Val(dwarf.AttrName).(string)
|
name, ok2 := entry.Val(dwarf.AttrName).(string)
|
||||||
|
if !ok2 {
|
||||||
|
originOffset, hasAbstractOrigin := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
|
||||||
|
if hasAbstractOrigin {
|
||||||
|
name, ok2 = abstractOriginNameTable[originOffset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var fn Function
|
var fn Function
|
||||||
if (ok1 == !inlined) && ok2 {
|
if (ok1 == !inlined) && ok2 {
|
||||||
if inlined {
|
if inlined {
|
||||||
@ -559,45 +565,8 @@ func (bi *BinaryInfo) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.
|
|||||||
// * After go1.11 the runtimeTypeToDIE map is used to look up the address of
|
// * After go1.11 the runtimeTypeToDIE map is used to look up the address of
|
||||||
// the type and map it drectly to a DIE.
|
// the type and map it drectly to a DIE.
|
||||||
func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind int64, err error) {
|
func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind int64, err error) {
|
||||||
var go17 bool
|
|
||||||
var typestring *Variable
|
|
||||||
|
|
||||||
bi := _type.bi
|
bi := _type.bi
|
||||||
|
|
||||||
// Determine if we are in go1.7 or later
|
|
||||||
typestring, err = _type.structMember("_string")
|
|
||||||
if err == nil {
|
|
||||||
typestring = typestring.maybeDereference()
|
|
||||||
} else {
|
|
||||||
err = nil
|
|
||||||
go17 = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !go17 {
|
|
||||||
// pre-go1.7 compatibility
|
|
||||||
if typestring == nil || typestring.Addr == 0 || typestring.Kind != reflect.String {
|
|
||||||
return nil, 0, fmt.Errorf("invalid interface type")
|
|
||||||
}
|
|
||||||
typestring.loadValue(LoadConfig{false, 0, 512, 0, 0})
|
|
||||||
if typestring.Unreadable != nil {
|
|
||||||
return nil, 0, fmt.Errorf("invalid interface type: %v", typestring.Unreadable)
|
|
||||||
}
|
|
||||||
|
|
||||||
typename := constant.StringVal(typestring.Value)
|
|
||||||
|
|
||||||
t, err := parser.ParseExpr(typename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("invalid interface type, unparsable data type: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
typ, err := bi.findTypeExpr(t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, fmt.Errorf("interface type %q not found for %#x: %v", typename, dataAddr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return typ, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_type = _type.maybeDereference()
|
_type = _type.maybeDereference()
|
||||||
|
|
||||||
// go 1.11 implementation: use extended attribute in debug_info
|
// go 1.11 implementation: use extended attribute in debug_info
|
||||||
@ -880,7 +849,7 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err
|
|||||||
buf.WriteString("interface {")
|
buf.WriteString("interface {")
|
||||||
|
|
||||||
methods, _ := _type.structMember(interfacetypeFieldMhdr)
|
methods, _ := _type.structMember(interfacetypeFieldMhdr)
|
||||||
methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1})
|
methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1, 0})
|
||||||
if methods.Unreadable != nil {
|
if methods.Unreadable != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -941,7 +910,7 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error)
|
|||||||
buf.WriteString("struct {")
|
buf.WriteString("struct {")
|
||||||
|
|
||||||
fields, _ := _type.structMember("fields")
|
fields, _ := _type.structMember("fields")
|
||||||
fields.loadArrayValues(0, LoadConfig{false, 2, 0, 4096, -1})
|
fields.loadArrayValues(0, LoadConfig{false, 2, 0, 4096, -1, 0})
|
||||||
if fields.Unreadable != nil {
|
if fields.Unreadable != nil {
|
||||||
return "", fields.Unreadable
|
return "", fields.Unreadable
|
||||||
}
|
}
|
@ -15,9 +15,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -32,6 +32,8 @@ const (
|
|||||||
hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated
|
hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated
|
||||||
|
|
||||||
maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame
|
maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame
|
||||||
|
|
||||||
|
maxMapBucketsFactor = 100 // Maximum numbers of map buckets to read for every requested map entry when loading variables through (*EvalScope).LocalVariables and (*EvalScope).FunctionArguments.
|
||||||
)
|
)
|
||||||
|
|
||||||
type floatSpecial uint8
|
type floatSpecial uint8
|
||||||
@ -122,10 +124,39 @@ type LoadConfig struct {
|
|||||||
MaxArrayValues int
|
MaxArrayValues int
|
||||||
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
|
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
|
||||||
MaxStructFields int
|
MaxStructFields int
|
||||||
|
|
||||||
|
// MaxMapBuckets is the maximum number of map buckets to read before giving up.
|
||||||
|
// A value of 0 will read as many buckets as necessary until the entire map
|
||||||
|
// is read or MaxArrayValues is reached.
|
||||||
|
//
|
||||||
|
// Loading a map is an operation that issues O(num_buckets) operations.
|
||||||
|
// Normally the number of buckets is proportional to the number of elements
|
||||||
|
// in the map, since the runtime tries to keep the load factor of maps
|
||||||
|
// between 40% and 80%.
|
||||||
|
//
|
||||||
|
// It is possible, however, to create very sparse maps either by:
|
||||||
|
// a) adding lots of entries to a map and then deleting most of them, or
|
||||||
|
// b) using the make(mapType, N) expression with a very large N
|
||||||
|
//
|
||||||
|
// When this happens delve will have to scan many empty buckets to find the
|
||||||
|
// few entries in the map.
|
||||||
|
// MaxMapBuckets can be set to avoid annoying slowdowns␣while reading
|
||||||
|
// very sparse maps.
|
||||||
|
//
|
||||||
|
// Since there is no good way for a user of delve to specify the value of
|
||||||
|
// MaxMapBuckets, this field is not actually exposed through the API.
|
||||||
|
// Instead (*EvalScope).LocalVariables and (*EvalScope).FunctionArguments
|
||||||
|
// set this field automatically to MaxArrayValues * maxMapBucketsFactor.
|
||||||
|
// Every other invocation uses the default value of 0, obtaining the old behavior.
|
||||||
|
// In practice this means that debuggers using the ListLocalVars or
|
||||||
|
// ListFunctionArgs API will not experience a massive slowdown when a very
|
||||||
|
// sparse map is in scope, but evaluating a single variable will still work
|
||||||
|
// correctly, even if the variable in question is a very sparse map.
|
||||||
|
MaxMapBuckets int
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadSingleValue = LoadConfig{false, 0, 64, 0, 0}
|
var loadSingleValue = LoadConfig{false, 0, 64, 0, 0, 0}
|
||||||
var loadFullValue = LoadConfig{true, 1, 64, 64, -1}
|
var loadFullValue = LoadConfig{true, 1, 64, 64, -1, 0}
|
||||||
|
|
||||||
// G status, from: src/runtime/runtime2.go
|
// G status, from: src/runtime/runtime2.go
|
||||||
const (
|
const (
|
||||||
@ -169,6 +200,12 @@ type G struct {
|
|||||||
Unreadable error // could not read the G struct
|
Unreadable error // could not read the G struct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Ancestor struct {
|
||||||
|
ID int64 // Goroutine ID
|
||||||
|
Unreadable error
|
||||||
|
pcsVar *Variable
|
||||||
|
}
|
||||||
|
|
||||||
// EvalScope is the scope for variable evaluation. Contains the thread,
|
// EvalScope is the scope for variable evaluation. Contains the thread,
|
||||||
// current location (PC), and canonical frame address.
|
// current location (PC), and canonical frame address.
|
||||||
type EvalScope struct {
|
type EvalScope struct {
|
||||||
@ -405,13 +442,13 @@ func (scope *EvalScope) PtrSize() int {
|
|||||||
return scope.BinInfo.Arch.PtrSize()
|
return scope.BinInfo.Arch.PtrSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoGError returned when a G could not be found
|
// ErrNoGoroutine returned when a G could not be found
|
||||||
// for a specific thread.
|
// for a specific thread.
|
||||||
type NoGError struct {
|
type ErrNoGoroutine struct {
|
||||||
tid int
|
tid int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ng NoGError) Error() string {
|
func (ng ErrNoGoroutine) Error() string {
|
||||||
return fmt.Sprintf("no G executing on thread %d", ng.tid)
|
return fmt.Sprintf("no G executing on thread %d", ng.tid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,7 +470,7 @@ func (v *Variable) parseG() (*G, error) {
|
|||||||
if thread, ok := mem.(Thread); ok {
|
if thread, ok := mem.(Thread); ok {
|
||||||
id = thread.ThreadID()
|
id = thread.ThreadID()
|
||||||
}
|
}
|
||||||
return nil, NoGError{tid: id}
|
return nil, ErrNoGoroutine{tid: id}
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
if _, isptr := v.RealType.(*godwarf.PtrType); !isptr {
|
if _, isptr := v.RealType.(*godwarf.PtrType); !isptr {
|
||||||
@ -441,7 +478,7 @@ func (v *Variable) parseG() (*G, error) {
|
|||||||
}
|
}
|
||||||
v = v.maybeDereference()
|
v = v.maybeDereference()
|
||||||
}
|
}
|
||||||
v.loadValue(LoadConfig{false, 2, 64, 0, -1})
|
v.loadValue(LoadConfig{false, 2, 64, 0, -1, 0})
|
||||||
if v.Unreadable != nil {
|
if v.Unreadable != nil {
|
||||||
return nil, v.Unreadable
|
return nil, v.Unreadable
|
||||||
}
|
}
|
||||||
@ -586,12 +623,102 @@ func (g *G) StartLoc() Location {
|
|||||||
return Location{PC: g.StartPC, File: f, Line: l, Fn: fn}
|
return Location{PC: g.StartPC, File: f, Line: l, Fn: fn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errTracebackAncestorsDisabled = errors.New("tracebackancestors is disabled")
|
||||||
|
|
||||||
|
// Ancestors returns the list of ancestors for g.
|
||||||
|
func (g *G) Ancestors(n int) ([]Ancestor, error) {
|
||||||
|
scope := globalScope(g.Thread.BinInfo(), g.Thread)
|
||||||
|
tbav, err := scope.EvalExpression("runtime.debug.tracebackancestors", loadSingleValue)
|
||||||
|
if err == nil && tbav.Unreadable == nil && tbav.Kind == reflect.Int {
|
||||||
|
tba, _ := constant.Int64Val(tbav.Value)
|
||||||
|
if tba == 0 {
|
||||||
|
return nil, errTracebackAncestorsDisabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
av, err := g.variable.structMember("ancestors")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
av = av.maybeDereference()
|
||||||
|
av.loadValue(LoadConfig{MaxArrayValues: n, MaxVariableRecurse: 1, MaxStructFields: -1})
|
||||||
|
if av.Unreadable != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if av.Addr == 0 {
|
||||||
|
// no ancestors
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := make([]Ancestor, len(av.Children))
|
||||||
|
|
||||||
|
for i := range av.Children {
|
||||||
|
if av.Children[i].Unreadable != nil {
|
||||||
|
r[i].Unreadable = av.Children[i].Unreadable
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
goidv := av.Children[i].fieldVariable("goid")
|
||||||
|
if goidv.Unreadable != nil {
|
||||||
|
r[i].Unreadable = goidv.Unreadable
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r[i].ID, _ = constant.Int64Val(goidv.Value)
|
||||||
|
pcsVar := av.Children[i].fieldVariable("pcs")
|
||||||
|
if pcsVar.Unreadable != nil {
|
||||||
|
r[i].Unreadable = pcsVar.Unreadable
|
||||||
|
}
|
||||||
|
pcsVar.loaded = false
|
||||||
|
pcsVar.Children = pcsVar.Children[:0]
|
||||||
|
r[i].pcsVar = pcsVar
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack returns the stack trace of ancestor 'a' as saved by the runtime.
|
||||||
|
func (a *Ancestor) Stack(n int) ([]Stackframe, error) {
|
||||||
|
if a.Unreadable != nil {
|
||||||
|
return nil, a.Unreadable
|
||||||
|
}
|
||||||
|
pcsVar := a.pcsVar.clone()
|
||||||
|
pcsVar.loadValue(LoadConfig{MaxArrayValues: n})
|
||||||
|
if pcsVar.Unreadable != nil {
|
||||||
|
return nil, pcsVar.Unreadable
|
||||||
|
}
|
||||||
|
r := make([]Stackframe, len(pcsVar.Children))
|
||||||
|
for i := range pcsVar.Children {
|
||||||
|
if pcsVar.Children[i].Unreadable != nil {
|
||||||
|
r[i] = Stackframe{Err: pcsVar.Children[i].Unreadable}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pcsVar.Children[i].Kind != reflect.Uint {
|
||||||
|
return nil, fmt.Errorf("wrong type for pcs item %d: %v", i, pcsVar.Children[i].Kind)
|
||||||
|
}
|
||||||
|
pc, _ := constant.Int64Val(pcsVar.Children[i].Value)
|
||||||
|
fn := a.pcsVar.bi.PCToFunc(uint64(pc))
|
||||||
|
if fn == nil {
|
||||||
|
loc := Location{PC: uint64(pc)}
|
||||||
|
r[i] = Stackframe{Current: loc, Call: loc}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pc2 := uint64(pc)
|
||||||
|
if pc2-1 >= fn.Entry {
|
||||||
|
pc2--
|
||||||
|
}
|
||||||
|
f, ln := fn.cu.lineInfo.PCToLine(fn.Entry, pc2)
|
||||||
|
loc := Location{PC: uint64(pc), File: f, Line: ln, Fn: fn}
|
||||||
|
r[i] = Stackframe{Current: loc, Call: loc}
|
||||||
|
}
|
||||||
|
r[len(r)-1].Bottom = pcsVar.Len == int64(len(pcsVar.Children))
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the list of saved return addresses used by stack barriers
|
// Returns the list of saved return addresses used by stack barriers
|
||||||
func (g *G) stkbar() ([]savedLR, error) {
|
func (g *G) stkbar() ([]savedLR, error) {
|
||||||
if g.stkbarVar == nil { // stack barriers were removed in Go 1.9
|
if g.stkbarVar == nil { // stack barriers were removed in Go 1.9
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
g.stkbarVar.loadValue(LoadConfig{false, 1, 0, int(g.stkbarVar.Len), 3})
|
g.stkbarVar.loadValue(LoadConfig{false, 1, 0, int(g.stkbarVar.Len), 3, 0})
|
||||||
if g.stkbarVar.Unreadable != nil {
|
if g.stkbarVar.Unreadable != nil {
|
||||||
return nil, fmt.Errorf("unreadable stkbar: %v", g.stkbarVar.Unreadable)
|
return nil, fmt.Errorf("unreadable stkbar: %v", g.stkbarVar.Unreadable)
|
||||||
}
|
}
|
||||||
@ -658,6 +785,7 @@ func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) {
|
|||||||
vars = filterVariables(vars, func(v *Variable) bool {
|
vars = filterVariables(vars, func(v *Variable) bool {
|
||||||
return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0
|
return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0
|
||||||
})
|
})
|
||||||
|
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
|
||||||
loadValues(vars, cfg)
|
loadValues(vars, cfg)
|
||||||
return vars, nil
|
return vars, nil
|
||||||
}
|
}
|
||||||
@ -671,6 +799,7 @@ func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
|
|||||||
vars = filterVariables(vars, func(v *Variable) bool {
|
vars = filterVariables(vars, func(v *Variable) bool {
|
||||||
return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0
|
return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0
|
||||||
})
|
})
|
||||||
|
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
|
||||||
loadValues(vars, cfg)
|
loadValues(vars, cfg)
|
||||||
return vars, nil
|
return vars, nil
|
||||||
}
|
}
|
||||||
@ -875,7 +1004,11 @@ func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variabl
|
|||||||
mem := scope.Mem
|
mem := scope.Mem
|
||||||
if pieces != nil {
|
if pieces != nil {
|
||||||
addr = fakeAddress
|
addr = fakeAddress
|
||||||
mem = newCompositeMemory(scope.Mem, scope.Regs, pieces)
|
var cmem *compositeMemory
|
||||||
|
cmem, err = newCompositeMemory(scope.Mem, scope.Regs, pieces)
|
||||||
|
if cmem != nil {
|
||||||
|
mem = cmem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v := scope.newVariable(n, uintptr(addr), t, mem)
|
v := scope.newVariable(n, uintptr(addr), t, mem)
|
||||||
@ -895,6 +1028,10 @@ func (v *Variable) maybeDereference() *Variable {
|
|||||||
|
|
||||||
switch t := v.RealType.(type) {
|
switch t := v.RealType.(type) {
|
||||||
case *godwarf.PtrType:
|
case *godwarf.PtrType:
|
||||||
|
if v.Addr == 0 && len(v.Children) == 1 && v.loaded {
|
||||||
|
// fake pointer variable constructed by casting an integer to a pointer type
|
||||||
|
return &v.Children[0]
|
||||||
|
}
|
||||||
ptrval, err := readUintRaw(v.mem, uintptr(v.Addr), t.ByteSize)
|
ptrval, err := readUintRaw(v.mem, uintptr(v.Addr), t.ByteSize)
|
||||||
r := v.newVariable("", uintptr(ptrval), t.Type, DereferenceMemory(v.mem))
|
r := v.newVariable("", uintptr(ptrval), t.Type, DereferenceMemory(v.mem))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1569,6 +1706,11 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
|||||||
if it == nil {
|
if it == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
it.maxNumBuckets = uint64(cfg.MaxMapBuckets)
|
||||||
|
|
||||||
|
if v.Len == 0 || int64(v.mapSkip) >= v.Len || cfg.MaxArrayValues == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for skip := 0; skip < v.mapSkip; skip++ {
|
for skip := 0; skip < v.mapSkip; skip++ {
|
||||||
if ok := it.next(); !ok {
|
if ok := it.next(); !ok {
|
||||||
@ -1580,9 +1722,6 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
|||||||
count := 0
|
count := 0
|
||||||
errcount := 0
|
errcount := 0
|
||||||
for it.next() {
|
for it.next() {
|
||||||
if count >= cfg.MaxArrayValues {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
key := it.key()
|
key := it.key()
|
||||||
var val *Variable
|
var val *Variable
|
||||||
if it.values.fieldType.Size() > 0 {
|
if it.values.fieldType.Size() > 0 {
|
||||||
@ -1601,6 +1740,9 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
|||||||
if errcount > maxErrCount {
|
if errcount > maxErrCount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if count >= cfg.MaxArrayValues || int64(count) >= v.Len {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1618,6 +1760,8 @@ type mapIterator struct {
|
|||||||
values *Variable
|
values *Variable
|
||||||
overflow *Variable
|
overflow *Variable
|
||||||
|
|
||||||
|
maxNumBuckets uint64 // maximum number of buckets to scan
|
||||||
|
|
||||||
idx int64
|
idx int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1683,6 +1827,10 @@ func (it *mapIterator) nextBucket() bool {
|
|||||||
} else {
|
} else {
|
||||||
it.b = nil
|
it.b = nil
|
||||||
|
|
||||||
|
if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for it.bidx < it.numbuckets {
|
for it.bidx < it.numbuckets {
|
||||||
it.b = it.buckets.clone()
|
it.b = it.buckets.clone()
|
||||||
it.b.Addr += uintptr(uint64(it.buckets.DwarfType.Size()) * it.bidx)
|
it.b.Addr += uintptr(uint64(it.buckets.DwarfType.Size()) * it.bidx)
|
@ -1,4 +1,4 @@
|
|||||||
package native
|
package winutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,11 +6,11 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/arch/x86/x86asm"
|
"golang.org/x/arch/x86/x86asm"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Regs represents CPU registers on an AMD64 processor.
|
// AMD64Registers represents CPU registers on an AMD64 processor.
|
||||||
type Regs struct {
|
type AMD64Registers struct {
|
||||||
rax uint64
|
rax uint64
|
||||||
rbx uint64
|
rbx uint64
|
||||||
rcx uint64
|
rcx uint64
|
||||||
@ -33,11 +33,48 @@ type Regs struct {
|
|||||||
fs uint64
|
fs uint64
|
||||||
gs uint64
|
gs uint64
|
||||||
tls uint64
|
tls uint64
|
||||||
context *_CONTEXT
|
Context *CONTEXT
|
||||||
fltSave *_XMM_SAVE_AREA32
|
fltSave *XMM_SAVE_AREA32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Regs) Slice() []proc.Register {
|
// NewAMD64Registers creates a new AMD64Registers struct from a CONTEXT
|
||||||
|
// struct and the TEB base address of the thread.
|
||||||
|
func NewAMD64Registers(context *CONTEXT, TebBaseAddress uint64, floatingPoint bool) *AMD64Registers {
|
||||||
|
regs := &AMD64Registers{
|
||||||
|
rax: uint64(context.Rax),
|
||||||
|
rbx: uint64(context.Rbx),
|
||||||
|
rcx: uint64(context.Rcx),
|
||||||
|
rdx: uint64(context.Rdx),
|
||||||
|
rdi: uint64(context.Rdi),
|
||||||
|
rsi: uint64(context.Rsi),
|
||||||
|
rbp: uint64(context.Rbp),
|
||||||
|
rsp: uint64(context.Rsp),
|
||||||
|
r8: uint64(context.R8),
|
||||||
|
r9: uint64(context.R9),
|
||||||
|
r10: uint64(context.R10),
|
||||||
|
r11: uint64(context.R11),
|
||||||
|
r12: uint64(context.R12),
|
||||||
|
r13: uint64(context.R13),
|
||||||
|
r14: uint64(context.R14),
|
||||||
|
r15: uint64(context.R15),
|
||||||
|
rip: uint64(context.Rip),
|
||||||
|
eflags: uint64(context.EFlags),
|
||||||
|
cs: uint64(context.SegCs),
|
||||||
|
fs: uint64(context.SegFs),
|
||||||
|
gs: uint64(context.SegGs),
|
||||||
|
tls: TebBaseAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
if floatingPoint {
|
||||||
|
regs.fltSave = &context.FltSave
|
||||||
|
}
|
||||||
|
regs.Context = context
|
||||||
|
|
||||||
|
return regs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice returns the registers as a list of (name, value) pairs.
|
||||||
|
func (r *AMD64Registers) Slice(floatingPoint bool) []proc.Register {
|
||||||
var regs = []struct {
|
var regs = []struct {
|
||||||
k string
|
k string
|
||||||
v uint64
|
v uint64
|
||||||
@ -66,7 +103,7 @@ func (r *Regs) Slice() []proc.Register {
|
|||||||
{"TLS", r.tls},
|
{"TLS", r.tls},
|
||||||
}
|
}
|
||||||
outlen := len(regs)
|
outlen := len(regs)
|
||||||
if r.fltSave != nil {
|
if r.fltSave != nil && floatingPoint {
|
||||||
outlen += 6 + 8 + 2 + 16
|
outlen += 6 + 8 + 2 + 16
|
||||||
}
|
}
|
||||||
out := make([]proc.Register, 0, outlen)
|
out := make([]proc.Register, 0, outlen)
|
||||||
@ -77,7 +114,7 @@ func (r *Regs) Slice() []proc.Register {
|
|||||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.fltSave != nil {
|
if r.fltSave != nil && floatingPoint {
|
||||||
out = proc.AppendWordReg(out, "CW", r.fltSave.ControlWord)
|
out = proc.AppendWordReg(out, "CW", r.fltSave.ControlWord)
|
||||||
out = proc.AppendWordReg(out, "SW", r.fltSave.StatusWord)
|
out = proc.AppendWordReg(out, "SW", r.fltSave.StatusWord)
|
||||||
out = proc.AppendWordReg(out, "TW", uint16(r.fltSave.TagWord))
|
out = proc.AppendWordReg(out, "TW", uint16(r.fltSave.TagWord))
|
||||||
@ -101,81 +138,40 @@ func (r *Regs) Slice() []proc.Register {
|
|||||||
|
|
||||||
// PC returns the current program counter
|
// PC returns the current program counter
|
||||||
// i.e. the RIP CPU register.
|
// i.e. the RIP CPU register.
|
||||||
func (r *Regs) PC() uint64 {
|
func (r *AMD64Registers) PC() uint64 {
|
||||||
return r.rip
|
return r.rip
|
||||||
}
|
}
|
||||||
|
|
||||||
// SP returns the stack pointer location,
|
// SP returns the stack pointer location,
|
||||||
// i.e. the RSP register.
|
// i.e. the RSP register.
|
||||||
func (r *Regs) SP() uint64 {
|
func (r *AMD64Registers) SP() uint64 {
|
||||||
return r.rsp
|
return r.rsp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Regs) BP() uint64 {
|
func (r *AMD64Registers) BP() uint64 {
|
||||||
return r.rbp
|
return r.rbp
|
||||||
}
|
}
|
||||||
|
|
||||||
// CX returns the value of the RCX register.
|
// CX returns the value of the RCX register.
|
||||||
func (r *Regs) CX() uint64 {
|
func (r *AMD64Registers) CX() uint64 {
|
||||||
return r.rcx
|
return r.rcx
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS returns the value of the register
|
// TLS returns the value of the register
|
||||||
// that contains the location of the thread
|
// that contains the location of the thread
|
||||||
// local storage segment.
|
// local storage segment.
|
||||||
func (r *Regs) TLS() uint64 {
|
func (r *AMD64Registers) TLS() uint64 {
|
||||||
return r.tls
|
return r.tls
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Regs) GAddr() (uint64, bool) {
|
// GAddr returns the address of the G variable if it is known, 0 and false
|
||||||
|
// otherwise.
|
||||||
|
func (r *AMD64Registers) GAddr() (uint64, bool) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPC sets the RIP register to the value specified by `pc`.
|
// Get returns the value of the n-th register (in x86asm order).
|
||||||
func (thread *Thread) SetPC(pc uint64) error {
|
func (r *AMD64Registers) Get(n int) (uint64, error) {
|
||||||
context := newCONTEXT()
|
|
||||||
context.ContextFlags = _CONTEXT_ALL
|
|
||||||
|
|
||||||
err := _GetThreadContext(thread.os.hThread, context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Rip = pc
|
|
||||||
|
|
||||||
return _SetThreadContext(thread.os.hThread, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSP sets the RSP register to the value specified by `sp`.
|
|
||||||
func (thread *Thread) SetSP(sp uint64) error {
|
|
||||||
context := newCONTEXT()
|
|
||||||
context.ContextFlags = _CONTEXT_ALL
|
|
||||||
|
|
||||||
err := _GetThreadContext(thread.os.hThread, context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Rsp = sp
|
|
||||||
|
|
||||||
return _SetThreadContext(thread.os.hThread, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (thread *Thread) SetDX(dx uint64) error {
|
|
||||||
context := newCONTEXT()
|
|
||||||
context.ContextFlags = _CONTEXT_ALL
|
|
||||||
|
|
||||||
err := _GetThreadContext(thread.os.hThread, context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Rdx = dx
|
|
||||||
|
|
||||||
return _SetThreadContext(thread.os.hThread, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Regs) Get(n int) (uint64, error) {
|
|
||||||
reg := x86asm.Reg(n)
|
reg := x86asm.Reg(n)
|
||||||
const (
|
const (
|
||||||
mask8 = 0x000f
|
mask8 = 0x000f
|
||||||
@ -332,59 +328,103 @@ func (r *Regs) Get(n int) (uint64, error) {
|
|||||||
return 0, proc.ErrUnknownRegister
|
return 0, proc.ErrUnknownRegister
|
||||||
}
|
}
|
||||||
|
|
||||||
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
// Copy returns a copy of these registers that is guarenteed not to change.
|
||||||
context := newCONTEXT()
|
func (r *AMD64Registers) Copy() proc.Registers {
|
||||||
|
var rr AMD64Registers
|
||||||
context.ContextFlags = _CONTEXT_ALL
|
|
||||||
err := _GetThreadContext(thread.os.hThread, context)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var threadInfo _THREAD_BASIC_INFORMATION
|
|
||||||
status := _NtQueryInformationThread(thread.os.hThread, _ThreadBasicInformation, uintptr(unsafe.Pointer(&threadInfo)), uint32(unsafe.Sizeof(threadInfo)), nil)
|
|
||||||
if !_NT_SUCCESS(status) {
|
|
||||||
return nil, fmt.Errorf("NtQueryInformationThread failed: it returns 0x%x", status)
|
|
||||||
}
|
|
||||||
|
|
||||||
regs := &Regs{
|
|
||||||
rax: uint64(context.Rax),
|
|
||||||
rbx: uint64(context.Rbx),
|
|
||||||
rcx: uint64(context.Rcx),
|
|
||||||
rdx: uint64(context.Rdx),
|
|
||||||
rdi: uint64(context.Rdi),
|
|
||||||
rsi: uint64(context.Rsi),
|
|
||||||
rbp: uint64(context.Rbp),
|
|
||||||
rsp: uint64(context.Rsp),
|
|
||||||
r8: uint64(context.R8),
|
|
||||||
r9: uint64(context.R9),
|
|
||||||
r10: uint64(context.R10),
|
|
||||||
r11: uint64(context.R11),
|
|
||||||
r12: uint64(context.R12),
|
|
||||||
r13: uint64(context.R13),
|
|
||||||
r14: uint64(context.R14),
|
|
||||||
r15: uint64(context.R15),
|
|
||||||
rip: uint64(context.Rip),
|
|
||||||
eflags: uint64(context.EFlags),
|
|
||||||
cs: uint64(context.SegCs),
|
|
||||||
fs: uint64(context.SegFs),
|
|
||||||
gs: uint64(context.SegGs),
|
|
||||||
tls: uint64(threadInfo.TebBaseAddress),
|
|
||||||
}
|
|
||||||
|
|
||||||
if floatingPoint {
|
|
||||||
regs.fltSave = &context.FltSave
|
|
||||||
}
|
|
||||||
regs.context = context
|
|
||||||
|
|
||||||
return regs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Regs) Copy() proc.Registers {
|
|
||||||
var rr Regs
|
|
||||||
rr = *r
|
rr = *r
|
||||||
rr.context = newCONTEXT()
|
rr.Context = NewCONTEXT()
|
||||||
*(rr.context) = *(r.context)
|
*(rr.Context) = *(r.Context)
|
||||||
rr.fltSave = &rr.context.FltSave
|
rr.fltSave = &rr.Context.FltSave
|
||||||
return &rr
|
return &rr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// M128A tracks the _M128A windows struct.
|
||||||
|
type M128A struct {
|
||||||
|
Low uint64
|
||||||
|
High int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMM_SAVE_AREA32 tracks the _XMM_SAVE_AREA32 windows struct.
|
||||||
|
type XMM_SAVE_AREA32 struct {
|
||||||
|
ControlWord uint16
|
||||||
|
StatusWord uint16
|
||||||
|
TagWord byte
|
||||||
|
Reserved1 byte
|
||||||
|
ErrorOpcode uint16
|
||||||
|
ErrorOffset uint32
|
||||||
|
ErrorSelector uint16
|
||||||
|
Reserved2 uint16
|
||||||
|
DataOffset uint32
|
||||||
|
DataSelector uint16
|
||||||
|
Reserved3 uint16
|
||||||
|
MxCsr uint32
|
||||||
|
MxCsr_Mask uint32
|
||||||
|
FloatRegisters [8]M128A
|
||||||
|
XmmRegisters [256]byte
|
||||||
|
Reserved4 [96]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONTEXT tracks the _CONTEXT of windows.
|
||||||
|
type CONTEXT struct {
|
||||||
|
P1Home uint64
|
||||||
|
P2Home uint64
|
||||||
|
P3Home uint64
|
||||||
|
P4Home uint64
|
||||||
|
P5Home uint64
|
||||||
|
P6Home uint64
|
||||||
|
|
||||||
|
ContextFlags uint32
|
||||||
|
MxCsr uint32
|
||||||
|
|
||||||
|
SegCs uint16
|
||||||
|
SegDs uint16
|
||||||
|
SegEs uint16
|
||||||
|
SegFs uint16
|
||||||
|
SegGs uint16
|
||||||
|
SegSs uint16
|
||||||
|
EFlags uint32
|
||||||
|
|
||||||
|
Dr0 uint64
|
||||||
|
Dr1 uint64
|
||||||
|
Dr2 uint64
|
||||||
|
Dr3 uint64
|
||||||
|
Dr6 uint64
|
||||||
|
Dr7 uint64
|
||||||
|
|
||||||
|
Rax uint64
|
||||||
|
Rcx uint64
|
||||||
|
Rdx uint64
|
||||||
|
Rbx uint64
|
||||||
|
Rsp uint64
|
||||||
|
Rbp uint64
|
||||||
|
Rsi uint64
|
||||||
|
Rdi uint64
|
||||||
|
R8 uint64
|
||||||
|
R9 uint64
|
||||||
|
R10 uint64
|
||||||
|
R11 uint64
|
||||||
|
R12 uint64
|
||||||
|
R13 uint64
|
||||||
|
R14 uint64
|
||||||
|
R15 uint64
|
||||||
|
|
||||||
|
Rip uint64
|
||||||
|
|
||||||
|
FltSave XMM_SAVE_AREA32
|
||||||
|
|
||||||
|
VectorRegister [26]M128A
|
||||||
|
VectorControl uint64
|
||||||
|
|
||||||
|
DebugControl uint64
|
||||||
|
LastBranchToRip uint64
|
||||||
|
LastBranchFromRip uint64
|
||||||
|
LastExceptionToRip uint64
|
||||||
|
LastExceptionFromRip uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCONTEXT allocates Windows CONTEXT structure aligned to 16 bytes.
|
||||||
|
func NewCONTEXT() *CONTEXT {
|
||||||
|
var c *CONTEXT
|
||||||
|
buf := make([]byte, unsafe.Sizeof(*c)+15)
|
||||||
|
return (*CONTEXT)(unsafe.Pointer((uintptr(unsafe.Pointer(&buf[15]))) &^ 15))
|
||||||
|
}
|
@ -20,9 +20,9 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/cosiner/argv"
|
"github.com/cosiner/argv"
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/go-delve/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/go-delve/delve/service/api"
|
||||||
"github.com/derekparker/delve/service/debugger"
|
"github.com/go-delve/delve/service/debugger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const optimizedFunctionWarning = "Warning: debugging optimized function"
|
const optimizedFunctionWarning = "Warning: debugging optimized function"
|
||||||
@ -32,6 +32,7 @@ type cmdPrefix int
|
|||||||
const (
|
const (
|
||||||
noPrefix = cmdPrefix(0)
|
noPrefix = cmdPrefix(0)
|
||||||
onPrefix = cmdPrefix(1 << iota)
|
onPrefix = cmdPrefix(1 << iota)
|
||||||
|
deferredPrefix
|
||||||
)
|
)
|
||||||
|
|
||||||
type callContext struct {
|
type callContext struct {
|
||||||
@ -113,24 +114,24 @@ Type "help" followed by the name of a command for more information about it.`},
|
|||||||
|
|
||||||
break [name] <linespec>
|
break [name] <linespec>
|
||||||
|
|
||||||
See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/locspec.md for the syntax of linespec.
|
See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of linespec.
|
||||||
|
|
||||||
See also: "help on", "help cond" and "help clear"`},
|
See also: "help on", "help cond" and "help clear"`},
|
||||||
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: `Set tracepoint.
|
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: `Set tracepoint.
|
||||||
|
|
||||||
trace [name] <linespec>
|
trace [name] <linespec>
|
||||||
|
|
||||||
A tracepoint is a breakpoint that does not stop the execution of the program, instead when the tracepoint is hit a notification is displayed. See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/locspec.md for the syntax of linespec.
|
A tracepoint is a breakpoint that does not stop the execution of the program, instead when the tracepoint is hit a notification is displayed. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of linespec.
|
||||||
|
|
||||||
See also: "help on", "help cond" and "help clear"`},
|
See also: "help on", "help cond" and "help clear"`},
|
||||||
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
|
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
|
||||||
|
|
||||||
restart [checkpoint]
|
restart [checkpoint]
|
||||||
restart [-noargs] newargv...
|
restart [-noargs] newargv...
|
||||||
|
|
||||||
For recorded processes restarts from the start or from the specified
|
For recorded processes restarts from the start or from the specified
|
||||||
checkpoint. For normal processes restarts the process, optionally changing
|
checkpoint. For normal processes restarts the process, optionally changing
|
||||||
the arguments. With -noargs, the process starts with an empty commandline.
|
the arguments. With -noargs, the process starts with an empty commandline.
|
||||||
`},
|
`},
|
||||||
{aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
|
{aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
|
||||||
{aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."},
|
{aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."},
|
||||||
@ -139,6 +140,8 @@ See also: "help on", "help cond" and "help clear"`},
|
|||||||
{aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
|
{aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
|
||||||
{aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
{aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||||
|
|
||||||
|
call [-unsafe] <function call expression>
|
||||||
|
|
||||||
Current limitations:
|
Current limitations:
|
||||||
- only pointers to stack-allocated objects can be passed as argument.
|
- only pointers to stack-allocated objects can be passed as argument.
|
||||||
- only some automatic type conversions are supported.
|
- only some automatic type conversions are supported.
|
||||||
@ -186,11 +189,11 @@ Called without arguments it will show information about the current goroutine.
|
|||||||
Called with a single argument it will switch to the specified goroutine.
|
Called with a single argument it will switch to the specified goroutine.
|
||||||
Called with more arguments it will execute a command on the specified goroutine.`},
|
Called with more arguments it will execute a command on the specified goroutine.`},
|
||||||
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
|
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
|
||||||
{aliases: []string{"print", "p"}, allowedPrefixes: onPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression.
|
{aliases: []string{"print", "p"}, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression.
|
||||||
|
|
||||||
[goroutine <n>] [frame <m>] print <expression>
|
[goroutine <n>] [frame <m>] print <expression>
|
||||||
|
|
||||||
See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/expr.md for a description of supported expressions.`},
|
See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md for a description of supported expressions.`},
|
||||||
{aliases: []string{"whatis"}, cmdFn: whatisCommand, helpMsg: `Prints type of an expression.
|
{aliases: []string{"whatis"}, cmdFn: whatisCommand, helpMsg: `Prints type of an expression.
|
||||||
|
|
||||||
whatis <expression>`},
|
whatis <expression>`},
|
||||||
@ -198,7 +201,7 @@ See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/expr.md for a des
|
|||||||
|
|
||||||
[goroutine <n>] [frame <m>] set <variable> = <value>
|
[goroutine <n>] [frame <m>] set <variable> = <value>
|
||||||
|
|
||||||
See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/expr.md for a description of supported expressions. Only numerical variables and pointers can be changed.`},
|
See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md for a description of supported expressions. Only numerical variables and pointers can be changed.`},
|
||||||
{aliases: []string{"sources"}, cmdFn: sources, helpMsg: `Print list of source files.
|
{aliases: []string{"sources"}, cmdFn: sources, helpMsg: `Print list of source files.
|
||||||
|
|
||||||
sources [<regex>]
|
sources [<regex>]
|
||||||
@ -214,12 +217,12 @@ If regex is specified only the functions matching it will be returned.`},
|
|||||||
types [<regex>]
|
types [<regex>]
|
||||||
|
|
||||||
If regex is specified only the types matching it will be returned.`},
|
If regex is specified only the types matching it will be returned.`},
|
||||||
{aliases: []string{"args"}, allowedPrefixes: onPrefix, cmdFn: args, helpMsg: `Print function arguments.
|
{aliases: []string{"args"}, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: args, helpMsg: `Print function arguments.
|
||||||
|
|
||||||
[goroutine <n>] [frame <m>] args [-v] [<regex>]
|
[goroutine <n>] [frame <m>] args [-v] [<regex>]
|
||||||
|
|
||||||
If regex is specified only function arguments with a name matching it will be returned. If -v is specified more information about each function argument will be shown.`},
|
If regex is specified only function arguments with a name matching it will be returned. If -v is specified more information about each function argument will be shown.`},
|
||||||
{aliases: []string{"locals"}, allowedPrefixes: onPrefix, cmdFn: locals, helpMsg: `Print local variables.
|
{aliases: []string{"locals"}, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: locals, helpMsg: `Print local variables.
|
||||||
|
|
||||||
[goroutine <n>] [frame <m>] locals [-v] [<regex>]
|
[goroutine <n>] [frame <m>] locals [-v] [<regex>]
|
||||||
|
|
||||||
@ -248,10 +251,13 @@ When connected to a headless instance started with the --accept-multiclient, pas
|
|||||||
Show source around current point or provided linespec.`},
|
Show source around current point or provided linespec.`},
|
||||||
{aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace.
|
{aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace.
|
||||||
|
|
||||||
[goroutine <n>] [frame <m>] stack [<depth>] [-full] [-g] [-s] [-offsets]
|
[goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>]
|
||||||
|
|
||||||
-full every stackframe is decorated with the value of its local variables and arguments.
|
-full every stackframe is decorated with the value of its local variables and arguments.
|
||||||
-offsets prints frame offset of each frame
|
-offsets prints frame offset of each frame.
|
||||||
|
-defer prints deferred function call stack for each frame.
|
||||||
|
-a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled)
|
||||||
|
-adepth <depth> configures depth of ancestor stacktrace
|
||||||
`},
|
`},
|
||||||
{aliases: []string{"frame"},
|
{aliases: []string{"frame"},
|
||||||
cmdFn: func(t *Term, ctx callContext, arg string) error {
|
cmdFn: func(t *Term, ctx callContext, arg string) error {
|
||||||
@ -259,8 +265,8 @@ Show source around current point or provided linespec.`},
|
|||||||
},
|
},
|
||||||
helpMsg: `Set the current frame, or execute command on a different frame.
|
helpMsg: `Set the current frame, or execute command on a different frame.
|
||||||
|
|
||||||
frame <m>
|
frame <m>
|
||||||
frame <m> <command>
|
frame <m> <command>
|
||||||
|
|
||||||
The first form sets frame used by subsequent commands such as "print" or "set".
|
The first form sets frame used by subsequent commands such as "print" or "set".
|
||||||
The second form runs the command on the given frame.`},
|
The second form runs the command on the given frame.`},
|
||||||
@ -270,8 +276,8 @@ The second form runs the command on the given frame.`},
|
|||||||
},
|
},
|
||||||
helpMsg: `Move the current frame up.
|
helpMsg: `Move the current frame up.
|
||||||
|
|
||||||
up [<m>]
|
up [<m>]
|
||||||
up [<m>] <command>
|
up [<m>] <command>
|
||||||
|
|
||||||
Move the current frame up by <m>. The second form runs the command on the given frame.`},
|
Move the current frame up by <m>. The second form runs the command on the given frame.`},
|
||||||
{aliases: []string{"down"},
|
{aliases: []string{"down"},
|
||||||
@ -280,10 +286,15 @@ Move the current frame up by <m>. The second form runs the command on the given
|
|||||||
},
|
},
|
||||||
helpMsg: `Move the current frame down.
|
helpMsg: `Move the current frame down.
|
||||||
|
|
||||||
down [<m>]
|
down [<m>]
|
||||||
down [<m>] <command>
|
down [<m>] <command>
|
||||||
|
|
||||||
Move the current frame down by <m>. The second form runs the command on the given frame.`},
|
Move the current frame down by <m>. The second form runs the command on the given frame.`},
|
||||||
|
{aliases: []string{"deferred"}, cmdFn: c.deferredCommand, helpMsg: `Executes command in the context of a deferred call.
|
||||||
|
|
||||||
|
deferred <n> <command>
|
||||||
|
|
||||||
|
Executes the specified command (print, args, locals) in the context of the n-th deferred call in the current frame.`},
|
||||||
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: `Executes a file containing a list of delve commands
|
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: `Executes a file containing a list of delve commands
|
||||||
|
|
||||||
source <path>`},
|
source <path>`},
|
||||||
@ -334,6 +345,7 @@ Defines <alias> as an alias to <command> or removes an alias.`},
|
|||||||
edit [locspec]
|
edit [locspec]
|
||||||
|
|
||||||
If locspec is omitted edit will open the current source file in the editor, otherwise it will open the specified location.`},
|
If locspec is omitted edit will open the current source file in the editor, otherwise it will open the specified location.`},
|
||||||
|
{aliases: []string{"libraries"}, cmdFn: libraries, helpMsg: `List loaded dynamic libraries`},
|
||||||
}
|
}
|
||||||
|
|
||||||
if client == nil || client.Recorded() {
|
if client == nil || client.Recorded() {
|
||||||
@ -347,7 +359,9 @@ If locspec is omitted edit will open the current source file in the editor, othe
|
|||||||
cmdFn: checkpoint,
|
cmdFn: checkpoint,
|
||||||
helpMsg: `Creates a checkpoint at the current position.
|
helpMsg: `Creates a checkpoint at the current position.
|
||||||
|
|
||||||
checkpoint [where]`,
|
checkpoint [note]
|
||||||
|
|
||||||
|
The "note" is arbitrary text that can be used to identify the checkpoint, if it is not specified it defaults to the current filename:line position.`,
|
||||||
})
|
})
|
||||||
c.cmds = append(c.cmds, command{
|
c.cmds = append(c.cmds, command{
|
||||||
aliases: []string{"checkpoints"},
|
aliases: []string{"checkpoints"},
|
||||||
@ -426,7 +440,7 @@ func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) erro
|
|||||||
|
|
||||||
// Call takes a command to execute.
|
// Call takes a command to execute.
|
||||||
func (c *Commands) Call(cmdstr string, t *Term) error {
|
func (c *Commands) Call(cmdstr string, t *Term) error {
|
||||||
ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: c.frame}}
|
ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: c.frame, DeferredCall: 0}}
|
||||||
return c.CallWithContext(cmdstr, t, ctx)
|
return c.CallWithContext(cmdstr, t, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,6 +573,27 @@ func (a byGoroutineID) Len() int { return len(a) }
|
|||||||
func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||||
|
|
||||||
|
// The number of goroutines we're going to request on each RPC call
|
||||||
|
const goroutineBatchSize = 10000
|
||||||
|
|
||||||
|
func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, bPrintStack bool, state *api.DebuggerState) error {
|
||||||
|
for _, g := range gs {
|
||||||
|
prefix := " "
|
||||||
|
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
||||||
|
prefix = "* "
|
||||||
|
}
|
||||||
|
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
|
||||||
|
if bPrintStack {
|
||||||
|
stack, err := t.client.Stacktrace(g.ID, 10, false, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printStack(stack, "\t", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func goroutines(t *Term, ctx callContext, argstr string) error {
|
func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||||
args := strings.Split(argstr, " ")
|
args := strings.Split(argstr, " ")
|
||||||
var fgl = fglUserCurrent
|
var fgl = fglUserCurrent
|
||||||
@ -593,26 +628,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gs, err := t.client.ListGoroutines()
|
var (
|
||||||
if err != nil {
|
start = 0
|
||||||
return err
|
gslen = 0
|
||||||
}
|
gs []*api.Goroutine
|
||||||
sort.Sort(byGoroutineID(gs))
|
)
|
||||||
fmt.Printf("[%d goroutines]\n", len(gs))
|
for start >= 0 {
|
||||||
for _, g := range gs {
|
gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize)
|
||||||
prefix := " "
|
if err != nil {
|
||||||
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
return err
|
||||||
prefix = "* "
|
|
||||||
}
|
}
|
||||||
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
|
sort.Sort(byGoroutineID(gs))
|
||||||
if bPrintStack {
|
err = printGoroutines(t, gs, fgl, bPrintStack, state)
|
||||||
stack, err := t.client.Stacktrace(g.ID, 10, false, nil)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
printStack(stack, "\t", false)
|
|
||||||
}
|
}
|
||||||
|
gslen += len(gs)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("[%d goroutines]\n", gslen)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -714,6 +747,22 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) deferredCommand(t *Term, ctx callContext, argstr string) error {
|
||||||
|
ctx.Prefix = deferredPrefix
|
||||||
|
|
||||||
|
space := strings.Index(argstr, " ")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ctx.Scope.DeferredCall, err = strconv.Atoi(argstr[:space])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ctx.Scope.DeferredCall <= 0 {
|
||||||
|
return errors.New("argument of deferred must be a number greater than 0 (use 'stack -defer' to see the list of deferred calls)")
|
||||||
|
}
|
||||||
|
return c.CallWithContext(argstr[space:], t, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func printscope(t *Term) error {
|
func printscope(t *Term) error {
|
||||||
state, err := t.client.GetState()
|
state, err := t.client.GetState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -980,7 +1029,13 @@ func (c *Commands) call(t *Term, ctx callContext, args string) error {
|
|||||||
if err := scopePrefixSwitch(t, ctx); err != nil {
|
if err := scopePrefixSwitch(t, ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
state, err := exitedToError(t.client.Call(args))
|
const unsafePrefix = "-unsafe "
|
||||||
|
unsafe := false
|
||||||
|
if strings.HasPrefix(args, unsafePrefix) {
|
||||||
|
unsafe = true
|
||||||
|
args = args[len(unsafePrefix):]
|
||||||
|
}
|
||||||
|
state, err := exitedToError(t.client.Call(args, unsafe))
|
||||||
c.frame = 0
|
c.frame = 0
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printcontextNoState(t)
|
printcontextNoState(t)
|
||||||
@ -1164,6 +1219,9 @@ func edit(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(editor, fmt.Sprintf("+%d", lineno), file)
|
cmd := exec.Command(editor, fmt.Sprintf("+%d", lineno), file)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1356,6 +1414,20 @@ func stackCommand(t *Term, ctx callContext, args string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printStack(stack, "", sa.offsets)
|
printStack(stack, "", sa.offsets)
|
||||||
|
if sa.ancestors > 0 {
|
||||||
|
ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ancestor := range ancestors {
|
||||||
|
fmt.Printf("Created by Goroutine %d:\n", ancestor.ID)
|
||||||
|
if ancestor.Unreadable != "" {
|
||||||
|
fmt.Printf("\t%s\n", ancestor.Unreadable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
printStack(ancestor.Stack, "\t", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1364,6 +1436,9 @@ type stackArgs struct {
|
|||||||
full bool
|
full bool
|
||||||
offsets bool
|
offsets bool
|
||||||
readDefers bool
|
readDefers bool
|
||||||
|
|
||||||
|
ancestors int
|
||||||
|
ancestorDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStackArgs(argstr string) (stackArgs, error) {
|
func parseStackArgs(argstr string) (stackArgs, error) {
|
||||||
@ -1373,7 +1448,18 @@ func parseStackArgs(argstr string) (stackArgs, error) {
|
|||||||
}
|
}
|
||||||
if argstr != "" {
|
if argstr != "" {
|
||||||
args := strings.Split(argstr, " ")
|
args := strings.Split(argstr, " ")
|
||||||
for i := range args {
|
for i := 0; i < len(args); i++ {
|
||||||
|
numarg := func(name string) (int, error) {
|
||||||
|
if i >= len(args) {
|
||||||
|
return 0, fmt.Errorf("expected number after %s", name)
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(args[i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("expected number after %s: %v", name, err)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
|
||||||
|
}
|
||||||
switch args[i] {
|
switch args[i] {
|
||||||
case "-full":
|
case "-full":
|
||||||
r.full = true
|
r.full = true
|
||||||
@ -1381,6 +1467,20 @@ func parseStackArgs(argstr string) (stackArgs, error) {
|
|||||||
r.offsets = true
|
r.offsets = true
|
||||||
case "-defer":
|
case "-defer":
|
||||||
r.readDefers = true
|
r.readDefers = true
|
||||||
|
case "-a":
|
||||||
|
i++
|
||||||
|
n, err := numarg("-a")
|
||||||
|
if err != nil {
|
||||||
|
return stackArgs{}, err
|
||||||
|
}
|
||||||
|
r.ancestors = n
|
||||||
|
case "-adepth":
|
||||||
|
i++
|
||||||
|
n, err := numarg("-adepth")
|
||||||
|
if err != nil {
|
||||||
|
return stackArgs{}, err
|
||||||
|
}
|
||||||
|
r.ancestorDepth = n
|
||||||
default:
|
default:
|
||||||
n, err := strconv.Atoi(args[i])
|
n, err := strconv.Atoi(args[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1390,6 +1490,9 @@ func parseStackArgs(argstr string) (stackArgs, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.ancestors > 0 && r.ancestorDepth == 0 {
|
||||||
|
r.ancestorDepth = r.depth
|
||||||
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1526,6 +1629,18 @@ func disassCommand(t *Term, ctx callContext, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func libraries(t *Term, ctx callContext, args string) error {
|
||||||
|
libs, err := t.client.ListDynamicLibraries()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d := digits(len(libs))
|
||||||
|
for i := range libs {
|
||||||
|
fmt.Printf("%"+strconv.Itoa(d)+"d. %s\n", i, libs[i].Path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func digits(n int) int {
|
func digits(n int) int {
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
return 1
|
return 1
|
||||||
@ -1565,7 +1680,7 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for j, d := range stack[i].Defers {
|
for j, d := range stack[i].Defers {
|
||||||
deferHeader := fmt.Sprintf("%s defer %d: ", s, j)
|
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
|
||||||
s2 := strings.Repeat(" ", len(deferHeader))
|
s2 := strings.Repeat(" ", len(deferHeader))
|
||||||
if d.Unreadable != "" {
|
if d.Unreadable != "" {
|
||||||
fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
|
fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
|
||||||
@ -1669,7 +1784,14 @@ func printcontextThread(t *Term, th *api.Thread) {
|
|||||||
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
|
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
|
||||||
var arg []string
|
var arg []string
|
||||||
for _, ar := range th.BreakpointInfo.Arguments {
|
for _, ar := range th.BreakpointInfo.Arguments {
|
||||||
arg = append(arg, ar.SinglelineString())
|
// For AI compatibility return values are included in the
|
||||||
|
// argument list. This is a relic of the dark ages when the
|
||||||
|
// Go debug information did not distinguish between the two.
|
||||||
|
// Filter them out here instead, so during trace operations
|
||||||
|
// they are not printed as an argument.
|
||||||
|
if (ar.Flags & api.VariableArgument) != 0 {
|
||||||
|
arg = append(arg, ar.SinglelineString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
args = strings.Join(arg, ", ")
|
args = strings.Join(arg, ", ")
|
||||||
}
|
}
|
||||||
@ -1874,6 +1996,9 @@ func (c *Commands) executeFile(t *Term, name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Call(line, t); err != nil {
|
if err := c.Call(line, t); err != nil {
|
||||||
|
if _, isExitRequest := err.(ExitRequestError); isExitRequest {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Printf("%s:%d: %v\n", name, lineno, err)
|
fmt.Printf("%s:%d: %v\n", name, lineno, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1923,7 +2048,7 @@ func checkpoints(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
w := new(tabwriter.Writer)
|
w := new(tabwriter.Writer)
|
||||||
w.Init(os.Stdout, 4, 4, 2, ' ', 0)
|
w.Init(os.Stdout, 4, 4, 2, ' ', 0)
|
||||||
fmt.Fprintln(w, "ID\tWhen\tWhere")
|
fmt.Fprintln(w, "ID\tWhen\tNote")
|
||||||
for _, cp := range cps {
|
for _, cp := range cps {
|
||||||
fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where)
|
fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where)
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/config"
|
"github.com/go-delve/delve/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func configureCmd(t *Term, ctx callContext, args string) error {
|
func configureCmd(t *Term, ctx callContext, args string) error {
|
@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/go-delve/delve/service/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DisasmPrint(dv api.AsmInstructions, out io.Writer) {
|
func DisasmPrint(dv api.AsmInstructions, out io.Writer) {
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func replaceDocPath(s string) string {
|
func replaceDocPath(s string) string {
|
||||||
const docpath = "$GOPATH/src/github.com/derekparker/delve/"
|
const docpath = "$GOPATH/src/github.com/go-delve/delve/"
|
||||||
|
|
||||||
for {
|
for {
|
||||||
start := strings.Index(s, docpath)
|
start := strings.Index(s, docpath)
|
||||||
@ -22,7 +22,7 @@ func replaceDocPath(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
text := s[start+len(docpath) : end]
|
text := s[start+len(docpath) : end]
|
||||||
s = s[:start] + fmt.Sprintf("[%s](//github.com/derekparker/delve/tree/master/%s)", text, text) + s[end:]
|
s = s[:start] + fmt.Sprintf("[%s](//github.com/go-delve/delve/tree/master/%s)", text, text) + s[end:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,9 +13,9 @@ import (
|
|||||||
|
|
||||||
"github.com/peterh/liner"
|
"github.com/peterh/liner"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/config"
|
"github.com/go-delve/delve/pkg/config"
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/go-delve/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/go-delve/delve/service/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -64,20 +64,6 @@ type Term struct {
|
|||||||
|
|
||||||
// New returns a new Term.
|
// New returns a new Term.
|
||||||
func New(client service.Client, conf *config.Config) *Term {
|
func New(client service.Client, conf *config.Config) *Term {
|
||||||
if client != nil && client.IsMulticlient() {
|
|
||||||
state, _ := client.GetStateNonBlocking()
|
|
||||||
// The error return of GetState will usually be the ErrProcessExited,
|
|
||||||
// which we don't care about. If there are other errors they will show up
|
|
||||||
// later, here we are only concerned about stopping a running target so
|
|
||||||
// that we can initialize our connection.
|
|
||||||
if state != nil && state.Running {
|
|
||||||
_, err := client.Halt()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "could not halt: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmds := DebugCommands(client)
|
cmds := DebugCommands(client)
|
||||||
if conf != nil && conf.Aliases != nil {
|
if conf != nil && conf.Aliases != nil {
|
||||||
cmds.Merge(conf.Aliases)
|
cmds.Merge(conf.Aliases)
|
||||||
@ -204,6 +190,9 @@ func (t *Term) Run() (int, error) {
|
|||||||
if t.InitFile != "" {
|
if t.InitFile != "" {
|
||||||
err := t.cmds.executeFile(t, t.InitFile)
|
err := t.cmds.executeFile(t, t.InitFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(ExitRequestError); ok {
|
||||||
|
return t.handleExit()
|
||||||
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Error executing init file: %s\n", err)
|
fmt.Fprintf(os.Stderr, "Error executing init file: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,7 +253,14 @@ func (t *Term) substitutePath(path string) string {
|
|||||||
if t.conf == nil {
|
if t.conf == nil {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
separator := string(os.PathSeparator)
|
|
||||||
|
// On windows paths returned from headless server are as c:/dir/dir
|
||||||
|
// though os.PathSeparator is '\\'
|
||||||
|
|
||||||
|
separator := "/" //make it default
|
||||||
|
if strings.Index(path, "\\") != -1 { //dependent on the path
|
||||||
|
separator = "\\"
|
||||||
|
}
|
||||||
for _, r := range t.conf.SubstitutePath {
|
for _, r := range t.conf.SubstitutePath {
|
||||||
from := crossPlatformPath(r.From)
|
from := crossPlatformPath(r.From)
|
||||||
to := r.To
|
to := r.To
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user