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
|
||||
go:
|
||||
- 1.10.3
|
||||
- 1.11.9
|
||||
- 1.12.4
|
||||
install:
|
||||
- export PATH=$PATH:$HOME/gopath/bin
|
||||
- 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/unused
|
||||
- go get -u github.com/mdempsky/unconvert
|
||||
- go get -u github.com/gordonklaus/ineffassign
|
||||
script:
|
||||
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
||||
- go vet $(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/)
|
||||
- unused $(go list ./... | grep -v /vendor/)
|
||||
- unconvert $(go list ./... | grep -v /vendor/)
|
||||
- ineffassign .
|
||||
|
@ -28,11 +28,11 @@ import (
|
||||
"github.com/beego/bee/cmd/commands/version"
|
||||
beeLogger "github.com/beego/bee/logger"
|
||||
"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/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{
|
||||
@ -43,7 +43,7 @@ var cmdDlv = &commands.Command{
|
||||
|
||||
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() },
|
||||
Run: runDlv,
|
||||
@ -228,7 +228,7 @@ func startWatcher(paths []string, ch chan int) {
|
||||
|
||||
// Wait 1s before re-build until there is no file change
|
||||
scheduleTime := time.Now().Add(1 * time.Second)
|
||||
time.Sleep(scheduleTime.Sub(time.Now()))
|
||||
time.Sleep(time.Until(scheduleTime))
|
||||
_, err := buildDebug()
|
||||
if err != nil {
|
||||
utils.Notify("Build Failed: "+err.Error(), "bee")
|
||||
|
@ -85,7 +85,7 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
|
||||
go func() {
|
||||
// Wait 1s before autobuild until there is no file change.
|
||||
scheduleTime = time.Now().Add(1 * time.Second)
|
||||
time.Sleep(scheduleTime.Sub(time.Now()))
|
||||
time.Sleep(time.Until(scheduleTime))
|
||||
AutoBuild(files, isgenerate)
|
||||
|
||||
if config.Conf.EnableReload {
|
||||
|
@ -57,7 +57,7 @@ Prints the current Bee, Beego and Go version alongside the platform information.
|
||||
}
|
||||
var outputFormat string
|
||||
|
||||
const version = "1.10.0"
|
||||
const version = "1.10.1"
|
||||
|
||||
func init() {
|
||||
fs := flag.NewFlagSet("version", flag.ContinueOnError)
|
||||
|
@ -116,7 +116,7 @@ func ParsePackagesFromDir(dirpath string) {
|
||||
err = parsePackageFromDir(fpath)
|
||||
if err != nil {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ func CheckEnv(appname string) (apppath, packpath string, err error) {
|
||||
apppath = filepath.Join(gosrcpath, appname)
|
||||
|
||||
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)
|
||||
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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"runtime"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
configDir string = ".dlv"
|
||||
configFile string = "config.yml"
|
||||
configDir string = "dlv"
|
||||
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 {
|
||||
// Directory path will be substituted if it matches `From`.
|
||||
From string
|
||||
@ -23,7 +26,7 @@ type SubstitutePathRule struct {
|
||||
To string
|
||||
}
|
||||
|
||||
// Slice of source code path substitution rules.
|
||||
// SubstitutePathRules is a slice of source code path substitution rules.
|
||||
type SubstitutePathRules []SubstitutePathRule
|
||||
|
||||
// Config defines all configuration options available to be set through the config file.
|
||||
@ -43,10 +46,14 @@ type Config struct {
|
||||
// If ShowLocationExpr is true whatis will print the DWARF location
|
||||
// expression for its argument.
|
||||
ShowLocationExpr bool `yaml:"show-location-expr"`
|
||||
|
||||
|
||||
// Source list line-number color (3/4 bit color codes as defined
|
||||
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
|
||||
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.
|
||||
@ -54,12 +61,32 @@ func LoadConfig() *Config {
|
||||
err := createConfigPath()
|
||||
if err != nil {
|
||||
fmt.Printf("Could not create config directory: %v.", err)
|
||||
return nil
|
||||
return &Config{}
|
||||
}
|
||||
fullConfigFile, err := GetConfigFilePath(configFile)
|
||||
if err != nil {
|
||||
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)
|
||||
@ -67,7 +94,7 @@ func LoadConfig() *Config {
|
||||
f, err = createDefaultConfig(fullConfigFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating default config file: %v", err)
|
||||
return nil
|
||||
return &Config{}
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
@ -80,19 +107,25 @@ func LoadConfig() *Config {
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to read config data: %v.", err)
|
||||
return nil
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
var c Config
|
||||
err = yaml.Unmarshal(data, &c)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// SaveConfig will marshal and save the config struct
|
||||
// to disk.
|
||||
func SaveConfig(conf *Config) error {
|
||||
fullConfigFile, err := GetConfigFilePath(configFile)
|
||||
if err != nil {
|
||||
@ -114,15 +147,42 @@ func SaveConfig(conf *Config) error {
|
||||
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) {
|
||||
f, err := os.Create(path)
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@ -158,6 +218,9 @@ substitute-path:
|
||||
|
||||
# Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
|
||||
# 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
|
||||
}
|
||||
@ -173,9 +236,43 @@ func createConfigPath() error {
|
||||
|
||||
// GetConfigFilePath gets the full path to the given config file name.
|
||||
func GetConfigFilePath(file string) (string, error) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
if configPath := os.Getenv("XDG_CONFIG_HOME"); configPath != "" {
|
||||
return path.Join(configPath, configDir, file), nil
|
||||
}
|
||||
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"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// To specify a single quote use backslash to escape it: '\''
|
||||
func SplitQuotedFields(in string, quote rune) []string {
|
@ -7,7 +7,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
type parsefunc func(*parseContext) parsefunc
|
@ -5,7 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
type DWRule struct {
|
@ -16,8 +16,8 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -41,6 +41,20 @@ const (
|
||||
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
|
||||
// specific Type structures (CharType, StructType, etc.).
|
||||
//TODO: remove this use dwarf.Type
|
||||
@ -48,6 +62,9 @@ type Type interface {
|
||||
Common() *CommonType
|
||||
String() string
|
||||
Size() int64
|
||||
|
||||
stringIntl(recCheck) string
|
||||
sizeIntl(recCheck) int64
|
||||
}
|
||||
|
||||
// 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) Size() int64 { return c.ByteSize }
|
||||
func (c *CommonType) Size() int64 { return c.ByteSize }
|
||||
func (c *CommonType) sizeIntl(recCheck) int64 { return c.ByteSize }
|
||||
|
||||
// Basic types
|
||||
|
||||
@ -75,7 +93,9 @@ type BasicType struct {
|
||||
|
||||
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 != "" {
|
||||
return t.Name
|
||||
}
|
||||
@ -136,9 +156,27 @@ type QualType struct {
|
||||
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.
|
||||
type ArrayType struct {
|
||||
@ -148,18 +186,36 @@ type ArrayType struct {
|
||||
Count int64 // if == -1, an incomplete array, like char x[].
|
||||
}
|
||||
|
||||
func (t *ArrayType) String() string {
|
||||
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.String()
|
||||
func (t *ArrayType) String() string { return t.stringIntl(make(recCheck)) }
|
||||
|
||||
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.
|
||||
type VoidType struct {
|
||||
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.
|
||||
type PtrType struct {
|
||||
@ -167,7 +223,16 @@ type PtrType struct {
|
||||
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.
|
||||
type StructType struct {
|
||||
@ -189,14 +254,21 @@ type StructField struct {
|
||||
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 != "" {
|
||||
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
|
||||
if t.StructName != "" {
|
||||
s += " " + t.StructName
|
||||
@ -210,7 +282,7 @@ func (t *StructType) Defn() string {
|
||||
if i > 0 {
|
||||
s += "; "
|
||||
}
|
||||
s += f.Name + " " + f.Type.String()
|
||||
s += f.Name + " " + f.Type.stringIntl(recCheck)
|
||||
s += "@" + strconv.FormatInt(f.ByteOffset, 10)
|
||||
if f.BitSize > 0 {
|
||||
s += " : " + strconv.FormatInt(f.BitSize, 10)
|
||||
@ -228,11 +300,18 @@ type SliceType struct {
|
||||
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 != "" {
|
||||
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
|
||||
@ -241,7 +320,9 @@ type StringType struct {
|
||||
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 != "" {
|
||||
return t.Name
|
||||
}
|
||||
@ -253,7 +334,9 @@ type InterfaceType struct {
|
||||
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 != "" {
|
||||
return t.Name
|
||||
}
|
||||
@ -275,7 +358,9 @@ type EnumValue struct {
|
||||
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"
|
||||
if t.EnumName != "" {
|
||||
s += " " + t.EnumName
|
||||
@ -298,17 +383,24 @@ type FuncType struct {
|
||||
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("
|
||||
for i, t := range t.ParamType {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += t.String()
|
||||
s += t.stringIntl(recCheck)
|
||||
}
|
||||
s += ")"
|
||||
if t.ReturnType != nil {
|
||||
s += " " + t.ReturnType.String()
|
||||
s += " " + t.ReturnType.stringIntl(recCheck)
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -318,7 +410,9 @@ type DotDotDotType struct {
|
||||
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.
|
||||
type TypedefType struct {
|
||||
@ -326,9 +420,20 @@ type TypedefType struct {
|
||||
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
|
||||
// the runtime-internal structure, with extra fields.
|
||||
@ -338,7 +443,14 @@ type MapType struct {
|
||||
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 != "" {
|
||||
return t.Name
|
||||
}
|
||||
@ -351,7 +463,14 @@ type ChanType struct {
|
||||
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 != "" {
|
||||
return t.Name
|
||||
}
|
||||
@ -586,9 +705,10 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
|
||||
switch t.ReflectKind {
|
||||
case reflect.Slice:
|
||||
slice := new(SliceType)
|
||||
typ = slice
|
||||
typeCache[off] = slice
|
||||
slice.ElemType = typeOf(e, AttrGoElem)
|
||||
t = &slice.StructType
|
||||
typ = slice
|
||||
case reflect.String:
|
||||
str := new(StringType)
|
||||
t = &str.StructType
|
||||
@ -798,19 +918,22 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
|
||||
switch t.ReflectKind {
|
||||
case reflect.Map:
|
||||
m := new(MapType)
|
||||
typ = m
|
||||
typeCache[off] = typ
|
||||
m.KeyType = typeOf(e, AttrGoKey)
|
||||
m.ElemType = typeOf(e, AttrGoElem)
|
||||
t = &m.TypedefType
|
||||
typ = m
|
||||
case reflect.Chan:
|
||||
c := new(ChanType)
|
||||
typ = c
|
||||
typeCache[off] = typ
|
||||
c.ElemType = typeOf(e, AttrGoElem)
|
||||
t = &c.TypedefType
|
||||
typ = c
|
||||
case reflect.Interface:
|
||||
it := new(InterfaceType)
|
||||
t = &it.TypedefType
|
||||
typ = it
|
||||
typeCache[off] = it
|
||||
t = &it.TypedefType
|
||||
default:
|
||||
typ = t
|
||||
}
|
||||
@ -876,3 +999,13 @@ func zeroArray(t 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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
type DebugLinePrologue struct {
|
@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
type Location struct {
|
@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/util"
|
||||
"github.com/go-delve/delve/pkg/dwarf/util"
|
||||
)
|
||||
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
)
|
||||
|
||||
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 (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"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 {
|
||||
if regName == reg.Name {
|
||||
dregs[dwarfReg] = op.DwarfRegisterFromBytes(reg.Bytes)
|
@ -12,40 +12,30 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/line"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/derekparker/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/line"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
"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 {
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
// GOOS operating system this binary is executing on.
|
||||
GOOS string
|
||||
|
||||
// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
|
||||
Functions []Function
|
||||
@ -54,7 +44,32 @@ type BinaryInfo struct {
|
||||
// LookupFunc maps function names to a description of the 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
|
||||
moduleData []moduleData
|
||||
@ -71,8 +86,6 @@ type BinaryInfo struct {
|
||||
|
||||
loadErrMu sync.Mutex
|
||||
loadErr error
|
||||
|
||||
dwarfReader *dwarf.Reader
|
||||
}
|
||||
|
||||
// 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.
|
||||
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")
|
||||
|
||||
// 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)
|
||||
|
||||
type compileUnit struct {
|
||||
Name string // univocal name for non-go compile units
|
||||
LowPC uint64
|
||||
Ranges [][2]uint64
|
||||
name string // univocal name for non-go compile units
|
||||
lowPC uint64
|
||||
ranges [][2]uint64
|
||||
|
||||
entry *dwarf.Entry // debug_info entry describing this compile unit
|
||||
isgo bool // true if this is the go compile unit
|
||||
@ -277,6 +296,12 @@ type buildIDHeader struct {
|
||||
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.
|
||||
func NewBinaryInfo(goos, goarch string) *BinaryInfo {
|
||||
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'.
|
||||
// It is expected this will be called in parallel with other initialization steps
|
||||
// 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)
|
||||
if err == nil {
|
||||
bi.lastModified = fi.ModTime()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
bi.Path = path
|
||||
switch bi.GOOS {
|
||||
case "linux":
|
||||
return bi.LoadBinaryInfoElf(path, entryPoint, wg)
|
||||
return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, &wg)
|
||||
case "windows":
|
||||
return bi.LoadBinaryInfoPE(path, entryPoint, wg)
|
||||
return bi.LoadBinaryInfoPE(path, entryPoint, &wg)
|
||||
case "darwin":
|
||||
return bi.LoadBinaryInfoMacho(path, entryPoint, wg)
|
||||
return bi.LoadBinaryInfoMacho(path, entryPoint, &wg)
|
||||
}
|
||||
return errors.New("unsupported operating system")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GStructOffset returns the offset of the G
|
||||
@ -398,12 +425,35 @@ func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
|
||||
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.
|
||||
func (bi *BinaryInfo) Close() error {
|
||||
if bi.sepDebugCloser != nil {
|
||||
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{}) {
|
||||
@ -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 {
|
||||
var base uint64
|
||||
if cu := bi.findCompileUnit(pc); cu != nil {
|
||||
base = cu.LowPC
|
||||
base = cu.lowPC
|
||||
}
|
||||
|
||||
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.
|
||||
func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit {
|
||||
for _, cu := range bi.compileUnits {
|
||||
for _, rng := range cu.Ranges {
|
||||
for _, rng := range cu.ranges {
|
||||
if pc >= rng[0] && pc < rng[1] {
|
||||
return cu
|
||||
}
|
||||
@ -558,35 +608,32 @@ func (e *ErrNoBuildIDNote) Error() string {
|
||||
// in GDB's documentation [1], and if found returns two handles, one
|
||||
// for the bare file, and another for its corresponding elf.File.
|
||||
// [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")
|
||||
if buildid == nil {
|
||||
return nil, nil, &ErrNoBuildIDNote{}
|
||||
//
|
||||
// Alternatively, if the debug file cannot be found be the build-id, Delve
|
||||
// will look in directories specified by the debug-info-directories config value.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
br := buildid.Open()
|
||||
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())
|
||||
if debugFilePath == "" {
|
||||
return nil, nil, ErrNoDebugInfoFound
|
||||
}
|
||||
|
||||
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)
|
||||
sepFile, err := os.OpenFile(debugFilePath, 0, os.ModePerm)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
bi.dwarf, err = elfFile.DWARF()
|
||||
if err != nil {
|
||||
var sepFile *os.File
|
||||
var serr error
|
||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile)
|
||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile, debugInfoDirectories)
|
||||
if serr != nil {
|
||||
if _, ok := serr.(*ErrNoBuildIDNote); ok {
|
||||
return err
|
||||
}
|
||||
return serr
|
||||
}
|
||||
bi.sepDebugCloser = sepFile
|
||||
@ -714,9 +792,16 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) {
|
||||
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
|
||||
// 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 ////////////////////////////////////////////////////////////////
|
@ -30,7 +30,8 @@ type Breakpoint struct {
|
||||
Kind BreakpointKind
|
||||
|
||||
// Breakpoint information
|
||||
Tracepoint bool // Tracepoint flag
|
||||
Tracepoint bool // Tracepoint flag
|
||||
TraceReturn bool
|
||||
Goroutine bool // Retrieve goroutine information
|
||||
Stacktrace int // Number of stack frames to retrieve
|
||||
Variables []string // Variables to evaluate
|
||||
@ -45,7 +46,7 @@ type Breakpoint struct {
|
||||
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
|
||||
// deferred function, DeferReturns is populated with the
|
||||
// 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
|
||||
// the function, not when the function is called directly
|
||||
DeferReturns []uint64
|
||||
@ -220,13 +221,16 @@ func (bpmap *BreakpointMap) ResetBreakpointIDCounter() {
|
||||
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
|
||||
|
||||
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
|
||||
// function, call proc.Process.SetBreakpoint instead, this function exists
|
||||
// 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 {
|
||||
// We can overlap one internal breakpoint with one user breakpoint, we
|
||||
// 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
|
||||
}
|
||||
|
||||
fnName := ""
|
||||
if fn != nil {
|
||||
fnName = fn.Name
|
||||
}
|
||||
|
||||
newBreakpoint := &Breakpoint{
|
||||
FunctionName: fn.Name,
|
||||
FunctionName: fnName,
|
||||
File: f,
|
||||
Line: l,
|
||||
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.
|
||||
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)
|
||||
if err == nil {
|
||||
bp.ID = id
|
||||
@ -421,12 +430,6 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
|
||||
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
|
||||
}
|
||||
|
@ -5,9 +5,8 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"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,
|
||||
@ -144,8 +143,13 @@ func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error)
|
||||
|
||||
// Process represents a core file.
|
||||
type Process struct {
|
||||
mem proc.MemoryReader
|
||||
Threads map[int]*Thread
|
||||
pid int
|
||||
|
||||
entryPoint uint64
|
||||
|
||||
bi *proc.BinaryInfo
|
||||
core *Core
|
||||
breakpoints proc.BreakpointMap
|
||||
currentThread *Thread
|
||||
selectedGoroutine *proc.G
|
||||
@ -154,12 +158,16 @@ type Process struct {
|
||||
|
||||
// Thread represents a thread in the core file being debugged.
|
||||
type Thread struct {
|
||||
th *LinuxPrStatus
|
||||
fpregs []proc.Register
|
||||
th osThread
|
||||
p *Process
|
||||
common proc.CommonThread
|
||||
}
|
||||
|
||||
type osThread interface {
|
||||
registers(floatingPoint bool) (proc.Registers, error)
|
||||
pid() int
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrWriteCore is returned when attempting to write to the core
|
||||
// process memory.
|
||||
@ -175,45 +183,66 @@ var (
|
||||
ErrChangeRegisterCore = errors.New("can not change register values of core process")
|
||||
)
|
||||
|
||||
type openFn func(string, string) (*Process, error)
|
||||
|
||||
var openFns = []openFn{readLinuxAMD64Core, readAMD64Minidump}
|
||||
|
||||
// ErrUnrecognizedFormat is returned when the core file is not recognized as
|
||||
// any of the supported formats.
|
||||
var ErrUnrecognizedFormat = errors.New("unrecognized core format")
|
||||
|
||||
// OpenCore will open the core file and return a Process struct.
|
||||
func OpenCore(corePath, exePath string) (*Process, error) {
|
||||
core, err := readCore(corePath, exePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &Process{
|
||||
core: core,
|
||||
breakpoints: proc.NewBreakpointMap(),
|
||||
bi: proc.NewBinaryInfo("linux", "amd64"),
|
||||
}
|
||||
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 the DWARF information cannot be found in the binary, Delve will look
|
||||
// for external debug files in the directories passed in.
|
||||
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
|
||||
var p *Process
|
||||
var err error
|
||||
for _, openFn := range openFns {
|
||||
p, err = openFn(corePath, exePath)
|
||||
if err != ErrUnrecognizedFormat {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, th := range p.core.Threads {
|
||||
p.currentThread = th
|
||||
break
|
||||
if err := p.initialize(exePath, debugInfoDirs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||
|
||||
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.
|
||||
func (p *Process) BinInfo() *proc.BinaryInfo {
|
||||
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.
|
||||
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
|
||||
// the length read is shorter than the length of the `data` buffer.
|
||||
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) {
|
||||
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
|
||||
// the value of the instruction pointer register.
|
||||
func (t *Thread) Location() (*proc.Location, error) {
|
||||
f, l, fn := t.p.bi.PCToLine(t.th.Reg.Rip)
|
||||
return &proc.Location{PC: t.th.Reg.Rip, File: f, Line: l, Fn: fn}, nil
|
||||
regs, err := t.th.registers(false)
|
||||
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.
|
||||
@ -268,16 +302,12 @@ func (t *Thread) Breakpoint() proc.BreakpointState {
|
||||
|
||||
// ThreadID returns the ID for this thread.
|
||||
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.
|
||||
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
|
||||
r := &Registers{&t.th.Reg, nil}
|
||||
if floatingPoint {
|
||||
r.fpregs = t.fpregs
|
||||
}
|
||||
return r, nil
|
||||
return t.th.registers(floatingPoint)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *Process) Pid() int {
|
||||
return p.core.Pid
|
||||
return p.pid
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
|
||||
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.
|
||||
func (p *Process) ThreadList() []proc.Thread {
|
||||
r := make([]proc.Thread, 0, len(p.core.Threads))
|
||||
for _, v := range p.core.Threads {
|
||||
r := make([]proc.Thread, 0, len(p.Threads))
|
||||
for _, v := range p.Threads {
|
||||
r = append(r, v)
|
||||
}
|
||||
return r
|
||||
@ -463,64 +493,6 @@ func (p *Process) ThreadList() []proc.Thread {
|
||||
|
||||
// FindThread will return the thread with the corresponding thread ID.
|
||||
func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
|
||||
t, ok := p.core.Threads[threadID]
|
||||
t, ok := p.Threads[threadID]
|
||||
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"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/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
|
||||
// systems.
|
||||
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
|
||||
const NT_AUXV elf.NType = 0x6
|
||||
|
||||
// PC returns the value of RIP.
|
||||
func (r *LinuxCoreRegisters) PC() uint64 {
|
||||
return r.Rip
|
||||
}
|
||||
const elfErrorBadMagicNumber = "bad magic number"
|
||||
|
||||
// SP returns the value of RSP.
|
||||
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
|
||||
// readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
|
||||
// exePath. For details on the Linux ELF core format, see:
|
||||
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
|
||||
// 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,
|
||||
// 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)
|
||||
if err != nil {
|
||||
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
|
||||
// Go >=1.11 and <1.11 produce different errors when reading a non-elf file.
|
||||
return nil, ErrUnrecognizedFormat
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
exe, err := os.Open(exePath)
|
||||
@ -288,37 +69,51 @@ func readCore(corePath, exePath string) (*Core, error) {
|
||||
memory := buildMemory(coreFile, exeELF, exe, notes)
|
||||
entryPoint := findEntryPoint(notes)
|
||||
|
||||
core := &Core{
|
||||
MemoryReader: memory,
|
||||
Threads: map[int]*Thread{},
|
||||
entryPoint: entryPoint,
|
||||
p := &Process{
|
||||
mem: memory,
|
||||
Threads: map[int]*Thread{},
|
||||
entryPoint: entryPoint,
|
||||
bi: proc.NewBinaryInfo("linux", "amd64"),
|
||||
breakpoints: proc.NewBreakpointMap(),
|
||||
}
|
||||
|
||||
var lastThread *Thread
|
||||
var lastThread *linuxAMD64Thread
|
||||
for _, note := range notes {
|
||||
switch note.Type {
|
||||
case elf.NT_PRSTATUS:
|
||||
t := note.Desc.(*LinuxPrStatus)
|
||||
lastThread = &Thread{t, nil, nil, proc.CommonThread{}}
|
||||
core.Threads[int(t.Pid)] = lastThread
|
||||
lastThread = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
|
||||
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:
|
||||
if lastThread != nil {
|
||||
lastThread.fpregs = note.Desc.(*proc.LinuxX86Xstate).Decode()
|
||||
lastThread.regs.Fpregs = note.Desc.(*linutil.AMD64Xstate).Decode()
|
||||
}
|
||||
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 Core struct {
|
||||
proc.MemoryReader
|
||||
Threads map[int]*Thread
|
||||
Pid int
|
||||
type linuxAMD64Thread struct {
|
||||
regs linutil.AMD64Registers
|
||||
t *LinuxPrStatus
|
||||
}
|
||||
|
||||
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.
|
||||
@ -415,8 +210,8 @@ func readNote(r io.ReadSeeker) (*Note, error) {
|
||||
}
|
||||
note.Desc = data
|
||||
case NT_X86_XSTATE:
|
||||
var fpregs proc.LinuxX86Xstate
|
||||
if err := proc.LinuxX86XstateRead(desc, true, &fpregs); err != nil {
|
||||
var fpregs linutil.AMD64Xstate
|
||||
if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
note.Desc = &fpregs
|
||||
@ -516,7 +311,7 @@ type LinuxPrStatus struct {
|
||||
Sighold uint64
|
||||
Pid, Ppid, Pgrp, Sid int32
|
||||
Utime, Stime, CUtime, CStime LinuxCoreTimeval
|
||||
Reg LinuxCoreRegisters
|
||||
Reg linutil.AMD64PtraceRegs
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
|
||||
return nil
|
||||
@ -110,7 +118,7 @@ func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem M
|
||||
|
||||
file, line, fn := bininfo.PCToLine(pc)
|
||||
if fn == nil {
|
||||
return nil
|
||||
return &Location{PC: pc}
|
||||
}
|
||||
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
||||
}
|
@ -14,9 +14,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/derekparker/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
)
|
||||
|
||||
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":
|
||||
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:
|
||||
b, _ := constant.Int64Val(argv.Value)
|
||||
s := string(b)
|
||||
v.Value = constant.MakeString(s)
|
||||
v.Len = int64(len(s))
|
||||
return v, nil
|
||||
|
||||
case reflect.Slice:
|
||||
switch elemType := argv.RealType.(*godwarf.SliceType).ElemType.(type) {
|
||||
case reflect.Slice, reflect.Array:
|
||||
var elem godwarf.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:
|
||||
if elemType.Name != "uint8" && elemType.Name != "byte" {
|
||||
return nil, nil
|
||||
@ -1198,7 +1208,7 @@ func (err *typeConvErr) Error() string {
|
||||
|
||||
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
|
||||
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 nil
|
||||
@ -1255,6 +1265,34 @@ func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
|
||||
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) {
|
||||
if idx < 0 || int64(idx) >= v.Len {
|
||||
return nil, fmt.Errorf("index out of bounds")
|
@ -11,11 +11,10 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"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.
|
||||
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
|
||||
// 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()
|
||||
if !p.Common().fncallEnabled {
|
||||
return errFuncCallUnsupportedBackend
|
||||
@ -126,7 +125,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
argmem, err := funcCallArgFrame(fn, argvars, g, bi)
|
||||
argmem, err := funcCallArgFrame(fn, argvars, g, bi, checkEscape)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -153,10 +152,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
|
||||
}
|
||||
|
||||
func fncallLog(fmtstr string, args ...interface{}) {
|
||||
if !logflags.FnCall() {
|
||||
return
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"layer": "proc", "kind": "fncall"}).Infof(fmtstr, args...)
|
||||
logflags.FnCallLogger().Infof(fmtstr, args...)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
return nil, 0, nil, fnvar.Unreadable
|
||||
}
|
||||
@ -259,7 +255,7 @@ type funcCallArg struct {
|
||||
|
||||
// funcCallArgFrame checks type and pointer escaping for the arguments and
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -278,9 +274,11 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
|
||||
formalArg := &formalArgs[i]
|
||||
actualArg := actualArgs[i]
|
||||
|
||||
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
|
||||
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
|
||||
return nil, fmt.Errorf("can not pass %s to %s: %v", actualArg.Name, formalArg.name, err)
|
||||
if checkEscape {
|
||||
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
|
||||
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
|
||||
@ -343,7 +341,13 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
|
||||
func escapeCheck(v *Variable, name string, g *G) error {
|
||||
switch v.Kind {
|
||||
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)
|
||||
case reflect.Chan, reflect.String, reflect.Slice:
|
||||
return escapeCheckPointer(v.Base, name, g)
|
@ -68,23 +68,20 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -130,7 +127,7 @@ type Process struct {
|
||||
common proc.CommonProcess
|
||||
}
|
||||
|
||||
// Thread is a thread.
|
||||
// Thread represents an operating system thread.
|
||||
type Thread struct {
|
||||
ID int
|
||||
strID string
|
||||
@ -142,8 +139,7 @@ type Thread struct {
|
||||
}
|
||||
|
||||
// ErrBackendUnavailable is returned when the stub program can not be found.
|
||||
type ErrBackendUnavailable struct {
|
||||
}
|
||||
type ErrBackendUnavailable struct{}
|
||||
|
||||
func (err *ErrBackendUnavailable) Error() string {
|
||||
return "backend unavailable"
|
||||
@ -172,11 +168,7 @@ type gdbRegister struct {
|
||||
// Detach.
|
||||
// Use Listen, Dial or Connect to complete connection.
|
||||
func New(process *os.Process) *Process {
|
||||
logger := logrus.New().WithFields(logrus.Fields{"layer": "gdbconn"})
|
||||
logger.Logger.Level = logrus.DebugLevel
|
||||
if !logflags.GdbWire() {
|
||||
logger.Logger.Out = ioutil.Discard
|
||||
}
|
||||
logger := logflags.GdbWireLogger()
|
||||
p := &Process{
|
||||
conn: gdbConn{
|
||||
maxTransmitAttempts: maxTransmitAttempts,
|
||||
@ -205,7 +197,7 @@ func New(process *os.Process) *Process {
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
go func() {
|
||||
@ -219,7 +211,7 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
|
||||
if conn == nil {
|
||||
return errors.New("could not connect")
|
||||
}
|
||||
return p.Connect(conn, path, pid)
|
||||
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||
case status := <-p.waitChan:
|
||||
listener.Close()
|
||||
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.
|
||||
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 {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
return p.Connect(conn, path, pid)
|
||||
return p.Connect(conn, path, pid, debugInfoDirs)
|
||||
}
|
||||
select {
|
||||
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
|
||||
// some stubs do not provide ways to determine path and pid automatically
|
||||
// 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.pid = pid
|
||||
err := p.conn.handshake()
|
||||
if err != nil {
|
||||
@ -270,55 +261,7 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) 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 = 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()
|
||||
if err := p.initialize(path, debugInfoDirs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -335,36 +278,6 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
|
||||
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
|
||||
}
|
||||
|
||||
@ -405,7 +318,7 @@ func getLdEnvVars() []string {
|
||||
// LLDBLaunch starts an instance of lldb-server and connects to it, asking
|
||||
// it to launch the specified target program with the specified arguments
|
||||
// (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 {
|
||||
case "windows":
|
||||
return nil, ErrUnsupportedOS
|
||||
@ -482,9 +395,9 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
p.conn.isDebugserver = isDebugserver
|
||||
|
||||
if listener != nil {
|
||||
err = p.Listen(listener, cmd[0], 0)
|
||||
err = p.Listen(listener, cmd[0], 0, debugInfoDirs)
|
||||
} else {
|
||||
err = p.Dial(port, cmd[0], 0)
|
||||
err = p.Dial(port, cmd[0], 0, debugInfoDirs)
|
||||
}
|
||||
if err != nil {
|
||||
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
|
||||
// for some stubs that do not provide an automated way of determining it
|
||||
// (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" {
|
||||
return nil, ErrUnsupportedOS
|
||||
}
|
||||
|
||||
isDebugserver := false
|
||||
var proc *exec.Cmd
|
||||
var process *exec.Cmd
|
||||
var listener net.Listener
|
||||
var port string
|
||||
if _, err := os.Stat(debugserverExecutable); err == nil {
|
||||
@ -512,32 +425,31 @@ func LLDBAttach(pid int, path string) (*Process, error) {
|
||||
if err != nil {
|
||||
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 {
|
||||
if _, err := exec.LookPath("lldb-server"); err != nil {
|
||||
return nil, &ErrBackendUnavailable{}
|
||||
}
|
||||
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
|
||||
proc.Stderr = os.Stderr
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
process.SysProcAttr = sysProcAttr(false)
|
||||
|
||||
proc.SysProcAttr = sysProcAttr(false)
|
||||
|
||||
err := proc.Start()
|
||||
err := process.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := New(proc.Process)
|
||||
p := New(process.Process)
|
||||
p.conn.isDebugserver = isDebugserver
|
||||
|
||||
if listener != nil {
|
||||
err = p.Listen(listener, path, pid)
|
||||
err = p.Listen(listener, path, pid, debugInfoDirs)
|
||||
} else {
|
||||
err = p.Dial(port, path, pid)
|
||||
err = p.Dial(port, path, pid, debugInfoDirs)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -545,10 +457,80 @@ func LLDBAttach(pid int, path string) (*Process, error) {
|
||||
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
|
||||
// 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)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
@ -659,7 +641,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
|
||||
|
||||
// resume all threads
|
||||
var threadID string
|
||||
var sig uint8 = 0
|
||||
var sig uint8
|
||||
var tu = threadUpdater{p: p}
|
||||
var err error
|
||||
continueLoop:
|
||||
@ -697,6 +679,14 @@ continueLoop:
|
||||
// process so we must stop here.
|
||||
case 0x91, 0x92, 0x93, 0x94, 0x95, 0x96: /* TARGET_EXC_BAD_ACCESS */
|
||||
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:
|
||||
// any other signal is always propagated to inferior
|
||||
}
|
||||
@ -706,13 +696,19 @@ continueLoop:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.BinInfo().GOOS == "linux" {
|
||||
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.setCurrentBreakpoints(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, thread := range p.threads {
|
||||
if thread.strID == threadID {
|
||||
var err error = nil
|
||||
var err error
|
||||
switch sig {
|
||||
case 0x91:
|
||||
err = errors.New("bad access")
|
||||
@ -734,6 +730,13 @@ continueLoop:
|
||||
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.
|
||||
func (p *Process) StepInstruction() error {
|
||||
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) {
|
||||
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 {
|
||||
return "", 0, nil, nil, err
|
||||
@ -1310,7 +1310,6 @@ func (t *Thread) Blocked() bool {
|
||||
default:
|
||||
return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (thread *Thread) SetCurrentBreakpoint() error {
|
||||
thread.clearBreakpointState()
|
||||
regs, err := thread.Registers(false)
|
||||
func (t *Thread) SetCurrentBreakpoint() error {
|
||||
t.clearBreakpointState()
|
||||
regs, err := t.Registers(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pc := regs.PC()
|
||||
if bp, ok := thread.p.FindBreakpoint(pc); ok {
|
||||
if thread.regs.PC() != bp.Addr {
|
||||
if err := thread.SetPC(bp.Addr); err != nil {
|
||||
if bp, ok := t.p.FindBreakpoint(pc); ok {
|
||||
if t.regs.PC() != bp.Addr {
|
||||
if err := t.SetPC(bp.Addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
thread.CurrentBreakpoint = bp.CheckCondition(thread)
|
||||
if thread.CurrentBreakpoint.Breakpoint != nil && thread.CurrentBreakpoint.Active {
|
||||
if g, err := proc.GetG(thread); err == nil {
|
||||
thread.CurrentBreakpoint.HitCount[g.ID]++
|
||||
t.CurrentBreakpoint = bp.CheckCondition(t)
|
||||
if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
|
||||
if g, err := proc.GetG(t); err == nil {
|
||||
t.CurrentBreakpoint.HitCount[g.ID]++
|
||||
}
|
||||
thread.CurrentBreakpoint.TotalHitCount++
|
||||
t.CurrentBreakpoint.TotalHitCount++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -1827,9 +1826,12 @@ func (t *Thread) SetDX(dx uint64) error {
|
||||
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))
|
||||
for _, reginfo := range regs.regsInfo {
|
||||
if reginfo.Group == "float" && !floatingPoint {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case reginfo.Name == "eflags":
|
||||
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:
|
||||
r = proc.AppendQwordReg(r, reginfo.Name, binary.LittleEndian.Uint64(regs.regs[reginfo.Name].value))
|
||||
case reginfo.Bitsize == 80:
|
||||
if !floatingPoint {
|
||||
continue
|
||||
}
|
||||
idx := 0
|
||||
for _, stprefix := range []string{"stmm", "st"} {
|
||||
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]))
|
||||
|
||||
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:
|
||||
if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") {
|
||||
if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") || !floatingPoint {
|
||||
continue
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -227,7 +227,8 @@ type gdbRegisterInfo struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Bitsize int `xml:"bitsize,attr"`
|
||||
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,
|
@ -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
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -82,7 +82,7 @@ func Replay(tracedir string, quiet bool) (*Process, error) {
|
||||
|
||||
p := New(rrcmd.Process)
|
||||
p.tracedir = tracedir
|
||||
err = p.Dial(init.port, init.exe, 0)
|
||||
err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
|
||||
if err != nil {
|
||||
rrcmd.Process.Kill()
|
||||
return nil, err
|
||||
@ -257,11 +257,11 @@ func splitQuotedFields(in string) []string {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if tracedir == "" {
|
||||
return nil, "", err
|
||||
}
|
||||
p, err = Replay(tracedir, quiet)
|
||||
p, err = Replay(tracedir, quiet, debugInfoDirs)
|
||||
return p, tracedir, err
|
||||
}
|
@ -68,6 +68,7 @@ type Info interface {
|
||||
// ErrProcessExited or ProcessDetachedError).
|
||||
Valid() (bool, error)
|
||||
BinInfo() *BinaryInfo
|
||||
EntryPoint() (uint64, error)
|
||||
// Common returns a struct with fields common to all backends
|
||||
Common() *CommonProcess
|
||||
|
||||
@ -86,6 +87,7 @@ type ThreadInfo interface {
|
||||
// GoroutineInfo is an interface for getting information on running goroutines.
|
||||
type GoroutineInfo interface {
|
||||
SelectedGoroutine() *G
|
||||
SetSelectedGoroutine(*G)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
)
|
||||
|
||||
const cacheEnabled = true
|
||||
@ -93,7 +94,7 @@ type compositeMemory struct {
|
||||
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{}}
|
||||
for _, piece := range pieces {
|
||||
if piece.IsRegister {
|
||||
@ -102,6 +103,9 @@ func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []o
|
||||
if sz == 0 && len(pieces) == 1 {
|
||||
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]...)
|
||||
} else {
|
||||
buf := make([]byte, piece.Size)
|
||||
@ -109,7 +113,7 @@ func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []o
|
||||
cmem.data = append(cmem.data, buf...)
|
||||
}
|
||||
}
|
||||
return cmem
|
||||
return cmem, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil
|
||||
}
|
@ -6,18 +6,18 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Attach returns ErrNativeBackendDisabled.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
func Attach(pid int, _ []string) (*Process, error) {
|
||||
return nil, ErrNativeBackendDisabled
|
||||
}
|
||||
|
||||
@ -75,7 +75,9 @@ func (dbp *Process) detach(kill bool) error {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -85,17 +87,17 @@ func (t *Thread) Blocked() bool {
|
||||
}
|
||||
|
||||
// SetPC sets the value of the PC register.
|
||||
func (thread *Thread) SetPC(pc uint64) error {
|
||||
func (t *Thread) SetPC(pc uint64) error {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
// SetSP sets the value of the SP register.
|
||||
func (thread *Thread) SetSP(sp uint64) error {
|
||||
func (t *Thread) SetSP(sp uint64) error {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
// SetDX sets the value of the DX register.
|
||||
func (thread *Thread) SetDX(dx uint64) error {
|
||||
func (t *Thread) SetDX(dx uint64) error {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
@ -126,3 +128,5 @@ func (t *Thread) restoreRegisters(sr proc.Registers) error {
|
||||
func (t *Thread) Stopped() bool {
|
||||
panic(ErrNativeBackendDisabled)
|
||||
}
|
||||
|
||||
func initialize(dbp *Process) error { return nil }
|
@ -6,13 +6,14 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// Process represents all of the information the debugger
|
||||
// is holding onto regarding the process we are debugging.
|
||||
type Process struct {
|
||||
bi *proc.BinaryInfo
|
||||
bi *proc.BinaryInfo
|
||||
|
||||
pid int // Process Pid
|
||||
|
||||
// Breakpoint table, holds information on breakpoints.
|
||||
@ -184,32 +185,6 @@ func (dbp *Process) Breakpoints() *proc.BreakpointMap {
|
||||
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
|
||||
// sends SIGSTOP to all threads.
|
||||
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) {
|
||||
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())
|
||||
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
|
||||
@ -379,26 +351,23 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Returns a new Process struct.
|
||||
func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
|
||||
err := dbp.LoadInformation(path)
|
||||
if err != nil {
|
||||
return dbp, err
|
||||
// initialize will ensure that all relevant information is loaded
|
||||
// so the process is ready to be debugged.
|
||||
func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
|
||||
if err := initialize(dbp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
// because without calling SetGStructOffset we can not read the G struct of currentThread
|
||||
// but without calling updateThreadList we can not examine memory to determine
|
||||
// the offset of g struct inside TLS
|
||||
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
|
||||
|
||||
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
|
||||
|
||||
return dbp, nil
|
||||
// SetSelectedGoroutine will set internally the goroutine that should be
|
||||
// the default for any command executed, the goroutine being actively
|
||||
// followed.
|
||||
func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
|
||||
dbp.selectedGoroutine = g
|
||||
}
|
||||
|
||||
// ClearInternalBreakpoints will clear all non-user set breakpoints. These
|
@ -13,12 +13,11 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// OSProcessDetails holds Darwin specific information.
|
||||
@ -38,7 +37,7 @@ type OSProcessDetails struct {
|
||||
// custom fork/exec process in order to take advantage of
|
||||
// PT_SIGEXC on Darwin which will turn Unix signals into
|
||||
// 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
|
||||
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
|
||||
return nil, proc.ErrNotExecutable
|
||||
@ -119,7 +118,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
}
|
||||
|
||||
dbp.os.initialized = true
|
||||
dbp, err = initializeDebugProcess(dbp, argv0Go)
|
||||
err = dbp.initialize(argv0Go, []string{})
|
||||
if err != nil {
|
||||
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.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
func Attach(pid int, _ []string) (*Process, error) {
|
||||
dbp := New(pid)
|
||||
|
||||
kret := C.acquire_mach_task(C.int(pid),
|
||||
@ -155,7 +154,7 @@ func Attach(pid int) (*Process, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp, err = initializeDebugProcess(dbp, "")
|
||||
err = dbp.initialize("", []string{})
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
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) {
|
||||
var status sys.WaitStatus
|
||||
wpid, err := sys.Wait4(pid, &status, options, nil)
|
||||
@ -464,7 +459,9 @@ func (dbp *Process) detach(kill bool) error {
|
||||
return PtraceDetach(dbp.pid, 0)
|
||||
}
|
||||
|
||||
func (dbp *Process) entryPoint() (uint64, error) {
|
||||
func (dbp *Process) EntryPoint() (uint64, error) {
|
||||
//TODO(aarzilli): implement this
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func initialize(dbp *Process) error { return nil }
|
@ -12,15 +12,15 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/derekparker/delve/pkg/proc/linutil"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc/linutil"
|
||||
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// Process statuses
|
||||
@ -46,7 +46,9 @@ type OSProcessDetails struct {
|
||||
// Launch creates and begins debugging a new process. First entry in
|
||||
// `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.
|
||||
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 (
|
||||
process *exec.Cmd
|
||||
err error
|
||||
@ -88,11 +90,16 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
if err != nil {
|
||||
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.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
// Attach to an existing process with the given PID. Once attached, if
|
||||
// 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.common = proc.NewCommonProcess(true)
|
||||
|
||||
@ -106,7 +113,7 @@ func Attach(pid int) (*Process, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp, err = initializeDebugProcess(dbp, "")
|
||||
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
|
||||
if err != nil {
|
||||
dbp.Detach(false)
|
||||
return nil, err
|
||||
@ -114,6 +121,33 @@ func Attach(pid int) (*Process, error) {
|
||||
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.
|
||||
func (dbp *Process) kill() (err error) {
|
||||
if dbp.exited {
|
||||
@ -199,7 +233,7 @@ func (dbp *Process) updateThreadList() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return linutil.ElfUpdateSharedObjects(dbp)
|
||||
}
|
||||
|
||||
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) {
|
||||
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 (dbp *Process) loadProcessInformation() {
|
||||
}
|
||||
|
||||
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
|
||||
for _, th := range dbp.threads {
|
||||
if th.CurrentBreakpoint.Breakpoint == nil {
|
||||
@ -479,7 +489,9 @@ func (dbp *Process) detach(kill bool) error {
|
||||
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))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
|
@ -7,13 +7,12 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
sys "golang.org/x/sys/windows"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// 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.
|
||||
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])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -77,11 +76,14 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
|
||||
dbp.pid = p.Pid
|
||||
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 newDebugProcess(dbp *Process, exepath string) (*Process, error) {
|
||||
func initialize(dbp *Process) error {
|
||||
// It should not actually be possible for the
|
||||
// call to waitForDebugEvent to fail, since Windows
|
||||
// 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)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if tid == 0 {
|
||||
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
|
||||
// not resume the target.
|
||||
for _, thread := range dbp.threads {
|
||||
_, err := _SuspendThread(thread.os.hThread)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dbp.execPtraceFunc(func() {
|
||||
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return initializeDebugProcess(dbp, exepath)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Attach(pid int) (*Process, error) {
|
||||
func Attach(pid int, _ []string) (*Process, error) {
|
||||
// TODO: Probably should have SeDebugPrivilege before starting here.
|
||||
err := _DebugActiveProcess(uint32(pid))
|
||||
if err != nil {
|
||||
@ -163,11 +161,9 @@ func Attach(pid int) (*Process, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp, err := newDebugProcess(New(pid), exepath)
|
||||
if err != nil {
|
||||
if dbp != nil {
|
||||
dbp.Detach(false)
|
||||
}
|
||||
dbp := New(pid)
|
||||
if err = dbp.initialize(exepath, []string{}); err != nil {
|
||||
dbp.Detach(true)
|
||||
return nil, err
|
||||
}
|
||||
return dbp, nil
|
||||
@ -245,6 +241,8 @@ const (
|
||||
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) {
|
||||
var debugEvent _DEBUG_EVENT
|
||||
shouldExit := false
|
||||
@ -346,6 +344,10 @@ func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, e
|
||||
case _EXCEPTION_SINGLE_STEP:
|
||||
dbp.os.breakThread = tid
|
||||
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:
|
||||
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
|
||||
}
|
||||
@ -386,10 +388,6 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||
return th, nil
|
||||
}
|
||||
|
||||
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
|
||||
return 0, nil, fmt.Errorf("not implemented: wait")
|
||||
}
|
||||
@ -482,7 +480,7 @@ func (dbp *Process) detach(kill bool) error {
|
||||
return _DebugActiveProcessStop(uint32(dbp.pid))
|
||||
}
|
||||
|
||||
func (dbp *Process) entryPoint() (uint64, error) {
|
||||
func (dbp *Process) EntryPoint() (uint64, error) {
|
||||
return dbp.os.entryPoint, nil
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
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.
|
||||
@ -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
|
||||
// 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
|
||||
func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
|
||||
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(®set.PtraceFpRegs)), 0, 0)
|
||||
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.AMD64PtraceFpRegs)), 0, 0)
|
||||
if err == syscall.Errno(0) || err == syscall.ENODEV {
|
||||
// ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
|
||||
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}
|
||||
_, _, 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.ENODEV {
|
||||
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/derekparker/delve/issues/1022
|
||||
if err == syscall.ENODEV || err == syscall.EIO {
|
||||
// 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
|
||||
}
|
||||
return
|
||||
@ -78,6 +79,6 @@ func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
|
||||
}
|
||||
|
||||
regset.Xsave = xstateargs[:iov.Len]
|
||||
err = proc.LinuxX86XstateRead(regset.Xsave, false, ®set)
|
||||
err = linutil.AMD64XstateRead(regset.Xsave, false, ®set)
|
||||
return regset, err
|
||||
}
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
"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.
|
||||
@ -42,7 +42,7 @@ type Regs struct {
|
||||
fpregs []proc.Register
|
||||
}
|
||||
|
||||
func (r *Regs) Slice() []proc.Register {
|
||||
func (r *Regs) Slice(floatingPoint bool) []proc.Register {
|
||||
var regs = []struct {
|
||||
k string
|
||||
v uint64
|
||||
@ -78,7 +78,9 @@ func (r *Regs) Slice() []proc.Register {
|
||||
out = proc.AppendQwordReg(out, reg.k, reg.v)
|
||||
}
|
||||
}
|
||||
out = append(out, r.fpregs...)
|
||||
if floatingPoint {
|
||||
out = append(out, r.fpregs...)
|
||||
}
|
||||
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 (
|
||||
"syscall"
|
||||
|
||||
"github.com/go-delve/delve/pkg/proc/winutil"
|
||||
)
|
||||
|
||||
type _NTSTATUS int32
|
||||
@ -97,6 +99,11 @@ func _NT_SUCCESS(x _NTSTATUS) bool {
|
||||
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 _GetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.GetThreadContext
|
||||
//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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// Thread represents a single thread in the traced process
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
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
|
@ -7,7 +7,8 @@ import (
|
||||
|
||||
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
|
||||
@ -83,21 +84,21 @@ func (t *Thread) Blocked() bool {
|
||||
}
|
||||
|
||||
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
|
||||
sr := savedRegs.(*Regs)
|
||||
sr := savedRegs.(*linutil.AMD64Registers)
|
||||
|
||||
var restoreRegistersErr error
|
||||
t.dbp.execPtraceFunc(func() {
|
||||
restoreRegistersErr = sys.PtraceSetRegs(t.ID, sr.regs)
|
||||
restoreRegistersErr = sys.PtraceSetRegs(t.ID, (*sys.PtraceRegs)(sr.Regs))
|
||||
if restoreRegistersErr != nil {
|
||||
return
|
||||
}
|
||||
if sr.fpregset.Xsave != nil {
|
||||
iov := sys.Iovec{Base: &sr.fpregset.Xsave[0], Len: uint64(len(sr.fpregset.Xsave))}
|
||||
if sr.Fpregset.Xsave != nil {
|
||||
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)
|
||||
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
|
||||
})
|
||||
if restoreRegistersErr == syscall.Errno(0) {
|
@ -6,7 +6,8 @@ import (
|
||||
|
||||
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
|
||||
@ -19,7 +20,7 @@ type OSSpecificDetails struct {
|
||||
}
|
||||
|
||||
func (t *Thread) singleStep() error {
|
||||
context := newCONTEXT()
|
||||
context := winutil.NewCONTEXT()
|
||||
context.ContextFlags = _CONTEXT_ALL
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
var ErrNotRecorded = errors.New("not a recording")
|
||||
|
||||
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
|
||||
const UnrecoveredPanic = "unrecovered-panic"
|
||||
const (
|
||||
// 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
|
||||
// process id and exit status.
|
||||
@ -41,6 +49,31 @@ func (pe ProcessDetachedError) Error() string {
|
||||
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.
|
||||
// Assumes that `file` is normalized to lower case and '/' on Windows.
|
||||
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
|
||||
// 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:
|
||||
// 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) {
|
||||
bi := p.BinInfo()
|
||||
origfn := bi.LookupFunc[funcName]
|
||||
@ -88,6 +121,33 @@ func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset
|
||||
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.
|
||||
func Next(dbp Process) (err error) {
|
||||
if _, err := dbp.Valid(); err != nil {
|
||||
@ -184,7 +244,8 @@ func Continue(dbp Process) error {
|
||||
return conditionErrors(threads)
|
||||
}
|
||||
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
|
||||
if err := conditionErrors(threads); err != nil {
|
||||
return err
|
||||
@ -204,7 +265,7 @@ func Continue(dbp Process) error {
|
||||
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
|
||||
if err := dbp.ClearInternalBreakpoints(); err != nil {
|
||||
return err
|
||||
@ -281,7 +342,7 @@ func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string)
|
||||
if g := dbp.SelectedGoroutine(); g != nil {
|
||||
g.CurrentLoc = *loc
|
||||
}
|
||||
return nil
|
||||
return curthread.SetCurrentBreakpoint()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -436,14 +497,22 @@ func StepOut(dbp Process) error {
|
||||
return Continue(dbp)
|
||||
}
|
||||
|
||||
// GoroutinesInfo returns an array of G structures representing the information
|
||||
// Delve cares about from the internal runtime G structure.
|
||||
func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
// GoroutinesInfo searches for goroutines starting at index 'start', and
|
||||
// returns an array of up to 'count' (or all found elements, if 'count' is 0)
|
||||
// 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 {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
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 (
|
||||
@ -465,12 +534,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
|
||||
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
allglenBytes := make([]byte, 8)
|
||||
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
||||
|
||||
@ -480,17 +549,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
// try old name (pre Go 1.6)
|
||||
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
|
||||
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
allg = append(allg, &G{Unreadable: err})
|
||||
@ -504,7 +576,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
if thg, allocated := threadg[g.ID]; allocated {
|
||||
loc, err := thg.Thread.Location()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
g.Thread = thg.Thread
|
||||
// Prefer actual thread location information.
|
||||
@ -515,36 +587,78 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
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
|
||||
// specified by `gid`.
|
||||
func FindGoroutine(dbp Process, gid int) (*G, error) {
|
||||
if gid == -1 {
|
||||
return dbp.SelectedGoroutine(), nil
|
||||
if selg := dbp.SelectedGoroutine(); (gid == -1) || (selg != nil && selg.ID == gid) || (selg == nil && gid == 0) {
|
||||
// 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 err != nil {
|
||||
return nil, err
|
||||
if gid == 0 {
|
||||
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
||||
}
|
||||
for i := range gs {
|
||||
if gs[i].ID == gid {
|
||||
if gs[i].Unreadable != nil {
|
||||
return nil, gs[i].Unreadable
|
||||
}
|
||||
return gs[i], nil
|
||||
|
||||
// Calling GoroutinesInfo could be slow if there are many goroutines
|
||||
// running, check if a running goroutine has been requested first.
|
||||
for _, thread := range dbp.ThreadList() {
|
||||
g, _ := GetG(thread)
|
||||
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)
|
||||
}
|
||||
|
||||
// ConvertEvalScope returns a new EvalScope in the context of the
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -564,7 +678,7 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
|
||||
thread = g.Thread
|
||||
}
|
||||
|
||||
locs, err := g.Stacktrace(frame+1, false)
|
||||
locs, err := g.Stacktrace(frame+1, deferCall > 0)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -606,21 +733,30 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
|
||||
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.
|
||||
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) {
|
||||
func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
||||
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
||||
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
|
||||
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
|
||||
}
|
||||
if err == nil {
|
||||
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint)
|
||||
bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpc, writeBreakpoint)
|
||||
if err == nil {
|
||||
bp.Name = UnrecoveredPanic
|
||||
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
|
@ -23,7 +23,7 @@ type Registers interface {
|
||||
// GAddr returns the address of the G variable if it is known, 0 and false otherwise
|
||||
GAddr() (uint64, bool)
|
||||
Get(int) (uint64, error)
|
||||
Slice() []Register
|
||||
Slice(floatingPoint bool) []Register
|
||||
// Copy returns a copy of the registers that is guaranteed not to change
|
||||
// when the registers of the associated thread change.
|
||||
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, " "))
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
if regname, ok := dwarfToName[i]; ok {
|
||||
regslice := regs.Slice()
|
||||
regslice := regs.Slice(true)
|
||||
for _, reg := range regslice {
|
||||
if reg.Name == regname {
|
||||
return reg.Bytes
|
@ -7,9 +7,9 @@ import (
|
||||
"go/constant"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/frame"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/dwarf/frame"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
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 {
|
||||
fnname := ""
|
||||
if r.Current.Fn != nil {
|
||||
fnname = r.Current.Fn.Name
|
||||
}
|
||||
switch fnname {
|
||||
r.Call = r.Current
|
||||
if !it.top && r.Current.Fn != nil && it.pc != r.Current.Fn.Entry {
|
||||
// if the return address is the entry point of the function that
|
||||
// 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.
|
||||
switch r.Current.Fn.Name {
|
||||
case "runtime.mstart", "runtime.systemstack_switch":
|
||||
// these frames are inserted by runtime.systemstack and there is no CALL
|
||||
// instruction to look for at pc - 1
|
||||
r.Call = r.Current
|
||||
default:
|
||||
if r.Current.Fn != nil && it.pc == r.Current.Fn.Entry {
|
||||
// if the return address is the entry point of the function that
|
||||
// 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
|
||||
}
|
||||
r.lastpc = it.pc - 1
|
||||
r.Call.File, r.Call.Line = r.Current.Fn.cu.lineInfo.PCToLine(r.Current.Fn.Entry, it.pc-1)
|
||||
}
|
||||
} else {
|
||||
r.Call = r.Current
|
||||
}
|
||||
return r
|
||||
}
|
||||
@ -594,6 +579,7 @@ type Defer struct {
|
||||
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)
|
||||
link *Defer // Next deferred function
|
||||
argSz int64
|
||||
|
||||
variable *Variable
|
||||
Unreadable error
|
||||
@ -644,7 +630,7 @@ func (g *G) readDefers(frames []Stackframe) {
|
||||
}
|
||||
|
||||
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 {
|
||||
d.Unreadable = d.variable.Unreadable
|
||||
return
|
||||
@ -660,6 +646,7 @@ func (d *Defer) load() {
|
||||
|
||||
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").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()
|
||||
if linkvar.Addr != 0 {
|
||||
@ -685,3 +672,40 @@ func (d *Defer) Next() *Defer {
|
||||
}
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
)
|
||||
|
||||
// Thread represents a thread.
|
||||
@ -225,15 +225,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
||||
}
|
||||
|
||||
if !csource {
|
||||
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 == "runtime.deferreturn" {
|
||||
deferreturns = append(deferreturns, instr.Loc.PC)
|
||||
}
|
||||
}
|
||||
deferreturns := findDeferReturnCalls(text)
|
||||
|
||||
// Set breakpoint on the most recently deferred function (if any)
|
||||
var deferpc uint64
|
||||
@ -333,6 +325,20 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
||||
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.
|
||||
// If includeCurrentFn is true it will also remove all instructions
|
||||
// belonging to the current function.
|
||||
@ -373,21 +379,16 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
if strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
|
||||
if fn != nil && strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
|
||||
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'
|
||||
// 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
|
||||
pc, _ := FirstPCAfterPrologue(dbp, fn, false)
|
||||
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
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
|
||||
// the current thread from the thread local storage area.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@ -18,12 +17,12 @@ import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/line"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/derekparker/delve/pkg/goversion"
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/line"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
"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
|
||||
|
||||
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] }
|
||||
|
||||
type packageVarsByAddr []packageVar
|
||||
@ -230,18 +229,18 @@ outer:
|
||||
if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
|
||||
cu.isgo = true
|
||||
}
|
||||
cu.Name, _ = entry.Val(dwarf.AttrName).(string)
|
||||
cu.name, _ = entry.Val(dwarf.AttrName).(string)
|
||||
compdir, _ := entry.Val(dwarf.AttrCompDir).(string)
|
||||
if compdir != "" {
|
||||
cu.Name = filepath.Join(compdir, cu.Name)
|
||||
cu.name = filepath.Join(compdir, cu.name)
|
||||
}
|
||||
cu.Ranges, _ = bi.dwarf.Ranges(entry)
|
||||
for i := range cu.Ranges {
|
||||
cu.Ranges[i][0] += bi.staticBase
|
||||
cu.Ranges[i][1] += bi.staticBase
|
||||
cu.ranges, _ = bi.dwarf.Ranges(entry)
|
||||
for i := range cu.ranges {
|
||||
cu.ranges[i][0] += bi.staticBase
|
||||
cu.ranges[i][1] += bi.staticBase
|
||||
}
|
||||
if len(cu.Ranges) >= 1 {
|
||||
cu.LowPC = cu.Ranges[0][0]
|
||||
if len(cu.ranges) >= 1 {
|
||||
cu.lowPC = cu.ranges[0][0]
|
||||
}
|
||||
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
|
||||
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
|
||||
@ -398,6 +397,13 @@ outer:
|
||||
highpc = ranges[0][1] + bi.staticBase
|
||||
}
|
||||
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
|
||||
if (ok1 == !inlined) && ok2 {
|
||||
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
|
||||
// the type and map it drectly to a DIE.
|
||||
func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind int64, err error) {
|
||||
var go17 bool
|
||||
var typestring *Variable
|
||||
|
||||
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()
|
||||
|
||||
// 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 {")
|
||||
|
||||
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 {
|
||||
return "", nil
|
||||
}
|
||||
@ -941,7 +910,7 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error)
|
||||
buf.WriteString("struct {")
|
||||
|
||||
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 {
|
||||
return "", fields.Unreadable
|
||||
}
|
@ -15,9 +15,9 @@ import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/derekparker/delve/pkg/dwarf/godwarf"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/go-delve/delve/pkg/dwarf/godwarf"
|
||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||
"github.com/go-delve/delve/pkg/dwarf/reader"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -32,6 +32,8 @@ const (
|
||||
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
|
||||
|
||||
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
|
||||
@ -122,10 +124,39 @@ type LoadConfig struct {
|
||||
MaxArrayValues int
|
||||
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
|
||||
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 loadFullValue = LoadConfig{true, 1, 64, 64, -1}
|
||||
var loadSingleValue = LoadConfig{false, 0, 64, 0, 0, 0}
|
||||
var loadFullValue = LoadConfig{true, 1, 64, 64, -1, 0}
|
||||
|
||||
// G status, from: src/runtime/runtime2.go
|
||||
const (
|
||||
@ -169,6 +200,12 @@ type 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,
|
||||
// current location (PC), and canonical frame address.
|
||||
type EvalScope struct {
|
||||
@ -405,13 +442,13 @@ func (scope *EvalScope) PtrSize() int {
|
||||
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.
|
||||
type NoGError struct {
|
||||
type ErrNoGoroutine struct {
|
||||
tid int
|
||||
}
|
||||
|
||||
func (ng NoGError) Error() string {
|
||||
func (ng ErrNoGoroutine) Error() string {
|
||||
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 {
|
||||
id = thread.ThreadID()
|
||||
}
|
||||
return nil, NoGError{tid: id}
|
||||
return nil, ErrNoGoroutine{tid: id}
|
||||
}
|
||||
for {
|
||||
if _, isptr := v.RealType.(*godwarf.PtrType); !isptr {
|
||||
@ -441,7 +478,7 @@ func (v *Variable) parseG() (*G, error) {
|
||||
}
|
||||
v = v.maybeDereference()
|
||||
}
|
||||
v.loadValue(LoadConfig{false, 2, 64, 0, -1})
|
||||
v.loadValue(LoadConfig{false, 2, 64, 0, -1, 0})
|
||||
if v.Unreadable != nil {
|
||||
return nil, v.Unreadable
|
||||
}
|
||||
@ -586,12 +623,102 @@ func (g *G) StartLoc() Location {
|
||||
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
|
||||
func (g *G) stkbar() ([]savedLR, error) {
|
||||
if g.stkbarVar == nil { // stack barriers were removed in Go 1.9
|
||||
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 {
|
||||
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 {
|
||||
return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0
|
||||
})
|
||||
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
|
||||
loadValues(vars, cfg)
|
||||
return vars, nil
|
||||
}
|
||||
@ -671,6 +799,7 @@ func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
|
||||
vars = filterVariables(vars, func(v *Variable) bool {
|
||||
return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0
|
||||
})
|
||||
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
|
||||
loadValues(vars, cfg)
|
||||
return vars, nil
|
||||
}
|
||||
@ -875,7 +1004,11 @@ func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variabl
|
||||
mem := scope.Mem
|
||||
if pieces != nil {
|
||||
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)
|
||||
@ -895,6 +1028,10 @@ func (v *Variable) maybeDereference() *Variable {
|
||||
|
||||
switch t := v.RealType.(type) {
|
||||
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)
|
||||
r := v.newVariable("", uintptr(ptrval), t.Type, DereferenceMemory(v.mem))
|
||||
if err != nil {
|
||||
@ -1569,6 +1706,11 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
||||
if it == nil {
|
||||
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++ {
|
||||
if ok := it.next(); !ok {
|
||||
@ -1580,9 +1722,6 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
||||
count := 0
|
||||
errcount := 0
|
||||
for it.next() {
|
||||
if count >= cfg.MaxArrayValues {
|
||||
break
|
||||
}
|
||||
key := it.key()
|
||||
var val *Variable
|
||||
if it.values.fieldType.Size() > 0 {
|
||||
@ -1601,6 +1740,9 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
|
||||
if errcount > maxErrCount {
|
||||
break
|
||||
}
|
||||
if count >= cfg.MaxArrayValues || int64(count) >= v.Len {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1618,6 +1760,8 @@ type mapIterator struct {
|
||||
values *Variable
|
||||
overflow *Variable
|
||||
|
||||
maxNumBuckets uint64 // maximum number of buckets to scan
|
||||
|
||||
idx int64
|
||||
}
|
||||
|
||||
@ -1683,6 +1827,10 @@ func (it *mapIterator) nextBucket() bool {
|
||||
} else {
|
||||
it.b = nil
|
||||
|
||||
if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets {
|
||||
return false
|
||||
}
|
||||
|
||||
for it.bidx < it.numbuckets {
|
||||
it.b = it.buckets.clone()
|
||||
it.b.Addr += uintptr(uint64(it.buckets.DwarfType.Size()) * it.bidx)
|
@ -1,4 +1,4 @@
|
||||
package native
|
||||
package winutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -6,11 +6,11 @@ import (
|
||||
|
||||
"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.
|
||||
type Regs struct {
|
||||
// AMD64Registers represents CPU registers on an AMD64 processor.
|
||||
type AMD64Registers struct {
|
||||
rax uint64
|
||||
rbx uint64
|
||||
rcx uint64
|
||||
@ -33,11 +33,48 @@ type Regs struct {
|
||||
fs uint64
|
||||
gs uint64
|
||||
tls uint64
|
||||
context *_CONTEXT
|
||||
fltSave *_XMM_SAVE_AREA32
|
||||
Context *CONTEXT
|
||||
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 {
|
||||
k string
|
||||
v uint64
|
||||
@ -66,7 +103,7 @@ func (r *Regs) Slice() []proc.Register {
|
||||
{"TLS", r.tls},
|
||||
}
|
||||
outlen := len(regs)
|
||||
if r.fltSave != nil {
|
||||
if r.fltSave != nil && floatingPoint {
|
||||
outlen += 6 + 8 + 2 + 16
|
||||
}
|
||||
out := make([]proc.Register, 0, outlen)
|
||||
@ -77,7 +114,7 @@ func (r *Regs) Slice() []proc.Register {
|
||||
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, "SW", r.fltSave.StatusWord)
|
||||
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
|
||||
// i.e. the RIP CPU register.
|
||||
func (r *Regs) PC() uint64 {
|
||||
func (r *AMD64Registers) PC() uint64 {
|
||||
return r.rip
|
||||
}
|
||||
|
||||
// SP returns the stack pointer location,
|
||||
// i.e. the RSP register.
|
||||
func (r *Regs) SP() uint64 {
|
||||
func (r *AMD64Registers) SP() uint64 {
|
||||
return r.rsp
|
||||
}
|
||||
|
||||
func (r *Regs) BP() uint64 {
|
||||
func (r *AMD64Registers) BP() uint64 {
|
||||
return r.rbp
|
||||
}
|
||||
|
||||
// CX returns the value of the RCX register.
|
||||
func (r *Regs) CX() uint64 {
|
||||
func (r *AMD64Registers) CX() uint64 {
|
||||
return r.rcx
|
||||
}
|
||||
|
||||
// TLS returns the value of the register
|
||||
// that contains the location of the thread
|
||||
// local storage segment.
|
||||
func (r *Regs) TLS() uint64 {
|
||||
func (r *AMD64Registers) TLS() uint64 {
|
||||
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
|
||||
}
|
||||
|
||||
// SetPC sets the RIP register to the value specified by `pc`.
|
||||
func (thread *Thread) SetPC(pc 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) {
|
||||
// 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
|
||||
@ -332,59 +328,103 @@ func (r *Regs) Get(n int) (uint64, error) {
|
||||
return 0, proc.ErrUnknownRegister
|
||||
}
|
||||
|
||||
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
|
||||
context := 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)
|
||||
}
|
||||
|
||||
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
|
||||
// Copy returns a copy of these registers that is guarenteed not to change.
|
||||
func (r *AMD64Registers) Copy() proc.Registers {
|
||||
var rr AMD64Registers
|
||||
rr = *r
|
||||
rr.context = newCONTEXT()
|
||||
*(rr.context) = *(r.context)
|
||||
rr.fltSave = &rr.context.FltSave
|
||||
rr.Context = NewCONTEXT()
|
||||
*(rr.Context) = *(r.Context)
|
||||
rr.fltSave = &rr.Context.FltSave
|
||||
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"
|
||||
|
||||
"github.com/cosiner/argv"
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"github.com/derekparker/delve/service/debugger"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
"github.com/go-delve/delve/service/debugger"
|
||||
)
|
||||
|
||||
const optimizedFunctionWarning = "Warning: debugging optimized function"
|
||||
@ -32,6 +32,7 @@ type cmdPrefix int
|
||||
const (
|
||||
noPrefix = cmdPrefix(0)
|
||||
onPrefix = cmdPrefix(1 << iota)
|
||||
deferredPrefix
|
||||
)
|
||||
|
||||
type callContext struct {
|
||||
@ -113,24 +114,24 @@ Type "help" followed by the name of a command for more information about it.`},
|
||||
|
||||
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"`},
|
||||
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: `Set tracepoint.
|
||||
|
||||
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"`},
|
||||
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
|
||||
|
||||
restart [checkpoint]
|
||||
restart [-noargs] newargv...
|
||||
restart [checkpoint]
|
||||
restart [-noargs] newargv...
|
||||
|
||||
For recorded processes restarts from the start or from the specified
|
||||
checkpoint. For normal processes restarts the process, optionally changing
|
||||
the arguments. With -noargs, the process starts with an empty commandline.
|
||||
For recorded processes restarts from the start or from the specified
|
||||
checkpoint. For normal processes restarts the process, optionally changing
|
||||
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{"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{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||
|
||||
call [-unsafe] <function call expression>
|
||||
|
||||
Current limitations:
|
||||
- only pointers to stack-allocated objects can be passed as argument.
|
||||
- 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 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{"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>
|
||||
|
||||
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.
|
||||
|
||||
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>
|
||||
|
||||
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.
|
||||
|
||||
sources [<regex>]
|
||||
@ -214,12 +217,12 @@ If regex is specified only the functions matching it will be returned.`},
|
||||
types [<regex>]
|
||||
|
||||
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>]
|
||||
|
||||
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>]
|
||||
|
||||
@ -248,10 +251,13 @@ When connected to a headless instance started with the --accept-multiclient, pas
|
||||
Show source around current point or provided linespec.`},
|
||||
{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.
|
||||
-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"},
|
||||
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.
|
||||
|
||||
frame <m>
|
||||
frame <m> <command>
|
||||
frame <m>
|
||||
frame <m> <command>
|
||||
|
||||
The first form sets frame used by subsequent commands such as "print" or "set".
|
||||
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.
|
||||
|
||||
up [<m>]
|
||||
up [<m>] <command>
|
||||
up [<m>]
|
||||
up [<m>] <command>
|
||||
|
||||
Move the current frame up by <m>. The second form runs the command on the given frame.`},
|
||||
{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.
|
||||
|
||||
down [<m>]
|
||||
down [<m>] <command>
|
||||
down [<m>]
|
||||
down [<m>] <command>
|
||||
|
||||
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
|
||||
|
||||
source <path>`},
|
||||
@ -334,6 +345,7 @@ Defines <alias> as an alias to <command> or removes an alias.`},
|
||||
edit [locspec]
|
||||
|
||||
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() {
|
||||
@ -347,7 +359,9 @@ If locspec is omitted edit will open the current source file in the editor, othe
|
||||
cmdFn: checkpoint,
|
||||
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{
|
||||
aliases: []string{"checkpoints"},
|
||||
@ -426,7 +440,7 @@ func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) erro
|
||||
|
||||
// Call takes a command to execute.
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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) 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 {
|
||||
args := strings.Split(argstr, " ")
|
||||
var fgl = fglUserCurrent
|
||||
@ -593,26 +628,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gs, err := t.client.ListGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(byGoroutineID(gs))
|
||||
fmt.Printf("[%d goroutines]\n", len(gs))
|
||||
for _, g := range gs {
|
||||
prefix := " "
|
||||
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
||||
prefix = "* "
|
||||
var (
|
||||
start = 0
|
||||
gslen = 0
|
||||
gs []*api.Goroutine
|
||||
)
|
||||
for start >= 0 {
|
||||
gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
sort.Sort(byGoroutineID(gs))
|
||||
err = printGoroutines(t, gs, fgl, bPrintStack, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gslen += len(gs)
|
||||
}
|
||||
fmt.Printf("[%d goroutines]\n", gslen)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -714,6 +747,22 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi
|
||||
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 {
|
||||
state, err := t.client.GetState()
|
||||
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 {
|
||||
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
|
||||
if err != nil {
|
||||
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.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
@ -1356,6 +1414,20 @@ func stackCommand(t *Term, ctx callContext, args string) error {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -1364,6 +1436,9 @@ type stackArgs struct {
|
||||
full bool
|
||||
offsets bool
|
||||
readDefers bool
|
||||
|
||||
ancestors int
|
||||
ancestorDepth int
|
||||
}
|
||||
|
||||
func parseStackArgs(argstr string) (stackArgs, error) {
|
||||
@ -1373,7 +1448,18 @@ func parseStackArgs(argstr string) (stackArgs, error) {
|
||||
}
|
||||
if 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] {
|
||||
case "-full":
|
||||
r.full = true
|
||||
@ -1381,6 +1467,20 @@ func parseStackArgs(argstr string) (stackArgs, error) {
|
||||
r.offsets = true
|
||||
case "-defer":
|
||||
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:
|
||||
n, err := strconv.Atoi(args[i])
|
||||
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
|
||||
}
|
||||
|
||||
@ -1526,6 +1629,18 @@ func disassCommand(t *Term, ctx callContext, args string) error {
|
||||
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 {
|
||||
if n <= 0 {
|
||||
return 1
|
||||
@ -1565,7 +1680,7 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
|
||||
}
|
||||
|
||||
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))
|
||||
if 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 {
|
||||
var arg []string
|
||||
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, ", ")
|
||||
}
|
||||
@ -1874,6 +1996,9 @@ func (c *Commands) executeFile(t *Term, name string) error {
|
||||
}
|
||||
|
||||
if err := c.Call(line, t); err != nil {
|
||||
if _, isExitRequest := err.(ExitRequestError); isExitRequest {
|
||||
return 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.Init(os.Stdout, 4, 4, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tWhen\tWhere")
|
||||
fmt.Fprintln(w, "ID\tWhen\tNote")
|
||||
for _, cp := range cps {
|
||||
fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where)
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/derekparker/delve/pkg/config"
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
)
|
||||
|
||||
func configureCmd(t *Term, ctx callContext, args string) error {
|
@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
)
|
||||
|
||||
func DisasmPrint(dv api.AsmInstructions, out io.Writer) {
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func replaceDocPath(s string) string {
|
||||
const docpath = "$GOPATH/src/github.com/derekparker/delve/"
|
||||
const docpath = "$GOPATH/src/github.com/go-delve/delve/"
|
||||
|
||||
for {
|
||||
start := strings.Index(s, docpath)
|
||||
@ -22,7 +22,7 @@ func replaceDocPath(s string) string {
|
||||
}
|
||||
|
||||
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/derekparker/delve/pkg/config"
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -64,20 +64,6 @@ type Term struct {
|
||||
|
||||
// New returns a new 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)
|
||||
if conf != nil && conf.Aliases != nil {
|
||||
cmds.Merge(conf.Aliases)
|
||||
@ -204,6 +190,9 @@ func (t *Term) Run() (int, error) {
|
||||
if t.InitFile != "" {
|
||||
err := t.cmds.executeFile(t, t.InitFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(ExitRequestError); ok {
|
||||
return t.handleExit()
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
from := crossPlatformPath(r.From)
|
||||
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