Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for windows/arm64 #3063

Merged
merged 14 commits into from
Sep 21, 2022
4 changes: 4 additions & 0 deletions _fixtures/testvariablescgo/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
#elif __i386__
#define BREAKPOINT asm("int3;")
#elif __aarch64__
#ifdef WIN32
#define BREAKPOINT asm("brk 0xF000;")
#else
#define BREAKPOINT asm("brk 0;")
#endif
#endif

#define N 100

Expand Down
9 changes: 9 additions & 0 deletions _scripts/test_windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,12 @@ $x = $LastExitCode
if ($version -ne "gotip") {
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
}
4 changes: 2 additions & 2 deletions pkg/proc/amd64_arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
if rule.Offset == crosscall2SPOffsetBad {
switch bi.GOOS {
case "windows":
rule.Offset += crosscall2SPOffsetWindows
rule.Offset += crosscall2SPOffsetWindowsAMD64
default:
rule.Offset += crosscall2SPOffsetNonWindows
rule.Offset += crosscall2SPOffset
}
}
fctxt.CFA = rule
Expand Down
6 changes: 3 additions & 3 deletions pkg/proc/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func nameToDwarfFunc(n2d map[string]int) func(string) (int, bool) {

// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
const (
crosscall2SPOffsetBad = 0x8
crosscall2SPOffsetWindows = 0x118
crosscall2SPOffsetNonWindows = 0x58
crosscall2SPOffsetBad = 0x8
crosscall2SPOffsetWindowsAMD64 = 0x118
crosscall2SPOffset = 0x58
)
21 changes: 13 additions & 8 deletions pkg/proc/arm64_arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,25 @@ import (

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}
qmuntal marked this conversation as resolved.
Show resolved Hide resolved

// ARM64Arch returns an initialized ARM64
// struct.
func ARM64Arch(goos string) *Arch {
var brk []byte
if goos == "windows" {
brk = arm64WindowsBreakInstruction
} else {
brk = arm64BreakInstruction
}
return &Arch{
Name: "arm64",
ptrSize: 8,
maxInstructionLength: 4,
breakpointInstruction: arm64BreakInstruction,
breakInstrMovesPC: false,
breakpointInstruction: brk,
breakInstrMovesPC: goos == "windows",
derefTLS: false,
prologues: prologuesARM64,
fixFrameUnwindContext: arm64FixFrameUnwindContext,
Expand Down Expand Up @@ -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 {
rule := fctxt.CFA
if rule.Offset == crosscall2SPOffsetBad {
switch bi.GOOS {
case "windows":
rule.Offset += crosscall2SPOffsetWindows
default:
rule.Offset += crosscall2SPOffsetNonWindows
}
rule.Offset += crosscall2SPOffset
}
fctxt.CFA = rule
}
Expand Down
46 changes: 40 additions & 6 deletions pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ var (

supportedWindowsArch = map[_PEMachine]bool{
_IMAGE_FILE_MACHINE_AMD64: true,
_IMAGE_FILE_MACHINE_ARM64: true,
}

supportedDarwinArch = map[macho.Cpu]bool{
Expand Down Expand Up @@ -681,8 +682,18 @@ func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64

// GStructOffset returns the offset of the G
// struct in thread local storage.
func (bi *BinaryInfo) GStructOffset() uint64 {
return bi.gStructOffset
func (bi *BinaryInfo) GStructOffset(mem MemoryReadWriter) (uint64, error) {
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.
Expand Down Expand Up @@ -1589,8 +1600,6 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
if err != nil {
return err
}

//TODO(aarzilli): actually test this when Go supports PIE buildmode on Windows.
opth := peFile.OptionalHeader.(*pe.OptionalHeader64)
if entryPoint != 0 {
image.StaticBase = entryPoint - opth.ImageBase
Expand Down Expand Up @@ -1618,13 +1627,38 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
wg.Add(2)
go bi.parseDebugFramePE(image, peFile, debugInfoBytes, wg)
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
// to G struct per:
// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c

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) {
Expand Down
25 changes: 17 additions & 8 deletions pkg/proc/gdbserial/gdbserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,11 @@ func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs
// store the MOV instruction.
// If the stub doesn't support memory allocation reloadRegisters will
// overwrite some existing memory to store the MOV.
if addr, err := p.conn.allocMemory(256); err == nil {
if _, err := p.conn.writeMemory(addr, p.loadGInstr()); err == nil {
p.loadGInstrAddr = addr
if ginstr, err := p.loadGInstr(); err == nil {
if addr, err := p.conn.allocMemory(256); err == nil {
if _, err := p.conn.writeMemory(addr, ginstr); err == nil {
p.loadGInstrAddr = addr
}
}
}
}
Expand Down Expand Up @@ -1553,7 +1555,7 @@ func (t *gdbThread) Blocked() bool {
// loadGInstr returns the correct MOV instruction for the current
// OS/architecture that can be executed to load the address of G from an
// inferior's thread.
func (p *gdbProcess) loadGInstr() []byte {
func (p *gdbProcess) loadGInstr() ([]byte, error) {
var op []byte
switch p.bi.GOOS {
case "windows", "darwin", "freebsd":
Expand All @@ -1565,10 +1567,14 @@ func (p *gdbProcess) loadGInstr() []byte {
default:
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.Write(op)
binary.Write(buf, binary.LittleEndian, uint32(p.bi.GStructOffset()))
return buf.Bytes()
binary.Write(buf, binary.LittleEndian, uint32(offset))
return buf.Bytes(), nil
}

func (p *gdbProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
Expand Down Expand Up @@ -1727,7 +1733,10 @@ func (t *gdbThread) readSomeRegisters(regNames ...string) error {
// the MOV instruction used to load current G, executes this single
// instruction and then puts everything back the way it was.
func (t *gdbThread) reloadGAtPC() error {
movinstr := t.p.loadGInstr()
movinstr, err := t.p.loadGInstr()
if err != nil {
return err
}

if t.Blocked() {
t.regs.tls = 0
Expand Down Expand Up @@ -1760,7 +1769,7 @@ func (t *gdbThread) reloadGAtPC() error {
}

savedcode := make([]byte, len(movinstr))
_, err := t.p.ReadMemory(savedcode, pc)
_, err = t.p.ReadMemory(savedcode, pc)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/dump_other.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build (freebsd && amd64) || darwin
// +build freebsd,amd64 darwin
//go:build (freebsd && amd64) || darwin || (windows && arm64)
// +build freebsd,amd64 darwin windows,arm64

package native

Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/hwbreak_other.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build (linux && 386) || (darwin && arm64)
// +build linux,386 darwin,arm64
//go:build (linux && 386) || (darwin && arm64) || (windows && arm64)
// +build linux,386 darwin,arm64 windows,arm64

package native

Expand Down
8 changes: 6 additions & 2 deletions pkg/proc/native/proc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,8 +726,12 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
return fmt.Errorf("could not find function: %s", fnName)
}

offset, err := dbp.BinInfo().GStructOffset(dbp.Memory())
if err != nil {
return err
}
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 {
return err
}
Expand Down Expand Up @@ -762,7 +766,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
}
addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
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 {
return err
}
Expand Down
26 changes: 1 addition & 25 deletions pkg/proc/native/registers_windows.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
package native

import (
"fmt"
"unsafe"

"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/proc"
)
import "github.com/go-delve/delve/pkg/dwarf/op"

// SetPC sets the RIP register to the value specified by `pc`.
func (thread *nativeThread) setPC(pc uint64) error {
Expand Down Expand Up @@ -39,21 +33,3 @@ func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {

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 changes: 6 additions & 0 deletions pkg/proc/native/support_sentinel darwin.go
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions pkg/proc/native/support_sentinel.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// 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)
// +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

package your_operating_system_and_architecture_combination_is_not_supported_by_delve
package your_operating_system_is_not_supported_by_delve
6 changes: 6 additions & 0 deletions pkg/proc/native/support_sentinel_freebsd.go
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions pkg/proc/native/support_sentinel_linux.go
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 0 deletions pkg/proc/native/support_sentinel_windows.go
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions pkg/proc/native/syscall_windows_arm64.go
Original file line number Diff line number Diff line change
@@ -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
Loading