1
0
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:
Ruben Cid 2019-04-15 17:35:01 +02:00
commit 6bf5f9dbfe
136 changed files with 4996 additions and 2541 deletions

View File

@ -1,20 +1,17 @@
language: go language: go
go: go:
- 1.10.3 - 1.11.9
- 1.12.4
install: install:
- export PATH=$PATH:$HOME/gopath/bin - export PATH=$PATH:$HOME/gopath/bin
- go get -u github.com/opennota/check/cmd/structcheck - go get -u github.com/opennota/check/cmd/structcheck
- go get -u honnef.co/go/tools/cmd/gosimple
- go get -u honnef.co/go/tools/cmd/staticcheck - go get -u honnef.co/go/tools/cmd/staticcheck
- go get -u honnef.co/go/tools/cmd/unused
- go get -u github.com/mdempsky/unconvert - go get -u github.com/mdempsky/unconvert
- go get -u github.com/gordonklaus/ineffassign - go get -u github.com/gordonklaus/ineffassign
script: script:
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
- go vet $(go list ./... | grep -v /vendor/) - go vet $(go list ./... | grep -v /vendor/)
- structcheck $(go list ./... | grep -v /vendor/) - structcheck $(go list ./... | grep -v /vendor/)
- gosimple -ignore "$(cat gosimple.ignore)" $(go list ./... | grep -v /vendor/)
- staticcheck -ignore "$(cat staticcheck.ignore)" $(go list ./... | grep -v /vendor/) - staticcheck -ignore "$(cat staticcheck.ignore)" $(go list ./... | grep -v /vendor/)
- unused $(go list ./... | grep -v /vendor/)
- unconvert $(go list ./... | grep -v /vendor/) - unconvert $(go list ./... | grep -v /vendor/)
- ineffassign . - ineffassign .

View File

@ -28,11 +28,11 @@ import (
"github.com/beego/bee/cmd/commands/version" "github.com/beego/bee/cmd/commands/version"
beeLogger "github.com/beego/bee/logger" beeLogger "github.com/beego/bee/logger"
"github.com/beego/bee/utils" "github.com/beego/bee/utils"
"github.com/derekparker/delve/pkg/terminal"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/rpc2"
"github.com/derekparker/delve/service/rpccommon"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/go-delve/delve/pkg/terminal"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/rpc2"
"github.com/go-delve/delve/service/rpccommon"
) )
var cmdDlv = &commands.Command{ var cmdDlv = &commands.Command{
@ -43,7 +43,7 @@ var cmdDlv = &commands.Command{
To debug your application using Delve, use: {{"$ bee dlv" | bold}} To debug your application using Delve, use: {{"$ bee dlv" | bold}}
For more information on Delve: https://github.com/derekparker/delve For more information on Delve: https://github.com/go-delve/delve
`, `,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() }, PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: runDlv, Run: runDlv,
@ -228,7 +228,7 @@ func startWatcher(paths []string, ch chan int) {
// Wait 1s before re-build until there is no file change // Wait 1s before re-build until there is no file change
scheduleTime := time.Now().Add(1 * time.Second) scheduleTime := time.Now().Add(1 * time.Second)
time.Sleep(scheduleTime.Sub(time.Now())) time.Sleep(time.Until(scheduleTime))
_, err := buildDebug() _, err := buildDebug()
if err != nil { if err != nil {
utils.Notify("Build Failed: "+err.Error(), "bee") utils.Notify("Build Failed: "+err.Error(), "bee")

View File

@ -85,7 +85,7 @@ func NewWatcher(paths []string, files []string, isgenerate bool) {
go func() { go func() {
// Wait 1s before autobuild until there is no file change. // Wait 1s before autobuild until there is no file change.
scheduleTime = time.Now().Add(1 * time.Second) scheduleTime = time.Now().Add(1 * time.Second)
time.Sleep(scheduleTime.Sub(time.Now())) time.Sleep(time.Until(scheduleTime))
AutoBuild(files, isgenerate) AutoBuild(files, isgenerate)
if config.Conf.EnableReload { if config.Conf.EnableReload {

View File

@ -57,7 +57,7 @@ Prints the current Bee, Beego and Go version alongside the platform information.
} }
var outputFormat string var outputFormat string
const version = "1.10.0" const version = "1.10.1"
func init() { func init() {
fs := flag.NewFlagSet("version", flag.ContinueOnError) fs := flag.NewFlagSet("version", flag.ContinueOnError)

View File

@ -116,7 +116,7 @@ func ParsePackagesFromDir(dirpath string) {
err = parsePackageFromDir(fpath) err = parsePackageFromDir(fpath)
if err != nil { if err != nil {
// Send the error to through the channel and continue walking // Send the error to through the channel and continue walking
c <- fmt.Errorf("Error while parsing directory: %s", err.Error()) c <- fmt.Errorf("error while parsing directory: %s", err.Error())
return nil return nil
} }
} }

View File

@ -328,7 +328,7 @@ func CheckEnv(appname string) (apppath, packpath string, err error) {
apppath = filepath.Join(gosrcpath, appname) apppath = filepath.Join(gosrcpath, appname)
if _, e := os.Stat(apppath); !os.IsNotExist(e) { if _, e := os.Stat(apppath); !os.IsNotExist(e) {
err = fmt.Errorf("Cannot create application without removing '%s' first", apppath) err = fmt.Errorf("cannot create application without removing '%s' first", apppath)
beeLogger.Log.Errorf("Path '%s' already exists", apppath) beeLogger.Log.Errorf("Path '%s' already exists", apppath)
return return
} }

13
vendor/github.com/astaxie/beego/LICENSE generated vendored Normal file
View 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
View 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.

View File

@ -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
}

View File

@ -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 : */

View File

@ -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, &regs) })
if err != nil {
return nil, err
}
r := &Regs{&regs, 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
}

View File

@ -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
}

View File

@ -2,20 +2,23 @@ package config
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/user" "os/user"
"path" "path"
"runtime"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
const ( const (
configDir string = ".dlv" configDir string = "dlv"
configFile string = "config.yml" configDirHidden string = ".dlv"
configFile string = "config.yml"
) )
// Describes a rule for substitution of path to source code file. // SubstitutePathRule describes a rule for substitution of path to source code file.
type SubstitutePathRule struct { type SubstitutePathRule struct {
// Directory path will be substituted if it matches `From`. // Directory path will be substituted if it matches `From`.
From string From string
@ -23,7 +26,7 @@ type SubstitutePathRule struct {
To string To string
} }
// Slice of source code path substitution rules. // SubstitutePathRules is a slice of source code path substitution rules.
type SubstitutePathRules []SubstitutePathRule type SubstitutePathRules []SubstitutePathRule
// Config defines all configuration options available to be set through the config file. // Config defines all configuration options available to be set through the config file.
@ -47,6 +50,10 @@ type Config struct {
// Source list line-number color (3/4 bit color codes as defined // Source list line-number color (3/4 bit color codes as defined
// here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) // here: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
SourceListLineColor int `yaml:"source-list-line-color"` SourceListLineColor int `yaml:"source-list-line-color"`
// DebugFileDirectories is the list of directories Delve will use
// in order to resolve external debug info files.
DebugInfoDirectories []string `yaml:"debug-info-directories"`
} }
// LoadConfig attempts to populate a Config object from the config.yml file. // LoadConfig attempts to populate a Config object from the config.yml file.
@ -54,12 +61,32 @@ func LoadConfig() *Config {
err := createConfigPath() err := createConfigPath()
if err != nil { if err != nil {
fmt.Printf("Could not create config directory: %v.", err) fmt.Printf("Could not create config directory: %v.", err)
return nil return &Config{}
} }
fullConfigFile, err := GetConfigFilePath(configFile) fullConfigFile, err := GetConfigFilePath(configFile)
if err != nil { if err != nil {
fmt.Printf("Unable to get config file path: %v.", err) fmt.Printf("Unable to get config file path: %v.", err)
return nil return &Config{}
}
hasOldConfig, err := hasOldConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to determine if old config exists: %v\n", err)
}
if hasOldConfig {
userHomeDir := getUserHomeDir()
oldLocation := path.Join(userHomeDir, configDirHidden)
if err := moveOldConfig(); err != nil {
fmt.Fprintf(os.Stderr, "Unable to move old config: %v\n", err)
return &Config{}
}
if err := os.RemoveAll(oldLocation); err != nil {
fmt.Fprintf(os.Stderr, "Unable to remove old config location: %v\n", err)
return &Config{}
}
fmt.Fprintf(os.Stderr, "Successfully moved config from: %s to: %s\n", oldLocation, fullConfigFile)
} }
f, err := os.Open(fullConfigFile) f, err := os.Open(fullConfigFile)
@ -67,7 +94,7 @@ func LoadConfig() *Config {
f, err = createDefaultConfig(fullConfigFile) f, err = createDefaultConfig(fullConfigFile)
if err != nil { if err != nil {
fmt.Printf("Error creating default config file: %v", err) fmt.Printf("Error creating default config file: %v", err)
return nil return &Config{}
} }
} }
defer func() { defer func() {
@ -80,19 +107,25 @@ func LoadConfig() *Config {
data, err := ioutil.ReadAll(f) data, err := ioutil.ReadAll(f)
if err != nil { if err != nil {
fmt.Printf("Unable to read config data: %v.", err) fmt.Printf("Unable to read config data: %v.", err)
return nil return &Config{}
} }
var c Config var c Config
err = yaml.Unmarshal(data, &c) err = yaml.Unmarshal(data, &c)
if err != nil { if err != nil {
fmt.Printf("Unable to decode config file: %v.", err) fmt.Printf("Unable to decode config file: %v.", err)
return nil return &Config{}
}
if len(c.DebugInfoDirectories) == 0 {
c.DebugInfoDirectories = []string{"/usr/lib/debug/.build-id"}
} }
return &c return &c
} }
// SaveConfig will marshal and save the config struct
// to disk.
func SaveConfig(conf *Config) error { func SaveConfig(conf *Config) error {
fullConfigFile, err := GetConfigFilePath(configFile) fullConfigFile, err := GetConfigFilePath(configFile)
if err != nil { if err != nil {
@ -114,15 +147,42 @@ func SaveConfig(conf *Config) error {
return err return err
} }
// moveOldConfig attempts to move config to new location
// $HOME/.dlv to $XDG_CONFIG_HOME/dlv
func moveOldConfig() error {
if os.Getenv("XDG_CONFIG_HOME") == "" && runtime.GOOS != "linux" {
return nil
}
userHomeDir := getUserHomeDir()
p := path.Join(userHomeDir, configDirHidden, configFile)
_, err := os.Stat(p)
if err != nil {
return fmt.Errorf("unable to read config file located at: %s", p)
}
newFile, err := GetConfigFilePath(configFile)
if err != nil {
return fmt.Errorf("unable to read config file located at: %s", err)
}
if err := os.Rename(p, newFile); err != nil {
return fmt.Errorf("unable to move %s to %s", p, newFile)
}
return nil
}
func createDefaultConfig(path string) (*os.File, error) { func createDefaultConfig(path string) (*os.File, error) {
f, err := os.Create(path) f, err := os.Create(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to create config file: %v.", err) return nil, fmt.Errorf("unable to create config file: %v", err)
} }
err = writeDefaultConfig(f) err = writeDefaultConfig(f)
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to write default configuration: %v.", err) return nil, fmt.Errorf("unable to write default configuration: %v", err)
} }
f.Seek(0, io.SeekStart)
return f, nil return f, nil
} }
@ -158,6 +218,9 @@ substitute-path:
# Uncomment the following line to make the whatis command also print the DWARF location expression of its argument. # Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
# show-location-expr: true # show-location-expr: true
# List of directories to use when searching for separate debug info files.
debug-info-directories: ["/usr/lib/debug/.build-id"]
`) `)
return err return err
} }
@ -173,9 +236,43 @@ func createConfigPath() error {
// GetConfigFilePath gets the full path to the given config file name. // GetConfigFilePath gets the full path to the given config file name.
func GetConfigFilePath(file string) (string, error) { func GetConfigFilePath(file string) (string, error) {
usr, err := user.Current() if configPath := os.Getenv("XDG_CONFIG_HOME"); configPath != "" {
if err != nil { return path.Join(configPath, configDir, file), nil
return "", err
} }
return path.Join(usr.HomeDir, configDir, file), nil
userHomeDir := getUserHomeDir()
if runtime.GOOS == "linux" {
return path.Join(userHomeDir, ".config", configDir, file), nil
}
return path.Join(userHomeDir, configDirHidden, file), nil
}
// Checks if the user has a config at the old location: $HOME/.dlv
func hasOldConfig() (bool, error) {
// If you don't have XDG_CONFIG_HOME set and aren't on Linux you have nothing to move
if os.Getenv("XDG_CONFIG_HOME") == "" && runtime.GOOS != "linux" {
return false, nil
}
userHomeDir := getUserHomeDir()
o := path.Join(userHomeDir, configDirHidden, configFile)
_, err := os.Stat(o)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
func getUserHomeDir() string {
userHomeDir := "."
usr, err := user.Current()
if err == nil {
userHomeDir = usr.HomeDir
}
return userHomeDir
} }

View File

@ -5,7 +5,7 @@ import (
"unicode" "unicode"
) )
// Like strings.Fields but ignores spaces inside areas surrounded // SplitQuotedFields is like strings.Fields but ignores spaces inside areas surrounded
// by the specified quote character. // by the specified quote character.
// To specify a single quote use backslash to escape it: '\'' // To specify a single quote use backslash to escape it: '\''
func SplitQuotedFields(in string, quote rune) []string { func SplitQuotedFields(in string, quote rune) []string {

View File

@ -7,7 +7,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
type parsefunc func(*parseContext) parsefunc type parsefunc func(*parseContext) parsefunc

View File

@ -5,7 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
type DWRule struct { type DWRule struct {

View File

@ -16,8 +16,8 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
const ( const (
@ -41,6 +41,20 @@ const (
encImaginaryFloat = 0x09 encImaginaryFloat = 0x09
) )
const cyclicalTypeStop = "<cyclical>" // guard value printed for types with a cyclical definition, to avoid inifinite recursion in Type.String
type recCheck map[dwarf.Offset]struct{}
func (recCheck recCheck) acquire(off dwarf.Offset) (release func()) {
if _, rec := recCheck[off]; rec {
return nil
}
recCheck[off] = struct{}{}
return func() {
delete(recCheck, off)
}
}
// A Type conventionally represents a pointer to any of the // A Type conventionally represents a pointer to any of the
// specific Type structures (CharType, StructType, etc.). // specific Type structures (CharType, StructType, etc.).
//TODO: remove this use dwarf.Type //TODO: remove this use dwarf.Type
@ -48,6 +62,9 @@ type Type interface {
Common() *CommonType Common() *CommonType
String() string String() string
Size() int64 Size() int64
stringIntl(recCheck) string
sizeIntl(recCheck) int64
} }
// A CommonType holds fields common to multiple types. // A CommonType holds fields common to multiple types.
@ -62,7 +79,8 @@ type CommonType struct {
func (c *CommonType) Common() *CommonType { return c } func (c *CommonType) Common() *CommonType { return c }
func (c *CommonType) Size() int64 { return c.ByteSize } func (c *CommonType) Size() int64 { return c.ByteSize }
func (c *CommonType) sizeIntl(recCheck) int64 { return c.ByteSize }
// Basic types // Basic types
@ -75,7 +93,9 @@ type BasicType struct {
func (b *BasicType) Basic() *BasicType { return b } func (b *BasicType) Basic() *BasicType { return b }
func (t *BasicType) String() string { func (t *BasicType) String() string { return t.stringIntl(nil) }
func (t *BasicType) stringIntl(recCheck) string {
if t.Name != "" { if t.Name != "" {
return t.Name return t.Name
} }
@ -136,9 +156,27 @@ type QualType struct {
Type Type Type Type
} }
func (t *QualType) String() string { return t.Qual + " " + t.Type.String() } func (t *QualType) String() string { return t.stringIntl(make(recCheck)) }
func (t *QualType) Size() int64 { return t.Type.Size() } func (t *QualType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
return t.Qual + " " + t.Type.stringIntl(recCheck)
}
func (t *QualType) Size() int64 { return t.sizeIntl(make(recCheck)) }
func (t *QualType) sizeIntl(recCheck recCheck) int64 {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return t.CommonType.ByteSize
}
defer release()
return t.Type.sizeIntl(recCheck)
}
// An ArrayType represents a fixed size array type. // An ArrayType represents a fixed size array type.
type ArrayType struct { type ArrayType struct {
@ -148,18 +186,36 @@ type ArrayType struct {
Count int64 // if == -1, an incomplete array, like char x[]. Count int64 // if == -1, an incomplete array, like char x[].
} }
func (t *ArrayType) String() string { func (t *ArrayType) String() string { return t.stringIntl(make(recCheck)) }
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.String()
func (t *ArrayType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
return "[" + strconv.FormatInt(t.Count, 10) + "]" + t.Type.stringIntl(recCheck)
} }
func (t *ArrayType) Size() int64 { return t.Count * t.Type.Size() } func (t *ArrayType) Size() int64 { return t.sizeIntl(make(recCheck)) }
func (t *ArrayType) sizeIntl(recCheck recCheck) int64 {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return t.CommonType.ByteSize
}
defer release()
return t.Count * t.Type.sizeIntl(recCheck)
}
// A VoidType represents the C void type. // A VoidType represents the C void type.
type VoidType struct { type VoidType struct {
CommonType CommonType
} }
func (t *VoidType) String() string { return "void" } func (t *VoidType) String() string { return t.stringIntl(nil) }
func (t *VoidType) stringIntl(recCheck) string { return "void" }
// A PtrType represents a pointer type. // A PtrType represents a pointer type.
type PtrType struct { type PtrType struct {
@ -167,7 +223,16 @@ type PtrType struct {
Type Type Type Type
} }
func (t *PtrType) String() string { return "*" + t.Type.String() } func (t *PtrType) String() string { return t.stringIntl(make(recCheck)) }
func (t *PtrType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
return "*" + t.Type.stringIntl(recCheck)
}
// A StructType represents a struct, union, or C++ class type. // A StructType represents a struct, union, or C++ class type.
type StructType struct { type StructType struct {
@ -189,14 +254,21 @@ type StructField struct {
Embedded bool Embedded bool
} }
func (t *StructType) String() string { func (t *StructType) String() string { return t.stringIntl(make(recCheck)) }
func (t *StructType) stringIntl(recCheck recCheck) string {
if t.StructName != "" { if t.StructName != "" {
return t.Kind + " " + t.StructName return t.Kind + " " + t.StructName
} }
return t.Defn() return t.Defn(recCheck)
} }
func (t *StructType) Defn() string { func (t *StructType) Defn(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
s := t.Kind s := t.Kind
if t.StructName != "" { if t.StructName != "" {
s += " " + t.StructName s += " " + t.StructName
@ -210,7 +282,7 @@ func (t *StructType) Defn() string {
if i > 0 { if i > 0 {
s += "; " s += "; "
} }
s += f.Name + " " + f.Type.String() s += f.Name + " " + f.Type.stringIntl(recCheck)
s += "@" + strconv.FormatInt(f.ByteOffset, 10) s += "@" + strconv.FormatInt(f.ByteOffset, 10)
if f.BitSize > 0 { if f.BitSize > 0 {
s += " : " + strconv.FormatInt(f.BitSize, 10) s += " : " + strconv.FormatInt(f.BitSize, 10)
@ -228,11 +300,18 @@ type SliceType struct {
ElemType Type ElemType Type
} }
func (t *SliceType) String() string { func (t *SliceType) String() string { return t.stringIntl(make(recCheck)) }
func (t *SliceType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
if t.Name != "" { if t.Name != "" {
return t.Name return t.Name
} }
return "[]" + t.ElemType.String() return "[]" + t.ElemType.stringIntl(recCheck)
} }
// A StringType represents a Go string type. It looks like a StructType, describing // A StringType represents a Go string type. It looks like a StructType, describing
@ -241,7 +320,9 @@ type StringType struct {
StructType StructType
} }
func (t *StringType) String() string { func (t *StringType) String() string { return t.stringIntl(nil) }
func (t *StringType) stringIntl(recCheck recCheck) string {
if t.Name != "" { if t.Name != "" {
return t.Name return t.Name
} }
@ -253,7 +334,9 @@ type InterfaceType struct {
TypedefType TypedefType
} }
func (t *InterfaceType) String() string { func (t *InterfaceType) String() string { return t.stringIntl(nil) }
func (t *InterfaceType) stringIntl(recCheck recCheck) string {
if t.Name != "" { if t.Name != "" {
return t.Name return t.Name
} }
@ -275,7 +358,9 @@ type EnumValue struct {
Val int64 Val int64
} }
func (t *EnumType) String() string { func (t *EnumType) String() string { return t.stringIntl(nil) }
func (t *EnumType) stringIntl(recCheck recCheck) string {
s := "enum" s := "enum"
if t.EnumName != "" { if t.EnumName != "" {
s += " " + t.EnumName s += " " + t.EnumName
@ -298,17 +383,24 @@ type FuncType struct {
ParamType []Type ParamType []Type
} }
func (t *FuncType) String() string { func (t *FuncType) String() string { return t.stringIntl(make(recCheck)) }
func (t *FuncType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
s := "func(" s := "func("
for i, t := range t.ParamType { for i, t := range t.ParamType {
if i > 0 { if i > 0 {
s += ", " s += ", "
} }
s += t.String() s += t.stringIntl(recCheck)
} }
s += ")" s += ")"
if t.ReturnType != nil { if t.ReturnType != nil {
s += " " + t.ReturnType.String() s += " " + t.ReturnType.stringIntl(recCheck)
} }
return s return s
} }
@ -318,7 +410,9 @@ type DotDotDotType struct {
CommonType CommonType
} }
func (t *DotDotDotType) String() string { return "..." } func (t *DotDotDotType) String() string { return t.stringIntl(nil) }
func (t *DotDotDotType) stringIntl(recCheck recCheck) string { return "..." }
// A TypedefType represents a named type. // A TypedefType represents a named type.
type TypedefType struct { type TypedefType struct {
@ -326,9 +420,20 @@ type TypedefType struct {
Type Type Type Type
} }
func (t *TypedefType) String() string { return t.Name } func (t *TypedefType) String() string { return t.stringIntl(nil) }
func (t *TypedefType) Size() int64 { return t.Type.Size() } func (t *TypedefType) stringIntl(recCheck recCheck) string { return t.Name }
func (t *TypedefType) Size() int64 { return t.sizeIntl(make(recCheck)) }
func (t *TypedefType) sizeIntl(recCheck recCheck) int64 {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return t.CommonType.ByteSize
}
defer release()
return t.Type.sizeIntl(recCheck)
}
// A MapType represents a Go map type. It looks like a TypedefType, describing // A MapType represents a Go map type. It looks like a TypedefType, describing
// the runtime-internal structure, with extra fields. // the runtime-internal structure, with extra fields.
@ -338,7 +443,14 @@ type MapType struct {
ElemType Type ElemType Type
} }
func (t *MapType) String() string { func (t *MapType) String() string { return t.stringIntl(make(recCheck)) }
func (t *MapType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
if t.Name != "" { if t.Name != "" {
return t.Name return t.Name
} }
@ -351,7 +463,14 @@ type ChanType struct {
ElemType Type ElemType Type
} }
func (t *ChanType) String() string { func (t *ChanType) String() string { return t.stringIntl(make(recCheck)) }
func (t *ChanType) stringIntl(recCheck recCheck) string {
release := recCheck.acquire(t.CommonType.Offset)
if release == nil {
return cyclicalTypeStop
}
defer release()
if t.Name != "" { if t.Name != "" {
return t.Name return t.Name
} }
@ -586,9 +705,10 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
switch t.ReflectKind { switch t.ReflectKind {
case reflect.Slice: case reflect.Slice:
slice := new(SliceType) slice := new(SliceType)
typ = slice
typeCache[off] = slice
slice.ElemType = typeOf(e, AttrGoElem) slice.ElemType = typeOf(e, AttrGoElem)
t = &slice.StructType t = &slice.StructType
typ = slice
case reflect.String: case reflect.String:
str := new(StringType) str := new(StringType)
t = &str.StructType t = &str.StructType
@ -798,19 +918,22 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ
switch t.ReflectKind { switch t.ReflectKind {
case reflect.Map: case reflect.Map:
m := new(MapType) m := new(MapType)
typ = m
typeCache[off] = typ
m.KeyType = typeOf(e, AttrGoKey) m.KeyType = typeOf(e, AttrGoKey)
m.ElemType = typeOf(e, AttrGoElem) m.ElemType = typeOf(e, AttrGoElem)
t = &m.TypedefType t = &m.TypedefType
typ = m
case reflect.Chan: case reflect.Chan:
c := new(ChanType) c := new(ChanType)
typ = c
typeCache[off] = typ
c.ElemType = typeOf(e, AttrGoElem) c.ElemType = typeOf(e, AttrGoElem)
t = &c.TypedefType t = &c.TypedefType
typ = c
case reflect.Interface: case reflect.Interface:
it := new(InterfaceType) it := new(InterfaceType)
t = &it.TypedefType
typ = it typ = it
typeCache[off] = it
t = &it.TypedefType
default: default:
typ = t typ = t
} }
@ -876,3 +999,13 @@ func zeroArray(t Type) {
t = at.Type t = at.Type
} }
} }
func resolveTypedef(typ Type) Type {
for {
if tt, ok := typ.(*TypedefType); ok {
typ = tt.Type
} else {
return typ
}
}
}

View File

@ -5,7 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"path/filepath" "path/filepath"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
type DebugLinePrologue struct { type DebugLinePrologue struct {

View File

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
type Location struct { type Location struct {

View File

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/derekparker/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/dwarf/util"
) )
type Opcode byte type Opcode byte

View File

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
) )
type Reader struct { type Reader struct {

View 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
}

View File

@ -3,8 +3,8 @@ package proc
import ( import (
"encoding/binary" "encoding/binary"
"github.com/derekparker/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"golang.org/x/arch/x86/x86asm" "golang.org/x/arch/x86/x86asm"
) )
@ -284,7 +284,7 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.
} }
} }
for _, reg := range regs.Slice() { for _, reg := range regs.Slice(true) {
for dwarfReg, regName := range amd64DwarfToName { for dwarfReg, regName := range amd64DwarfToName {
if regName == reg.Name { if regName == reg.Name {
dregs[dwarfReg] = op.DwarfRegisterFromBytes(reg.Bytes) dregs[dwarfReg] = op.DwarfRegisterFromBytes(reg.Bytes)

View File

@ -12,40 +12,30 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/derekparker/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/line" "github.com/go-delve/delve/pkg/dwarf/line"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/goversion" "github.com/go-delve/delve/pkg/goversion"
) )
// BinaryInfo holds information on the binary being executed. // BinaryInfo holds information on the binaries being executed (this
// includes both the executable and also any loaded libraries).
type BinaryInfo struct { type BinaryInfo struct {
lastModified time.Time // Time the executable of this process was last modified // Path on disk of the binary being executed.
Path string
// Architecture of this binary.
Arch Arch
GOOS string // GOOS operating system this binary is executing on.
closer io.Closer GOOS string
sepDebugCloser io.Closer
staticBase uint64
// Maps package names to package paths, needed to lookup types inside DWARF info
packageMap map[string]string
Arch Arch
dwarf *dwarf.Data
frameEntries frame.FrameDescriptionEntries
loclist loclistReader
compileUnits []*compileUnit
types map[string]dwarf.Offset
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
gStructOffset uint64
// Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point // Functions is a list of all DW_TAG_subprogram entries in debug_info, sorted by entry point
Functions []Function Functions []Function
@ -54,7 +44,32 @@ type BinaryInfo struct {
// LookupFunc maps function names to a description of the function. // LookupFunc maps function names to a description of the function.
LookupFunc map[string]*Function LookupFunc map[string]*Function
typeCache map[dwarf.Offset]godwarf.Type // Images is a list of loaded shared libraries (also known as
// shared objects on linux or DLLs on windws).
Images []*Image
ElfDynamicSection ElfDynamicSection
lastModified time.Time // Time the executable of this process was last modified
closer io.Closer
sepDebugCloser io.Closer
staticBase uint64
// Maps package names to package paths, needed to lookup types inside DWARF info
packageMap map[string]string
dwarf *dwarf.Data
dwarfReader *dwarf.Reader
frameEntries frame.FrameDescriptionEntries
loclist loclistReader
compileUnits []*compileUnit
types map[string]dwarf.Offset
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
typeCache map[dwarf.Offset]godwarf.Type
gStructOffset uint64
loadModuleDataOnce sync.Once loadModuleDataOnce sync.Once
moduleData []moduleData moduleData []moduleData
@ -71,8 +86,6 @@ type BinaryInfo struct {
loadErrMu sync.Mutex loadErrMu sync.Mutex
loadErr error loadErr error
dwarfReader *dwarf.Reader
} }
// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture. // ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture.
@ -84,14 +97,20 @@ var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/
// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture. // ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported") var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported")
// ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
// position independant executable.
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE") var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
// ErrNoDebugInfoFound is returned when Delve cannot open the debug_info
// section or find an external debug info file.
var ErrNoDebugInfoFound = errors.New("could not open debug info")
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231) const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
type compileUnit struct { type compileUnit struct {
Name string // univocal name for non-go compile units name string // univocal name for non-go compile units
LowPC uint64 lowPC uint64
Ranges [][2]uint64 ranges [][2]uint64
entry *dwarf.Entry // debug_info entry describing this compile unit entry *dwarf.Entry // debug_info entry describing this compile unit
isgo bool // true if this is the go compile unit isgo bool // true if this is the go compile unit
@ -277,6 +296,12 @@ type buildIDHeader struct {
Type uint32 Type uint32
} }
// ElfDynamicSection describes the .dynamic section of an ELF executable.
type ElfDynamicSection struct {
Addr uint64 // relocated address of where the .dynamic section is mapped in memory
Size uint64 // size of the .dynamic section of the executable
}
// NewBinaryInfo returns an initialized but unloaded BinaryInfo struct. // NewBinaryInfo returns an initialized but unloaded BinaryInfo struct.
func NewBinaryInfo(goos, goarch string) *BinaryInfo { func NewBinaryInfo(goos, goarch string) *BinaryInfo {
r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)} r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)}
@ -293,22 +318,24 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo {
// LoadBinaryInfo will load and store the information from the binary at 'path'. // LoadBinaryInfo will load and store the information from the binary at 'path'.
// It is expected this will be called in parallel with other initialization steps // It is expected this will be called in parallel with other initialization steps
// so a sync.WaitGroup must be provided. // so a sync.WaitGroup must be provided.
func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, wg *sync.WaitGroup) error { func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDirs []string) error {
fi, err := os.Stat(path) fi, err := os.Stat(path)
if err == nil { if err == nil {
bi.lastModified = fi.ModTime() bi.lastModified = fi.ModTime()
} }
var wg sync.WaitGroup
defer wg.Wait()
bi.Path = path
switch bi.GOOS { switch bi.GOOS {
case "linux": case "linux":
return bi.LoadBinaryInfoElf(path, entryPoint, wg) return bi.LoadBinaryInfoElf(path, entryPoint, debugInfoDirs, &wg)
case "windows": case "windows":
return bi.LoadBinaryInfoPE(path, entryPoint, wg) return bi.LoadBinaryInfoPE(path, entryPoint, &wg)
case "darwin": case "darwin":
return bi.LoadBinaryInfoMacho(path, entryPoint, wg) return bi.LoadBinaryInfoMacho(path, entryPoint, &wg)
} }
return errors.New("unsupported operating system") return errors.New("unsupported operating system")
return nil
} }
// GStructOffset returns the offset of the G // GStructOffset returns the offset of the G
@ -398,12 +425,35 @@ func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
return nil return nil
} }
// Image represents a loaded library file (shared object on linux, DLL on windows).
type Image struct {
Path string
addr uint64
}
// AddImage adds the specified image to bi.
func (bi *BinaryInfo) AddImage(path string, addr uint64) {
if !strings.HasPrefix(path, "/") {
return
}
for _, image := range bi.Images {
if image.Path == path && image.addr == addr {
return
}
}
//TODO(aarzilli): actually load informations about the image here
bi.Images = append(bi.Images, &Image{Path: path, addr: addr})
}
// Close closes all internal readers. // Close closes all internal readers.
func (bi *BinaryInfo) Close() error { func (bi *BinaryInfo) Close() error {
if bi.sepDebugCloser != nil { if bi.sepDebugCloser != nil {
bi.sepDebugCloser.Close() bi.sepDebugCloser.Close()
} }
return bi.closer.Close() if bi.closer != nil {
return bi.closer.Close()
}
return nil
} }
func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) { func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) {
@ -488,7 +538,7 @@ func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, r
func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte { func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
var base uint64 var base uint64
if cu := bi.findCompileUnit(pc); cu != nil { if cu := bi.findCompileUnit(pc); cu != nil {
base = cu.LowPC base = cu.lowPC
} }
bi.loclist.Seek(int(off)) bi.loclist.Seek(int(off))
@ -509,7 +559,7 @@ func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte {
// findCompileUnit returns the compile unit containing address pc. // findCompileUnit returns the compile unit containing address pc.
func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit { func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit {
for _, cu := range bi.compileUnits { for _, cu := range bi.compileUnits {
for _, rng := range cu.Ranges { for _, rng := range cu.ranges {
if pc >= rng[0] && pc < rng[1] { if pc >= rng[0] && pc < rng[1] {
return cu return cu
} }
@ -558,35 +608,32 @@ func (e *ErrNoBuildIDNote) Error() string {
// in GDB's documentation [1], and if found returns two handles, one // in GDB's documentation [1], and if found returns two handles, one
// for the bare file, and another for its corresponding elf.File. // for the bare file, and another for its corresponding elf.File.
// [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html // [1] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, error) { //
buildid := exe.Section(".note.gnu.build-id") // Alternatively, if the debug file cannot be found be the build-id, Delve
if buildid == nil { // will look in directories specified by the debug-info-directories config value.
return nil, nil, &ErrNoBuildIDNote{} func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File, debugInfoDirectories []string) (*os.File, *elf.File, error) {
var debugFilePath string
for _, dir := range debugInfoDirectories {
var potentialDebugFilePath string
if strings.Contains(dir, "build-id") {
desc1, desc2, err := parseBuildID(exe)
if err != nil {
continue
}
potentialDebugFilePath = fmt.Sprintf("%s/%s/%s.debug", dir, desc1, desc2)
} else {
potentialDebugFilePath = fmt.Sprintf("%s/%s.debug", dir, filepath.Base(bi.Path))
}
_, err := os.Stat(potentialDebugFilePath)
if err == nil {
debugFilePath = potentialDebugFilePath
break
}
} }
if debugFilePath == "" {
br := buildid.Open() return nil, nil, ErrNoDebugInfoFound
bh := new(buildIDHeader)
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
return nil, nil, errors.New("can't read build-id header: " + err.Error())
} }
sepFile, err := os.OpenFile(debugFilePath, 0, os.ModePerm)
name := make([]byte, bh.Namesz)
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
return nil, nil, errors.New("can't read build-id name: " + err.Error())
}
if strings.TrimSpace(string(name)) != "GNU\x00" {
return nil, nil, errors.New("invalid build-id signature")
}
descBinary := make([]byte, bh.Descsz)
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
return nil, nil, errors.New("can't read build-id desc: " + err.Error())
}
desc := hex.EncodeToString(descBinary)
debugPath := fmt.Sprintf("/usr/lib/debug/.build-id/%s/%s.debug", desc[:2], desc[2:])
sepFile, err := os.OpenFile(debugPath, 0, os.ModePerm)
if err != nil { if err != nil {
return nil, nil, errors.New("can't open separate debug file: " + err.Error()) return nil, nil, errors.New("can't open separate debug file: " + err.Error())
} }
@ -594,19 +641,48 @@ func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File,
elfFile, err := elf.NewFile(sepFile) elfFile, err := elf.NewFile(sepFile)
if err != nil { if err != nil {
sepFile.Close() sepFile.Close()
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, err.Error()) return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, err.Error())
} }
if elfFile.Machine != elf.EM_X86_64 { if elfFile.Machine != elf.EM_X86_64 {
sepFile.Close() sepFile.Close()
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugPath, ErrUnsupportedLinuxArch.Error()) return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, ErrUnsupportedLinuxArch.Error())
} }
return sepFile, elfFile, nil return sepFile, elfFile, nil
} }
func parseBuildID(exe *elf.File) (string, string, error) {
buildid := exe.Section(".note.gnu.build-id")
if buildid == nil {
return "", "", &ErrNoBuildIDNote{}
}
br := buildid.Open()
bh := new(buildIDHeader)
if err := binary.Read(br, binary.LittleEndian, bh); err != nil {
return "", "", errors.New("can't read build-id header: " + err.Error())
}
name := make([]byte, bh.Namesz)
if err := binary.Read(br, binary.LittleEndian, name); err != nil {
return "", "", errors.New("can't read build-id name: " + err.Error())
}
if strings.TrimSpace(string(name)) != "GNU\x00" {
return "", "", errors.New("invalid build-id signature")
}
descBinary := make([]byte, bh.Descsz)
if err := binary.Read(br, binary.LittleEndian, descBinary); err != nil {
return "", "", errors.New("can't read build-id desc: " + err.Error())
}
desc := hex.EncodeToString(descBinary)
return desc[:2], desc[2:], nil
}
// LoadBinaryInfoElf specifically loads information from an ELF binary. // LoadBinaryInfoElf specifically loads information from an ELF binary.
func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync.WaitGroup) error { func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, debugInfoDirectories []string, wg *sync.WaitGroup) error {
exe, err := os.OpenFile(path, 0, os.ModePerm) exe, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil { if err != nil {
return err return err
@ -628,17 +704,19 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync
} }
} }
if dynsec := elfFile.Section(".dynamic"); dynsec != nil {
bi.ElfDynamicSection.Addr = dynsec.Addr + bi.staticBase
bi.ElfDynamicSection.Size = dynsec.Size
}
dwarfFile := elfFile dwarfFile := elfFile
bi.dwarf, err = elfFile.DWARF() bi.dwarf, err = elfFile.DWARF()
if err != nil { if err != nil {
var sepFile *os.File var sepFile *os.File
var serr error var serr error
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile) sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(elfFile, debugInfoDirectories)
if serr != nil { if serr != nil {
if _, ok := serr.(*ErrNoBuildIDNote); ok {
return err
}
return serr return serr
} }
bi.sepDebugCloser = sepFile bi.sepDebugCloser = sepFile
@ -714,9 +792,16 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) {
break break
} }
} }
if tls == nil {
bi.gStructOffset = ^uint64(8) + 1 // -8
return
}
memsz := tls.Memsz
memsz = (memsz + uint64(bi.Arch.PtrSize()) - 1) & ^uint64(bi.Arch.PtrSize()-1) // align to pointer-sized-boundary
// The TLS register points to the end of the TLS block, which is // The TLS register points to the end of the TLS block, which is
// tls.Memsz long. runtime.tlsg is an offset from the beginning of that block. // tls.Memsz long. runtime.tlsg is an offset from the beginning of that block.
bi.gStructOffset = ^(tls.Memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value bi.gStructOffset = ^(memsz) + 1 + tlsg.Value // -tls.Memsz + tlsg.Value
} }
// PE //////////////////////////////////////////////////////////////// // PE ////////////////////////////////////////////////////////////////

View File

@ -30,7 +30,8 @@ type Breakpoint struct {
Kind BreakpointKind Kind BreakpointKind
// Breakpoint information // Breakpoint information
Tracepoint bool // Tracepoint flag Tracepoint bool // Tracepoint flag
TraceReturn bool
Goroutine bool // Retrieve goroutine information Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate Variables []string // Variables to evaluate
@ -45,7 +46,7 @@ type Breakpoint struct {
// Next uses NextDeferBreakpoints for the breakpoint it sets on the // Next uses NextDeferBreakpoints for the breakpoint it sets on the
// deferred function, DeferReturns is populated with the // deferred function, DeferReturns is populated with the
// addresses of calls to runtime.deferreturn in the current // addresses of calls to runtime.deferreturn in the current
// function. This insures that the breakpoint on the deferred // function. This ensures that the breakpoint on the deferred
// function only triggers on panic or on the defer call to // function only triggers on panic or on the defer call to
// the function, not when the function is called directly // the function, not when the function is called directly
DeferReturns []uint64 DeferReturns []uint64
@ -220,13 +221,16 @@ func (bpmap *BreakpointMap) ResetBreakpointIDCounter() {
bpmap.breakpointIDCounter = 0 bpmap.breakpointIDCounter = 0
} }
type writeBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error) // WriteBreakpointFn is a type that represents a function to be used for
// writting breakpoings into the target.
type WriteBreakpointFn func(addr uint64) (file string, line int, fn *Function, originalData []byte, err error)
type clearBreakpointFn func(*Breakpoint) error type clearBreakpointFn func(*Breakpoint) error
// Set creates a breakpoint at addr calling writeBreakpoint. Do not call this // Set creates a breakpoint at addr calling writeBreakpoint. Do not call this
// function, call proc.Process.SetBreakpoint instead, this function exists // function, call proc.Process.SetBreakpoint instead, this function exists
// to implement proc.Process.SetBreakpoint. // to implement proc.Process.SetBreakpoint.
func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) { func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
if bp, ok := bpmap.M[addr]; ok { if bp, ok := bpmap.M[addr]; ok {
// We can overlap one internal breakpoint with one user breakpoint, we // We can overlap one internal breakpoint with one user breakpoint, we
// need to support this otherwise a conditional breakpoint can mask a // need to support this otherwise a conditional breakpoint can mask a
@ -248,8 +252,13 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
return nil, err return nil, err
} }
fnName := ""
if fn != nil {
fnName = fn.Name
}
newBreakpoint := &Breakpoint{ newBreakpoint := &Breakpoint{
FunctionName: fn.Name, FunctionName: fnName,
File: f, File: f,
Line: l, Line: l,
Addr: addr, Addr: addr,
@ -274,7 +283,7 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
} }
// SetWithID creates a breakpoint at addr, with the specified ID. // SetWithID creates a breakpoint at addr, with the specified ID.
func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint writeBreakpointFn) (*Breakpoint, error) { func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint) bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint)
if err == nil { if err == nil {
bp.ID = id bp.ID = id
@ -421,12 +430,6 @@ func (rbpi *returnBreakpointInfo) Collect(thread Thread) []*Variable {
return (v.Flags & VariableReturnArgument) != 0 return (v.Flags & VariableReturnArgument) != 0
}) })
// Go saves the return variables in the opposite order that the user
// specifies them so here we reverse the slice to make it easier to
// understand.
for i := 0; i < len(vars)/2; i++ {
vars[i], vars[len(vars)-i-1] = vars[len(vars)-i-1], vars[i]
}
return vars return vars
} }

View File

@ -5,9 +5,8 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"io" "io"
"sync"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// A SplicedMemory represents a memory space formed from multiple regions, // A SplicedMemory represents a memory space formed from multiple regions,
@ -144,8 +143,13 @@ func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error)
// Process represents a core file. // Process represents a core file.
type Process struct { type Process struct {
mem proc.MemoryReader
Threads map[int]*Thread
pid int
entryPoint uint64
bi *proc.BinaryInfo bi *proc.BinaryInfo
core *Core
breakpoints proc.BreakpointMap breakpoints proc.BreakpointMap
currentThread *Thread currentThread *Thread
selectedGoroutine *proc.G selectedGoroutine *proc.G
@ -154,12 +158,16 @@ type Process struct {
// Thread represents a thread in the core file being debugged. // Thread represents a thread in the core file being debugged.
type Thread struct { type Thread struct {
th *LinuxPrStatus th osThread
fpregs []proc.Register
p *Process p *Process
common proc.CommonThread common proc.CommonThread
} }
type osThread interface {
registers(floatingPoint bool) (proc.Registers, error)
pid() int
}
var ( var (
// ErrWriteCore is returned when attempting to write to the core // ErrWriteCore is returned when attempting to write to the core
// process memory. // process memory.
@ -175,45 +183,66 @@ var (
ErrChangeRegisterCore = errors.New("can not change register values of core process") ErrChangeRegisterCore = errors.New("can not change register values of core process")
) )
type openFn func(string, string) (*Process, error)
var openFns = []openFn{readLinuxAMD64Core, readAMD64Minidump}
// ErrUnrecognizedFormat is returned when the core file is not recognized as
// any of the supported formats.
var ErrUnrecognizedFormat = errors.New("unrecognized core format")
// OpenCore will open the core file and return a Process struct. // OpenCore will open the core file and return a Process struct.
func OpenCore(corePath, exePath string) (*Process, error) { // If the DWARF information cannot be found in the binary, Delve will look
core, err := readCore(corePath, exePath) // for external debug files in the directories passed in.
if err != nil { func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
return nil, err var p *Process
} var err error
p := &Process{ for _, openFn := range openFns {
core: core, p, err = openFn(corePath, exePath)
breakpoints: proc.NewBreakpointMap(), if err != ErrUnrecognizedFormat {
bi: proc.NewBinaryInfo("linux", "amd64"), break
} }
for _, thread := range core.Threads {
thread.p = p
}
var wg sync.WaitGroup
err = p.bi.LoadBinaryInfo(exePath, core.entryPoint, &wg)
wg.Wait()
if err == nil {
err = p.bi.LoadError()
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, th := range p.core.Threads { if err := p.initialize(exePath, debugInfoDirs); err != nil {
p.currentThread = th return nil, err
break
} }
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return p, nil return p, nil
} }
// initialize for core files doesn't do much
// aside from call the post initialization setup.
func (p *Process) initialize(path string, debugInfoDirs []string) error {
return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
}
// BinInfo will return the binary info. // BinInfo will return the binary info.
func (p *Process) BinInfo() *proc.BinaryInfo { func (p *Process) BinInfo() *proc.BinaryInfo {
return p.bi return p.bi
} }
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// EntryPoint will return the entry point address for this core file.
func (p *Process) EntryPoint() (uint64, error) {
return p.entryPoint, nil
}
// writeBreakpoint is a noop function since you
// cannot write breakpoints into core files.
func (p *Process) writeBreakpoint(addr uint64) (file string, line int, fn *proc.Function, originalData []byte, err error) {
return "", 0, nil, nil, errors.New("cannot write a breakpoint to a core file")
}
// Recorded returns whether this is a live or recorded process. Always returns true for core files. // Recorded returns whether this is a live or recorded process. Always returns true for core files.
func (p *Process) Recorded() (bool, string) { return true, "" } func (p *Process) Recorded() (bool, string) { return true, "" }
@ -239,7 +268,7 @@ func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not
// read memory into `data`, returning the length read, and returning an error if // read memory into `data`, returning the length read, and returning an error if
// the length read is shorter than the length of the `data` buffer. // the length read is shorter than the length of the `data` buffer.
func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) { func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
n, err = t.p.core.ReadMemory(data, addr) n, err = t.p.mem.ReadMemory(data, addr)
if err == nil && n != len(data) { if err == nil && n != len(data) {
err = ErrShortRead err = ErrShortRead
} }
@ -255,8 +284,13 @@ func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
// Location returns the location of this thread based on // Location returns the location of this thread based on
// the value of the instruction pointer register. // the value of the instruction pointer register.
func (t *Thread) Location() (*proc.Location, error) { func (t *Thread) Location() (*proc.Location, error) {
f, l, fn := t.p.bi.PCToLine(t.th.Reg.Rip) regs, err := t.th.registers(false)
return &proc.Location{PC: t.th.Reg.Rip, File: f, Line: l, Fn: fn}, nil if err != nil {
return nil, err
}
pc := regs.PC()
f, l, fn := t.p.bi.PCToLine(pc)
return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
} }
// Breakpoint returns the current breakpoint this thread is stopped at. // Breakpoint returns the current breakpoint this thread is stopped at.
@ -268,16 +302,12 @@ func (t *Thread) Breakpoint() proc.BreakpointState {
// ThreadID returns the ID for this thread. // ThreadID returns the ID for this thread.
func (t *Thread) ThreadID() int { func (t *Thread) ThreadID() int {
return int(t.th.Pid) return int(t.th.pid())
} }
// Registers returns the current value of the registers for this thread. // Registers returns the current value of the registers for this thread.
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) { func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
r := &Registers{&t.th.Reg, nil} return t.th.registers(floatingPoint)
if floatingPoint {
r.fpregs = t.fpregs
}
return r, nil
} }
// RestoreRegisters will only return an error for core files, // RestoreRegisters will only return an error for core files,
@ -406,7 +436,7 @@ func (p *Process) Common() *proc.CommonProcess {
// Pid returns the process ID of this process. // Pid returns the process ID of this process.
func (p *Process) Pid() int { func (p *Process) Pid() int {
return p.core.Pid return p.pid
} }
// ResumeNotify is a no-op on core files as we cannot // ResumeNotify is a no-op on core files as we cannot
@ -444,7 +474,7 @@ func (p *Process) SwitchGoroutine(gid int) error {
// SwitchThread will change the selected and active thread. // SwitchThread will change the selected and active thread.
func (p *Process) SwitchThread(tid int) error { func (p *Process) SwitchThread(tid int) error {
if th, ok := p.core.Threads[tid]; ok { if th, ok := p.Threads[tid]; ok {
p.currentThread = th p.currentThread = th
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread()) p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return nil return nil
@ -454,8 +484,8 @@ func (p *Process) SwitchThread(tid int) error {
// ThreadList will return a list of all threads currently in the process. // ThreadList will return a list of all threads currently in the process.
func (p *Process) ThreadList() []proc.Thread { func (p *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(p.core.Threads)) r := make([]proc.Thread, 0, len(p.Threads))
for _, v := range p.core.Threads { for _, v := range p.Threads {
r = append(r, v) r = append(r, v)
} }
return r return r
@ -463,64 +493,6 @@ func (p *Process) ThreadList() []proc.Thread {
// FindThread will return the thread with the corresponding thread ID. // FindThread will return the thread with the corresponding thread ID.
func (p *Process) FindThread(threadID int) (proc.Thread, bool) { func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
t, ok := p.core.Threads[threadID] t, ok := p.Threads[threadID]
return t, ok return t, ok
} }
// Registers represents the CPU registers.
type Registers struct {
*LinuxCoreRegisters
fpregs []proc.Register
}
// Slice will return a slice containing all registers and their values.
func (r *Registers) Slice() []proc.Register {
var regs = []struct {
k string
v uint64
}{
{"Rip", r.Rip},
{"Rsp", r.Rsp},
{"Rax", r.Rax},
{"Rbx", r.Rbx},
{"Rcx", r.Rcx},
{"Rdx", r.Rdx},
{"Rdi", r.Rdi},
{"Rsi", r.Rsi},
{"Rbp", r.Rbp},
{"R8", r.R8},
{"R9", r.R9},
{"R10", r.R10},
{"R11", r.R11},
{"R12", r.R12},
{"R13", r.R13},
{"R14", r.R14},
{"R15", r.R15},
{"Orig_rax", r.Orig_rax},
{"Cs", r.Cs},
{"Eflags", r.Eflags},
{"Ss", r.Ss},
{"Fs_base", r.Fs_base},
{"Gs_base", r.Gs_base},
{"Ds", r.Ds},
{"Es", r.Es},
{"Fs", r.Fs},
{"Gs", r.Gs},
}
out := make([]proc.Register, 0, len(regs))
for _, reg := range regs {
if reg.k == "Eflags" {
out = proc.AppendEflagReg(out, reg.k, reg.v)
} else {
out = proc.AppendQwordReg(out, reg.k, reg.v)
}
}
out = append(out, r.fpregs...)
return out
}
// Copy will return a copy of the registers that is guarenteed
// not to change.
func (r *Registers) Copy() proc.Registers {
return r
}

View File

@ -7,45 +7,12 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"strings"
"golang.org/x/arch/x86/x86asm" "github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil"
) )
// Copied from golang.org/x/sys/unix.PtraceRegs since it's not available on
// all systems.
type LinuxCoreRegisters struct {
R15 uint64
R14 uint64
R13 uint64
R12 uint64
Rbp uint64
Rbx uint64
R11 uint64
R10 uint64
R9 uint64
R8 uint64
Rax uint64
Rcx uint64
Rdx uint64
Rsi uint64
Rdi uint64
Orig_rax uint64
Rip uint64
Cs uint64
Eflags uint64
Rsp uint64
Ss uint64
Fs_base uint64
Gs_base uint64
Ds uint64
Es uint64
Fs uint64
Gs uint64
}
// Copied from golang.org/x/sys/unix.Timeval since it's not available on all // Copied from golang.org/x/sys/unix.Timeval since it's not available on all
// systems. // systems.
type LinuxCoreTimeval struct { type LinuxCoreTimeval struct {
@ -62,207 +29,21 @@ const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAV
// NT_AUXV is the note type for notes containing a copy of the Auxv array // NT_AUXV is the note type for notes containing a copy of the Auxv array
const NT_AUXV elf.NType = 0x6 const NT_AUXV elf.NType = 0x6
// PC returns the value of RIP. const elfErrorBadMagicNumber = "bad magic number"
func (r *LinuxCoreRegisters) PC() uint64 {
return r.Rip
}
// SP returns the value of RSP. // readLinuxAMD64Core reads a core file from corePath corresponding to the executable at
func (r *LinuxCoreRegisters) SP() uint64 {
return r.Rsp
}
// BP returns the value of RBP.
func (r *LinuxCoreRegisters) BP() uint64 {
return r.Rbp
}
// CX returns the value of RCX.
func (r *LinuxCoreRegisters) CX() uint64 {
return r.Rcx
}
// TLS returns the location of the thread local storate,
// which will be the value of Fs_base.
func (r *LinuxCoreRegisters) TLS() uint64 {
return r.Fs_base
}
// GAddr returns the address of the G struct. Always returns 0
// and false for core files.
func (r *LinuxCoreRegisters) GAddr() (uint64, bool) {
return 0, false
}
// Get returns the value of the register requested via the
// register number, returning an error if that register
// could not be found.
func (r *LinuxCoreRegisters) Get(n int) (uint64, error) {
reg := x86asm.Reg(n)
const (
mask8 = 0x000f
mask16 = 0x00ff
mask32 = 0xffff
)
switch reg {
// 8-bit
case x86asm.AL:
return r.Rax & mask8, nil
case x86asm.CL:
return r.Rcx & mask8, nil
case x86asm.DL:
return r.Rdx & mask8, nil
case x86asm.BL:
return r.Rbx & mask8, nil
case x86asm.AH:
return (r.Rax >> 8) & mask8, nil
case x86asm.CH:
return (r.Rcx >> 8) & mask8, nil
case x86asm.DH:
return (r.Rdx >> 8) & mask8, nil
case x86asm.BH:
return (r.Rbx >> 8) & mask8, nil
case x86asm.SPB:
return r.Rsp & mask8, nil
case x86asm.BPB:
return r.Rbp & mask8, nil
case x86asm.SIB:
return r.Rsi & mask8, nil
case x86asm.DIB:
return r.Rdi & mask8, nil
case x86asm.R8B:
return r.R8 & mask8, nil
case x86asm.R9B:
return r.R9 & mask8, nil
case x86asm.R10B:
return r.R10 & mask8, nil
case x86asm.R11B:
return r.R11 & mask8, nil
case x86asm.R12B:
return r.R12 & mask8, nil
case x86asm.R13B:
return r.R13 & mask8, nil
case x86asm.R14B:
return r.R14 & mask8, nil
case x86asm.R15B:
return r.R15 & mask8, nil
// 16-bit
case x86asm.AX:
return r.Rax & mask16, nil
case x86asm.CX:
return r.Rcx & mask16, nil
case x86asm.DX:
return r.Rdx & mask16, nil
case x86asm.BX:
return r.Rbx & mask16, nil
case x86asm.SP:
return r.Rsp & mask16, nil
case x86asm.BP:
return r.Rbp & mask16, nil
case x86asm.SI:
return r.Rsi & mask16, nil
case x86asm.DI:
return r.Rdi & mask16, nil
case x86asm.R8W:
return r.R8 & mask16, nil
case x86asm.R9W:
return r.R9 & mask16, nil
case x86asm.R10W:
return r.R10 & mask16, nil
case x86asm.R11W:
return r.R11 & mask16, nil
case x86asm.R12W:
return r.R12 & mask16, nil
case x86asm.R13W:
return r.R13 & mask16, nil
case x86asm.R14W:
return r.R14 & mask16, nil
case x86asm.R15W:
return r.R15 & mask16, nil
// 32-bit
case x86asm.EAX:
return r.Rax & mask32, nil
case x86asm.ECX:
return r.Rcx & mask32, nil
case x86asm.EDX:
return r.Rdx & mask32, nil
case x86asm.EBX:
return r.Rbx & mask32, nil
case x86asm.ESP:
return r.Rsp & mask32, nil
case x86asm.EBP:
return r.Rbp & mask32, nil
case x86asm.ESI:
return r.Rsi & mask32, nil
case x86asm.EDI:
return r.Rdi & mask32, nil
case x86asm.R8L:
return r.R8 & mask32, nil
case x86asm.R9L:
return r.R9 & mask32, nil
case x86asm.R10L:
return r.R10 & mask32, nil
case x86asm.R11L:
return r.R11 & mask32, nil
case x86asm.R12L:
return r.R12 & mask32, nil
case x86asm.R13L:
return r.R13 & mask32, nil
case x86asm.R14L:
return r.R14 & mask32, nil
case x86asm.R15L:
return r.R15 & mask32, nil
// 64-bit
case x86asm.RAX:
return r.Rax, nil
case x86asm.RCX:
return r.Rcx, nil
case x86asm.RDX:
return r.Rdx, nil
case x86asm.RBX:
return r.Rbx, nil
case x86asm.RSP:
return r.Rsp, nil
case x86asm.RBP:
return r.Rbp, nil
case x86asm.RSI:
return r.Rsi, nil
case x86asm.RDI:
return r.Rdi, nil
case x86asm.R8:
return r.R8, nil
case x86asm.R9:
return r.R9, nil
case x86asm.R10:
return r.R10, nil
case x86asm.R11:
return r.R11, nil
case x86asm.R12:
return r.R12, nil
case x86asm.R13:
return r.R13, nil
case x86asm.R14:
return r.R14, nil
case x86asm.R15:
return r.R15, nil
}
return 0, proc.ErrUnknownRegister
}
// readCore reads a core file from corePath corresponding to the executable at
// exePath. For details on the Linux ELF core format, see: // exePath. For details on the Linux ELF core format, see:
// http://www.gabriel.urdhr.fr/2015/05/29/core-file/, // http://www.gabriel.urdhr.fr/2015/05/29/core-file/,
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html, // http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c, // elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
// and, if absolutely desperate, readelf.c from the binutils source. // and, if absolutely desperate, readelf.c from the binutils source.
func readCore(corePath, exePath string) (*Core, error) { func readLinuxAMD64Core(corePath, exePath string) (*Process, error) {
coreFile, err := elf.Open(corePath) coreFile, err := elf.Open(corePath)
if err != nil { if err != nil {
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
// Go >=1.11 and <1.11 produce different errors when reading a non-elf file.
return nil, ErrUnrecognizedFormat
}
return nil, err return nil, err
} }
exe, err := os.Open(exePath) exe, err := os.Open(exePath)
@ -288,37 +69,51 @@ func readCore(corePath, exePath string) (*Core, error) {
memory := buildMemory(coreFile, exeELF, exe, notes) memory := buildMemory(coreFile, exeELF, exe, notes)
entryPoint := findEntryPoint(notes) entryPoint := findEntryPoint(notes)
core := &Core{ p := &Process{
MemoryReader: memory, mem: memory,
Threads: map[int]*Thread{}, Threads: map[int]*Thread{},
entryPoint: entryPoint, entryPoint: entryPoint,
bi: proc.NewBinaryInfo("linux", "amd64"),
breakpoints: proc.NewBreakpointMap(),
} }
var lastThread *Thread var lastThread *linuxAMD64Thread
for _, note := range notes { for _, note := range notes {
switch note.Type { switch note.Type {
case elf.NT_PRSTATUS: case elf.NT_PRSTATUS:
t := note.Desc.(*LinuxPrStatus) t := note.Desc.(*LinuxPrStatus)
lastThread = &Thread{t, nil, nil, proc.CommonThread{}} lastThread = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
core.Threads[int(t.Pid)] = lastThread p.Threads[int(t.Pid)] = &Thread{lastThread, p, proc.CommonThread{}}
if p.currentThread == nil {
p.currentThread = p.Threads[int(t.Pid)]
}
case NT_X86_XSTATE: case NT_X86_XSTATE:
if lastThread != nil { if lastThread != nil {
lastThread.fpregs = note.Desc.(*proc.LinuxX86Xstate).Decode() lastThread.regs.Fpregs = note.Desc.(*linutil.AMD64Xstate).Decode()
} }
case elf.NT_PRPSINFO: case elf.NT_PRPSINFO:
core.Pid = int(note.Desc.(*LinuxPrPsInfo).Pid) p.pid = int(note.Desc.(*LinuxPrPsInfo).Pid)
} }
} }
return core, nil return p, nil
} }
// Core represents a core file. type linuxAMD64Thread struct {
type Core struct { regs linutil.AMD64Registers
proc.MemoryReader t *LinuxPrStatus
Threads map[int]*Thread }
Pid int
entryPoint uint64 func (t *linuxAMD64Thread) registers(floatingPoint bool) (proc.Registers, error) {
var r linutil.AMD64Registers
r.Regs = t.regs.Regs
if floatingPoint {
r.Fpregs = t.regs.Fpregs
}
return &r, nil
}
func (t *linuxAMD64Thread) pid() int {
return int(t.t.Pid)
} }
// Note is a note from the PT_NOTE prog. // Note is a note from the PT_NOTE prog.
@ -415,8 +210,8 @@ func readNote(r io.ReadSeeker) (*Note, error) {
} }
note.Desc = data note.Desc = data
case NT_X86_XSTATE: case NT_X86_XSTATE:
var fpregs proc.LinuxX86Xstate var fpregs linutil.AMD64Xstate
if err := proc.LinuxX86XstateRead(desc, true, &fpregs); err != nil { if err := linutil.AMD64XstateRead(desc, true, &fpregs); err != nil {
return nil, err return nil, err
} }
note.Desc = &fpregs note.Desc = &fpregs
@ -516,7 +311,7 @@ type LinuxPrStatus struct {
Sighold uint64 Sighold uint64
Pid, Ppid, Pgrp, Sid int32 Pid, Ppid, Pgrp, Sid int32
Utime, Stime, CUtime, CStime LinuxCoreTimeval Utime, Stime, CUtime, CStime LinuxCoreTimeval
Reg LinuxCoreRegisters Reg linutil.AMD64PtraceRegs
Fpvalid int32 Fpvalid int32
} }

View 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) + ")"
}
}

View 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})
}

View 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
}

View File

@ -65,6 +65,14 @@ func (inst *AsmInstruction) IsCall() bool {
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
} }
// IsRet returns true if the instruction is a RET or LRET instruction.
func (inst *AsmInstruction) IsRet() bool {
if inst.Inst == nil {
return false
}
return inst.Inst.Op == x86asm.RET || inst.Inst.Op == x86asm.LRET
}
func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location { func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL { if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
return nil return nil
@ -110,7 +118,7 @@ func resolveCallArg(inst *archInst, currentGoroutine bool, regs Registers, mem M
file, line, fn := bininfo.PCToLine(pc) file, line, fn := bininfo.PCToLine(pc)
if fn == nil { if fn == nil {
return nil return &Location{PC: pc}
} }
return &Location{PC: pc, File: file, Line: line, Fn: fn} return &Location{PC: pc, File: file, Line: line, Fn: fn}
} }

View File

@ -14,9 +14,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/goversion" "github.com/go-delve/delve/pkg/goversion"
) )
var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented") var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not implemented")
@ -114,15 +114,25 @@ func (scope *EvalScope) evalToplevelTypeCast(t ast.Expr, cfg LoadConfig) (*Varia
case "string": case "string":
switch argv.Kind { switch argv.Kind {
case reflect.String:
s := constant.StringVal(argv.Value)
v.Value = constant.MakeString(s)
v.Len = int64(len(s))
return v, nil
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr: case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
b, _ := constant.Int64Val(argv.Value) b, _ := constant.Int64Val(argv.Value)
s := string(b) s := string(b)
v.Value = constant.MakeString(s) v.Value = constant.MakeString(s)
v.Len = int64(len(s)) v.Len = int64(len(s))
return v, nil return v, nil
case reflect.Slice, reflect.Array:
case reflect.Slice: var elem godwarf.Type
switch elemType := argv.RealType.(*godwarf.SliceType).ElemType.(type) { if argv.Kind == reflect.Slice {
elem = argv.RealType.(*godwarf.SliceType).ElemType
} else {
elem = argv.RealType.(*godwarf.ArrayType).Type
}
switch elemType := elem.(type) {
case *godwarf.UintType: case *godwarf.UintType:
if elemType.Name != "uint8" && elemType.Name != "byte" { if elemType.Name != "uint8" && elemType.Name != "byte" {
return nil, nil return nil, nil
@ -1198,7 +1208,7 @@ func (err *typeConvErr) Error() string {
func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error { func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
if v.DwarfType != nil { if v.DwarfType != nil {
if typ != nil && typ.String() != v.RealType.String() { if typ == nil || !sameType(typ, v.RealType) {
return &typeConvErr{v.DwarfType, typ} return &typeConvErr{v.DwarfType, typ}
} }
return nil return nil
@ -1255,6 +1265,34 @@ func (v *Variable) isType(typ godwarf.Type, kind reflect.Kind) error {
return nil return nil
} }
func sameType(t1, t2 godwarf.Type) bool {
// Because of a bug in the go linker a type that refers to another type
// (for example a pointer type) will usually use the typedef but rarely use
// the non-typedef entry directly.
// For types that we read directly from go this is fine because it's
// consistent, however we also synthesize some types ourselves
// (specifically pointers and slices) and we always use a reference through
// a typedef.
t1 = resolveTypedef(t1)
t2 = resolveTypedef(t2)
if tt1, isptr1 := t1.(*godwarf.PtrType); isptr1 {
tt2, isptr2 := t2.(*godwarf.PtrType)
if !isptr2 {
return false
}
return sameType(tt1.Type, tt2.Type)
}
if tt1, isslice1 := t1.(*godwarf.SliceType); isslice1 {
tt2, isslice2 := t2.(*godwarf.SliceType)
if !isslice2 {
return false
}
return sameType(tt1.ElemType, tt2.ElemType)
}
return t1.String() == t2.String()
}
func (v *Variable) sliceAccess(idx int) (*Variable, error) { func (v *Variable) sliceAccess(idx int) (*Variable, error) {
if idx < 0 || int64(idx) >= v.Len { if idx < 0 || int64(idx) >= v.Len {
return nil, fmt.Errorf("index out of bounds") return nil, fmt.Errorf("index out of bounds")

View File

@ -11,11 +11,10 @@ import (
"reflect" "reflect"
"sort" "sort"
"github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/sirupsen/logrus"
"golang.org/x/arch/x86/x86asm" "golang.org/x/arch/x86/x86asm"
) )
@ -81,7 +80,7 @@ type functionCallState struct {
// CallFunction starts a debugger injected function call on the current thread of p. // CallFunction starts a debugger injected function call on the current thread of p.
// See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a // See runtime.debugCallV1 in $GOROOT/src/runtime/asm_amd64.s for a
// description of the protocol. // description of the protocol.
func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error { func CallFunction(p Process, expr string, retLoadCfg *LoadConfig, checkEscape bool) error {
bi := p.BinInfo() bi := p.BinInfo()
if !p.Common().fncallEnabled { if !p.Common().fncallEnabled {
return errFuncCallUnsupportedBackend return errFuncCallUnsupportedBackend
@ -126,7 +125,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
return err return err
} }
argmem, err := funcCallArgFrame(fn, argvars, g, bi) argmem, err := funcCallArgFrame(fn, argvars, g, bi, checkEscape)
if err != nil { if err != nil {
return err return err
} }
@ -153,10 +152,7 @@ func CallFunction(p Process, expr string, retLoadCfg *LoadConfig) error {
} }
func fncallLog(fmtstr string, args ...interface{}) { func fncallLog(fmtstr string, args ...interface{}) {
if !logflags.FnCall() { logflags.FnCallLogger().Infof(fmtstr, args...)
return
}
logrus.WithFields(logrus.Fields{"layer": "proc", "kind": "fncall"}).Infof(fmtstr, args...)
} }
// writePointer writes val as an architecture pointer at addr in mem. // writePointer writes val as an architecture pointer at addr in mem.
@ -218,7 +214,7 @@ func funcCallEvalExpr(p Process, expr string) (fn *Function, closureAddr uint64,
if fnvar.Kind != reflect.Func { if fnvar.Kind != reflect.Func {
return nil, 0, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun)) return nil, 0, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun))
} }
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0}) fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0, 0})
if fnvar.Unreadable != nil { if fnvar.Unreadable != nil {
return nil, 0, nil, fnvar.Unreadable return nil, 0, nil, fnvar.Unreadable
} }
@ -259,7 +255,7 @@ type funcCallArg struct {
// funcCallArgFrame checks type and pointer escaping for the arguments and // funcCallArgFrame checks type and pointer escaping for the arguments and
// returns the argument frame. // returns the argument frame.
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo) (argmem []byte, err error) { func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo, checkEscape bool) (argmem []byte, err error) {
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false) argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -278,9 +274,11 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
formalArg := &formalArgs[i] formalArg := &formalArgs[i]
actualArg := actualArgs[i] actualArg := actualArgs[i]
//TODO(aarzilli): only apply the escapeCheck to leaking parameters. if checkEscape {
if err := escapeCheck(actualArg, formalArg.name, g); err != nil { //TODO(aarzilli): only apply the escapeCheck to leaking parameters.
return nil, fmt.Errorf("can not pass %s to %s: %v", actualArg.Name, formalArg.name, err) if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
return nil, fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fn.Name, err)
}
} }
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled //TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
@ -343,7 +341,13 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
func escapeCheck(v *Variable, name string, g *G) error { func escapeCheck(v *Variable, name string, g *G) error {
switch v.Kind { switch v.Kind {
case reflect.Ptr: case reflect.Ptr:
w := v.maybeDereference() var w *Variable
if len(v.Children) == 1 {
// this branch is here to support pointers constructed with typecasts from ints or the '&' operator
w = &v.Children[0]
} else {
w = v.maybeDereference()
}
return escapeCheckPointer(w.Addr, name, g) return escapeCheckPointer(w.Addr, name, g)
case reflect.Chan, reflect.String, reflect.Slice: case reflect.Chan, reflect.String, reflect.Slice:
return escapeCheckPointer(v.Base, name, g) return escapeCheckPointer(v.Base, name, g)

View File

@ -68,23 +68,20 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"io/ioutil"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"golang.org/x/arch/x86/x86asm" "golang.org/x/arch/x86/x86asm"
"github.com/derekparker/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil" "github.com/go-delve/delve/pkg/proc/linutil"
"github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -130,7 +127,7 @@ type Process struct {
common proc.CommonProcess common proc.CommonProcess
} }
// Thread is a thread. // Thread represents an operating system thread.
type Thread struct { type Thread struct {
ID int ID int
strID string strID string
@ -142,8 +139,7 @@ type Thread struct {
} }
// ErrBackendUnavailable is returned when the stub program can not be found. // ErrBackendUnavailable is returned when the stub program can not be found.
type ErrBackendUnavailable struct { type ErrBackendUnavailable struct{}
}
func (err *ErrBackendUnavailable) Error() string { func (err *ErrBackendUnavailable) Error() string {
return "backend unavailable" return "backend unavailable"
@ -172,11 +168,7 @@ type gdbRegister struct {
// Detach. // Detach.
// Use Listen, Dial or Connect to complete connection. // Use Listen, Dial or Connect to complete connection.
func New(process *os.Process) *Process { func New(process *os.Process) *Process {
logger := logrus.New().WithFields(logrus.Fields{"layer": "gdbconn"}) logger := logflags.GdbWireLogger()
logger.Logger.Level = logrus.DebugLevel
if !logflags.GdbWire() {
logger.Logger.Out = ioutil.Discard
}
p := &Process{ p := &Process{
conn: gdbConn{ conn: gdbConn{
maxTransmitAttempts: maxTransmitAttempts, maxTransmitAttempts: maxTransmitAttempts,
@ -205,7 +197,7 @@ func New(process *os.Process) *Process {
} }
// Listen waits for a connection from the stub. // Listen waits for a connection from the stub.
func (p *Process) Listen(listener net.Listener, path string, pid int) error { func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string) error {
acceptChan := make(chan net.Conn) acceptChan := make(chan net.Conn)
go func() { go func() {
@ -219,7 +211,7 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
if conn == nil { if conn == nil {
return errors.New("could not connect") return errors.New("could not connect")
} }
return p.Connect(conn, path, pid) return p.Connect(conn, path, pid, debugInfoDirs)
case status := <-p.waitChan: case status := <-p.waitChan:
listener.Close() listener.Close()
return fmt.Errorf("stub exited while waiting for connection: %v", status) return fmt.Errorf("stub exited while waiting for connection: %v", status)
@ -227,11 +219,11 @@ func (p *Process) Listen(listener net.Listener, path string, pid int) error {
} }
// Dial attempts to connect to the stub. // Dial attempts to connect to the stub.
func (p *Process) Dial(addr string, path string, pid int) error { func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string) error {
for { for {
conn, err := net.Dial("tcp", addr) conn, err := net.Dial("tcp", addr)
if err == nil { if err == nil {
return p.Connect(conn, path, pid) return p.Connect(conn, path, pid, debugInfoDirs)
} }
select { select {
case status := <-p.waitChan: case status := <-p.waitChan:
@ -248,9 +240,8 @@ func (p *Process) Dial(addr string, path string, pid int) error {
// program and the PID of the target process, both are optional, however // program and the PID of the target process, both are optional, however
// some stubs do not provide ways to determine path and pid automatically // some stubs do not provide ways to determine path and pid automatically
// and Connect will be unable to function without knowing them. // and Connect will be unable to function without knowing them.
func (p *Process) Connect(conn net.Conn, path string, pid int) error { func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
p.conn.conn = conn p.conn.conn = conn
p.conn.pid = pid p.conn.pid = pid
err := p.conn.handshake() err := p.conn.handshake()
if err != nil { if err != nil {
@ -270,55 +261,7 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
} }
} }
if path == "" { if err := p.initialize(path, debugInfoDirs); err != nil {
// If we are attaching to a running process and the user didn't specify
// the executable file manually we must ask the stub for it.
// We support both qXfer:exec-file:read:: (the gdb way) and calling
// qProcessInfo (the lldb way).
// Unfortunately debugserver on macOS supports neither.
path, err = p.conn.readExecFile()
if err != nil {
if isProtocolErrorUnsupported(err) {
_, path, err = p.loadProcessInfo(pid)
if err != nil {
conn.Close()
return err
}
} else {
conn.Close()
return fmt.Errorf("could not determine executable path: %v", err)
}
}
}
if path == "" {
// try using jGetLoadedDynamicLibrariesInfos which is the only way to do
// this supported on debugserver (but only on macOS >= 12.10)
images, _ := p.conn.getLoadedDynamicLibraries()
for _, image := range images {
if image.MachHeader.FileType == macho.TypeExec {
path = image.Pathname
break
}
}
}
var entryPoint uint64
if auxv, err := p.conn.readAuxv(); err == nil {
// If we can't read the auxiliary vector it just means it's not supported
// by the OS or by the stub. If we are debugging a PIE and the entry point
// is needed proc.LoadBinaryInfo will complain about it.
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
}
var wg sync.WaitGroup
err = p.bi.LoadBinaryInfo(path, entryPoint, &wg)
wg.Wait()
if err == nil {
err = p.bi.LoadError()
}
if err != nil {
conn.Close()
return err return err
} }
@ -335,36 +278,6 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error {
p.loadGInstrAddr = addr p.loadGInstrAddr = addr
} }
} }
err = p.updateThreadList(&threadUpdater{p: p})
if err != nil {
conn.Close()
p.bi.Close()
return err
}
if p.conn.pid <= 0 {
p.conn.pid, _, err = p.loadProcessInfo(0)
if err != nil && !isProtocolErrorUnsupported(err) {
conn.Close()
p.bi.Close()
return err
}
}
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
proc.CreateUnrecoveredPanicBreakpoint(p, p.writeBreakpoint, &p.breakpoints)
panicpc, err := proc.FindFunctionLocation(p, "runtime.startpanic", true, 0)
if err == nil {
bp, err := p.breakpoints.SetWithID(-1, panicpc, p.writeBreakpoint)
if err == nil {
bp.Name = proc.UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
}
}
return nil return nil
} }
@ -405,7 +318,7 @@ func getLdEnvVars() []string {
// LLDBLaunch starts an instance of lldb-server and connects to it, asking // LLDBLaunch starts an instance of lldb-server and connects to it, asking
// it to launch the specified target program with the specified arguments // it to launch the specified target program with the specified arguments
// (cmd) on the specified directory wd. // (cmd) on the specified directory wd.
func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) { func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
return nil, ErrUnsupportedOS return nil, ErrUnsupportedOS
@ -482,9 +395,9 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
p.conn.isDebugserver = isDebugserver p.conn.isDebugserver = isDebugserver
if listener != nil { if listener != nil {
err = p.Listen(listener, cmd[0], 0) err = p.Listen(listener, cmd[0], 0, debugInfoDirs)
} else { } else {
err = p.Dial(port, cmd[0], 0) err = p.Dial(port, cmd[0], 0, debugInfoDirs)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -497,13 +410,13 @@ func LLDBLaunch(cmd []string, wd string, foreground bool) (*Process, error) {
// Path is path to the target's executable, path only needs to be specified // Path is path to the target's executable, path only needs to be specified
// for some stubs that do not provide an automated way of determining it // for some stubs that do not provide an automated way of determining it
// (for example debugserver). // (for example debugserver).
func LLDBAttach(pid int, path string) (*Process, error) { func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return nil, ErrUnsupportedOS return nil, ErrUnsupportedOS
} }
isDebugserver := false isDebugserver := false
var proc *exec.Cmd var process *exec.Cmd
var listener net.Listener var listener net.Listener
var port string var port string
if _, err := os.Stat(debugserverExecutable); err == nil { if _, err := os.Stat(debugserverExecutable); err == nil {
@ -512,32 +425,31 @@ func LLDBAttach(pid int, path string) (*Process, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
proc = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid)) process = exec.Command(debugserverExecutable, "-R", fmt.Sprintf("127.0.0.1:%d", listener.Addr().(*net.TCPAddr).Port), "--attach="+strconv.Itoa(pid))
} else { } else {
if _, err := exec.LookPath("lldb-server"); err != nil { if _, err := exec.LookPath("lldb-server"); err != nil {
return nil, &ErrBackendUnavailable{} return nil, &ErrBackendUnavailable{}
} }
port = unusedPort() port = unusedPort()
proc = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port) process = exec.Command("lldb-server", "gdbserver", "--attach", strconv.Itoa(pid), port)
} }
proc.Stdout = os.Stdout process.Stdout = os.Stdout
proc.Stderr = os.Stderr process.Stderr = os.Stderr
process.SysProcAttr = sysProcAttr(false)
proc.SysProcAttr = sysProcAttr(false) err := process.Start()
err := proc.Start()
if err != nil { if err != nil {
return nil, err return nil, err
} }
p := New(proc.Process) p := New(process.Process)
p.conn.isDebugserver = isDebugserver p.conn.isDebugserver = isDebugserver
if listener != nil { if listener != nil {
err = p.Listen(listener, path, pid) err = p.Listen(listener, path, pid, debugInfoDirs)
} else { } else {
err = p.Dial(port, path, pid) err = p.Dial(port, path, pid, debugInfoDirs)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -545,10 +457,80 @@ func LLDBAttach(pid int, path string) (*Process, error) {
return p, nil return p, nil
} }
// loadProcessInfo uses qProcessInfo to load the inferior's PID and // EntryPoint will return the process entry point address, useful for
// debugging PIEs.
func (p *Process) EntryPoint() (uint64, error) {
var entryPoint uint64
if auxv, err := p.conn.readAuxv(); err == nil {
// If we can't read the auxiliary vector it just means it's not supported
// by the OS or by the stub. If we are debugging a PIE and the entry point
// is needed proc.LoadBinaryInfo will complain about it.
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
}
return entryPoint, nil
}
// initialize uses qProcessInfo to load the inferior's PID and
// executable path. This command is not supported by all stubs and not all // executable path. This command is not supported by all stubs and not all
// stubs will report both the PID and executable path. // stubs will report both the PID and executable path.
func (p *Process) loadProcessInfo(pid int) (int, string, error) { func (p *Process) initialize(path string, debugInfoDirs []string) error {
var err error
if path == "" {
// If we are attaching to a running process and the user didn't specify
// the executable file manually we must ask the stub for it.
// We support both qXfer:exec-file:read:: (the gdb way) and calling
// qProcessInfo (the lldb way).
// Unfortunately debugserver on macOS supports neither.
path, err = p.conn.readExecFile()
if err != nil {
if isProtocolErrorUnsupported(err) {
_, path, err = queryProcessInfo(p, p.Pid())
if err != nil {
p.conn.conn.Close()
return err
}
} else {
p.conn.conn.Close()
return fmt.Errorf("could not determine executable path: %v", err)
}
}
}
if path == "" {
// try using jGetLoadedDynamicLibrariesInfos which is the only way to do
// this supported on debugserver (but only on macOS >= 12.10)
images, _ := p.conn.getLoadedDynamicLibraries()
for _, image := range images {
if image.MachHeader.FileType == macho.TypeExec {
path = image.Pathname
break
}
}
}
err = p.updateThreadList(&threadUpdater{p: p})
if err != nil {
p.conn.conn.Close()
p.bi.Close()
return err
}
if p.conn.pid <= 0 {
p.conn.pid, _, err = queryProcessInfo(p, 0)
if err != nil && !isProtocolErrorUnsupported(err) {
p.conn.conn.Close()
p.bi.Close()
return err
}
}
if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil {
p.conn.conn.Close()
return err
}
return nil
}
func queryProcessInfo(p *Process, pid int) (int, string, error) {
pi, err := p.conn.queryProcessInfo(pid) pi, err := p.conn.queryProcessInfo(pid)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
@ -659,7 +641,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
// resume all threads // resume all threads
var threadID string var threadID string
var sig uint8 = 0 var sig uint8
var tu = threadUpdater{p: p} var tu = threadUpdater{p: p}
var err error var err error
continueLoop: continueLoop:
@ -697,6 +679,14 @@ continueLoop:
// process so we must stop here. // process so we must stop here.
case 0x91, 0x92, 0x93, 0x94, 0x95, 0x96: /* TARGET_EXC_BAD_ACCESS */ case 0x91, 0x92, 0x93, 0x94, 0x95, 0x96: /* TARGET_EXC_BAD_ACCESS */
break continueLoop break continueLoop
// Signal 0 is returned by rr when it reaches the start of the process
// in backward continue mode.
case 0:
if p.conn.direction == proc.Backward {
break continueLoop
}
default: default:
// any other signal is always propagated to inferior // any other signal is always propagated to inferior
} }
@ -706,13 +696,19 @@ continueLoop:
return nil, err return nil, err
} }
if p.BinInfo().GOOS == "linux" {
if err := linutil.ElfUpdateSharedObjects(p); err != nil {
return nil, err
}
}
if err := p.setCurrentBreakpoints(); err != nil { if err := p.setCurrentBreakpoints(); err != nil {
return nil, err return nil, err
} }
for _, thread := range p.threads { for _, thread := range p.threads {
if thread.strID == threadID { if thread.strID == threadID {
var err error = nil var err error
switch sig { switch sig {
case 0x91: case 0x91:
err = errors.New("bad access") err = errors.New("bad access")
@ -734,6 +730,13 @@ continueLoop:
return nil, fmt.Errorf("could not find thread %s", threadID) return nil, fmt.Errorf("could not find thread %s", threadID)
} }
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// StepInstruction will step exactly one CPU instruction. // StepInstruction will step exactly one CPU instruction.
func (p *Process) StepInstruction() error { func (p *Process) StepInstruction() error {
thread := p.currentThread thread := p.currentThread
@ -1025,9 +1028,6 @@ func (p *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) { func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
f, l, fn := p.bi.PCToLine(uint64(addr)) f, l, fn := p.bi.PCToLine(uint64(addr))
if fn == nil {
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
}
if err := p.conn.setBreakpoint(addr); err != nil { if err := p.conn.setBreakpoint(addr); err != nil {
return "", 0, nil, nil, err return "", 0, nil, nil, err
@ -1310,7 +1310,6 @@ func (t *Thread) Blocked() bool {
default: default:
return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall") return strings.HasPrefix(fn.Name, "syscall.Syscall") || strings.HasPrefix(fn.Name, "syscall.RawSyscall")
} }
return false
} }
// loadGInstr returns the correct MOV instruction for the current // loadGInstr returns the correct MOV instruction for the current
@ -1569,25 +1568,25 @@ func (t *Thread) clearBreakpointState() {
} }
// SetCurrentBreakpoint will find and set the threads current breakpoint. // SetCurrentBreakpoint will find and set the threads current breakpoint.
func (thread *Thread) SetCurrentBreakpoint() error { func (t *Thread) SetCurrentBreakpoint() error {
thread.clearBreakpointState() t.clearBreakpointState()
regs, err := thread.Registers(false) regs, err := t.Registers(false)
if err != nil { if err != nil {
return err return err
} }
pc := regs.PC() pc := regs.PC()
if bp, ok := thread.p.FindBreakpoint(pc); ok { if bp, ok := t.p.FindBreakpoint(pc); ok {
if thread.regs.PC() != bp.Addr { if t.regs.PC() != bp.Addr {
if err := thread.SetPC(bp.Addr); err != nil { if err := t.SetPC(bp.Addr); err != nil {
return err return err
} }
} }
thread.CurrentBreakpoint = bp.CheckCondition(thread) t.CurrentBreakpoint = bp.CheckCondition(t)
if thread.CurrentBreakpoint.Breakpoint != nil && thread.CurrentBreakpoint.Active { if t.CurrentBreakpoint.Breakpoint != nil && t.CurrentBreakpoint.Active {
if g, err := proc.GetG(thread); err == nil { if g, err := proc.GetG(t); err == nil {
thread.CurrentBreakpoint.HitCount[g.ID]++ t.CurrentBreakpoint.HitCount[g.ID]++
} }
thread.CurrentBreakpoint.TotalHitCount++ t.CurrentBreakpoint.TotalHitCount++
} }
} }
return nil return nil
@ -1827,9 +1826,12 @@ func (t *Thread) SetDX(dx uint64) error {
return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value) return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
} }
func (regs *gdbRegisters) Slice() []proc.Register { func (regs *gdbRegisters) Slice(floatingPoint bool) []proc.Register {
r := make([]proc.Register, 0, len(regs.regsInfo)) r := make([]proc.Register, 0, len(regs.regsInfo))
for _, reginfo := range regs.regsInfo { for _, reginfo := range regs.regsInfo {
if reginfo.Group == "float" && !floatingPoint {
continue
}
switch { switch {
case reginfo.Name == "eflags": case reginfo.Name == "eflags":
r = proc.AppendEflagReg(r, reginfo.Name, uint64(binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value))) r = proc.AppendEflagReg(r, reginfo.Name, uint64(binary.LittleEndian.Uint32(regs.regs[reginfo.Name].value)))
@ -1842,6 +1844,9 @@ func (regs *gdbRegisters) Slice() []proc.Register {
case reginfo.Bitsize == 64: case reginfo.Bitsize == 64:
r = proc.AppendQwordReg(r, reginfo.Name, binary.LittleEndian.Uint64(regs.regs[reginfo.Name].value)) r = proc.AppendQwordReg(r, reginfo.Name, binary.LittleEndian.Uint64(regs.regs[reginfo.Name].value))
case reginfo.Bitsize == 80: case reginfo.Bitsize == 80:
if !floatingPoint {
continue
}
idx := 0 idx := 0
for _, stprefix := range []string{"stmm", "st"} { for _, stprefix := range []string{"stmm", "st"} {
if strings.HasPrefix(reginfo.Name, stprefix) { if strings.HasPrefix(reginfo.Name, stprefix) {
@ -1853,10 +1858,12 @@ func (regs *gdbRegisters) Slice() []proc.Register {
r = proc.AppendX87Reg(r, idx, binary.LittleEndian.Uint16(value[8:]), binary.LittleEndian.Uint64(value[:8])) r = proc.AppendX87Reg(r, idx, binary.LittleEndian.Uint16(value[8:]), binary.LittleEndian.Uint64(value[:8]))
case reginfo.Bitsize == 128: case reginfo.Bitsize == 128:
r = proc.AppendSSEReg(r, strings.ToUpper(reginfo.Name), regs.regs[reginfo.Name].value) if floatingPoint {
r = proc.AppendSSEReg(r, strings.ToUpper(reginfo.Name), regs.regs[reginfo.Name].value)
}
case reginfo.Bitsize == 256: case reginfo.Bitsize == 256:
if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") { if !strings.HasPrefix(strings.ToLower(reginfo.Name), "ymm") || !floatingPoint {
continue continue
} }

View File

@ -16,8 +16,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/derekparker/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -227,7 +227,8 @@ type gdbRegisterInfo struct {
Name string `xml:"name,attr"` Name string `xml:"name,attr"`
Bitsize int `xml:"bitsize,attr"` Bitsize int `xml:"bitsize,attr"`
Offset int Offset int
Regnum int `xml:"regnum,attr"` Regnum int `xml:"regnum,attr"`
Group string `xml:"group,attr"`
} }
// readTargetXml reads target.xml file from stub using qXfer:features:read, // readTargetXml reads target.xml file from stub using qXfer:features:read,

View File

@ -53,7 +53,7 @@ func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
// Replay starts an instance of rr in replay mode, with the specified trace // Replay starts an instance of rr in replay mode, with the specified trace
// directory, and connects to it. // directory, and connects to it.
func Replay(tracedir string, quiet bool) (*Process, error) { func Replay(tracedir string, quiet bool, debugInfoDirs []string) (*Process, error) {
if err := checkRRAvailabe(); err != nil { if err := checkRRAvailabe(); err != nil {
return nil, err return nil, err
} }
@ -82,7 +82,7 @@ func Replay(tracedir string, quiet bool) (*Process, error) {
p := New(rrcmd.Process) p := New(rrcmd.Process)
p.tracedir = tracedir p.tracedir = tracedir
err = p.Dial(init.port, init.exe, 0) err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
if err != nil { if err != nil {
rrcmd.Process.Kill() rrcmd.Process.Kill()
return nil, err return nil, err
@ -257,11 +257,11 @@ func splitQuotedFields(in string) []string {
} }
// RecordAndReplay acts like calling Record and then Replay. // RecordAndReplay acts like calling Record and then Replay.
func RecordAndReplay(cmd []string, wd string, quiet bool) (p *Process, tracedir string, err error) { func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (p *Process, tracedir string, err error) {
tracedir, err = Record(cmd, wd, quiet) tracedir, err = Record(cmd, wd, quiet)
if tracedir == "" { if tracedir == "" {
return nil, "", err return nil, "", err
} }
p, err = Replay(tracedir, quiet) p, err = Replay(tracedir, quiet, debugInfoDirs)
return p, tracedir, err return p, tracedir, err
} }

View File

@ -68,6 +68,7 @@ type Info interface {
// ErrProcessExited or ProcessDetachedError). // ErrProcessExited or ProcessDetachedError).
Valid() (bool, error) Valid() (bool, error)
BinInfo() *BinaryInfo BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
// Common returns a struct with fields common to all backends // Common returns a struct with fields common to all backends
Common() *CommonProcess Common() *CommonProcess
@ -86,6 +87,7 @@ type ThreadInfo interface {
// GoroutineInfo is an interface for getting information on running goroutines. // GoroutineInfo is an interface for getting information on running goroutines.
type GoroutineInfo interface { type GoroutineInfo interface {
SelectedGoroutine() *G SelectedGoroutine() *G
SetSelectedGoroutine(*G)
} }
// ProcessManipulation is an interface for changing the execution state of a process. // ProcessManipulation is an interface for changing the execution state of a process.

View 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
}

View 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 Developers
// 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 Developers 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, &regset.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
}

View File

@ -2,8 +2,9 @@ package proc
import ( import (
"errors" "errors"
"fmt"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
) )
const cacheEnabled = true const cacheEnabled = true
@ -93,7 +94,7 @@ type compositeMemory struct {
data []byte data []byte
} }
func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece) *compositeMemory { func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece) (*compositeMemory, error) {
cmem := &compositeMemory{realmem: mem, regs: regs, pieces: pieces, data: []byte{}} cmem := &compositeMemory{realmem: mem, regs: regs, pieces: pieces, data: []byte{}}
for _, piece := range pieces { for _, piece := range pieces {
if piece.IsRegister { if piece.IsRegister {
@ -102,6 +103,9 @@ func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []o
if sz == 0 && len(pieces) == 1 { if sz == 0 && len(pieces) == 1 {
sz = len(reg) sz = len(reg)
} }
if sz > len(reg) {
return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", sz, piece.RegNum, len(reg))
}
cmem.data = append(cmem.data, reg[:sz]...) cmem.data = append(cmem.data, reg[:sz]...)
} else { } else {
buf := make([]byte, piece.Size) buf := make([]byte, piece.Size)
@ -109,7 +113,7 @@ func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []o
cmem.data = append(cmem.data, buf...) cmem.data = append(cmem.data, buf...)
} }
} }
return cmem return cmem, nil
} }
func (mem *compositeMemory) ReadMemory(data []byte, addr uintptr) (int, error) { func (mem *compositeMemory) ReadMemory(data []byte, addr uintptr) (int, error) {

View File

@ -88,7 +88,7 @@ func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
if err != nil { if err != nil {
return nil, err return nil, err
} }
v.loadValue(LoadConfig{false, 1, 0, 0, -1}) v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
addr, _ := constant.Int64Val(v.Value) addr, _ := constant.Int64Val(v.Value)
return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil return v.newVariable(v.Name, uintptr(addr), rtyp, mem), nil
} }

View File

@ -6,18 +6,18 @@ import (
"errors" "errors"
"sync" "sync"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation") var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
// Launch returns ErrNativeBackendDisabled. // Launch returns ErrNativeBackendDisabled.
func Launch(cmd []string, wd string, foreground bool) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
return nil, ErrNativeBackendDisabled return nil, ErrNativeBackendDisabled
} }
// Attach returns ErrNativeBackendDisabled. // Attach returns ErrNativeBackendDisabled.
func Attach(pid int) (*Process, error) { func Attach(pid int, _ []string) (*Process, error) {
return nil, ErrNativeBackendDisabled return nil, ErrNativeBackendDisabled
} }
@ -75,7 +75,9 @@ func (dbp *Process) detach(kill bool) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
func (dbp *Process) entryPoint() (uint64, error) { // EntryPoint returns the entry point for the process,
// useful for PIEs.
func (dbp *Process) EntryPoint() (uint64, error) {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
@ -85,17 +87,17 @@ func (t *Thread) Blocked() bool {
} }
// SetPC sets the value of the PC register. // SetPC sets the value of the PC register.
func (thread *Thread) SetPC(pc uint64) error { func (t *Thread) SetPC(pc uint64) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
// SetSP sets the value of the SP register. // SetSP sets the value of the SP register.
func (thread *Thread) SetSP(sp uint64) error { func (t *Thread) SetSP(sp uint64) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
// SetDX sets the value of the DX register. // SetDX sets the value of the DX register.
func (thread *Thread) SetDX(dx uint64) error { func (t *Thread) SetDX(dx uint64) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
@ -126,3 +128,5 @@ func (t *Thread) restoreRegisters(sr proc.Registers) error {
func (t *Thread) Stopped() bool { func (t *Thread) Stopped() bool {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }
func initialize(dbp *Process) error { return nil }

View File

@ -6,13 +6,14 @@ import (
"runtime" "runtime"
"sync" "sync"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// Process represents all of the information the debugger // Process represents all of the information the debugger
// is holding onto regarding the process we are debugging. // is holding onto regarding the process we are debugging.
type Process struct { type Process struct {
bi *proc.BinaryInfo bi *proc.BinaryInfo
pid int // Process Pid pid int // Process Pid
// Breakpoint table, holds information on breakpoints. // Breakpoint table, holds information on breakpoints.
@ -184,32 +185,6 @@ func (dbp *Process) Breakpoints() *proc.BreakpointMap {
return &dbp.breakpoints return &dbp.breakpoints
} }
// LoadInformation finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *Process) LoadInformation(path string) error {
var wg sync.WaitGroup
path = findExecutable(path, dbp.pid)
entryPoint, err := dbp.entryPoint()
if err != nil {
return err
}
wg.Add(1)
go dbp.loadProcessInformation(&wg)
err = dbp.bi.LoadBinaryInfo(path, entryPoint, &wg)
wg.Wait()
if err == nil {
err = dbp.bi.LoadError()
}
return err
}
// RequestManualStop sets the `halt` flag and // RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads. // sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error { func (dbp *Process) RequestManualStop() error {
@ -236,9 +211,6 @@ func (dbp *Process) CheckAndClearManualStopRequest() bool {
func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) { func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
f, l, fn := dbp.bi.PCToLine(uint64(addr)) f, l, fn := dbp.bi.PCToLine(uint64(addr))
if fn == nil {
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
}
originalData := make([]byte, dbp.bi.Arch.BreakpointSize()) originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr)) _, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
@ -379,26 +351,23 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
return nil, false return nil, false
} }
// Returns a new Process struct. // initialize will ensure that all relevant information is loaded
func initializeDebugProcess(dbp *Process, path string) (*Process, error) { // so the process is ready to be debugged.
err := dbp.LoadInformation(path) func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
if err != nil { if err := initialize(dbp); err != nil {
return dbp, err return err
} }
if err := dbp.updateThreadList(); err != nil { if err := dbp.updateThreadList(); err != nil {
return dbp, err return err
} }
return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint)
}
// selectedGoroutine can not be set correctly by the call to updateThreadList // SetSelectedGoroutine will set internally the goroutine that should be
// because without calling SetGStructOffset we can not read the G struct of currentThread // the default for any command executed, the goroutine being actively
// but without calling updateThreadList we can not examine memory to determine // followed.
// the offset of g struct inside TLS func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread) dbp.selectedGoroutine = g
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
return dbp, nil
} }
// ClearInternalBreakpoints will clear all non-user set breakpoints. These // ClearInternalBreakpoints will clear all non-user set breakpoints. These

View File

@ -13,12 +13,11 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sync"
"unsafe" "unsafe"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// OSProcessDetails holds Darwin specific information. // OSProcessDetails holds Darwin specific information.
@ -38,7 +37,7 @@ type OSProcessDetails struct {
// custom fork/exec process in order to take advantage of // custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin which will turn Unix signals into // PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions. // Mach exceptions.
func Launch(cmd []string, wd string, foreground bool) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
// check that the argument to Launch is an executable file // check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, proc.ErrNotExecutable return nil, proc.ErrNotExecutable
@ -119,7 +118,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
} }
dbp.os.initialized = true dbp.os.initialized = true
dbp, err = initializeDebugProcess(dbp, argv0Go) err = dbp.initialize(argv0Go, []string{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,7 +131,7 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) { func Attach(pid int, _ []string) (*Process, error) {
dbp := New(pid) dbp := New(pid)
kret := C.acquire_mach_task(C.int(pid), kret := C.acquire_mach_task(C.int(pid),
@ -155,7 +154,7 @@ func Attach(pid int) (*Process, error) {
return nil, err return nil, err
} }
dbp, err = initializeDebugProcess(dbp, "") err = dbp.initialize("", []string{})
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
@ -383,10 +382,6 @@ func (dbp *Process) waitForStop() ([]int, error) {
} }
} }
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
var status sys.WaitStatus var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, options, nil) wpid, err := sys.Wait4(pid, &status, options, nil)
@ -464,7 +459,9 @@ func (dbp *Process) detach(kill bool) error {
return PtraceDetach(dbp.pid, 0) return PtraceDetach(dbp.pid, 0)
} }
func (dbp *Process) entryPoint() (uint64, error) { func (dbp *Process) EntryPoint() (uint64, error) {
//TODO(aarzilli): implement this //TODO(aarzilli): implement this
return 0, nil return 0, nil
} }
func initialize(dbp *Process) error { return nil }

View File

@ -12,15 +12,15 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/linutil" "github.com/go-delve/delve/pkg/proc/linutil"
"github.com/mattn/go-isatty"
isatty "github.com/mattn/go-isatty"
) )
// Process statuses // Process statuses
@ -46,7 +46,9 @@ type OSProcessDetails struct {
// Launch creates and begins debugging a new process. First entry in // Launch creates and begins debugging a new process. First entry in
// `cmd` is the program to run, and then rest are the arguments // `cmd` is the program to run, and then rest are the arguments
// to be supplied to that process. `wd` is working directory of the program. // to be supplied to that process. `wd` is working directory of the program.
func Launch(cmd []string, wd string, foreground bool) (*Process, error) { // If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in.
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) {
var ( var (
process *exec.Cmd process *exec.Cmd
err error err error
@ -88,11 +90,16 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err) return nil, fmt.Errorf("waiting for target execve failed: %s", err)
} }
return initializeDebugProcess(dbp, process.Path) if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err
}
return dbp, nil
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID. Once attached, if
func Attach(pid int) (*Process, error) { // the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in.
func Attach(pid int, debugInfoDirs []string) (*Process, error) {
dbp := New(pid) dbp := New(pid)
dbp.common = proc.NewCommonProcess(true) dbp.common = proc.NewCommonProcess(true)
@ -106,7 +113,7 @@ func Attach(pid int) (*Process, error) {
return nil, err return nil, err
} }
dbp, err = initializeDebugProcess(dbp, "") err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
@ -114,6 +121,33 @@ func Attach(pid int) (*Process, error) {
return dbp, nil return dbp, nil
} }
func initialize(dbp *Process) error {
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
if err == nil {
// removes newline character
comm = bytes.TrimSuffix(comm, []byte("\n"))
}
if comm == nil || len(comm) <= 0 {
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
if err != nil {
return fmt.Errorf("could not read proc stat: %v", err)
}
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
rexp, err := regexp.Compile(expr)
if err != nil {
return fmt.Errorf("regexp compile error: %v", err)
}
match := rexp.FindSubmatch(stat)
if match == nil {
return fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
}
comm = match[1]
}
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
return nil
}
// kill kills the target process. // kill kills the target process.
func (dbp *Process) kill() (err error) { func (dbp *Process) kill() (err error) {
if dbp.exited { if dbp.exited {
@ -199,7 +233,7 @@ func (dbp *Process) updateThreadList() error {
return err return err
} }
} }
return nil return linutil.ElfUpdateSharedObjects(dbp)
} }
func findExecutable(path string, pid int) string { func findExecutable(path string, pid int) string {
@ -295,35 +329,7 @@ func (dbp *Process) trapWaitInternal(pid int, halt bool) (*Thread, error) {
} }
} }
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) { func (dbp *Process) loadProcessInformation() {
defer wg.Done()
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
if err == nil {
// removes newline character
comm = bytes.TrimSuffix(comm, []byte("\n"))
}
if comm == nil || len(comm) <= 0 {
stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
if err != nil {
fmt.Printf("Could not read proc stat: %v\n", err)
os.Exit(1)
}
expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
rexp, err := regexp.Compile(expr)
if err != nil {
fmt.Printf("Regexp compile error: %v\n", err)
os.Exit(1)
}
match := rexp.FindSubmatch(stat)
if match == nil {
fmt.Printf("No match found using regexp '%s' in /proc/%d/stat\n", expr, dbp.pid)
os.Exit(1)
}
comm = match[1]
}
dbp.os.comm = strings.Replace(string(comm), "%", "%%", -1)
} }
func status(pid int, comm string) rune { func status(pid int, comm string) rune {
@ -447,6 +453,10 @@ func (dbp *Process) stop(trapthread *Thread) (err error) {
} }
} }
if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
return err
}
// set breakpoints on all threads // set breakpoints on all threads
for _, th := range dbp.threads { for _, th := range dbp.threads {
if th.CurrentBreakpoint.Breakpoint == nil { if th.CurrentBreakpoint.Breakpoint == nil {
@ -479,7 +489,9 @@ func (dbp *Process) detach(kill bool) error {
return nil return nil
} }
func (dbp *Process) entryPoint() (uint64, error) { // EntryPoint will return the process entry point address, useful for
// debugging PIEs.
func (dbp *Process) EntryPoint() (uint64, error) {
auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid)) auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
if err != nil { if err != nil {
return 0, fmt.Errorf("could not read auxiliary vector: %v", err) return 0, fmt.Errorf("could not read auxiliary vector: %v", err)

View File

@ -7,13 +7,12 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sync"
"syscall" "syscall"
"unsafe" "unsafe"
sys "golang.org/x/sys/windows" sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// OSProcessDetails holds Windows specific information. // OSProcessDetails holds Windows specific information.
@ -37,7 +36,7 @@ func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
} }
// Launch creates and begins debugging a new process. // Launch creates and begins debugging a new process.
func Launch(cmd []string, wd string, foreground bool) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
argv0Go, err := filepath.Abs(cmd[0]) argv0Go, err := filepath.Abs(cmd[0])
if err != nil { if err != nil {
return nil, err return nil, err
@ -77,11 +76,14 @@ func Launch(cmd []string, wd string, foreground bool) (*Process, error) {
dbp.pid = p.Pid dbp.pid = p.Pid
dbp.childProcess = true dbp.childProcess = true
return newDebugProcess(dbp, argv0Go) if err = dbp.initialize(argv0Go, []string{}); err != nil {
dbp.Detach(true)
return nil, err
}
return dbp, nil
} }
// newDebugProcess prepares process pid for debugging. func initialize(dbp *Process) error {
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
// It should not actually be possible for the // It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows // call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event // will always fire a CREATE_PROCESS_DEBUG_EVENT event
@ -93,29 +95,25 @@ func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
}) })
if err != nil { if err != nil {
return nil, err return err
} }
if tid == 0 { if tid == 0 {
dbp.postExit() dbp.postExit()
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} return proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
} }
// Suspend all threads so that the call to _ContinueDebugEvent will // Suspend all threads so that the call to _ContinueDebugEvent will
// not resume the target. // not resume the target.
for _, thread := range dbp.threads { for _, thread := range dbp.threads {
_, err := _SuspendThread(thread.os.hThread) _, err := _SuspendThread(thread.os.hThread)
if err != nil { if err != nil {
return nil, err return err
} }
} }
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
}) })
if err != nil { return err
return nil, err
}
return initializeDebugProcess(dbp, exepath)
} }
// findExePath searches for process pid, and returns its executable path. // findExePath searches for process pid, and returns its executable path.
@ -153,7 +151,7 @@ func findExePath(pid int) (string, error) {
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) { func Attach(pid int, _ []string) (*Process, error) {
// TODO: Probably should have SeDebugPrivilege before starting here. // TODO: Probably should have SeDebugPrivilege before starting here.
err := _DebugActiveProcess(uint32(pid)) err := _DebugActiveProcess(uint32(pid))
if err != nil { if err != nil {
@ -163,11 +161,9 @@ func Attach(pid int) (*Process, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbp, err := newDebugProcess(New(pid), exepath) dbp := New(pid)
if err != nil { if err = dbp.initialize(exepath, []string{}); err != nil {
if dbp != nil { dbp.Detach(true)
dbp.Detach(false)
}
return nil, err return nil, err
} }
return dbp, nil return dbp, nil
@ -245,6 +241,8 @@ const (
waitDontHandleExceptions waitDontHandleExceptions
) )
const _MS_VC_EXCEPTION = 0x406D1388 // part of VisualC protocol to set thread names
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) { func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
var debugEvent _DEBUG_EVENT var debugEvent _DEBUG_EVENT
shouldExit := false shouldExit := false
@ -346,6 +344,10 @@ func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, e
case _EXCEPTION_SINGLE_STEP: case _EXCEPTION_SINGLE_STEP:
dbp.os.breakThread = tid dbp.os.breakThread = tid
return tid, 0, nil return tid, 0, nil
case _MS_VC_EXCEPTION:
// This exception is sent to set the thread name in VisualC, we should
// mask it or it might crash the program.
continueStatus = _DBG_CONTINUE
default: default:
continueStatus = _DBG_EXCEPTION_NOT_HANDLED continueStatus = _DBG_EXCEPTION_NOT_HANDLED
} }
@ -386,10 +388,6 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
return th, nil return th, nil
} }
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
wg.Done()
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
return 0, nil, fmt.Errorf("not implemented: wait") return 0, nil, fmt.Errorf("not implemented: wait")
} }
@ -482,7 +480,7 @@ func (dbp *Process) detach(kill bool) error {
return _DebugActiveProcessStop(uint32(dbp.pid)) return _DebugActiveProcessStop(uint32(dbp.pid))
} }
func (dbp *Process) entryPoint() (uint64, error) { func (dbp *Process) EntryPoint() (uint64, error) {
return dbp.os.entryPoint, nil return dbp.os.entryPoint, nil
} }

View File

@ -6,7 +6,7 @@ import (
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc/linutil"
) )
// PtraceAttach executes the sys.PtraceAttach call. // PtraceAttach executes the sys.PtraceAttach call.
@ -57,8 +57,8 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
// See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html // See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html
// and amd64_supply_xsave in gdb/amd64-tdep.c.html // and amd64_supply_xsave in gdb/amd64-tdep.c.html
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developers Manual, Volume 1: Basic Architecture // and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developers Manual, Volume 1: Basic Architecture
func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) { func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.PtraceFpRegs)), 0, 0) _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.AMD64PtraceFpRegs)), 0, 0)
if err == syscall.Errno(0) || err == syscall.ENODEV { if err == syscall.Errno(0) || err == syscall.ENODEV {
// ignore ENODEV, it just means this CPU doesn't have X87 registers (??) // ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
err = nil err = nil
@ -68,8 +68,9 @@ func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE} iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0) _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) { if err != syscall.Errno(0) {
if err == syscall.ENODEV { if err == syscall.ENODEV || err == syscall.EIO {
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/derekparker/delve/issues/1022 // ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/go-delve/delve/issues/1022
// also ignore EIO, it means that we are running on an old kernel (pre 2.6.34) and PTRACE_GETREGSET is not implemented
err = nil err = nil
} }
return return
@ -78,6 +79,6 @@ func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) {
} }
regset.Xsave = xstateargs[:iov.Len] regset.Xsave = xstateargs[:iov.Len]
err = proc.LinuxX86XstateRead(regset.Xsave, false, &regset) err = linutil.AMD64XstateRead(regset.Xsave, false, &regset)
return regset, err return regset, err
} }

View File

@ -12,7 +12,7 @@ import (
"golang.org/x/arch/x86/x86asm" "golang.org/x/arch/x86/x86asm"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// Regs represents CPU registers on an AMD64 processor. // Regs represents CPU registers on an AMD64 processor.
@ -42,7 +42,7 @@ type Regs struct {
fpregs []proc.Register fpregs []proc.Register
} }
func (r *Regs) Slice() []proc.Register { func (r *Regs) Slice(floatingPoint bool) []proc.Register {
var regs = []struct { var regs = []struct {
k string k string
v uint64 v uint64
@ -78,7 +78,9 @@ func (r *Regs) Slice() []proc.Register {
out = proc.AppendQwordReg(out, reg.k, reg.v) out = proc.AppendQwordReg(out, reg.k, reg.v)
} }
} }
out = append(out, r.fpregs...) if floatingPoint {
out = append(out, r.fpregs...)
}
return out return out
} }

View 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)(&regs)) })
if err != nil {
return nil, err
}
r := &linutil.AMD64Registers{&regs, 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
}

View 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
}

View File

@ -4,6 +4,8 @@ package native
import ( import (
"syscall" "syscall"
"github.com/go-delve/delve/pkg/proc/winutil"
) )
type _NTSTATUS int32 type _NTSTATUS int32
@ -97,6 +99,11 @@ func _NT_SUCCESS(x _NTSTATUS) bool {
return x >= 0 return x >= 0
} }
// zsyscall_windows.go, an autogenerated file, wants to refer to the context
// structure as _CONTEXT, but we need to have it in pkg/proc/winutil.CONTEXT
// because it's also used on non-windows operating systems.
type _CONTEXT = winutil.CONTEXT
//sys _NtQueryInformationThread(threadHandle syscall.Handle, infoclass int32, info uintptr, infolen uint32, retlen *uint32) (status _NTSTATUS) = ntdll.NtQueryInformationThread //sys _NtQueryInformationThread(threadHandle syscall.Handle, infoclass int32, info uintptr, infolen uint32, retlen *uint32) (status _NTSTATUS) = ntdll.NtQueryInformationThread
//sys _GetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.GetThreadContext //sys _GetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.GetThreadContext
//sys _SetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.SetThreadContext //sys _SetThreadContext(thread syscall.Handle, context *_CONTEXT) (err error) = kernel32.SetThreadContext

View 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
}

View File

@ -3,7 +3,7 @@ package native
import ( import (
"fmt" "fmt"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// Thread represents a single thread in the traced process // Thread represents a single thread in the traced process

View File

@ -12,7 +12,7 @@ import (
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// WaitStatus is a synonym for the platform-specific WaitStatus // WaitStatus is a synonym for the platform-specific WaitStatus

View File

@ -7,7 +7,8 @@ import (
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
) )
type WaitStatus sys.WaitStatus type WaitStatus sys.WaitStatus
@ -83,21 +84,21 @@ func (t *Thread) Blocked() bool {
} }
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error { func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
sr := savedRegs.(*Regs) sr := savedRegs.(*linutil.AMD64Registers)
var restoreRegistersErr error var restoreRegistersErr error
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {
restoreRegistersErr = sys.PtraceSetRegs(t.ID, sr.regs) restoreRegistersErr = sys.PtraceSetRegs(t.ID, (*sys.PtraceRegs)(sr.Regs))
if restoreRegistersErr != nil { if restoreRegistersErr != nil {
return return
} }
if sr.fpregset.Xsave != nil { if sr.Fpregset.Xsave != nil {
iov := sys.Iovec{Base: &sr.fpregset.Xsave[0], Len: uint64(len(sr.fpregset.Xsave))} iov := sys.Iovec{Base: &sr.Fpregset.Xsave[0], Len: uint64(len(sr.Fpregset.Xsave))}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0) _, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
return return
} }
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(0), uintptr(unsafe.Pointer(&sr.fpregset.PtraceFpRegs)), 0, 0) _, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(0), uintptr(unsafe.Pointer(&sr.Fpregset.AMD64PtraceFpRegs)), 0, 0)
return return
}) })
if restoreRegistersErr == syscall.Errno(0) { if restoreRegistersErr == syscall.Errno(0) {

View File

@ -6,7 +6,8 @@ import (
sys "golang.org/x/sys/windows" sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/winutil"
) )
// WaitStatus is a synonym for the platform-specific WaitStatus // WaitStatus is a synonym for the platform-specific WaitStatus
@ -19,7 +20,7 @@ type OSSpecificDetails struct {
} }
func (t *Thread) singleStep() error { func (t *Thread) singleStep() error {
context := newCONTEXT() context := winutil.NewCONTEXT()
context.ContextFlags = _CONTEXT_ALL context.ContextFlags = _CONTEXT_ALL
// Set the processor TRAP flag // Set the processor TRAP flag
@ -154,5 +155,5 @@ func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
} }
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error { func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
return _SetThreadContext(t.os.hThread, savedRegs.(*Regs).context) return _SetThreadContext(t.os.hThread, savedRegs.(*winutil.AMD64Registers).Context)
} }

View File

@ -19,8 +19,16 @@ var ErrNotExecutable = errors.New("not an executable file")
// only possible on recorded (traced) programs. // only possible on recorded (traced) programs.
var ErrNotRecorded = errors.New("not a recording") var ErrNotRecorded = errors.New("not a recording")
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint. const (
const UnrecoveredPanic = "unrecovered-panic" // UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
UnrecoveredPanic = "unrecovered-panic"
// FatalThrow is the name given to the breakpoint triggered when the target process dies because of a fatal runtime error
FatalThrow = "runtime-fatal-throw"
unrecoveredPanicID = -1
fatalThrowID = -2
)
// ErrProcessExited indicates that the process has exited and contains both // ErrProcessExited indicates that the process has exited and contains both
// process id and exit status. // process id and exit status.
@ -41,6 +49,31 @@ func (pe ProcessDetachedError) Error() string {
return "detached from the process" return "detached from the process"
} }
// PostInitializationSetup handles all of the initialization procedures
// that must happen after Delve creates or attaches to a process.
func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
entryPoint, err := p.EntryPoint()
if err != nil {
return err
}
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
if err == nil {
err = p.BinInfo().LoadError()
}
if err != nil {
return err
}
g, _ := GetG(p.CurrentThread())
p.SetSelectedGoroutine(g)
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
createFatalThrowBreakpoint(p, writeBreakpoint)
return nil
}
// FindFileLocation returns the PC for a given file:line. // FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normalized to lower case and '/' on Windows. // Assumes that `file` is normalized to lower case and '/' on Windows.
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) { func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
@ -69,7 +102,7 @@ func (err *ErrFunctionNotFound) Error() string {
// If lineOffset is passed FindFunctionLocation will return the address of that line // If lineOffset is passed FindFunctionLocation will return the address of that line
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point // Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
// Note that setting breakpoints at that address will cause surprising behavior: // Note that setting breakpoints at that address will cause surprising behavior:
// https://github.com/derekparker/delve/issues/170 // https://github.com/go-delve/delve/issues/170
func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) { func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) {
bi := p.BinInfo() bi := p.BinInfo()
origfn := bi.LookupFunc[funcName] origfn := bi.LookupFunc[funcName]
@ -88,6 +121,33 @@ func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset
return origfn.Entry, nil return origfn.Entry, nil
} }
// FunctionReturnLocations will return a list of addresses corresponding
// to 'ret' or 'call runtime.deferreturn'.
func FunctionReturnLocations(p Process, funcName string) ([]uint64, error) {
const deferReturn = "runtime.deferreturn"
g := p.SelectedGoroutine()
fn, ok := p.BinInfo().LookupFunc[funcName]
if !ok {
return nil, fmt.Errorf("unable to find function %s", funcName)
}
instructions, err := Disassemble(p, g, fn.Entry, fn.End)
if err != nil {
return nil, err
}
var addrs []uint64
for _, instruction := range instructions {
if instruction.IsRet() {
addrs = append(addrs, instruction.Loc.PC)
}
}
addrs = append(addrs, findDeferReturnCalls(instructions)...)
return addrs, nil
}
// Next continues execution until the next source line. // Next continues execution until the next source line.
func Next(dbp Process) (err error) { func Next(dbp Process) (err error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
@ -184,7 +244,8 @@ func Continue(dbp Process) error {
return conditionErrors(threads) return conditionErrors(threads)
} }
case curbp.Active && curbp.Internal: case curbp.Active && curbp.Internal:
if curbp.Kind == StepBreakpoint { switch curbp.Kind {
case StepBreakpoint:
// See description of proc.(*Process).next for the meaning of StepBreakpoints // See description of proc.(*Process).next for the meaning of StepBreakpoints
if err := conditionErrors(threads); err != nil { if err := conditionErrors(threads); err != nil {
return err return err
@ -204,7 +265,7 @@ func Continue(dbp Process) error {
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil { if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err return err
} }
} else { default:
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread) curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
if err := dbp.ClearInternalBreakpoints(); err != nil { if err := dbp.ClearInternalBreakpoints(); err != nil {
return err return err
@ -281,7 +342,7 @@ func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string)
if g := dbp.SelectedGoroutine(); g != nil { if g := dbp.SelectedGoroutine(); g != nil {
g.CurrentLoc = *loc g.CurrentLoc = *loc
} }
return nil return curthread.SetCurrentBreakpoint()
} }
} }
} }
@ -436,14 +497,22 @@ func StepOut(dbp Process) error {
return Continue(dbp) return Continue(dbp)
} }
// GoroutinesInfo returns an array of G structures representing the information // GoroutinesInfo searches for goroutines starting at index 'start', and
// Delve cares about from the internal runtime G structure. // returns an array of up to 'count' (or all found elements, if 'count' is 0)
func GoroutinesInfo(dbp Process) ([]*G, error) { // G structures representing the information Delve care about from the internal
// runtime G structure.
// GoroutinesInfo also returns the next index to be used as 'start' argument
// while scanning for all available goroutines, or -1 if there was an error
// or if the index already reached the last possible value.
func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return nil, err return nil, -1, err
} }
if dbp.Common().allGCache != nil { if dbp.Common().allGCache != nil {
return dbp.Common().allGCache, nil // We can't use the cached array to fulfill a subrange request
if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) {
return dbp.Common().allGCache, -1, nil
}
} }
var ( var (
@ -465,12 +534,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase) addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
allglenBytes := make([]byte, 8) allglenBytes := make([]byte, 8)
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr)) _, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
allglen := binary.LittleEndian.Uint64(allglenBytes) allglen := binary.LittleEndian.Uint64(allglenBytes)
@ -480,17 +549,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
// try old name (pre Go 1.6) // try old name (pre Go 1.6)
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase) allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
} }
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize()) faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr)) _, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
allgptr := binary.LittleEndian.Uint64(faddr) allgptr := binary.LittleEndian.Uint64(faddr)
for i := uint64(0); i < allglen; i++ { for i := uint64(start); i < allglen; i++ {
if count != 0 && len(allg) >= count {
return allg, int(i), nil
}
gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true) gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
if err != nil { if err != nil {
allg = append(allg, &G{Unreadable: err}) allg = append(allg, &G{Unreadable: err})
@ -504,7 +576,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
if thg, allocated := threadg[g.ID]; allocated { if thg, allocated := threadg[g.ID]; allocated {
loc, err := thg.Thread.Location() loc, err := thg.Thread.Location()
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
g.Thread = thg.Thread g.Thread = thg.Thread
// Prefer actual thread location information. // Prefer actual thread location information.
@ -515,36 +587,78 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
allg = append(allg, g) allg = append(allg, g)
} }
} }
dbp.Common().allGCache = allg if start == 0 {
dbp.Common().allGCache = allg
}
return allg, nil return allg, -1, nil
} }
// FindGoroutine returns a G struct representing the goroutine // FindGoroutine returns a G struct representing the goroutine
// specified by `gid`. // specified by `gid`.
func FindGoroutine(dbp Process, gid int) (*G, error) { func FindGoroutine(dbp Process, gid int) (*G, error) {
if gid == -1 { if selg := dbp.SelectedGoroutine(); (gid == -1) || (selg != nil && selg.ID == gid) || (selg == nil && gid == 0) {
return dbp.SelectedGoroutine(), nil // Return the currently selected goroutine in the following circumstances:
//
// 1. if the caller asks for gid == -1 (because that's what a goroutine ID of -1 means in our API).
// 2. if gid == selg.ID.
// this serves two purposes: (a) it's an optimizations that allows us
// to avoid reading any other goroutine and, more importantly, (b) we
// could be reading an incorrect value for the goroutine ID of a thread.
// This condition usually happens when a goroutine calls runtime.clone
// and for a short period of time two threads will appear to be running
// the same goroutine.
// 3. if the caller asks for gid == 0 and the selected goroutine is
// either 0 or nil.
// Goroutine 0 is special, it either means we have no current goroutine
// (for example, running C code), or that we are running on a speical
// stack (system stack, signal handling stack) and we didn't properly
// detect it.
// Since there could be multiple goroutines '0' running simultaneously
// if the user requests it return the one that's already selected or
// nil if there isn't a selected goroutine.
return selg, nil
} }
gs, err := GoroutinesInfo(dbp) if gid == 0 {
if err != nil { return nil, fmt.Errorf("Unknown goroutine %d", gid)
return nil, err
} }
for i := range gs {
if gs[i].ID == gid { // Calling GoroutinesInfo could be slow if there are many goroutines
if gs[i].Unreadable != nil { // running, check if a running goroutine has been requested first.
return nil, gs[i].Unreadable for _, thread := range dbp.ThreadList() {
} g, _ := GetG(thread)
return gs[i], nil if g != nil && g.ID == gid {
return g, nil
} }
} }
const goroutinesInfoLimit = 10
nextg := 0
for nextg >= 0 {
var gs []*G
var err error
gs, nextg, err = GoroutinesInfo(dbp, nextg, goroutinesInfoLimit)
if err != nil {
return nil, err
}
for i := range gs {
if gs[i].ID == gid {
if gs[i].Unreadable != nil {
return nil, gs[i].Unreadable
}
return gs[i], nil
}
}
}
return nil, fmt.Errorf("Unknown goroutine %d", gid) return nil, fmt.Errorf("Unknown goroutine %d", gid)
} }
// ConvertEvalScope returns a new EvalScope in the context of the // ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame. // specified goroutine ID and stack frame.
func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) { // If deferCall is > 0 the eval scope will be relative to the specified deferred call.
func ConvertEvalScope(dbp Process, gid, frame, deferCall int) (*EvalScope, error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return nil, err return nil, err
} }
@ -564,7 +678,7 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
thread = g.Thread thread = g.Thread
} }
locs, err := g.Stacktrace(frame+1, false) locs, err := g.Stacktrace(frame+1, deferCall > 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -573,6 +687,19 @@ func ConvertEvalScope(dbp Process, gid, frame int) (*EvalScope, error) {
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid) return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
} }
if deferCall > 0 {
if deferCall-1 >= len(locs[frame].Defers) {
return nil, fmt.Errorf("Frame %d only has %d deferred calls", frame, len(locs[frame].Defers))
}
d := locs[frame].Defers[deferCall-1]
if d.Unreadable != nil {
return nil, d.Unreadable
}
return d.EvalScope(ct)
}
return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
} }
@ -606,21 +733,30 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
return s return s
} }
// CreateUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint. // createUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
// This function is meant to be called by implementations of the Process interface. // This function is meant to be called by implementations of the Process interface.
func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpointFn, breakpoints *BreakpointMap) { func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0) panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound { if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0) panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
} }
if err == nil { if err == nil {
bp, err := breakpoints.SetWithID(-1, panicpc, writeBreakpoint) bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpc, writeBreakpoint)
if err == nil { if err == nil {
bp.Name = UnrecoveredPanic bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"} bp.Variables = []string{"runtime.curg._panic.arg"}
} }
} }
}
func createFatalThrowBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
fatalpc, err := FindFunctionLocation(p, "runtime.fatalthrow", true, 0)
if err == nil {
bp, err := p.Breakpoints().SetWithID(fatalThrowID, fatalpc, writeBreakpoint)
if err == nil {
bp.Name = FatalThrow
}
}
} }
// FirstPCAfterPrologue returns the address of the first // FirstPCAfterPrologue returns the address of the first

View File

@ -23,7 +23,7 @@ type Registers interface {
// GAddr returns the address of the G variable if it is known, 0 and false otherwise // GAddr returns the address of the G variable if it is known, 0 and false otherwise
GAddr() (uint64, bool) GAddr() (uint64, bool)
Get(int) (uint64, error) Get(int) (uint64, error)
Slice() []Register Slice(floatingPoint bool) []Register
// Copy returns a copy of the registers that is guaranteed not to change // Copy returns a copy of the registers that is guaranteed not to change
// when the registers of the associated thread change. // when the registers of the associated thread change.
Copy() Registers Copy() Registers
@ -250,99 +250,3 @@ func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string {
} }
return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " ")) return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " "))
} }
// PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
Padding [24]uint32
}
// LinuxX86Xstate represents amd64 XSAVE area. See Section 13.1 (and
// following) of Intel® 64 and IA-32 Architectures Software Developers
// 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 Developers 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, &regset.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
}

View File

@ -79,7 +79,7 @@ func GetDwarfRegister(regs Registers, i int) []byte {
return buf.Bytes() return buf.Bytes()
} }
if regname, ok := dwarfToName[i]; ok { if regname, ok := dwarfToName[i]; ok {
regslice := regs.Slice() regslice := regs.Slice(true)
for _, reg := range regslice { for _, reg := range regslice {
if reg.Name == regname { if reg.Name == regname {
return reg.Bytes return reg.Bytes

View File

@ -7,9 +7,9 @@ import (
"go/constant" "go/constant"
"strings" "strings"
"github.com/derekparker/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
) )
// This code is partly adapted from runtime.gentraceback in // This code is partly adapted from runtime.gentraceback in
@ -375,36 +375,21 @@ func (it *stackIterator) newStackframe(ret, retaddr uint64) Stackframe {
it.regs.FrameBase = it.frameBase(fn) it.regs.FrameBase = it.frameBase(fn)
} }
r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack, lastpc: it.pc} r := Stackframe{Current: Location{PC: it.pc, File: f, Line: l, Fn: fn}, Regs: it.regs, Ret: ret, addrret: retaddr, stackHi: it.stackhi, SystemStack: it.systemstack, lastpc: it.pc}
if !it.top { r.Call = r.Current
fnname := "" if !it.top && r.Current.Fn != nil && it.pc != r.Current.Fn.Entry {
if r.Current.Fn != nil { // if the return address is the entry point of the function that
fnname = r.Current.Fn.Name // contains it then this is some kind of fake return frame (for example
} // runtime.sigreturn) that didn't actually call the current frame,
switch fnname { // attempting to get the location of the CALL instruction would just
// obfuscate what's going on, since there is no CALL instruction.
switch r.Current.Fn.Name {
case "runtime.mstart", "runtime.systemstack_switch": case "runtime.mstart", "runtime.systemstack_switch":
// these frames are inserted by runtime.systemstack and there is no CALL // these frames are inserted by runtime.systemstack and there is no CALL
// instruction to look for at pc - 1 // instruction to look for at pc - 1
r.Call = r.Current
default: default:
if r.Current.Fn != nil && it.pc == r.Current.Fn.Entry { r.lastpc = it.pc - 1
// if the return address is the entry point of the function that r.Call.File, r.Call.Line = r.Current.Fn.cu.lineInfo.PCToLine(r.Current.Fn.Entry, it.pc-1)
// contains it then this is some kind of fake return frame (for example
// runtime.sigreturn) that didn't actually call the current frame,
// attempting to get the location of the CALL instruction would just
// obfuscate what's going on, since there is no CALL instruction.
r.Call = r.Current
} else {
r.lastpc = it.pc - 1
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(it.pc - 1)
if r.Call.Fn == nil {
r.Call.File = "?"
r.Call.Line = -1
}
r.Call.PC = r.Current.PC
}
} }
} else {
r.Call = r.Current
} }
return r return r
} }
@ -594,6 +579,7 @@ type Defer struct {
DeferPC uint64 // PC address of instruction that added this defer DeferPC uint64 // PC address of instruction that added this defer
SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space) SP uint64 // Value of SP register when this function was deferred (this field gets adjusted when the stack is moved to match the new stack space)
link *Defer // Next deferred function link *Defer // Next deferred function
argSz int64
variable *Variable variable *Variable
Unreadable error Unreadable error
@ -644,7 +630,7 @@ func (g *G) readDefers(frames []Stackframe) {
} }
func (d *Defer) load() { func (d *Defer) load() {
d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1}) d.variable.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
if d.variable.Unreadable != nil { if d.variable.Unreadable != nil {
d.Unreadable = d.variable.Unreadable d.Unreadable = d.variable.Unreadable
return return
@ -660,6 +646,7 @@ func (d *Defer) load() {
d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value) d.DeferPC, _ = constant.Uint64Val(d.variable.fieldVariable("pc").Value)
d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value) d.SP, _ = constant.Uint64Val(d.variable.fieldVariable("sp").Value)
d.argSz, _ = constant.Int64Val(d.variable.fieldVariable("siz").Value)
linkvar := d.variable.fieldVariable("link").maybeDereference() linkvar := d.variable.fieldVariable("link").maybeDereference()
if linkvar.Addr != 0 { if linkvar.Addr != 0 {
@ -685,3 +672,40 @@ func (d *Defer) Next() *Defer {
} }
return d.link return d.link
} }
// EvalScope returns an EvalScope relative to the argument frame of this deferred call.
// The argument frame of a deferred call is stored in memory immediately
// after the deferred header.
func (d *Defer) EvalScope(thread Thread) (*EvalScope, error) {
scope, err := GoroutineScope(thread)
if err != nil {
return nil, fmt.Errorf("could not get scope: %v", err)
}
bi := thread.BinInfo()
scope.PC = d.DeferredPC
scope.File, scope.Line, scope.Fn = bi.PCToLine(d.DeferredPC)
if scope.Fn == nil {
return nil, fmt.Errorf("could not find function at %#x", d.DeferredPC)
}
// The arguments are stored immediately after the defer header struct, i.e.
// addr+sizeof(_defer). Since CFA in go is always the address of the first
// argument, that's what we use for the value of CFA.
// For SP we use CFA minus the size of one pointer because that would be
// the space occupied by pushing the return address on the stack during the
// CALL.
scope.Regs.CFA = (int64(d.variable.Addr) + d.variable.RealType.Common().ByteSize)
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = uint64(scope.Regs.CFA - int64(bi.Arch.PtrSize()))
bi.dwarfReader.Seek(scope.Fn.offset)
e, err := bi.dwarfReader.Next()
if err != nil {
return nil, fmt.Errorf("could not read DWARF function entry: %v", err)
}
scope.Regs.FrameBase, _, _, _ = bi.Location(e, dwarf.AttrFrameBase, scope.PC, scope.Regs)
scope.Mem = cacheMemory(scope.Mem, uintptr(scope.Regs.CFA), int(d.argSz))
return scope, nil
}

View File

@ -10,8 +10,8 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
) )
// Thread represents a thread. // Thread represents a thread.
@ -225,15 +225,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
} }
if !csource { if !csource {
deferreturns := []uint64{} deferreturns := findDeferReturnCalls(text)
// Find all runtime.deferreturn locations in the function
// See documentation of Breakpoint.DeferCond for why this is necessary
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == "runtime.deferreturn" {
deferreturns = append(deferreturns, instr.Loc.PC)
}
}
// Set breakpoint on the most recently deferred function (if any) // Set breakpoint on the most recently deferred function (if any)
var deferpc uint64 var deferpc uint64
@ -333,6 +325,20 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
return nil return nil
} }
func findDeferReturnCalls(text []AsmInstruction) []uint64 {
const deferreturn = "runtime.deferreturn"
deferreturns := []uint64{}
// Find all runtime.deferreturn locations in the function
// See documentation of Breakpoint.DeferCond for why this is necessary
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == deferreturn {
deferreturns = append(deferreturns, instr.Loc.PC)
}
}
return deferreturns
}
// Removes instructions belonging to inlined calls of topframe from pcs. // Removes instructions belonging to inlined calls of topframe from pcs.
// If includeCurrentFn is true it will also remove all instructions // If includeCurrentFn is true it will also remove all instructions
// belonging to the current function. // belonging to the current function.
@ -373,21 +379,16 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
instr := text[0] instr := text[0]
if instr.DestLoc == nil || instr.DestLoc.Fn == nil { if instr.DestLoc == nil {
// Call destination couldn't be resolved because this was not the
// current instruction, therefore the step-into breakpoint can not be set.
return nil return nil
} }
fn := instr.DestLoc.Fn fn := instr.DestLoc.Fn
// Ensure PC and Entry match, otherwise StepInto is likely to set
// its breakpoint before DestLoc.PC and hence run too far ahead.
// Calls to runtime.duffzero and duffcopy have this problem.
if fn.Entry != instr.DestLoc.PC {
return nil
}
// Skip unexported runtime functions // Skip unexported runtime functions
if strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) { if fn != nil && strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
return nil return nil
} }
@ -395,8 +396,19 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
// or entire packages from being stepped into with 'step' // or entire packages from being stepped into with 'step'
// those extra checks should be done here. // those extra checks should be done here.
pc := instr.DestLoc.PC
// We want to skip the function prologue but we should only do it if the
// destination address of the CALL instruction is the entry point of the
// function.
// Calls to runtime.duffzero and duffcopy inserted by the compiler can
// sometimes point inside the body of those functions, well after the
// prologue.
if fn != nil && fn.Entry == instr.DestLoc.PC {
pc, _ = FirstPCAfterPrologue(dbp, fn, false)
}
// Set a breakpoint after the function's prologue // Set a breakpoint after the function's prologue
pc, _ := FirstPCAfterPrologue(dbp, fn, false)
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil { if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok { if _, ok := err.(BreakpointExistsError); !ok {
return err return err
@ -465,6 +477,10 @@ func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
// In order to get around all this craziness, we read the address of the G structure for // In order to get around all this craziness, we read the address of the G structure for
// the current thread from the thread local storage area. // the current thread from the thread local storage area.
func GetG(thread Thread) (*G, error) { func GetG(thread Thread) (*G, error) {
if loc, _ := thread.Location(); loc.Fn != nil && loc.Fn.Name == "runtime.clone" {
// When threads are executing runtime.clone the value of TLS is unreliable.
return nil, nil
}
gaddr, err := getGVariable(thread) gaddr, err := getGVariable(thread)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/constant" "go/constant"
"go/parser"
"go/token" "go/token"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -18,12 +17,12 @@ import (
"sync" "sync"
"unsafe" "unsafe"
"github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/line" "github.com/go-delve/delve/pkg/dwarf/line"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/derekparker/delve/pkg/goversion" "github.com/go-delve/delve/pkg/goversion"
"github.com/derekparker/delve/pkg/logflags" "github.com/go-delve/delve/pkg/logflags"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -180,7 +179,7 @@ func (v functionsDebugInfoByEntry) Swap(i, j int) { v[i], v[j] = v[j], v[i]
type compileUnitsByLowpc []*compileUnit type compileUnitsByLowpc []*compileUnit
func (v compileUnitsByLowpc) Len() int { return len(v) } func (v compileUnitsByLowpc) Len() int { return len(v) }
func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].LowPC < v[j].LowPC } func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].lowPC < v[j].lowPC }
func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
type packageVarsByAddr []packageVar type packageVarsByAddr []packageVar
@ -230,18 +229,18 @@ outer:
if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage { if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
cu.isgo = true cu.isgo = true
} }
cu.Name, _ = entry.Val(dwarf.AttrName).(string) cu.name, _ = entry.Val(dwarf.AttrName).(string)
compdir, _ := entry.Val(dwarf.AttrCompDir).(string) compdir, _ := entry.Val(dwarf.AttrCompDir).(string)
if compdir != "" { if compdir != "" {
cu.Name = filepath.Join(compdir, cu.Name) cu.name = filepath.Join(compdir, cu.name)
} }
cu.Ranges, _ = bi.dwarf.Ranges(entry) cu.ranges, _ = bi.dwarf.Ranges(entry)
for i := range cu.Ranges { for i := range cu.ranges {
cu.Ranges[i][0] += bi.staticBase cu.ranges[i][0] += bi.staticBase
cu.Ranges[i][1] += bi.staticBase cu.ranges[i][1] += bi.staticBase
} }
if len(cu.Ranges) >= 1 { if len(cu.ranges) >= 1 {
cu.LowPC = cu.Ranges[0][0] cu.lowPC = cu.ranges[0][0]
} }
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64) lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
@ -398,6 +397,13 @@ outer:
highpc = ranges[0][1] + bi.staticBase highpc = ranges[0][1] + bi.staticBase
} }
name, ok2 := entry.Val(dwarf.AttrName).(string) name, ok2 := entry.Val(dwarf.AttrName).(string)
if !ok2 {
originOffset, hasAbstractOrigin := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
if hasAbstractOrigin {
name, ok2 = abstractOriginNameTable[originOffset]
}
}
var fn Function var fn Function
if (ok1 == !inlined) && ok2 { if (ok1 == !inlined) && ok2 {
if inlined { if inlined {
@ -559,45 +565,8 @@ func (bi *BinaryInfo) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.
// * After go1.11 the runtimeTypeToDIE map is used to look up the address of // * After go1.11 the runtimeTypeToDIE map is used to look up the address of
// the type and map it drectly to a DIE. // the type and map it drectly to a DIE.
func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind int64, err error) { func runtimeTypeToDIE(_type *Variable, dataAddr uintptr) (typ godwarf.Type, kind int64, err error) {
var go17 bool
var typestring *Variable
bi := _type.bi bi := _type.bi
// Determine if we are in go1.7 or later
typestring, err = _type.structMember("_string")
if err == nil {
typestring = typestring.maybeDereference()
} else {
err = nil
go17 = true
}
if !go17 {
// pre-go1.7 compatibility
if typestring == nil || typestring.Addr == 0 || typestring.Kind != reflect.String {
return nil, 0, fmt.Errorf("invalid interface type")
}
typestring.loadValue(LoadConfig{false, 0, 512, 0, 0})
if typestring.Unreadable != nil {
return nil, 0, fmt.Errorf("invalid interface type: %v", typestring.Unreadable)
}
typename := constant.StringVal(typestring.Value)
t, err := parser.ParseExpr(typename)
if err != nil {
return nil, 0, fmt.Errorf("invalid interface type, unparsable data type: %v", err)
}
typ, err := bi.findTypeExpr(t)
if err != nil {
return nil, 0, fmt.Errorf("interface type %q not found for %#x: %v", typename, dataAddr, err)
}
return typ, 0, nil
}
_type = _type.maybeDereference() _type = _type.maybeDereference()
// go 1.11 implementation: use extended attribute in debug_info // go 1.11 implementation: use extended attribute in debug_info
@ -880,7 +849,7 @@ func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, err
buf.WriteString("interface {") buf.WriteString("interface {")
methods, _ := _type.structMember(interfacetypeFieldMhdr) methods, _ := _type.structMember(interfacetypeFieldMhdr)
methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1}) methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1, 0})
if methods.Unreadable != nil { if methods.Unreadable != nil {
return "", nil return "", nil
} }
@ -941,7 +910,7 @@ func nameOfStructRuntimeType(_type *Variable, kind, tflag int64) (string, error)
buf.WriteString("struct {") buf.WriteString("struct {")
fields, _ := _type.structMember("fields") fields, _ := _type.structMember("fields")
fields.loadArrayValues(0, LoadConfig{false, 2, 0, 4096, -1}) fields.loadArrayValues(0, LoadConfig{false, 2, 0, 4096, -1, 0})
if fields.Unreadable != nil { if fields.Unreadable != nil {
return "", fields.Unreadable return "", fields.Unreadable
} }

View File

@ -15,9 +15,9 @@ import (
"strings" "strings"
"unsafe" "unsafe"
"github.com/derekparker/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/derekparker/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
) )
const ( const (
@ -32,6 +32,8 @@ const (
hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated
maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame
maxMapBucketsFactor = 100 // Maximum numbers of map buckets to read for every requested map entry when loading variables through (*EvalScope).LocalVariables and (*EvalScope).FunctionArguments.
) )
type floatSpecial uint8 type floatSpecial uint8
@ -122,10 +124,39 @@ type LoadConfig struct {
MaxArrayValues int MaxArrayValues int
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. // MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
MaxStructFields int MaxStructFields int
// MaxMapBuckets is the maximum number of map buckets to read before giving up.
// A value of 0 will read as many buckets as necessary until the entire map
// is read or MaxArrayValues is reached.
//
// Loading a map is an operation that issues O(num_buckets) operations.
// Normally the number of buckets is proportional to the number of elements
// in the map, since the runtime tries to keep the load factor of maps
// between 40% and 80%.
//
// It is possible, however, to create very sparse maps either by:
// a) adding lots of entries to a map and then deleting most of them, or
// b) using the make(mapType, N) expression with a very large N
//
// When this happens delve will have to scan many empty buckets to find the
// few entries in the map.
// MaxMapBuckets can be set to avoid annoying slowdowns␣while reading
// very sparse maps.
//
// Since there is no good way for a user of delve to specify the value of
// MaxMapBuckets, this field is not actually exposed through the API.
// Instead (*EvalScope).LocalVariables and (*EvalScope).FunctionArguments
// set this field automatically to MaxArrayValues * maxMapBucketsFactor.
// Every other invocation uses the default value of 0, obtaining the old behavior.
// In practice this means that debuggers using the ListLocalVars or
// ListFunctionArgs API will not experience a massive slowdown when a very
// sparse map is in scope, but evaluating a single variable will still work
// correctly, even if the variable in question is a very sparse map.
MaxMapBuckets int
} }
var loadSingleValue = LoadConfig{false, 0, 64, 0, 0} var loadSingleValue = LoadConfig{false, 0, 64, 0, 0, 0}
var loadFullValue = LoadConfig{true, 1, 64, 64, -1} var loadFullValue = LoadConfig{true, 1, 64, 64, -1, 0}
// G status, from: src/runtime/runtime2.go // G status, from: src/runtime/runtime2.go
const ( const (
@ -169,6 +200,12 @@ type G struct {
Unreadable error // could not read the G struct Unreadable error // could not read the G struct
} }
type Ancestor struct {
ID int64 // Goroutine ID
Unreadable error
pcsVar *Variable
}
// EvalScope is the scope for variable evaluation. Contains the thread, // EvalScope is the scope for variable evaluation. Contains the thread,
// current location (PC), and canonical frame address. // current location (PC), and canonical frame address.
type EvalScope struct { type EvalScope struct {
@ -405,13 +442,13 @@ func (scope *EvalScope) PtrSize() int {
return scope.BinInfo.Arch.PtrSize() return scope.BinInfo.Arch.PtrSize()
} }
// NoGError returned when a G could not be found // ErrNoGoroutine returned when a G could not be found
// for a specific thread. // for a specific thread.
type NoGError struct { type ErrNoGoroutine struct {
tid int tid int
} }
func (ng NoGError) Error() string { func (ng ErrNoGoroutine) Error() string {
return fmt.Sprintf("no G executing on thread %d", ng.tid) return fmt.Sprintf("no G executing on thread %d", ng.tid)
} }
@ -433,7 +470,7 @@ func (v *Variable) parseG() (*G, error) {
if thread, ok := mem.(Thread); ok { if thread, ok := mem.(Thread); ok {
id = thread.ThreadID() id = thread.ThreadID()
} }
return nil, NoGError{tid: id} return nil, ErrNoGoroutine{tid: id}
} }
for { for {
if _, isptr := v.RealType.(*godwarf.PtrType); !isptr { if _, isptr := v.RealType.(*godwarf.PtrType); !isptr {
@ -441,7 +478,7 @@ func (v *Variable) parseG() (*G, error) {
} }
v = v.maybeDereference() v = v.maybeDereference()
} }
v.loadValue(LoadConfig{false, 2, 64, 0, -1}) v.loadValue(LoadConfig{false, 2, 64, 0, -1, 0})
if v.Unreadable != nil { if v.Unreadable != nil {
return nil, v.Unreadable return nil, v.Unreadable
} }
@ -586,12 +623,102 @@ func (g *G) StartLoc() Location {
return Location{PC: g.StartPC, File: f, Line: l, Fn: fn} return Location{PC: g.StartPC, File: f, Line: l, Fn: fn}
} }
var errTracebackAncestorsDisabled = errors.New("tracebackancestors is disabled")
// Ancestors returns the list of ancestors for g.
func (g *G) Ancestors(n int) ([]Ancestor, error) {
scope := globalScope(g.Thread.BinInfo(), g.Thread)
tbav, err := scope.EvalExpression("runtime.debug.tracebackancestors", loadSingleValue)
if err == nil && tbav.Unreadable == nil && tbav.Kind == reflect.Int {
tba, _ := constant.Int64Val(tbav.Value)
if tba == 0 {
return nil, errTracebackAncestorsDisabled
}
}
av, err := g.variable.structMember("ancestors")
if err != nil {
return nil, err
}
av = av.maybeDereference()
av.loadValue(LoadConfig{MaxArrayValues: n, MaxVariableRecurse: 1, MaxStructFields: -1})
if av.Unreadable != nil {
return nil, err
}
if av.Addr == 0 {
// no ancestors
return nil, nil
}
r := make([]Ancestor, len(av.Children))
for i := range av.Children {
if av.Children[i].Unreadable != nil {
r[i].Unreadable = av.Children[i].Unreadable
continue
}
goidv := av.Children[i].fieldVariable("goid")
if goidv.Unreadable != nil {
r[i].Unreadable = goidv.Unreadable
continue
}
r[i].ID, _ = constant.Int64Val(goidv.Value)
pcsVar := av.Children[i].fieldVariable("pcs")
if pcsVar.Unreadable != nil {
r[i].Unreadable = pcsVar.Unreadable
}
pcsVar.loaded = false
pcsVar.Children = pcsVar.Children[:0]
r[i].pcsVar = pcsVar
}
return r, nil
}
// Stack returns the stack trace of ancestor 'a' as saved by the runtime.
func (a *Ancestor) Stack(n int) ([]Stackframe, error) {
if a.Unreadable != nil {
return nil, a.Unreadable
}
pcsVar := a.pcsVar.clone()
pcsVar.loadValue(LoadConfig{MaxArrayValues: n})
if pcsVar.Unreadable != nil {
return nil, pcsVar.Unreadable
}
r := make([]Stackframe, len(pcsVar.Children))
for i := range pcsVar.Children {
if pcsVar.Children[i].Unreadable != nil {
r[i] = Stackframe{Err: pcsVar.Children[i].Unreadable}
continue
}
if pcsVar.Children[i].Kind != reflect.Uint {
return nil, fmt.Errorf("wrong type for pcs item %d: %v", i, pcsVar.Children[i].Kind)
}
pc, _ := constant.Int64Val(pcsVar.Children[i].Value)
fn := a.pcsVar.bi.PCToFunc(uint64(pc))
if fn == nil {
loc := Location{PC: uint64(pc)}
r[i] = Stackframe{Current: loc, Call: loc}
continue
}
pc2 := uint64(pc)
if pc2-1 >= fn.Entry {
pc2--
}
f, ln := fn.cu.lineInfo.PCToLine(fn.Entry, pc2)
loc := Location{PC: uint64(pc), File: f, Line: ln, Fn: fn}
r[i] = Stackframe{Current: loc, Call: loc}
}
r[len(r)-1].Bottom = pcsVar.Len == int64(len(pcsVar.Children))
return r, nil
}
// Returns the list of saved return addresses used by stack barriers // Returns the list of saved return addresses used by stack barriers
func (g *G) stkbar() ([]savedLR, error) { func (g *G) stkbar() ([]savedLR, error) {
if g.stkbarVar == nil { // stack barriers were removed in Go 1.9 if g.stkbarVar == nil { // stack barriers were removed in Go 1.9
return nil, nil return nil, nil
} }
g.stkbarVar.loadValue(LoadConfig{false, 1, 0, int(g.stkbarVar.Len), 3}) g.stkbarVar.loadValue(LoadConfig{false, 1, 0, int(g.stkbarVar.Len), 3, 0})
if g.stkbarVar.Unreadable != nil { if g.stkbarVar.Unreadable != nil {
return nil, fmt.Errorf("unreadable stkbar: %v", g.stkbarVar.Unreadable) return nil, fmt.Errorf("unreadable stkbar: %v", g.stkbarVar.Unreadable)
} }
@ -658,6 +785,7 @@ func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) {
vars = filterVariables(vars, func(v *Variable) bool { vars = filterVariables(vars, func(v *Variable) bool {
return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0 return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0
}) })
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
loadValues(vars, cfg) loadValues(vars, cfg)
return vars, nil return vars, nil
} }
@ -671,6 +799,7 @@ func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
vars = filterVariables(vars, func(v *Variable) bool { vars = filterVariables(vars, func(v *Variable) bool {
return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0 return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0
}) })
cfg.MaxMapBuckets = maxMapBucketsFactor * cfg.MaxArrayValues
loadValues(vars, cfg) loadValues(vars, cfg)
return vars, nil return vars, nil
} }
@ -875,7 +1004,11 @@ func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variabl
mem := scope.Mem mem := scope.Mem
if pieces != nil { if pieces != nil {
addr = fakeAddress addr = fakeAddress
mem = newCompositeMemory(scope.Mem, scope.Regs, pieces) var cmem *compositeMemory
cmem, err = newCompositeMemory(scope.Mem, scope.Regs, pieces)
if cmem != nil {
mem = cmem
}
} }
v := scope.newVariable(n, uintptr(addr), t, mem) v := scope.newVariable(n, uintptr(addr), t, mem)
@ -895,6 +1028,10 @@ func (v *Variable) maybeDereference() *Variable {
switch t := v.RealType.(type) { switch t := v.RealType.(type) {
case *godwarf.PtrType: case *godwarf.PtrType:
if v.Addr == 0 && len(v.Children) == 1 && v.loaded {
// fake pointer variable constructed by casting an integer to a pointer type
return &v.Children[0]
}
ptrval, err := readUintRaw(v.mem, uintptr(v.Addr), t.ByteSize) ptrval, err := readUintRaw(v.mem, uintptr(v.Addr), t.ByteSize)
r := v.newVariable("", uintptr(ptrval), t.Type, DereferenceMemory(v.mem)) r := v.newVariable("", uintptr(ptrval), t.Type, DereferenceMemory(v.mem))
if err != nil { if err != nil {
@ -1569,6 +1706,11 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
if it == nil { if it == nil {
return return
} }
it.maxNumBuckets = uint64(cfg.MaxMapBuckets)
if v.Len == 0 || int64(v.mapSkip) >= v.Len || cfg.MaxArrayValues == 0 {
return
}
for skip := 0; skip < v.mapSkip; skip++ { for skip := 0; skip < v.mapSkip; skip++ {
if ok := it.next(); !ok { if ok := it.next(); !ok {
@ -1580,9 +1722,6 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
count := 0 count := 0
errcount := 0 errcount := 0
for it.next() { for it.next() {
if count >= cfg.MaxArrayValues {
break
}
key := it.key() key := it.key()
var val *Variable var val *Variable
if it.values.fieldType.Size() > 0 { if it.values.fieldType.Size() > 0 {
@ -1601,6 +1740,9 @@ func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
if errcount > maxErrCount { if errcount > maxErrCount {
break break
} }
if count >= cfg.MaxArrayValues || int64(count) >= v.Len {
break
}
} }
} }
@ -1618,6 +1760,8 @@ type mapIterator struct {
values *Variable values *Variable
overflow *Variable overflow *Variable
maxNumBuckets uint64 // maximum number of buckets to scan
idx int64 idx int64
} }
@ -1683,6 +1827,10 @@ func (it *mapIterator) nextBucket() bool {
} else { } else {
it.b = nil it.b = nil
if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets {
return false
}
for it.bidx < it.numbuckets { for it.bidx < it.numbuckets {
it.b = it.buckets.clone() it.b = it.buckets.clone()
it.b.Addr += uintptr(uint64(it.buckets.DwarfType.Size()) * it.bidx) it.b.Addr += uintptr(uint64(it.buckets.DwarfType.Size()) * it.bidx)

View File

@ -1,4 +1,4 @@
package native package winutil
import ( import (
"fmt" "fmt"
@ -6,11 +6,11 @@ import (
"golang.org/x/arch/x86/x86asm" "golang.org/x/arch/x86/x86asm"
"github.com/derekparker/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
) )
// Regs represents CPU registers on an AMD64 processor. // AMD64Registers represents CPU registers on an AMD64 processor.
type Regs struct { type AMD64Registers struct {
rax uint64 rax uint64
rbx uint64 rbx uint64
rcx uint64 rcx uint64
@ -33,11 +33,48 @@ type Regs struct {
fs uint64 fs uint64
gs uint64 gs uint64
tls uint64 tls uint64
context *_CONTEXT Context *CONTEXT
fltSave *_XMM_SAVE_AREA32 fltSave *XMM_SAVE_AREA32
} }
func (r *Regs) Slice() []proc.Register { // NewAMD64Registers creates a new AMD64Registers struct from a CONTEXT
// struct and the TEB base address of the thread.
func NewAMD64Registers(context *CONTEXT, TebBaseAddress uint64, floatingPoint bool) *AMD64Registers {
regs := &AMD64Registers{
rax: uint64(context.Rax),
rbx: uint64(context.Rbx),
rcx: uint64(context.Rcx),
rdx: uint64(context.Rdx),
rdi: uint64(context.Rdi),
rsi: uint64(context.Rsi),
rbp: uint64(context.Rbp),
rsp: uint64(context.Rsp),
r8: uint64(context.R8),
r9: uint64(context.R9),
r10: uint64(context.R10),
r11: uint64(context.R11),
r12: uint64(context.R12),
r13: uint64(context.R13),
r14: uint64(context.R14),
r15: uint64(context.R15),
rip: uint64(context.Rip),
eflags: uint64(context.EFlags),
cs: uint64(context.SegCs),
fs: uint64(context.SegFs),
gs: uint64(context.SegGs),
tls: TebBaseAddress,
}
if floatingPoint {
regs.fltSave = &context.FltSave
}
regs.Context = context
return regs
}
// Slice returns the registers as a list of (name, value) pairs.
func (r *AMD64Registers) Slice(floatingPoint bool) []proc.Register {
var regs = []struct { var regs = []struct {
k string k string
v uint64 v uint64
@ -66,7 +103,7 @@ func (r *Regs) Slice() []proc.Register {
{"TLS", r.tls}, {"TLS", r.tls},
} }
outlen := len(regs) outlen := len(regs)
if r.fltSave != nil { if r.fltSave != nil && floatingPoint {
outlen += 6 + 8 + 2 + 16 outlen += 6 + 8 + 2 + 16
} }
out := make([]proc.Register, 0, outlen) out := make([]proc.Register, 0, outlen)
@ -77,7 +114,7 @@ func (r *Regs) Slice() []proc.Register {
out = proc.AppendQwordReg(out, reg.k, reg.v) out = proc.AppendQwordReg(out, reg.k, reg.v)
} }
} }
if r.fltSave != nil { if r.fltSave != nil && floatingPoint {
out = proc.AppendWordReg(out, "CW", r.fltSave.ControlWord) out = proc.AppendWordReg(out, "CW", r.fltSave.ControlWord)
out = proc.AppendWordReg(out, "SW", r.fltSave.StatusWord) out = proc.AppendWordReg(out, "SW", r.fltSave.StatusWord)
out = proc.AppendWordReg(out, "TW", uint16(r.fltSave.TagWord)) out = proc.AppendWordReg(out, "TW", uint16(r.fltSave.TagWord))
@ -101,81 +138,40 @@ func (r *Regs) Slice() []proc.Register {
// PC returns the current program counter // PC returns the current program counter
// i.e. the RIP CPU register. // i.e. the RIP CPU register.
func (r *Regs) PC() uint64 { func (r *AMD64Registers) PC() uint64 {
return r.rip return r.rip
} }
// SP returns the stack pointer location, // SP returns the stack pointer location,
// i.e. the RSP register. // i.e. the RSP register.
func (r *Regs) SP() uint64 { func (r *AMD64Registers) SP() uint64 {
return r.rsp return r.rsp
} }
func (r *Regs) BP() uint64 { func (r *AMD64Registers) BP() uint64 {
return r.rbp return r.rbp
} }
// CX returns the value of the RCX register. // CX returns the value of the RCX register.
func (r *Regs) CX() uint64 { func (r *AMD64Registers) CX() uint64 {
return r.rcx return r.rcx
} }
// TLS returns the value of the register // TLS returns the value of the register
// that contains the location of the thread // that contains the location of the thread
// local storage segment. // local storage segment.
func (r *Regs) TLS() uint64 { func (r *AMD64Registers) TLS() uint64 {
return r.tls return r.tls
} }
func (r *Regs) GAddr() (uint64, bool) { // GAddr returns the address of the G variable if it is known, 0 and false
// otherwise.
func (r *AMD64Registers) GAddr() (uint64, bool) {
return 0, false return 0, false
} }
// SetPC sets the RIP register to the value specified by `pc`. // Get returns the value of the n-th register (in x86asm order).
func (thread *Thread) SetPC(pc uint64) error { func (r *AMD64Registers) Get(n int) (uint64, error) {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
err := _GetThreadContext(thread.os.hThread, context)
if err != nil {
return err
}
context.Rip = pc
return _SetThreadContext(thread.os.hThread, context)
}
// SetSP sets the RSP register to the value specified by `sp`.
func (thread *Thread) SetSP(sp uint64) error {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
err := _GetThreadContext(thread.os.hThread, context)
if err != nil {
return err
}
context.Rsp = sp
return _SetThreadContext(thread.os.hThread, context)
}
func (thread *Thread) SetDX(dx uint64) error {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
err := _GetThreadContext(thread.os.hThread, context)
if err != nil {
return err
}
context.Rdx = dx
return _SetThreadContext(thread.os.hThread, context)
}
func (r *Regs) Get(n int) (uint64, error) {
reg := x86asm.Reg(n) reg := x86asm.Reg(n)
const ( const (
mask8 = 0x000f mask8 = 0x000f
@ -332,59 +328,103 @@ func (r *Regs) Get(n int) (uint64, error) {
return 0, proc.ErrUnknownRegister return 0, proc.ErrUnknownRegister
} }
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) { // Copy returns a copy of these registers that is guarenteed not to change.
context := newCONTEXT() func (r *AMD64Registers) Copy() proc.Registers {
var rr AMD64Registers
context.ContextFlags = _CONTEXT_ALL
err := _GetThreadContext(thread.os.hThread, context)
if err != nil {
return nil, err
}
var threadInfo _THREAD_BASIC_INFORMATION
status := _NtQueryInformationThread(thread.os.hThread, _ThreadBasicInformation, uintptr(unsafe.Pointer(&threadInfo)), uint32(unsafe.Sizeof(threadInfo)), nil)
if !_NT_SUCCESS(status) {
return nil, fmt.Errorf("NtQueryInformationThread failed: it returns 0x%x", status)
}
regs := &Regs{
rax: uint64(context.Rax),
rbx: uint64(context.Rbx),
rcx: uint64(context.Rcx),
rdx: uint64(context.Rdx),
rdi: uint64(context.Rdi),
rsi: uint64(context.Rsi),
rbp: uint64(context.Rbp),
rsp: uint64(context.Rsp),
r8: uint64(context.R8),
r9: uint64(context.R9),
r10: uint64(context.R10),
r11: uint64(context.R11),
r12: uint64(context.R12),
r13: uint64(context.R13),
r14: uint64(context.R14),
r15: uint64(context.R15),
rip: uint64(context.Rip),
eflags: uint64(context.EFlags),
cs: uint64(context.SegCs),
fs: uint64(context.SegFs),
gs: uint64(context.SegGs),
tls: uint64(threadInfo.TebBaseAddress),
}
if floatingPoint {
regs.fltSave = &context.FltSave
}
regs.context = context
return regs, nil
}
func (r *Regs) Copy() proc.Registers {
var rr Regs
rr = *r rr = *r
rr.context = newCONTEXT() rr.Context = NewCONTEXT()
*(rr.context) = *(r.context) *(rr.Context) = *(r.Context)
rr.fltSave = &rr.context.FltSave rr.fltSave = &rr.Context.FltSave
return &rr return &rr
} }
// M128A tracks the _M128A windows struct.
type M128A struct {
Low uint64
High int64
}
// XMM_SAVE_AREA32 tracks the _XMM_SAVE_AREA32 windows struct.
type XMM_SAVE_AREA32 struct {
ControlWord uint16
StatusWord uint16
TagWord byte
Reserved1 byte
ErrorOpcode uint16
ErrorOffset uint32
ErrorSelector uint16
Reserved2 uint16
DataOffset uint32
DataSelector uint16
Reserved3 uint16
MxCsr uint32
MxCsr_Mask uint32
FloatRegisters [8]M128A
XmmRegisters [256]byte
Reserved4 [96]byte
}
// CONTEXT tracks the _CONTEXT of windows.
type CONTEXT struct {
P1Home uint64
P2Home uint64
P3Home uint64
P4Home uint64
P5Home uint64
P6Home uint64
ContextFlags uint32
MxCsr uint32
SegCs uint16
SegDs uint16
SegEs uint16
SegFs uint16
SegGs uint16
SegSs uint16
EFlags uint32
Dr0 uint64
Dr1 uint64
Dr2 uint64
Dr3 uint64
Dr6 uint64
Dr7 uint64
Rax uint64
Rcx uint64
Rdx uint64
Rbx uint64
Rsp uint64
Rbp uint64
Rsi uint64
Rdi uint64
R8 uint64
R9 uint64
R10 uint64
R11 uint64
R12 uint64
R13 uint64
R14 uint64
R15 uint64
Rip uint64
FltSave XMM_SAVE_AREA32
VectorRegister [26]M128A
VectorControl uint64
DebugControl uint64
LastBranchToRip uint64
LastBranchFromRip uint64
LastExceptionToRip uint64
LastExceptionFromRip uint64
}
// NewCONTEXT allocates Windows CONTEXT structure aligned to 16 bytes.
func NewCONTEXT() *CONTEXT {
var c *CONTEXT
buf := make([]byte, unsafe.Sizeof(*c)+15)
return (*CONTEXT)(unsafe.Pointer((uintptr(unsafe.Pointer(&buf[15]))) &^ 15))
}

View File

@ -20,9 +20,9 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/cosiner/argv" "github.com/cosiner/argv"
"github.com/derekparker/delve/service" "github.com/go-delve/delve/service"
"github.com/derekparker/delve/service/api" "github.com/go-delve/delve/service/api"
"github.com/derekparker/delve/service/debugger" "github.com/go-delve/delve/service/debugger"
) )
const optimizedFunctionWarning = "Warning: debugging optimized function" const optimizedFunctionWarning = "Warning: debugging optimized function"
@ -32,6 +32,7 @@ type cmdPrefix int
const ( const (
noPrefix = cmdPrefix(0) noPrefix = cmdPrefix(0)
onPrefix = cmdPrefix(1 << iota) onPrefix = cmdPrefix(1 << iota)
deferredPrefix
) )
type callContext struct { type callContext struct {
@ -113,24 +114,24 @@ Type "help" followed by the name of a command for more information about it.`},
break [name] <linespec> break [name] <linespec>
See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/locspec.md for the syntax of linespec. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of linespec.
See also: "help on", "help cond" and "help clear"`}, See also: "help on", "help cond" and "help clear"`},
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: `Set tracepoint. {aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: `Set tracepoint.
trace [name] <linespec> trace [name] <linespec>
A tracepoint is a breakpoint that does not stop the execution of the program, instead when the tracepoint is hit a notification is displayed. See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/locspec.md for the syntax of linespec. A tracepoint is a breakpoint that does not stop the execution of the program, instead when the tracepoint is hit a notification is displayed. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of linespec.
See also: "help on", "help cond" and "help clear"`}, See also: "help on", "help cond" and "help clear"`},
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process. {aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: `Restart process.
restart [checkpoint] restart [checkpoint]
restart [-noargs] newargv... restart [-noargs] newargv...
For recorded processes restarts from the start or from the specified For recorded processes restarts from the start or from the specified
checkpoint. For normal processes restarts the process, optionally changing checkpoint. For normal processes restarts the process, optionally changing
the arguments. With -noargs, the process starts with an empty commandline. the arguments. With -noargs, the process starts with an empty commandline.
`}, `},
{aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."}, {aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."},
{aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."}, {aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."},
@ -139,6 +140,8 @@ See also: "help on", "help cond" and "help clear"`},
{aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."}, {aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."},
{aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!) {aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!)
call [-unsafe] <function call expression>
Current limitations: Current limitations:
- only pointers to stack-allocated objects can be passed as argument. - only pointers to stack-allocated objects can be passed as argument.
- only some automatic type conversions are supported. - only some automatic type conversions are supported.
@ -186,11 +189,11 @@ Called without arguments it will show information about the current goroutine.
Called with a single argument it will switch to the specified goroutine. Called with a single argument it will switch to the specified goroutine.
Called with more arguments it will execute a command on the specified goroutine.`}, Called with more arguments it will execute a command on the specified goroutine.`},
{aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."}, {aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."},
{aliases: []string{"print", "p"}, allowedPrefixes: onPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression. {aliases: []string{"print", "p"}, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression.
[goroutine <n>] [frame <m>] print <expression> [goroutine <n>] [frame <m>] print <expression>
See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/expr.md for a description of supported expressions.`}, See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md for a description of supported expressions.`},
{aliases: []string{"whatis"}, cmdFn: whatisCommand, helpMsg: `Prints type of an expression. {aliases: []string{"whatis"}, cmdFn: whatisCommand, helpMsg: `Prints type of an expression.
whatis <expression>`}, whatis <expression>`},
@ -198,7 +201,7 @@ See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/expr.md for a des
[goroutine <n>] [frame <m>] set <variable> = <value> [goroutine <n>] [frame <m>] set <variable> = <value>
See $GOPATH/src/github.com/derekparker/delve/Documentation/cli/expr.md for a description of supported expressions. Only numerical variables and pointers can be changed.`}, See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/expr.md for a description of supported expressions. Only numerical variables and pointers can be changed.`},
{aliases: []string{"sources"}, cmdFn: sources, helpMsg: `Print list of source files. {aliases: []string{"sources"}, cmdFn: sources, helpMsg: `Print list of source files.
sources [<regex>] sources [<regex>]
@ -214,12 +217,12 @@ If regex is specified only the functions matching it will be returned.`},
types [<regex>] types [<regex>]
If regex is specified only the types matching it will be returned.`}, If regex is specified only the types matching it will be returned.`},
{aliases: []string{"args"}, allowedPrefixes: onPrefix, cmdFn: args, helpMsg: `Print function arguments. {aliases: []string{"args"}, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: args, helpMsg: `Print function arguments.
[goroutine <n>] [frame <m>] args [-v] [<regex>] [goroutine <n>] [frame <m>] args [-v] [<regex>]
If regex is specified only function arguments with a name matching it will be returned. If -v is specified more information about each function argument will be shown.`}, If regex is specified only function arguments with a name matching it will be returned. If -v is specified more information about each function argument will be shown.`},
{aliases: []string{"locals"}, allowedPrefixes: onPrefix, cmdFn: locals, helpMsg: `Print local variables. {aliases: []string{"locals"}, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: locals, helpMsg: `Print local variables.
[goroutine <n>] [frame <m>] locals [-v] [<regex>] [goroutine <n>] [frame <m>] locals [-v] [<regex>]
@ -248,10 +251,13 @@ When connected to a headless instance started with the --accept-multiclient, pas
Show source around current point or provided linespec.`}, Show source around current point or provided linespec.`},
{aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace. {aliases: []string{"stack", "bt"}, allowedPrefixes: onPrefix, cmdFn: stackCommand, helpMsg: `Print stack trace.
[goroutine <n>] [frame <m>] stack [<depth>] [-full] [-g] [-s] [-offsets] [goroutine <n>] [frame <m>] stack [<depth>] [-full] [-offsets] [-defer] [-a <n>] [-adepth <depth>]
-full every stackframe is decorated with the value of its local variables and arguments. -full every stackframe is decorated with the value of its local variables and arguments.
-offsets prints frame offset of each frame -offsets prints frame offset of each frame.
-defer prints deferred function call stack for each frame.
-a <n> prints stacktrace of n ancestors of the selected goroutine (target process must have tracebackancestors enabled)
-adepth <depth> configures depth of ancestor stacktrace
`}, `},
{aliases: []string{"frame"}, {aliases: []string{"frame"},
cmdFn: func(t *Term, ctx callContext, arg string) error { cmdFn: func(t *Term, ctx callContext, arg string) error {
@ -259,8 +265,8 @@ Show source around current point or provided linespec.`},
}, },
helpMsg: `Set the current frame, or execute command on a different frame. helpMsg: `Set the current frame, or execute command on a different frame.
frame <m> frame <m>
frame <m> <command> frame <m> <command>
The first form sets frame used by subsequent commands such as "print" or "set". The first form sets frame used by subsequent commands such as "print" or "set".
The second form runs the command on the given frame.`}, The second form runs the command on the given frame.`},
@ -270,8 +276,8 @@ The second form runs the command on the given frame.`},
}, },
helpMsg: `Move the current frame up. helpMsg: `Move the current frame up.
up [<m>] up [<m>]
up [<m>] <command> up [<m>] <command>
Move the current frame up by <m>. The second form runs the command on the given frame.`}, Move the current frame up by <m>. The second form runs the command on the given frame.`},
{aliases: []string{"down"}, {aliases: []string{"down"},
@ -280,10 +286,15 @@ Move the current frame up by <m>. The second form runs the command on the given
}, },
helpMsg: `Move the current frame down. helpMsg: `Move the current frame down.
down [<m>] down [<m>]
down [<m>] <command> down [<m>] <command>
Move the current frame down by <m>. The second form runs the command on the given frame.`}, Move the current frame down by <m>. The second form runs the command on the given frame.`},
{aliases: []string{"deferred"}, cmdFn: c.deferredCommand, helpMsg: `Executes command in the context of a deferred call.
deferred <n> <command>
Executes the specified command (print, args, locals) in the context of the n-th deferred call in the current frame.`},
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: `Executes a file containing a list of delve commands {aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: `Executes a file containing a list of delve commands
source <path>`}, source <path>`},
@ -334,6 +345,7 @@ Defines <alias> as an alias to <command> or removes an alias.`},
edit [locspec] edit [locspec]
If locspec is omitted edit will open the current source file in the editor, otherwise it will open the specified location.`}, If locspec is omitted edit will open the current source file in the editor, otherwise it will open the specified location.`},
{aliases: []string{"libraries"}, cmdFn: libraries, helpMsg: `List loaded dynamic libraries`},
} }
if client == nil || client.Recorded() { if client == nil || client.Recorded() {
@ -347,7 +359,9 @@ If locspec is omitted edit will open the current source file in the editor, othe
cmdFn: checkpoint, cmdFn: checkpoint,
helpMsg: `Creates a checkpoint at the current position. helpMsg: `Creates a checkpoint at the current position.
checkpoint [where]`, checkpoint [note]
The "note" is arbitrary text that can be used to identify the checkpoint, if it is not specified it defaults to the current filename:line position.`,
}) })
c.cmds = append(c.cmds, command{ c.cmds = append(c.cmds, command{
aliases: []string{"checkpoints"}, aliases: []string{"checkpoints"},
@ -426,7 +440,7 @@ func (c *Commands) CallWithContext(cmdstr string, t *Term, ctx callContext) erro
// Call takes a command to execute. // Call takes a command to execute.
func (c *Commands) Call(cmdstr string, t *Term) error { func (c *Commands) Call(cmdstr string, t *Term) error {
ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: c.frame}} ctx := callContext{Prefix: noPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: c.frame, DeferredCall: 0}}
return c.CallWithContext(cmdstr, t, ctx) return c.CallWithContext(cmdstr, t, ctx)
} }
@ -559,6 +573,27 @@ func (a byGoroutineID) Len() int { return len(a) }
func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID } func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
// The number of goroutines we're going to request on each RPC call
const goroutineBatchSize = 10000
func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, bPrintStack bool, state *api.DebuggerState) error {
for _, g := range gs {
prefix := " "
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
prefix = "* "
}
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
if bPrintStack {
stack, err := t.client.Stacktrace(g.ID, 10, false, nil)
if err != nil {
return err
}
printStack(stack, "\t", false)
}
}
return nil
}
func goroutines(t *Term, ctx callContext, argstr string) error { func goroutines(t *Term, ctx callContext, argstr string) error {
args := strings.Split(argstr, " ") args := strings.Split(argstr, " ")
var fgl = fglUserCurrent var fgl = fglUserCurrent
@ -593,26 +628,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
if err != nil { if err != nil {
return err return err
} }
gs, err := t.client.ListGoroutines() var (
if err != nil { start = 0
return err gslen = 0
} gs []*api.Goroutine
sort.Sort(byGoroutineID(gs)) )
fmt.Printf("[%d goroutines]\n", len(gs)) for start >= 0 {
for _, g := range gs { gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize)
prefix := " " if err != nil {
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID { return err
prefix = "* "
} }
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) sort.Sort(byGoroutineID(gs))
if bPrintStack { err = printGoroutines(t, gs, fgl, bPrintStack, state)
stack, err := t.client.Stacktrace(g.ID, 10, false, nil) if err != nil {
if err != nil { return err
return err
}
printStack(stack, "\t", false)
} }
gslen += len(gs)
} }
fmt.Printf("[%d goroutines]\n", gslen)
return nil return nil
} }
@ -714,6 +747,22 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi
return nil return nil
} }
func (c *Commands) deferredCommand(t *Term, ctx callContext, argstr string) error {
ctx.Prefix = deferredPrefix
space := strings.Index(argstr, " ")
var err error
ctx.Scope.DeferredCall, err = strconv.Atoi(argstr[:space])
if err != nil {
return err
}
if ctx.Scope.DeferredCall <= 0 {
return errors.New("argument of deferred must be a number greater than 0 (use 'stack -defer' to see the list of deferred calls)")
}
return c.CallWithContext(argstr[space:], t, ctx)
}
func printscope(t *Term) error { func printscope(t *Term) error {
state, err := t.client.GetState() state, err := t.client.GetState()
if err != nil { if err != nil {
@ -980,7 +1029,13 @@ func (c *Commands) call(t *Term, ctx callContext, args string) error {
if err := scopePrefixSwitch(t, ctx); err != nil { if err := scopePrefixSwitch(t, ctx); err != nil {
return err return err
} }
state, err := exitedToError(t.client.Call(args)) const unsafePrefix = "-unsafe "
unsafe := false
if strings.HasPrefix(args, unsafePrefix) {
unsafe = true
args = args[len(unsafePrefix):]
}
state, err := exitedToError(t.client.Call(args, unsafe))
c.frame = 0 c.frame = 0
if err != nil { if err != nil {
printcontextNoState(t) printcontextNoState(t)
@ -1164,6 +1219,9 @@ func edit(t *Term, ctx callContext, args string) error {
} }
cmd := exec.Command(editor, fmt.Sprintf("+%d", lineno), file) cmd := exec.Command(editor, fmt.Sprintf("+%d", lineno), file)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run() return cmd.Run()
} }
@ -1356,6 +1414,20 @@ func stackCommand(t *Term, ctx callContext, args string) error {
return err return err
} }
printStack(stack, "", sa.offsets) printStack(stack, "", sa.offsets)
if sa.ancestors > 0 {
ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth)
if err != nil {
return err
}
for _, ancestor := range ancestors {
fmt.Printf("Created by Goroutine %d:\n", ancestor.ID)
if ancestor.Unreadable != "" {
fmt.Printf("\t%s\n", ancestor.Unreadable)
continue
}
printStack(ancestor.Stack, "\t", false)
}
}
return nil return nil
} }
@ -1364,6 +1436,9 @@ type stackArgs struct {
full bool full bool
offsets bool offsets bool
readDefers bool readDefers bool
ancestors int
ancestorDepth int
} }
func parseStackArgs(argstr string) (stackArgs, error) { func parseStackArgs(argstr string) (stackArgs, error) {
@ -1373,7 +1448,18 @@ func parseStackArgs(argstr string) (stackArgs, error) {
} }
if argstr != "" { if argstr != "" {
args := strings.Split(argstr, " ") args := strings.Split(argstr, " ")
for i := range args { for i := 0; i < len(args); i++ {
numarg := func(name string) (int, error) {
if i >= len(args) {
return 0, fmt.Errorf("expected number after %s", name)
}
n, err := strconv.Atoi(args[i])
if err != nil {
return 0, fmt.Errorf("expected number after %s: %v", name, err)
}
return n, nil
}
switch args[i] { switch args[i] {
case "-full": case "-full":
r.full = true r.full = true
@ -1381,6 +1467,20 @@ func parseStackArgs(argstr string) (stackArgs, error) {
r.offsets = true r.offsets = true
case "-defer": case "-defer":
r.readDefers = true r.readDefers = true
case "-a":
i++
n, err := numarg("-a")
if err != nil {
return stackArgs{}, err
}
r.ancestors = n
case "-adepth":
i++
n, err := numarg("-adepth")
if err != nil {
return stackArgs{}, err
}
r.ancestorDepth = n
default: default:
n, err := strconv.Atoi(args[i]) n, err := strconv.Atoi(args[i])
if err != nil { if err != nil {
@ -1390,6 +1490,9 @@ func parseStackArgs(argstr string) (stackArgs, error) {
} }
} }
} }
if r.ancestors > 0 && r.ancestorDepth == 0 {
r.ancestorDepth = r.depth
}
return r, nil return r, nil
} }
@ -1526,6 +1629,18 @@ func disassCommand(t *Term, ctx callContext, args string) error {
return nil return nil
} }
func libraries(t *Term, ctx callContext, args string) error {
libs, err := t.client.ListDynamicLibraries()
if err != nil {
return err
}
d := digits(len(libs))
for i := range libs {
fmt.Printf("%"+strconv.Itoa(d)+"d. %s\n", i, libs[i].Path)
}
return nil
}
func digits(n int) int { func digits(n int) int {
if n <= 0 { if n <= 0 {
return 1 return 1
@ -1565,7 +1680,7 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
} }
for j, d := range stack[i].Defers { for j, d := range stack[i].Defers {
deferHeader := fmt.Sprintf("%s defer %d: ", s, j) deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
s2 := strings.Repeat(" ", len(deferHeader)) s2 := strings.Repeat(" ", len(deferHeader))
if d.Unreadable != "" { if d.Unreadable != "" {
fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable) fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
@ -1669,7 +1784,14 @@ func printcontextThread(t *Term, th *api.Thread) {
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig { if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
var arg []string var arg []string
for _, ar := range th.BreakpointInfo.Arguments { for _, ar := range th.BreakpointInfo.Arguments {
arg = append(arg, ar.SinglelineString()) // For AI compatibility return values are included in the
// argument list. This is a relic of the dark ages when the
// Go debug information did not distinguish between the two.
// Filter them out here instead, so during trace operations
// they are not printed as an argument.
if (ar.Flags & api.VariableArgument) != 0 {
arg = append(arg, ar.SinglelineString())
}
} }
args = strings.Join(arg, ", ") args = strings.Join(arg, ", ")
} }
@ -1874,6 +1996,9 @@ func (c *Commands) executeFile(t *Term, name string) error {
} }
if err := c.Call(line, t); err != nil { if err := c.Call(line, t); err != nil {
if _, isExitRequest := err.(ExitRequestError); isExitRequest {
return err
}
fmt.Printf("%s:%d: %v\n", name, lineno, err) fmt.Printf("%s:%d: %v\n", name, lineno, err)
} }
} }
@ -1923,7 +2048,7 @@ func checkpoints(t *Term, ctx callContext, args string) error {
} }
w := new(tabwriter.Writer) w := new(tabwriter.Writer)
w.Init(os.Stdout, 4, 4, 2, ' ', 0) w.Init(os.Stdout, 4, 4, 2, ' ', 0)
fmt.Fprintln(w, "ID\tWhen\tWhere") fmt.Fprintln(w, "ID\tWhen\tNote")
for _, cp := range cps { for _, cp := range cps {
fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where) fmt.Fprintf(w, "c%d\t%s\t%s\n", cp.ID, cp.When, cp.Where)
} }

View File

@ -8,7 +8,7 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/derekparker/delve/pkg/config" "github.com/go-delve/delve/pkg/config"
) )
func configureCmd(t *Term, ctx callContext, args string) error { func configureCmd(t *Term, ctx callContext, args string) error {

View File

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"text/tabwriter" "text/tabwriter"
"github.com/derekparker/delve/service/api" "github.com/go-delve/delve/service/api"
) )
func DisasmPrint(dv api.AsmInstructions, out io.Writer) { func DisasmPrint(dv api.AsmInstructions, out io.Writer) {

View File

@ -7,7 +7,7 @@ import (
) )
func replaceDocPath(s string) string { func replaceDocPath(s string) string {
const docpath = "$GOPATH/src/github.com/derekparker/delve/" const docpath = "$GOPATH/src/github.com/go-delve/delve/"
for { for {
start := strings.Index(s, docpath) start := strings.Index(s, docpath)
@ -22,7 +22,7 @@ func replaceDocPath(s string) string {
} }
text := s[start+len(docpath) : end] text := s[start+len(docpath) : end]
s = s[:start] + fmt.Sprintf("[%s](//github.com/derekparker/delve/tree/master/%s)", text, text) + s[end:] s = s[:start] + fmt.Sprintf("[%s](//github.com/go-delve/delve/tree/master/%s)", text, text) + s[end:]
} }
} }

View File

@ -13,9 +13,9 @@ import (
"github.com/peterh/liner" "github.com/peterh/liner"
"github.com/derekparker/delve/pkg/config" "github.com/go-delve/delve/pkg/config"
"github.com/derekparker/delve/service" "github.com/go-delve/delve/service"
"github.com/derekparker/delve/service/api" "github.com/go-delve/delve/service/api"
) )
const ( const (
@ -64,20 +64,6 @@ type Term struct {
// New returns a new Term. // New returns a new Term.
func New(client service.Client, conf *config.Config) *Term { func New(client service.Client, conf *config.Config) *Term {
if client != nil && client.IsMulticlient() {
state, _ := client.GetStateNonBlocking()
// The error return of GetState will usually be the ErrProcessExited,
// which we don't care about. If there are other errors they will show up
// later, here we are only concerned about stopping a running target so
// that we can initialize our connection.
if state != nil && state.Running {
_, err := client.Halt()
if err != nil {
fmt.Fprintf(os.Stderr, "could not halt: %v", err)
return nil
}
}
}
cmds := DebugCommands(client) cmds := DebugCommands(client)
if conf != nil && conf.Aliases != nil { if conf != nil && conf.Aliases != nil {
cmds.Merge(conf.Aliases) cmds.Merge(conf.Aliases)
@ -204,6 +190,9 @@ func (t *Term) Run() (int, error) {
if t.InitFile != "" { if t.InitFile != "" {
err := t.cmds.executeFile(t, t.InitFile) err := t.cmds.executeFile(t, t.InitFile)
if err != nil { if err != nil {
if _, ok := err.(ExitRequestError); ok {
return t.handleExit()
}
fmt.Fprintf(os.Stderr, "Error executing init file: %s\n", err) fmt.Fprintf(os.Stderr, "Error executing init file: %s\n", err)
} }
} }
@ -264,7 +253,14 @@ func (t *Term) substitutePath(path string) string {
if t.conf == nil { if t.conf == nil {
return path return path
} }
separator := string(os.PathSeparator)
// On windows paths returned from headless server are as c:/dir/dir
// though os.PathSeparator is '\\'
separator := "/" //make it default
if strings.Index(path, "\\") != -1 { //dependent on the path
separator = "\\"
}
for _, r := range t.conf.SubstitutePath { for _, r := range t.conf.SubstitutePath {
from := crossPlatformPath(r.From) from := crossPlatformPath(r.From)
to := r.To to := r.To

Some files were not shown because too many files have changed in this diff Show More