1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2010 The FreeBSD Foundation
5 * All rights reserved.
6 *
7 * This software was developed by Rui Paulo under sponsorship from the
8 * FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/types.h>
33#include <sys/ptrace.h>
34#include <sys/wait.h>
35
36#include <assert.h>
37#include <err.h>
38#include <errno.h>
39#include <signal.h>
40#include <stdio.h>
41
42#include "_libproc.h"
43
44#if defined(__aarch64__)
45#define	AARCH64_BRK		0xd4200000
46#define	AARCH64_BRK_IMM16_SHIFT	5
47#define	AARCH64_BRK_IMM16_VAL	(0xd << AARCH64_BRK_IMM16_SHIFT)
48#define	BREAKPOINT_INSTR	(AARCH64_BRK | AARCH64_BRK_IMM16_VAL)
49#define	BREAKPOINT_INSTR_SZ	4
50#elif defined(__amd64__) || defined(__i386__)
51#define	BREAKPOINT_INSTR	0xcc	/* int 0x3 */
52#define	BREAKPOINT_INSTR_SZ	1
53#define	BREAKPOINT_ADJUST_SZ	BREAKPOINT_INSTR_SZ
54#elif defined(__arm__)
55#define	BREAKPOINT_INSTR	0xe7ffffff	/* bkpt */
56#define	BREAKPOINT_INSTR_SZ	4
57#elif defined(__powerpc__)
58#define	BREAKPOINT_INSTR	0x7fe00008	/* trap */
59#define	BREAKPOINT_INSTR_SZ	4
60#elif defined(__riscv)
61#define	BREAKPOINT_INSTR	0x00100073	/* sbreak */
62#define	BREAKPOINT_INSTR_SZ	4
63#else
64#error "Add support for your architecture"
65#endif
66
67/*
68 * Use 4-bytes holder for breakpoint instruction on all the platforms.
69 * Works for x86 as well until it is endian-little platform.
70 * (We are coping one byte only on x86 from this 4-bytes piece of
71 * memory).
72 */
73typedef uint32_t instr_t;
74
75static int
76proc_stop(struct proc_handle *phdl)
77{
78	int status;
79
80	if (kill(proc_getpid(phdl), SIGSTOP) == -1) {
81		DPRINTF("kill %d", proc_getpid(phdl));
82		return (-1);
83	} else if (waitpid(proc_getpid(phdl), &status, WSTOPPED) == -1) {
84		DPRINTF("waitpid %d", proc_getpid(phdl));
85		return (-1);
86	} else if (!WIFSTOPPED(status)) {
87		DPRINTFX("waitpid: unexpected status 0x%x", status);
88		return (-1);
89	}
90
91	return (0);
92}
93
94int
95proc_bkptset(struct proc_handle *phdl, uintptr_t address,
96    unsigned long *saved)
97{
98	struct ptrace_io_desc piod;
99	int ret = 0, stopped;
100	instr_t instr;
101
102	*saved = 0;
103	if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
104	    phdl->status == PS_IDLE) {
105		errno = ENOENT;
106		return (-1);
107	}
108
109	DPRINTFX("adding breakpoint at 0x%lx", (unsigned long)address);
110
111	stopped = 0;
112	if (phdl->status != PS_STOP) {
113		if (proc_stop(phdl) != 0)
114			return (-1);
115		stopped = 1;
116	}
117
118	/*
119	 * Read the original instruction.
120	 */
121	instr = 0;
122	piod.piod_op = PIOD_READ_I;
123	piod.piod_offs = (void *)address;
124	piod.piod_addr = &instr;
125	piod.piod_len  = BREAKPOINT_INSTR_SZ;
126	if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
127		DPRINTF("ERROR: couldn't read instruction at address 0x%jx",
128		    (uintmax_t)address);
129		ret = -1;
130		goto done;
131	}
132	*saved = instr;
133	/*
134	 * Write a breakpoint instruction to that address.
135	 */
136	instr = BREAKPOINT_INSTR;
137	piod.piod_op = PIOD_WRITE_I;
138	piod.piod_offs = (void *)address;
139	piod.piod_addr = &instr;
140	piod.piod_len  = BREAKPOINT_INSTR_SZ;
141	if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
142		DPRINTF("ERROR: couldn't write instruction at address 0x%jx",
143		    (uintmax_t)address);
144		ret = -1;
145		goto done;
146	}
147
148done:
149	if (stopped)
150		/* Restart the process if we had to stop it. */
151		proc_continue(phdl);
152
153	return (ret);
154}
155
156int
157proc_bkptdel(struct proc_handle *phdl, uintptr_t address,
158    unsigned long saved)
159{
160	struct ptrace_io_desc piod;
161	int ret = 0, stopped;
162	instr_t instr;
163
164	if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
165	    phdl->status == PS_IDLE) {
166		errno = ENOENT;
167		return (-1);
168	}
169
170	DPRINTFX("removing breakpoint at 0x%lx", (unsigned long)address);
171
172	stopped = 0;
173	if (phdl->status != PS_STOP) {
174		if (proc_stop(phdl) != 0)
175			return (-1);
176		stopped = 1;
177	}
178
179	/*
180	 * Overwrite the breakpoint instruction that we setup previously.
181	 */
182	instr = saved;
183	piod.piod_op = PIOD_WRITE_I;
184	piod.piod_offs = (void *)address;
185	piod.piod_addr = &instr;
186	piod.piod_len  = BREAKPOINT_INSTR_SZ;
187	if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
188		DPRINTF("ERROR: couldn't write instruction at address 0x%jx",
189		    (uintmax_t)address);
190		ret = -1;
191	}
192
193	if (stopped)
194		/* Restart the process if we had to stop it. */
195		proc_continue(phdl);
196
197	return (ret);
198}
199
200/*
201 * Decrement pc so that we delete the breakpoint at the correct
202 * address, i.e. at the BREAKPOINT_INSTR address.
203 *
204 * This is only needed on some architectures where the pc value
205 * when reading registers points at the instruction after the
206 * breakpoint, e.g. x86.
207 */
208void
209proc_bkptregadj(unsigned long *pc)
210{
211
212	(void)pc;
213#ifdef BREAKPOINT_ADJUST_SZ
214	*pc = *pc - BREAKPOINT_ADJUST_SZ;
215#endif
216}
217
218/*
219 * Step over the breakpoint.
220 */
221int
222proc_bkptexec(struct proc_handle *phdl, unsigned long saved)
223{
224	unsigned long pc;
225	unsigned long samesaved;
226	int status;
227
228	if (proc_regget(phdl, REG_PC, &pc) < 0) {
229		DPRINTFX("ERROR: couldn't get PC register");
230		return (-1);
231	}
232	proc_bkptregadj(&pc);
233	if (proc_bkptdel(phdl, pc, saved) < 0) {
234		DPRINTFX("ERROR: couldn't delete breakpoint");
235		return (-1);
236	}
237	/*
238	 * Go back in time and step over the new instruction just
239	 * set up by proc_bkptdel().
240	 */
241	proc_regset(phdl, REG_PC, pc);
242	if (ptrace(PT_STEP, proc_getpid(phdl), (caddr_t)1, 0) < 0) {
243		DPRINTFX("ERROR: ptrace step failed");
244		return (-1);
245	}
246	proc_wstatus(phdl);
247	status = proc_getwstat(phdl);
248	if (!WIFSTOPPED(status)) {
249		DPRINTFX("ERROR: don't know why process stopped");
250		return (-1);
251	}
252	/*
253	 * Restore the breakpoint. The saved instruction should be
254	 * the same as the one that we were passed in.
255	 */
256	if (proc_bkptset(phdl, pc, &samesaved) < 0) {
257		DPRINTFX("ERROR: couldn't restore breakpoint");
258		return (-1);
259	}
260	assert(samesaved == saved);
261
262	return (0);
263}
264