1210688Srpaulo/*
2210688Srpaulo * Copyright (c) 2010 The FreeBSD Foundation
3210688Srpaulo * All rights reserved.
4210688Srpaulo *
5210688Srpaulo * This software was developed by Rui Paulo under sponsorship from the
6210688Srpaulo * FreeBSD Foundation.
7210688Srpaulo *
8210688Srpaulo * Redistribution and use in source and binary forms, with or without
9210688Srpaulo * modification, are permitted provided that the following conditions
10210688Srpaulo * are met:
11210688Srpaulo * 1. Redistributions of source code must retain the above copyright
12210688Srpaulo *    notice, this list of conditions and the following disclaimer.
13210688Srpaulo * 2. Redistributions in binary form must reproduce the above copyright
14210688Srpaulo *    notice, this list of conditions and the following disclaimer in the
15210688Srpaulo *    documentation and/or other materials provided with the distribution.
16210688Srpaulo *
17210688Srpaulo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18210688Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19210688Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20210688Srpaulo * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21210688Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22210688Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23210688Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24210688Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25210688Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26210688Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27210688Srpaulo * SUCH DAMAGE.
28210688Srpaulo */
29210688Srpaulo
30210688Srpaulo#include <sys/cdefs.h>
31210688Srpaulo__FBSDID("$FreeBSD$");
32210688Srpaulo
33210688Srpaulo#include <sys/types.h>
34210688Srpaulo#include <sys/ptrace.h>
35210688Srpaulo#include <sys/wait.h>
36210688Srpaulo#include <machine/_inttypes.h>
37210688Srpaulo
38210688Srpaulo#include <assert.h>
39210688Srpaulo#include <err.h>
40259969Smarkj#include <errno.h>
41259969Smarkj#include <signal.h>
42210688Srpaulo#include <stdio.h>
43210688Srpaulo#include "_libproc.h"
44210688Srpaulo
45210688Srpaulo#if defined(__i386__) || defined(__amd64__)
46210688Srpaulo#define BREAKPOINT_INSTR	0xcc	/* int 0x3 */
47210688Srpaulo#define	BREAKPOINT_INSTR_SZ	1
48233402Sgonzo#elif defined(__mips__)
49233402Sgonzo#define BREAKPOINT_INSTR	0xd	/* break */
50233402Sgonzo#define	BREAKPOINT_INSTR_SZ	4
51242723Sjhibbits#elif defined(__powerpc__)
52242723Sjhibbits#define BREAKPOINT_INSTR	0x7fe00008	/* trap */
53242723Sjhibbits#define BREAKPOINT_INSTR_SZ 4
54210688Srpaulo#else
55210688Srpaulo#error "Add support for your architecture"
56210688Srpaulo#endif
57210688Srpaulo
58259969Smarkjstatic int
59259969Smarkjproc_stop(struct proc_handle *phdl)
60259969Smarkj{
61259969Smarkj	int status;
62259969Smarkj
63259969Smarkj	if (kill(proc_getpid(phdl), SIGSTOP) == -1) {
64259969Smarkj		DPRINTF("kill %d", proc_getpid(phdl));
65259969Smarkj		return (-1);
66259969Smarkj	} else if (waitpid(proc_getpid(phdl), &status, WSTOPPED) == -1) {
67259969Smarkj		DPRINTF("waitpid %d", proc_getpid(phdl));
68259969Smarkj		return (-1);
69259969Smarkj	} else if (!WIFSTOPPED(status)) {
70259969Smarkj		DPRINTFX("waitpid: unexpected status 0x%x", status);
71259969Smarkj		return (-1);
72259969Smarkj	}
73259969Smarkj
74259969Smarkj	return (0);
75259969Smarkj}
76259969Smarkj
77210688Srpauloint
78210688Srpauloproc_bkptset(struct proc_handle *phdl, uintptr_t address,
79210688Srpaulo    unsigned long *saved)
80210688Srpaulo{
81210688Srpaulo	struct ptrace_io_desc piod;
82210688Srpaulo	unsigned long paddr, caddr;
83269754Smarkj	int ret = 0, stopped;
84210688Srpaulo
85210688Srpaulo	*saved = 0;
86210688Srpaulo	if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
87210688Srpaulo	    phdl->status == PS_IDLE) {
88210688Srpaulo		errno = ENOENT;
89210688Srpaulo		return (-1);
90210688Srpaulo	}
91210688Srpaulo
92259969Smarkj	DPRINTFX("adding breakpoint at 0x%lx", address);
93259969Smarkj
94269754Smarkj	stopped = 0;
95269754Smarkj	if (phdl->status != PS_STOP) {
96259969Smarkj		if (proc_stop(phdl) != 0)
97259969Smarkj			return (-1);
98269754Smarkj		stopped = 1;
99269754Smarkj	}
100259969Smarkj
101210688Srpaulo	/*
102210688Srpaulo	 * Read the original instruction.
103210688Srpaulo	 */
104210688Srpaulo	caddr = address;
105210688Srpaulo	paddr = 0;
106210688Srpaulo	piod.piod_op = PIOD_READ_I;
107210688Srpaulo	piod.piod_offs = (void *)caddr;
108210688Srpaulo	piod.piod_addr = &paddr;
109210688Srpaulo	piod.piod_len  = BREAKPOINT_INSTR_SZ;
110210688Srpaulo	if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
111259895Smarkj		DPRINTF("ERROR: couldn't read instruction at address 0x%"
112259895Smarkj		    PRIuPTR, address);
113259969Smarkj		ret = -1;
114259969Smarkj		goto done;
115210688Srpaulo	}
116210688Srpaulo	*saved = paddr;
117210688Srpaulo	/*
118210688Srpaulo	 * Write a breakpoint instruction to that address.
119210688Srpaulo	 */
120210688Srpaulo	caddr = address;
121210688Srpaulo	paddr = BREAKPOINT_INSTR;
122210688Srpaulo	piod.piod_op = PIOD_WRITE_I;
123210688Srpaulo	piod.piod_offs = (void *)caddr;
124210688Srpaulo	piod.piod_addr = &paddr;
125210688Srpaulo	piod.piod_len  = BREAKPOINT_INSTR_SZ;
126210688Srpaulo	if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
127259895Smarkj		DPRINTF("ERROR: couldn't write instruction at address 0x%"
128259895Smarkj		    PRIuPTR, address);
129259969Smarkj		ret = -1;
130259969Smarkj		goto done;
131210688Srpaulo	}
132210688Srpaulo
133259969Smarkjdone:
134269754Smarkj	if (stopped)
135259969Smarkj		/* Restart the process if we had to stop it. */
136269754Smarkj		proc_continue(phdl);
137259969Smarkj
138259969Smarkj	return (ret);
139210688Srpaulo}
140210688Srpaulo
141210688Srpauloint
142210688Srpauloproc_bkptdel(struct proc_handle *phdl, uintptr_t address,
143210688Srpaulo    unsigned long saved)
144210688Srpaulo{
145210688Srpaulo	struct ptrace_io_desc piod;
146210688Srpaulo	unsigned long paddr, caddr;
147269754Smarkj	int ret = 0, stopped;
148210688Srpaulo
149210688Srpaulo	if (phdl->status == PS_DEAD || phdl->status == PS_UNDEAD ||
150210688Srpaulo	    phdl->status == PS_IDLE) {
151210688Srpaulo		errno = ENOENT;
152210688Srpaulo		return (-1);
153210688Srpaulo	}
154259969Smarkj
155259969Smarkj	DPRINTFX("removing breakpoint at 0x%lx", address);
156259969Smarkj
157269754Smarkj	stopped = 0;
158269754Smarkj	if (phdl->status != PS_STOP) {
159259969Smarkj		if (proc_stop(phdl) != 0)
160259969Smarkj			return (-1);
161269754Smarkj		stopped = 1;
162269754Smarkj	}
163259969Smarkj
164210688Srpaulo	/*
165210688Srpaulo	 * Overwrite the breakpoint instruction that we setup previously.
166210688Srpaulo	 */
167210688Srpaulo	caddr = address;
168210688Srpaulo	paddr = saved;
169210688Srpaulo	piod.piod_op = PIOD_WRITE_I;
170210688Srpaulo	piod.piod_offs = (void *)caddr;
171210688Srpaulo	piod.piod_addr = &paddr;
172210688Srpaulo	piod.piod_len  = BREAKPOINT_INSTR_SZ;
173210688Srpaulo	if (ptrace(PT_IO, proc_getpid(phdl), (caddr_t)&piod, 0) < 0) {
174259895Smarkj		DPRINTF("ERROR: couldn't write instruction at address 0x%"
175259895Smarkj		    PRIuPTR, address);
176259969Smarkj		ret = -1;
177210688Srpaulo	}
178259969Smarkj
179269754Smarkj	if (stopped)
180259969Smarkj		/* Restart the process if we had to stop it. */
181269754Smarkj		proc_continue(phdl);
182210688Srpaulo
183259969Smarkj	return (ret);
184210688Srpaulo}
185210688Srpaulo
186210688Srpaulo/*
187210688Srpaulo * Decrement pc so that we delete the breakpoint at the correct
188210688Srpaulo * address, i.e. at the BREAKPOINT_INSTR address.
189210688Srpaulo */
190210688Srpaulovoid
191210688Srpauloproc_bkptregadj(unsigned long *pc)
192210688Srpaulo{
193210688Srpaulo	*pc = *pc - BREAKPOINT_INSTR_SZ;
194210688Srpaulo}
195210688Srpaulo
196210688Srpaulo/*
197210688Srpaulo * Step over the breakpoint.
198210688Srpaulo */
199210688Srpauloint
200210688Srpauloproc_bkptexec(struct proc_handle *phdl, unsigned long saved)
201210688Srpaulo{
202210688Srpaulo	unsigned long pc;
203210688Srpaulo	unsigned long samesaved;
204210688Srpaulo	int status;
205210688Srpaulo
206210688Srpaulo	if (proc_regget(phdl, REG_PC, &pc) < 0) {
207259895Smarkj		DPRINTFX("ERROR: couldn't get PC register");
208210688Srpaulo		return (-1);
209210688Srpaulo	}
210210688Srpaulo	proc_bkptregadj(&pc);
211210688Srpaulo	if (proc_bkptdel(phdl, pc, saved) < 0) {
212259895Smarkj		DPRINTFX("ERROR: couldn't delete breakpoint");
213210688Srpaulo		return (-1);
214210688Srpaulo	}
215210688Srpaulo	/*
216210688Srpaulo	 * Go back in time and step over the new instruction just
217210688Srpaulo	 * set up by proc_bkptdel().
218210688Srpaulo	 */
219210688Srpaulo	proc_regset(phdl, REG_PC, pc);
220210688Srpaulo	if (ptrace(PT_STEP, proc_getpid(phdl), (caddr_t)1, 0) < 0) {
221259895Smarkj		DPRINTFX("ERROR: ptrace step failed");
222210688Srpaulo		return (-1);
223210688Srpaulo	}
224211184Srpaulo	proc_wstatus(phdl);
225211184Srpaulo	status = proc_getwstat(phdl);
226210688Srpaulo	if (!WIFSTOPPED(status)) {
227259895Smarkj		DPRINTFX("ERROR: don't know why process stopped");
228210688Srpaulo		return (-1);
229210688Srpaulo	}
230210688Srpaulo	/*
231210688Srpaulo	 * Restore the breakpoint. The saved instruction should be
232210688Srpaulo	 * the same as the one that we were passed in.
233210688Srpaulo	 */
234210688Srpaulo	if (proc_bkptset(phdl, pc, &samesaved) < 0) {
235259895Smarkj		DPRINTFX("ERROR: couldn't restore breakpoint");
236210688Srpaulo		return (-1);
237210688Srpaulo	}
238210688Srpaulo	assert(samesaved == saved);
239210688Srpaulo
240210688Srpaulo	return (0);
241210688Srpaulo}
242