File: threads_linux_arm64.go

package info (click to toggle)
delve 1.24.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 14,092 kB
  • sloc: ansic: 111,943; sh: 169; asm: 141; makefile: 43; python: 23
file content (191 lines) | stat: -rw-r--r-- 6,144 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package native

import (
	"debug/elf"
	"errors"
	"fmt"
	"syscall"
	"unsafe"

	sys "golang.org/x/sys/unix"

	"github.com/go-delve/delve/pkg/proc"
	"github.com/go-delve/delve/pkg/proc/linutil"
)

func (thread *nativeThread) fpRegisters() ([]proc.Register, []byte, error) {
	var err error
	var arm_fpregs linutil.ARM64PtraceFpRegs
	thread.dbp.execPtraceFunc(func() { arm_fpregs.Vregs, err = ptraceGetFpRegset(thread.ID) })
	fpregs := arm_fpregs.Decode()
	if err != nil {
		err = fmt.Errorf("could not get floating point registers: %v", err.Error())
	}
	return fpregs, arm_fpregs.Vregs, err
}

func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
	sr := savedRegs.(*linutil.ARM64Registers)

	var restoreRegistersErr error
	t.dbp.execPtraceFunc(func() {
		restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
		if restoreRegistersErr != syscall.Errno(0) && restoreRegistersErr != nil {
			return
		}
		if sr.Fpregset != nil {
			iov := sys.Iovec{Base: &sr.Fpregset[0], Len: uint64(len(sr.Fpregset))}
			_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), uintptr(elf.NT_FPREGSET), uintptr(unsafe.Pointer(&iov)), 0, 0)
		}
	})
	if restoreRegistersErr == syscall.Errno(0) {
		restoreRegistersErr = nil
	}
	return restoreRegistersErr
}

const (
	_MAX_ARM64_WATCH = 16
	_NT_ARM_HW_WATCH = 0x403
	_TRAP_HWBKPT     = 0x4
)

type watchpointState struct {
	num      uint8
	debugVer uint8
	words    []uint64
}

func (wpstate *watchpointState) set(idx uint8, addr, ctrl uint64) {
	wpstate.words[1+idx*2] = addr
	wpstate.words[1+idx*2+1] = ctrl
}

// getWatchpoints reads the NT_ARM_HW_WATCH ptrace register set.
// The format of this register set is described by user_hwdebug_state in
// arch/arm64/include/uapi/asm/ptrace.h.
// It consists of one 64bit word containing:
//   - 1byte number of watchpoints
//   - 1byte debug architecture version (the 4 least significant bits of ID_AA64DFR0_EL1)
//   - 6bytes padding
//
// Followed by 2 64bit words for each watchpoint, up to a maximum of 16
// watchpoints. The first word contains the address at which the watchpoint
// is set (DBGWVRn_EL1), the second word is the control register for the
// watchpoint (DBGWCRn_EL1), as described by
// ARM - Architecture Reference Manual Armv8, for A-profile architectures
// section D13.3.11
// where only the BAS, LSC, PAC and E fields are accessible.
func (t *nativeThread) getWatchpoints() (*watchpointState, error) {
	words := make([]uint64, _MAX_ARM64_WATCH*2+1)
	iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(&words[0])), Len: uint64(len(words)) * uint64(unsafe.Sizeof(words[0]))}
	var err error
	t.dbp.execPtraceFunc(func() {
		_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(t.ID), _NT_ARM_HW_WATCH, uintptr(unsafe.Pointer(&iov)), 0, 0)
	})
	if err != syscall.Errno(0) {
		return nil, err
	}
	wpstate := &watchpointState{num: uint8(words[0] & 0xff), debugVer: uint8((words[0] >> 8) & 0xff), words: words}
	if wpstate.num > _MAX_ARM64_WATCH {
		// According to the specification this should never be more than 16 but
		// the code here will not work if this limit ever gets relaxed.
		wpstate.num = _MAX_ARM64_WATCH
	}
	// remove the watchpoint registers that don't exist from the words slice so
	// that we won't try later to write them (which would cause an ENOSPC
	// error).
	wpstate.words = wpstate.words[:wpstate.num*2+1]
	return wpstate, nil
}

// setWatchpoints saves the watchpoint state of the given thread.
// See (*nativeThread).getWatchpoints for a description of how this works.
func (t *nativeThread) setWatchpoints(wpstate *watchpointState) error {
	iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(&(wpstate.words[0]))), Len: uint64(len(wpstate.words)) * uint64(unsafe.Sizeof(wpstate.words[0]))}
	var err error
	t.dbp.execPtraceFunc(func() {
		_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_ARM_HW_WATCH, uintptr(unsafe.Pointer(&iov)), 0, 0)
	})
	if err != syscall.Errno(0) {
		return err
	}
	return nil
}

type ptraceSiginfoArm64 struct {
	signo uint32
	errno uint32
	code  uint32
	addr  uint64    // only valid if Signo is SIGTRAP, SIGFPE, SIGILL, SIGBUS or SIGEMT
	pad   [128]byte // the total size of siginfo_t on ARM64 is 128 bytes so this is more than enough padding for all the fields we don't care about
}

func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
	var siginfo ptraceSiginfoArm64
	var err error
	t.dbp.execPtraceFunc(func() {
		_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETSIGINFO, uintptr(t.ID), 0, uintptr(unsafe.Pointer(&siginfo)), 0, 0)
	})
	if err != syscall.Errno(0) {
		return nil, err
	}
	if siginfo.signo != uint32(sys.SIGTRAP) || (siginfo.code&0xffff) != _TRAP_HWBKPT {
		return nil, nil
	}

	for _, bp := range t.dbp.Breakpoints().M {
		if bp.WatchType != 0 && siginfo.addr >= bp.Addr && siginfo.addr < bp.Addr+uint64(bp.WatchType.Size()) {
			return bp, nil
		}
	}

	return nil, fmt.Errorf("could not find hardware breakpoint for address %#x", siginfo.addr)
}

func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
	wpstate, err := t.getWatchpoints()
	if err != nil {
		return err
	}
	if idx >= wpstate.num {
		return errors.New("hardware breakpoints exhausted")
	}

	const (
		readBreakpoint  = 0x1
		writeBreakpoint = 0x2
		lenBitOffset    = 5
		typeBitOffset   = 3
		privBitOffset   = 1
	)

	var typ uint64
	if wtype.Read() {
		typ |= readBreakpoint
	}
	if wtype.Write() {
		typ |= writeBreakpoint
	}

	len := uint64((1 << wtype.Size()) - 1) // arm wants the length expressed as address bitmask

	priv := uint64(3)

	ctrl := (len << lenBitOffset) | (typ << typeBitOffset) | (priv << privBitOffset) | 1
	wpstate.set(idx, addr, ctrl)

	return t.setWatchpoints(wpstate)
}

func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
	wpstate, err := t.getWatchpoints()
	if err != nil {
		return err
	}
	if idx >= wpstate.num {
		return errors.New("hardware breakpoints exhausted")
	}
	wpstate.set(idx, 0, 0)
	return t.setWatchpoints(wpstate)
}