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#ifdef __FBSDID
32__FBSDID("$FreeBSD: head/lib/libproc/proc_bkpt.c 287106 2015-08-24 12:17:15Z andrew $");
33#else
34__RCSID("$NetBSD: proc_bkpt.c,v 1.6 2018/07/20 20:50:34 christos Exp $");
35#endif
36
37#include <sys/types.h>
38#include <sys/ptrace.h>
39#include <sys/wait.h>
40
41#include <assert.h>
42#include <string.h>
43#include <err.h>
44#include <errno.h>
45#include <inttypes.h>
46#include <signal.h>
47#include <stdio.h>
48#include "_libproc.h"
49
50#ifndef PTRACE_BREAKPOINT
51#error "Add support for your architecture"
52#endif
53
54static int
55proc_stop(struct proc_handle *phdl)
56{
57	int status;
58
59	if (kill(proc_getpid(phdl), SIGSTOP) == -1) {
60		DPRINTF("kill %d", proc_getpid(phdl));
61		return (-1);
62	} else if (waitpid(proc_getpid(phdl), &status, WSTOPPED) == -1) {
63		DPRINTF("waitpid %d", proc_getpid(phdl));
64		return (-1);
65	} else if (!WIFSTOPPED(status)) {
66		DPRINTFX("waitpid: unexpected status 0x%x", status);
67		return (-1);
68	}
69
70	return (0);
71}
72
73int
74proc_bkptset(struct proc_handle *phdl, uintptr_t address,
75    proc_breakpoint_t *saved)
76{
77	struct ptrace_io_desc piod;
78	unsigned long paddr, caddr;
79	int ret = 0, stopped;
80	proc_breakpoint_t copy;
81
82	if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
83	    phdl->status == PS_IDLE) {
84		errno = ENOENT;
85		return (-1);
86	}
87
88	DPRINTFX("adding breakpoint at 0x%" PRIxPTR, address);
89
90	stopped = 0;
91	if (phdl->status != PS_STOP) {
92		if (proc_stop(phdl) != 0)
93			return (-1);
94		stopped = 1;
95	}
96
97	/*
98	 * Read the original instruction.
99	 */
100	caddr = address;
101	paddr = 0;
102	piod.piod_op = PIOD_READ_I;
103	piod.piod_offs = (void *)caddr;
104	piod.piod_addr = (void *)saved->data;
105	piod.piod_len  = sizeof(saved->data);
106	if (ptrace(PT_IO, proc_getpid(phdl), &piod, 0) < 0) {
107		DPRINTF("ERROR: couldn't read instruction at address 0x%"
108		    PRIxPTR, address);
109		ret = -1;
110		goto done;
111	}
112	/*
113	 * Write a breakpoint instruction to that address.
114	 */
115	caddr = address;
116	piod.piod_op = PIOD_WRITE_I;
117	piod.piod_offs = (void *)caddr;
118	piod.piod_addr = (void *)PTRACE_BREAKPOINT;
119	piod.piod_len  = sizeof(PTRACE_BREAKPOINT);
120	if (ptrace(PT_IO, proc_getpid(phdl), &piod, 0) < 0) {
121		DPRINTF("ERROR: couldn't write instruction at address 0x%"
122		    PRIxPTR, address);
123		ret = -1;
124		goto done;
125	}
126
127done:
128	if (stopped)
129		/* Restart the process if we had to stop it. */
130		proc_continue(phdl);
131
132	return (ret);
133}
134
135int
136proc_bkptdel(struct proc_handle *phdl, uintptr_t address,
137    proc_breakpoint_t *saved)
138{
139	struct ptrace_io_desc piod;
140	unsigned long paddr, caddr;
141	int ret = 0, stopped;
142
143	if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
144	    phdl->status == PS_IDLE) {
145		errno = ENOENT;
146		return (-1);
147	}
148
149	DPRINTFX("removing breakpoint at 0x%" PRIxPTR, address);
150
151	stopped = 0;
152	if (phdl->status != PS_STOP) {
153		if (proc_stop(phdl) != 0)
154			return (-1);
155		stopped = 1;
156	}
157
158	/*
159	 * Overwrite the breakpoint instruction that we setup previously.
160	 */
161	caddr = address;
162	piod.piod_op = PIOD_WRITE_I;
163	piod.piod_offs = (void *)caddr;
164	piod.piod_addr = (void *)saved->data;
165	piod.piod_len  = sizeof(saved->data);
166	if (ptrace(PT_IO, proc_getpid(phdl), &piod, 0) < 0) {
167		DPRINTF("ERROR: couldn't write instruction at address 0x%"
168		    PRIxPTR, address);
169		ret = -1;
170	}
171
172	if (stopped)
173		/* Restart the process if we had to stop it. */
174		proc_continue(phdl);
175
176	return (ret);
177}
178
179/*
180 * Decrement pc so that we delete the breakpoint at the correct
181 * address, i.e. at the breakpoint instruction address.
182 *
183 * This is only needed on some architectures where the pc value
184 * when reading registers points at the instruction after the
185 * breakpoint, e.g. x86.
186 */
187void
188proc_bkptregadj(unsigned long *pc)
189{
190
191	(void)pc;
192#ifdef PTRACE_BREAKPOINT_ADJ
193	*pc = *pc - PTRACE_BREAKPOINT_ADJ;
194#endif
195}
196
197/*
198 * Step over the breakpoint.
199 */
200int
201proc_bkptexec(struct proc_handle *phdl, proc_breakpoint_t *saved)
202{
203	unsigned long pc;
204	proc_breakpoint_t samesaved;
205	int status;
206
207	if (proc_regget(phdl, REG_PC, &pc) < 0) {
208		DPRINTFX("ERROR: couldn't get PC register");
209		return (-1);
210	}
211	proc_bkptregadj(&pc);
212	if (proc_bkptdel(phdl, pc, saved) < 0) {
213		DPRINTFX("ERROR: couldn't delete breakpoint");
214		return (-1);
215	}
216	/*
217	 * Go back in time and step over the new instruction just
218	 * set up by proc_bkptdel().
219	 */
220	proc_regset(phdl, REG_PC, pc);
221#ifdef PT_STEP
222	if (ptrace(PT_STEP, proc_getpid(phdl), (void *)(intptr_t)1, 0) < 0) {
223		DPRINTFX("ERROR: ptrace step failed");
224		return (-1);
225	}
226#endif
227	proc_wstatus(phdl);
228	status = proc_getwstat(phdl);
229	if (!WIFSTOPPED(status)) {
230		DPRINTFX("ERROR: don't know why process stopped");
231		return (-1);
232	}
233	/*
234	 * Restore the breakpoint. The saved instruction should be
235	 * the same as the one that we were passed in.
236	 */
237	if (proc_bkptset(phdl, pc, &samesaved) < 0) {
238		DPRINTFX("ERROR: couldn't restore breakpoint");
239		return (-1);
240	}
241
242	assert(memcmp(saved, &samesaved, sizeof(samesaved)) == 0);
243
244	return (0);
245}
246