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