Add support for windows/arm64 (#3063)
* Add support for windows/arm64 * split sentinel files and add winarm64 experiment * update loadBinaryInfoPE to support PIE binaries * skip TestDump on windows/arm64 * run windows/arm64 compilation on windows/amd64 * add entry point check for pie binaries * delete unusded code * document windows/arm64 breakpoint * implement changing windows/arm64 fp registers * update crosscall offset names * fix G load when using CGO * fix testvariablescgo * remove DerefGStructOffset * derefrence gstructoffset in GStructOffset() if necessary
This commit is contained in:
parent
8337b5a8a9
commit
4455d6a9ef
@ -7,8 +7,12 @@
|
|||||||
#elif __i386__
|
#elif __i386__
|
||||||
#define BREAKPOINT asm("int3;")
|
#define BREAKPOINT asm("int3;")
|
||||||
#elif __aarch64__
|
#elif __aarch64__
|
||||||
|
#ifdef WIN32
|
||||||
|
#define BREAKPOINT asm("brk 0xF000;")
|
||||||
|
#else
|
||||||
#define BREAKPOINT asm("brk 0;")
|
#define BREAKPOINT asm("brk 0;")
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#define N 100
|
#define N 100
|
||||||
|
|
||||||
|
|||||||
@ -77,3 +77,12 @@ $x = $LastExitCode
|
|||||||
if ($version -ne "gotip") {
|
if ($version -ne "gotip") {
|
||||||
Exit $x
|
Exit $x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO: Remove once we have a windows/arm64 builder.
|
||||||
|
# Test windows/arm64 compiles.
|
||||||
|
$env:GOARCH = "arm64"
|
||||||
|
go run _scripts/make.go build --tags exp.winarm64
|
||||||
|
$x = $LastExitCode
|
||||||
|
if ($version -ne "gotip") {
|
||||||
|
Exit $x
|
||||||
|
}
|
||||||
|
|||||||
@ -104,9 +104,9 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
|
|||||||
if rule.Offset == crosscall2SPOffsetBad {
|
if rule.Offset == crosscall2SPOffsetBad {
|
||||||
switch bi.GOOS {
|
switch bi.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
rule.Offset += crosscall2SPOffsetWindows
|
rule.Offset += crosscall2SPOffsetWindowsAMD64
|
||||||
default:
|
default:
|
||||||
rule.Offset += crosscall2SPOffsetNonWindows
|
rule.Offset += crosscall2SPOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fctxt.CFA = rule
|
fctxt.CFA = rule
|
||||||
|
|||||||
@ -150,6 +150,6 @@ func nameToDwarfFunc(n2d map[string]int) func(string) (int, bool) {
|
|||||||
// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
|
// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
|
||||||
const (
|
const (
|
||||||
crosscall2SPOffsetBad = 0x8
|
crosscall2SPOffsetBad = 0x8
|
||||||
crosscall2SPOffsetWindows = 0x118
|
crosscall2SPOffsetWindowsAMD64 = 0x118
|
||||||
crosscall2SPOffsetNonWindows = 0x58
|
crosscall2SPOffset = 0x58
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,15 +14,25 @@ import (
|
|||||||
|
|
||||||
var arm64BreakInstruction = []byte{0x0, 0x0, 0x20, 0xd4}
|
var arm64BreakInstruction = []byte{0x0, 0x0, 0x20, 0xd4}
|
||||||
|
|
||||||
|
// Windows ARM64 expects a breakpoint to be compiled to the instruction BRK #0xF000.
|
||||||
|
// See go.dev/issues/53837.
|
||||||
|
var arm64WindowsBreakInstruction = []byte{0x0, 0x0, 0x3e, 0xd4}
|
||||||
|
|
||||||
// ARM64Arch returns an initialized ARM64
|
// ARM64Arch returns an initialized ARM64
|
||||||
// struct.
|
// struct.
|
||||||
func ARM64Arch(goos string) *Arch {
|
func ARM64Arch(goos string) *Arch {
|
||||||
|
var brk []byte
|
||||||
|
if goos == "windows" {
|
||||||
|
brk = arm64WindowsBreakInstruction
|
||||||
|
} else {
|
||||||
|
brk = arm64BreakInstruction
|
||||||
|
}
|
||||||
return &Arch{
|
return &Arch{
|
||||||
Name: "arm64",
|
Name: "arm64",
|
||||||
ptrSize: 8,
|
ptrSize: 8,
|
||||||
maxInstructionLength: 4,
|
maxInstructionLength: 4,
|
||||||
breakpointInstruction: arm64BreakInstruction,
|
breakpointInstruction: brk,
|
||||||
breakInstrMovesPC: false,
|
breakInstrMovesPC: goos == "windows",
|
||||||
derefTLS: false,
|
derefTLS: false,
|
||||||
prologues: prologuesARM64,
|
prologues: prologuesARM64,
|
||||||
fixFrameUnwindContext: arm64FixFrameUnwindContext,
|
fixFrameUnwindContext: arm64FixFrameUnwindContext,
|
||||||
@ -102,12 +112,7 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
|
|||||||
if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End {
|
if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End {
|
||||||
rule := fctxt.CFA
|
rule := fctxt.CFA
|
||||||
if rule.Offset == crosscall2SPOffsetBad {
|
if rule.Offset == crosscall2SPOffsetBad {
|
||||||
switch bi.GOOS {
|
rule.Offset += crosscall2SPOffset
|
||||||
case "windows":
|
|
||||||
rule.Offset += crosscall2SPOffsetWindows
|
|
||||||
default:
|
|
||||||
rule.Offset += crosscall2SPOffsetNonWindows
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fctxt.CFA = rule
|
fctxt.CFA = rule
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,6 +137,7 @@ var (
|
|||||||
|
|
||||||
supportedWindowsArch = map[_PEMachine]bool{
|
supportedWindowsArch = map[_PEMachine]bool{
|
||||||
_IMAGE_FILE_MACHINE_AMD64: true,
|
_IMAGE_FILE_MACHINE_AMD64: true,
|
||||||
|
_IMAGE_FILE_MACHINE_ARM64: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
supportedDarwinArch = map[macho.Cpu]bool{
|
supportedDarwinArch = map[macho.Cpu]bool{
|
||||||
@ -681,8 +682,18 @@ func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64
|
|||||||
|
|
||||||
// GStructOffset returns the offset of the G
|
// GStructOffset returns the offset of the G
|
||||||
// struct in thread local storage.
|
// struct in thread local storage.
|
||||||
func (bi *BinaryInfo) GStructOffset() uint64 {
|
func (bi *BinaryInfo) GStructOffset(mem MemoryReadWriter) (uint64, error) {
|
||||||
return bi.gStructOffset
|
offset := bi.gStructOffset
|
||||||
|
if bi.GOOS == "windows" && bi.Arch.Name == "arm64" {
|
||||||
|
// The G struct offset from the TLS section is a pointer
|
||||||
|
// and the address must be dereferenced to find to actual G struct offset.
|
||||||
|
var err error
|
||||||
|
offset, err = readUintRaw(mem, offset, int64(bi.Arch.PtrSize()))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastModified returns the last modified time of the binary.
|
// LastModified returns the last modified time of the binary.
|
||||||
@ -1589,8 +1600,6 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO(aarzilli): actually test this when Go supports PIE buildmode on Windows.
|
|
||||||
opth := peFile.OptionalHeader.(*pe.OptionalHeader64)
|
opth := peFile.OptionalHeader.(*pe.OptionalHeader64)
|
||||||
if entryPoint != 0 {
|
if entryPoint != 0 {
|
||||||
image.StaticBase = entryPoint - opth.ImageBase
|
image.StaticBase = entryPoint - opth.ImageBase
|
||||||
@ -1618,13 +1627,38 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
|
|||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
go bi.parseDebugFramePE(image, peFile, debugInfoBytes, wg)
|
go bi.parseDebugFramePE(image, peFile, debugInfoBytes, wg)
|
||||||
go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, nil)
|
go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, nil)
|
||||||
|
if image.index == 0 {
|
||||||
|
// determine g struct offset only when loading the executable file
|
||||||
|
wg.Add(1)
|
||||||
|
go bi.setGStructOffsetPE(entryPoint, peFile, wg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bi *BinaryInfo) setGStructOffsetPE(entryPoint uint64, peFile *pe.File, wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
switch _PEMachine(peFile.Machine) {
|
||||||
|
case _IMAGE_FILE_MACHINE_AMD64:
|
||||||
// Use ArbitraryUserPointer (0x28) as pointer to pointer
|
// Use ArbitraryUserPointer (0x28) as pointer to pointer
|
||||||
// to G struct per:
|
// to G struct per:
|
||||||
// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
|
// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
|
||||||
|
|
||||||
bi.gStructOffset = 0x28
|
bi.gStructOffset = 0x28
|
||||||
return nil
|
case _IMAGE_FILE_MACHINE_ARM64:
|
||||||
|
// Use runtime.tls_g as pointer to offset from R18 to G struct:
|
||||||
|
// https://golang.org/src/runtime/sys_windows_arm64.s:runtime·wintls
|
||||||
|
for _, s := range peFile.Symbols {
|
||||||
|
if s.Name == "runtime.tls_g" {
|
||||||
|
i := int(s.SectionNumber) - 1
|
||||||
|
if 0 <= i && i < len(peFile.Sections) {
|
||||||
|
sect := peFile.Sections[i]
|
||||||
|
if s.Value < sect.VirtualSize {
|
||||||
|
bi.gStructOffset = entryPoint + uint64(sect.VirtualAddress) + uint64(s.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
|
func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
|
||||||
|
|||||||
@ -359,12 +359,14 @@ func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs
|
|||||||
// store the MOV instruction.
|
// store the MOV instruction.
|
||||||
// If the stub doesn't support memory allocation reloadRegisters will
|
// If the stub doesn't support memory allocation reloadRegisters will
|
||||||
// overwrite some existing memory to store the MOV.
|
// overwrite some existing memory to store the MOV.
|
||||||
|
if ginstr, err := p.loadGInstr(); err == nil {
|
||||||
if addr, err := p.conn.allocMemory(256); err == nil {
|
if addr, err := p.conn.allocMemory(256); err == nil {
|
||||||
if _, err := p.conn.writeMemory(addr, p.loadGInstr()); err == nil {
|
if _, err := p.conn.writeMemory(addr, ginstr); err == nil {
|
||||||
p.loadGInstrAddr = addr
|
p.loadGInstrAddr = addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tgt, nil
|
return tgt, nil
|
||||||
}
|
}
|
||||||
@ -1553,7 +1555,7 @@ func (t *gdbThread) Blocked() bool {
|
|||||||
// loadGInstr returns the correct MOV instruction for the current
|
// loadGInstr returns the correct MOV instruction for the current
|
||||||
// OS/architecture that can be executed to load the address of G from an
|
// OS/architecture that can be executed to load the address of G from an
|
||||||
// inferior's thread.
|
// inferior's thread.
|
||||||
func (p *gdbProcess) loadGInstr() []byte {
|
func (p *gdbProcess) loadGInstr() ([]byte, error) {
|
||||||
var op []byte
|
var op []byte
|
||||||
switch p.bi.GOOS {
|
switch p.bi.GOOS {
|
||||||
case "windows", "darwin", "freebsd":
|
case "windows", "darwin", "freebsd":
|
||||||
@ -1565,10 +1567,14 @@ func (p *gdbProcess) loadGInstr() []byte {
|
|||||||
default:
|
default:
|
||||||
panic("unsupported operating system attempting to find Goroutine on Thread")
|
panic("unsupported operating system attempting to find Goroutine on Thread")
|
||||||
}
|
}
|
||||||
|
offset, err := p.bi.GStructOffset(p.Memory())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
buf.Write(op)
|
buf.Write(op)
|
||||||
binary.Write(buf, binary.LittleEndian, uint32(p.bi.GStructOffset()))
|
binary.Write(buf, binary.LittleEndian, uint32(offset))
|
||||||
return buf.Bytes()
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *gdbProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
func (p *gdbProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
|
||||||
@ -1727,7 +1733,10 @@ func (t *gdbThread) readSomeRegisters(regNames ...string) error {
|
|||||||
// the MOV instruction used to load current G, executes this single
|
// the MOV instruction used to load current G, executes this single
|
||||||
// instruction and then puts everything back the way it was.
|
// instruction and then puts everything back the way it was.
|
||||||
func (t *gdbThread) reloadGAtPC() error {
|
func (t *gdbThread) reloadGAtPC() error {
|
||||||
movinstr := t.p.loadGInstr()
|
movinstr, err := t.p.loadGInstr()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if t.Blocked() {
|
if t.Blocked() {
|
||||||
t.regs.tls = 0
|
t.regs.tls = 0
|
||||||
@ -1760,7 +1769,7 @@ func (t *gdbThread) reloadGAtPC() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
savedcode := make([]byte, len(movinstr))
|
savedcode := make([]byte, len(movinstr))
|
||||||
_, err := t.p.ReadMemory(savedcode, pc)
|
_, err = t.p.ReadMemory(savedcode, pc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
//go:build (freebsd && amd64) || darwin
|
//go:build (freebsd && amd64) || darwin || (windows && arm64)
|
||||||
// +build freebsd,amd64 darwin
|
// +build freebsd,amd64 darwin windows,arm64
|
||||||
|
|
||||||
package native
|
package native
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
//go:build (linux && 386) || (darwin && arm64)
|
//go:build (linux && 386) || (darwin && arm64) || (windows && arm64)
|
||||||
// +build linux,386 darwin,arm64
|
// +build linux,386 darwin,arm64 windows,arm64
|
||||||
|
|
||||||
package native
|
package native
|
||||||
|
|
||||||
|
|||||||
@ -726,8 +726,12 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
|
|||||||
return fmt.Errorf("could not find function: %s", fnName)
|
return fmt.Errorf("could not find function: %s", fnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset, err := dbp.BinInfo().GStructOffset(dbp.Memory())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
key := fn.Entry
|
key := fn.Entry
|
||||||
err := dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, dbp.BinInfo().GStructOffset(), false)
|
err = dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, offset, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -762,7 +766,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
|
|||||||
}
|
}
|
||||||
addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
|
addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true)
|
err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, offset, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import "github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
"fmt"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/dwarf/op"
|
|
||||||
"github.com/go-delve/delve/pkg/proc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetPC sets the RIP register to the value specified by `pc`.
|
// SetPC sets the RIP register to the value specified by `pc`.
|
||||||
func (thread *nativeThread) setPC(pc uint64) error {
|
func (thread *nativeThread) setPC(pc uint64) error {
|
||||||
@ -39,21 +33,3 @@ func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
|
|||||||
|
|
||||||
return thread.setContext(context)
|
return thread.setContext(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registers(thread *nativeThread) (proc.Registers, error) {
|
|
||||||
context := newContext()
|
|
||||||
|
|
||||||
context.SetFlags(_CONTEXT_ALL)
|
|
||||||
err := thread.getContext(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 newRegisters(context, uint64(threadInfo.TebBaseAddress)), nil
|
|
||||||
}
|
|
||||||
|
|||||||
6
pkg/proc/native/support_sentinel darwin.go
Normal file
6
pkg/proc/native/support_sentinel darwin.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
|
||||||
|
|
||||||
|
//go:build darwin && !amd64 && !arm64
|
||||||
|
// +build darwin,!amd64,!arm64
|
||||||
|
|
||||||
|
package your_darwin_architectur_is_not_supported_by_delve
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
|
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
|
||||||
|
|
||||||
//go:build (!linux && !darwin && !windows && !freebsd) || (linux && !amd64 && !arm64 && !386) || (darwin && !amd64 && !arm64) || (windows && !amd64) || (freebsd && !amd64)
|
//go:build !linux && !darwin && !windows && !freebsd
|
||||||
// +build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64,!386 darwin,!amd64,!arm64 windows,!amd64 freebsd,!amd64
|
// +build !linux,!darwin,!windows,!freebsd
|
||||||
|
|
||||||
package your_operating_system_and_architecture_combination_is_not_supported_by_delve
|
package your_operating_system_is_not_supported_by_delve
|
||||||
|
|||||||
6
pkg/proc/native/support_sentinel_freebsd.go
Normal file
6
pkg/proc/native/support_sentinel_freebsd.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
|
||||||
|
|
||||||
|
//go:build freebsd && !amd64
|
||||||
|
// +build freebsd,!amd64
|
||||||
|
|
||||||
|
package your_freebsd_architecture_is_not_supported_by_delve
|
||||||
6
pkg/proc/native/support_sentinel_linux.go
Normal file
6
pkg/proc/native/support_sentinel_linux.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
|
||||||
|
|
||||||
|
//go:build linux && !amd64 && !arm64 && !386
|
||||||
|
// +build linux,!amd64,!arm64,!386
|
||||||
|
|
||||||
|
package your_linux_architecture_is_not_supported_by_delve
|
||||||
8
pkg/proc/native/support_sentinel_windows.go
Normal file
8
pkg/proc/native/support_sentinel_windows.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
|
||||||
|
|
||||||
|
//go:build windows && !amd64 && !(arm64 && exp.winarm64)
|
||||||
|
// +build windows
|
||||||
|
// +build !amd64
|
||||||
|
// +build !arm64 !exp.winarm64
|
||||||
|
|
||||||
|
package your_windows_architecture_is_not_supported_by_delve
|
||||||
23
pkg/proc/native/syscall_windows_arm64.go
Normal file
23
pkg/proc/native/syscall_windows_arm64.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package native
|
||||||
|
|
||||||
|
import "github.com/go-delve/delve/pkg/proc/winutil"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_CONTEXT_ARM64 = 0x00400000
|
||||||
|
_CONTEXT_CONTROL = (_CONTEXT_ARM64 | 0x1)
|
||||||
|
_CONTEXT_INTEGER = (_CONTEXT_ARM64 | 0x2)
|
||||||
|
_CONTEXT_FLOATING_POINT = (_CONTEXT_ARM64 | 0x4)
|
||||||
|
_CONTEXT_DEBUG_REGISTERS = (_CONTEXT_ARM64 | 0x8)
|
||||||
|
_CONTEXT_ARM64_X18 = (_CONTEXT_ARM64 | 0x10)
|
||||||
|
_CONTEXT_FULL = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_FLOATING_POINT)
|
||||||
|
_CONTEXT_ALL = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_FLOATING_POINT | _CONTEXT_DEBUG_REGISTERS | _CONTEXT_ARM64_X18)
|
||||||
|
_CONTEXT_EXCEPTION_ACTIVE = 0x8000000
|
||||||
|
_CONTEXT_SERVICE_ACTIVE = 0x10000000
|
||||||
|
_CONTEXT_EXCEPTION_REQUEST = 0x40000000
|
||||||
|
_CONTEXT_EXCEPTION_REPORTING = 0x80000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.ARM64CONTEXT
|
||||||
@ -2,6 +2,8 @@ package native
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
"github.com/go-delve/delve/pkg/proc/amd64util"
|
"github.com/go-delve/delve/pkg/proc/amd64util"
|
||||||
@ -12,8 +14,22 @@ func newContext() *winutil.AMD64CONTEXT {
|
|||||||
return winutil.NewAMD64CONTEXT()
|
return winutil.NewAMD64CONTEXT()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRegisters(context *winutil.AMD64CONTEXT, TebBaseAddress uint64) *winutil.AMD64Registers {
|
func registers(t *nativeThread) (proc.Registers, error) {
|
||||||
return winutil.NewAMD64Registers(context, TebBaseAddress)
|
context := newContext()
|
||||||
|
|
||||||
|
context.SetFlags(_CONTEXT_ALL)
|
||||||
|
err := t.getContext(context)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var threadInfo _THREAD_BASIC_INFORMATION
|
||||||
|
status := _NtQueryInformationThread(t.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)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *nativeThread) setContext(context *winutil.AMD64CONTEXT) error {
|
func (t *nativeThread) setContext(context *winutil.AMD64CONTEXT) error {
|
||||||
|
|||||||
38
pkg/proc/native/threads_windows_arm64.go
Normal file
38
pkg/proc/native/threads_windows_arm64.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
"github.com/go-delve/delve/pkg/proc/winutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newContext() *winutil.ARM64CONTEXT {
|
||||||
|
return winutil.NewARM64CONTEXT()
|
||||||
|
}
|
||||||
|
|
||||||
|
func registers(t *nativeThread) (proc.Registers, error) {
|
||||||
|
context := newContext()
|
||||||
|
|
||||||
|
context.SetFlags(_CONTEXT_ALL)
|
||||||
|
err := t.getContext(context)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return winutil.NewARM64Registers(context, t.dbp.iscgo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRegisters(context *winutil.ARM64CONTEXT, TebBaseAddress uint64, iscgo bool) *winutil.ARM64Registers {
|
||||||
|
return winutil.NewARM64Registers(context, iscgo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) setContext(context *winutil.ARM64CONTEXT) error {
|
||||||
|
return _SetThreadContext(t.os.hThread, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) getContext(context *winutil.ARM64CONTEXT) error {
|
||||||
|
return _GetThreadContext(t.os.hThread, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
||||||
|
return t.setContext(savedRegs.(*winutil.ARM64Registers).Context)
|
||||||
|
}
|
||||||
@ -112,7 +112,9 @@ func TestDwarfVersion(t *testing.T) {
|
|||||||
// Tests that we correctly read the version of compilation units
|
// Tests that we correctly read the version of compilation units
|
||||||
fixture := protest.BuildFixture("math", 0)
|
fixture := protest.BuildFixture("math", 0)
|
||||||
bi := NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
|
bi := NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
|
||||||
assertNoError(bi.LoadBinaryInfo(fixture.Path, 0, nil), t, "LoadBinaryInfo")
|
// Use a fake entry point so LoadBinaryInfo does not error in case the binary is PIE.
|
||||||
|
const fakeEntryPoint = 1
|
||||||
|
assertNoError(bi.LoadBinaryInfo(fixture.Path, fakeEntryPoint, nil), t, "LoadBinaryInfo")
|
||||||
for _, cu := range bi.Images[0].compileUnits {
|
for _, cu := range bi.Images[0].compileUnits {
|
||||||
if cu.Version != 4 {
|
if cu.Version != 4 {
|
||||||
t.Errorf("compile unit %q at %#x has bad version %d", cu.name, cu.entry.Offset, cu.Version)
|
t.Errorf("compile unit %q at %#x has bad version %d", cu.name, cu.entry.Offset, cu.Version)
|
||||||
@ -127,7 +129,9 @@ func TestRegabiFlagSentinel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
fixture := protest.BuildFixture("math", 0)
|
fixture := protest.BuildFixture("math", 0)
|
||||||
bi := NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
|
bi := NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
|
||||||
assertNoError(bi.LoadBinaryInfo(fixture.Path, 0, nil), t, "LoadBinaryInfo")
|
// Use a fake entry point so LoadBinaryInfo does not error in case the binary is PIE.
|
||||||
|
const fakeEntryPoint = 1
|
||||||
|
assertNoError(bi.LoadBinaryInfo(fixture.Path, fakeEntryPoint, nil), t, "LoadBinaryInfo")
|
||||||
if !bi.regabi {
|
if !bi.regabi {
|
||||||
t.Errorf("regabi flag not set %s GOEXPERIMENT=%s", runtime.Version(), os.Getenv("GOEXPERIMENT"))
|
t.Errorf("regabi flag not set %s GOEXPERIMENT=%s", runtime.Version(), os.Getenv("GOEXPERIMENT"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5234,7 +5234,7 @@ func TestIssue2319(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDump(t *testing.T) {
|
func TestDump(t *testing.T) {
|
||||||
if runtime.GOOS == "freebsd" || (runtime.GOOS == "darwin" && testBackend == "native") {
|
if runtime.GOOS == "freebsd" || (runtime.GOOS == "darwin" && testBackend == "native") || (runtime.GOOS == "windows" && runtime.GOARCH != "amd64") {
|
||||||
t.Skip("not supported")
|
t.Skip("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -438,8 +438,12 @@ func getGVariable(thread Thread) (*Variable, error) {
|
|||||||
|
|
||||||
gaddr, hasgaddr := regs.GAddr()
|
gaddr, hasgaddr := regs.GAddr()
|
||||||
if !hasgaddr {
|
if !hasgaddr {
|
||||||
var err error
|
bi := thread.BinInfo()
|
||||||
gaddr, err = readUintRaw(thread.ProcessMemory(), regs.TLS()+thread.BinInfo().GStructOffset(), int64(thread.BinInfo().Arch.PtrSize()))
|
offset, err := bi.GStructOffset(thread.ProcessMemory())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gaddr, err = readUintRaw(thread.ProcessMemory(), regs.TLS()+offset, int64(bi.Arch.PtrSize()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
186
pkg/proc/winutil/regs_arm64_arch.go
Normal file
186
pkg/proc/winutil/regs_arm64_arch.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package winutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
||||||
|
"github.com/go-delve/delve/pkg/dwarf/regnum"
|
||||||
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ARM64_MAX_BREAKPOINTS = 8
|
||||||
|
ARM64_MAX_WATCHPOINTS = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// neon128 tracks the neon128 windows struct.
|
||||||
|
type neon128 struct {
|
||||||
|
Low uint64
|
||||||
|
High int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARM64Registers represents CPU registers on an ARM64 processor.
|
||||||
|
type ARM64Registers struct {
|
||||||
|
iscgo bool
|
||||||
|
Regs [31]uint64
|
||||||
|
Sp uint64
|
||||||
|
Pc uint64
|
||||||
|
FloatRegisters [32]neon128
|
||||||
|
Fpcr uint32
|
||||||
|
Fpsr uint32
|
||||||
|
Bcr [ARM64_MAX_BREAKPOINTS]uint32
|
||||||
|
Bvr [ARM64_MAX_BREAKPOINTS]uint64
|
||||||
|
Wcr [ARM64_MAX_WATCHPOINTS]uint32
|
||||||
|
Wvr [ARM64_MAX_WATCHPOINTS]uint64
|
||||||
|
Context *ARM64CONTEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewARM64Registers creates a new ARM64Registers struct from a CONTEXT.
|
||||||
|
func NewARM64Registers(context *ARM64CONTEXT, iscgo bool) *ARM64Registers {
|
||||||
|
regs := &ARM64Registers{
|
||||||
|
iscgo: iscgo,
|
||||||
|
Regs: context.Regs,
|
||||||
|
Sp: context.Sp,
|
||||||
|
Pc: context.Pc,
|
||||||
|
FloatRegisters: context.FloatRegisters,
|
||||||
|
Fpcr: context.Fpcr,
|
||||||
|
Fpsr: context.Fpsr,
|
||||||
|
Bcr: context.Bcr,
|
||||||
|
Bvr: context.Bvr,
|
||||||
|
Wcr: context.Wcr,
|
||||||
|
Wvr: context.Wvr,
|
||||||
|
Context: context,
|
||||||
|
}
|
||||||
|
|
||||||
|
return regs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice returns the registers as a list of (name, value) pairs.
|
||||||
|
func (r *ARM64Registers) Slice(floatingPoint bool) ([]proc.Register, error) {
|
||||||
|
out := make([]proc.Register, 0, len(r.Regs)+len(r.FloatRegisters)+2)
|
||||||
|
for i, v := range r.Regs {
|
||||||
|
out = proc.AppendUint64Register(out, fmt.Sprintf("X%d", i), v)
|
||||||
|
}
|
||||||
|
out = proc.AppendUint64Register(out, "SP", r.Sp)
|
||||||
|
out = proc.AppendUint64Register(out, "PC", r.Pc)
|
||||||
|
if floatingPoint {
|
||||||
|
for i := range r.FloatRegisters {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
binary.Write(&buf, binary.LittleEndian, r.FloatRegisters[i].Low)
|
||||||
|
binary.Write(&buf, binary.LittleEndian, r.FloatRegisters[i].High)
|
||||||
|
out = proc.AppendBytesRegister(out, fmt.Sprintf("V%d", i), buf.Bytes())
|
||||||
|
}
|
||||||
|
out = proc.AppendUint64Register(out, "Fpcr", uint64(r.Fpcr))
|
||||||
|
out = proc.AppendUint64Register(out, "Fpsr", uint64(r.Fpsr))
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC returns the value of RIP register.
|
||||||
|
func (r *ARM64Registers) PC() uint64 {
|
||||||
|
return r.Pc
|
||||||
|
}
|
||||||
|
|
||||||
|
// SP returns the value of RSP register.
|
||||||
|
func (r *ARM64Registers) SP() uint64 {
|
||||||
|
return r.Sp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ARM64Registers) BP() uint64 {
|
||||||
|
return r.Regs[29]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS returns the address of the thread local storage memory segment.
|
||||||
|
func (r *ARM64Registers) TLS() uint64 {
|
||||||
|
if !r.iscgo {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return r.Regs[18]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GAddr returns the address of the G variable if it is known, 0 and false
|
||||||
|
// otherwise.
|
||||||
|
func (r *ARM64Registers) GAddr() (uint64, bool) {
|
||||||
|
return r.Regs[28], !r.iscgo
|
||||||
|
}
|
||||||
|
|
||||||
|
// LR returns the link register.
|
||||||
|
func (r *ARM64Registers) LR() uint64 {
|
||||||
|
return r.Regs[30]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a copy of these registers that is guaranteed not to change.
|
||||||
|
func (r *ARM64Registers) Copy() (proc.Registers, error) {
|
||||||
|
rr := *r
|
||||||
|
rr.Context = NewARM64CONTEXT()
|
||||||
|
*(rr.Context) = *(r.Context)
|
||||||
|
return &rr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARM64CONTEXT tracks the _ARM64_NT_CONTEXT of windows.
|
||||||
|
type ARM64CONTEXT struct {
|
||||||
|
ContextFlags uint32
|
||||||
|
Cpsr uint32
|
||||||
|
Regs [31]uint64
|
||||||
|
Sp uint64
|
||||||
|
Pc uint64
|
||||||
|
FloatRegisters [32]neon128
|
||||||
|
Fpcr uint32
|
||||||
|
Fpsr uint32
|
||||||
|
Bcr [ARM64_MAX_BREAKPOINTS]uint32
|
||||||
|
Bvr [ARM64_MAX_BREAKPOINTS]uint64
|
||||||
|
Wcr [ARM64_MAX_WATCHPOINTS]uint32
|
||||||
|
Wvr [ARM64_MAX_WATCHPOINTS]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewARM64CONTEXT allocates Windows CONTEXT structure aligned to 16 bytes.
|
||||||
|
func NewARM64CONTEXT() *ARM64CONTEXT {
|
||||||
|
var c *ARM64CONTEXT
|
||||||
|
buf := make([]byte, unsafe.Sizeof(*c)+15)
|
||||||
|
return (*ARM64CONTEXT)(unsafe.Pointer((uintptr(unsafe.Pointer(&buf[15]))) &^ 15))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ARM64CONTEXT) SetFlags(flags uint32) {
|
||||||
|
ctx.ContextFlags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ARM64CONTEXT) SetPC(pc uint64) {
|
||||||
|
ctx.Pc = pc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ARM64CONTEXT) SetTrap(trap bool) {
|
||||||
|
const v = 0x200000
|
||||||
|
if trap {
|
||||||
|
ctx.Cpsr |= v
|
||||||
|
} else {
|
||||||
|
ctx.Cpsr &= ^uint32(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ARM64CONTEXT) SetReg(regNum uint64, reg *op.DwarfRegister) error {
|
||||||
|
switch regNum {
|
||||||
|
case regnum.ARM64_PC:
|
||||||
|
ctx.Pc = reg.Uint64Val
|
||||||
|
return nil
|
||||||
|
case regnum.ARM64_SP:
|
||||||
|
ctx.Sp = reg.Uint64Val
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
switch {
|
||||||
|
case regNum >= regnum.ARM64_X0 && regNum <= regnum.ARM64_X0+30:
|
||||||
|
ctx.Regs[regNum-regnum.ARM64_X0] = reg.Uint64Val
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case regNum >= regnum.ARM64_V0 && regNum <= regnum.ARM64_V0+30:
|
||||||
|
i := regNum - regnum.ARM64_V0
|
||||||
|
ctx.FloatRegisters[i].Low = reg.Uint64Val
|
||||||
|
ctx.FloatRegisters[i].High = 0
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("changing register %d not implemented", regNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user