diff --git a/_fixtures/testvariablescgo/test.c b/_fixtures/testvariablescgo/test.c index 2299e14257..fdcae84f93 100644 --- a/_fixtures/testvariablescgo/test.c +++ b/_fixtures/testvariablescgo/test.c @@ -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 diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1 index a7a92f4f0b..782542173e 100644 --- a/_scripts/test_windows.ps1 +++ b/_scripts/test_windows.ps1 @@ -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 +} diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go index b000d566af..0cc06df322 100644 --- a/pkg/proc/amd64_arch.go +++ b/pkg/proc/amd64_arch.go @@ -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 diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index dc592c70c4..22de6b1686 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -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 ) diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index 4e44f3ae5d..ddd9797eef 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -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 } diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index aaa11a9aac..54abd0f1f2 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -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) { diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 29f2aeaed6..f898eb4e73 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -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 } diff --git a/pkg/proc/native/dump_other.go b/pkg/proc/native/dump_other.go index 0c4ce14a07..e4ebf44217 100644 --- a/pkg/proc/native/dump_other.go +++ b/pkg/proc/native/dump_other.go @@ -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 diff --git a/pkg/proc/native/hwbreak_other.go b/pkg/proc/native/hwbreak_other.go index 548f3336dd..261448303c 100644 --- a/pkg/proc/native/hwbreak_other.go +++ b/pkg/proc/native/hwbreak_other.go @@ -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 diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index a7f0dd6328..cfcc2878b1 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -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 } diff --git a/pkg/proc/native/registers_windows.go b/pkg/proc/native/registers_windows.go index 428d154164..aa998f3f03 100644 --- a/pkg/proc/native/registers_windows.go +++ b/pkg/proc/native/registers_windows.go @@ -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 -} diff --git a/pkg/proc/native/support_sentinel darwin.go b/pkg/proc/native/support_sentinel darwin.go new file mode 100644 index 0000000000..6c82942852 --- /dev/null +++ b/pkg/proc/native/support_sentinel darwin.go @@ -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 diff --git a/pkg/proc/native/support_sentinel.go b/pkg/proc/native/support_sentinel.go index bfad9ad21f..b8c7ba3e58 100644 --- a/pkg/proc/native/support_sentinel.go +++ b/pkg/proc/native/support_sentinel.go @@ -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 diff --git a/pkg/proc/native/support_sentinel_freebsd.go b/pkg/proc/native/support_sentinel_freebsd.go new file mode 100644 index 0000000000..f7a1bff93e --- /dev/null +++ b/pkg/proc/native/support_sentinel_freebsd.go @@ -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 diff --git a/pkg/proc/native/support_sentinel_linux.go b/pkg/proc/native/support_sentinel_linux.go new file mode 100644 index 0000000000..1325f09cc4 --- /dev/null +++ b/pkg/proc/native/support_sentinel_linux.go @@ -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 diff --git a/pkg/proc/native/support_sentinel_windows.go b/pkg/proc/native/support_sentinel_windows.go new file mode 100644 index 0000000000..883c6648ff --- /dev/null +++ b/pkg/proc/native/support_sentinel_windows.go @@ -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 diff --git a/pkg/proc/native/syscall_windows_arm64.go b/pkg/proc/native/syscall_windows_arm64.go new file mode 100644 index 0000000000..8b550f32cb --- /dev/null +++ b/pkg/proc/native/syscall_windows_arm64.go @@ -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 diff --git a/pkg/proc/native/threads_windows_amd64.go b/pkg/proc/native/threads_windows_amd64.go index e1b981b3b1..e5273f3f54 100644 --- a/pkg/proc/native/threads_windows_amd64.go +++ b/pkg/proc/native/threads_windows_amd64.go @@ -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 { diff --git a/pkg/proc/native/threads_windows_arm64.go b/pkg/proc/native/threads_windows_arm64.go new file mode 100644 index 0000000000..cbd8948f9a --- /dev/null +++ b/pkg/proc/native/threads_windows_arm64.go @@ -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) +} diff --git a/pkg/proc/proc_general_test.go b/pkg/proc/proc_general_test.go index 870efc8c08..872b79b609 100644 --- a/pkg/proc/proc_general_test.go +++ b/pkg/proc/proc_general_test.go @@ -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")) } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 57a3015c43..25f5c5dada 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -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") } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index b46fdad0ff..70622156da 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -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 } diff --git a/pkg/proc/winutil/regs_arm64_arch.go b/pkg/proc/winutil/regs_arm64_arch.go new file mode 100644 index 0000000000..4c0b044c80 --- /dev/null +++ b/pkg/proc/winutil/regs_arm64_arch.go @@ -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) + } + } +}