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:
Quim Muntal
2022-09-21 22:39:44 +02:00
committed by GitHub
parent 8337b5a8a9
commit 4455d6a9ef
23 changed files with 406 additions and 68 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -137,6 +137,7 @@ var (
supportedWindowsArch = map[_PEMachine]bool{
_IMAGE_FILE_MACHINE_AMD64: true,
_IMAGE_FILE_MACHINE_ARM64: true,
}
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
// 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.
@ -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
@ -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) {

View File

@ -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
}
}
}
}
@ -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":
@ -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) {
@ -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
@ -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
}

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View 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

View 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

View 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

View 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

View File

@ -2,6 +2,8 @@ package native
import (
"errors"
"fmt"
"unsafe"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/amd64util"
@ -12,8 +14,22 @@ func newContext() *winutil.AMD64CONTEXT {
return winutil.NewAMD64CONTEXT()
}
func newRegisters(context *winutil.AMD64CONTEXT, TebBaseAddress uint64) *winutil.AMD64Registers {
return winutil.NewAMD64Registers(context, TebBaseAddress)
func registers(t *nativeThread) (proc.Registers, error) {
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 {

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

View File

@ -112,7 +112,9 @@ func TestDwarfVersion(t *testing.T) {
// Tests that we correctly read the version of compilation units
fixture := protest.BuildFixture("math", 0)
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 {
if cu.Version != 4 {
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)
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 {
t.Errorf("regabi flag not set %s GOEXPERIMENT=%s", runtime.Version(), os.Getenv("GOEXPERIMENT"))
}

View File

@ -5234,7 +5234,7 @@ func TestIssue2319(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")
}

View File

@ -438,8 +438,12 @@ func getGVariable(thread Thread) (*Variable, error) {
gaddr, hasgaddr := regs.GAddr()
if !hasgaddr {
var err error
gaddr, err = readUintRaw(thread.ProcessMemory(), regs.TLS()+thread.BinInfo().GStructOffset(), int64(thread.BinInfo().Arch.PtrSize()))
bi := thread.BinInfo()
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 {
return nil, err
}

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