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