xref: /freebsd/lib/libproc/proc_bkpt.c (revision 57718be8fa0bd5edc11ab9a72e68cc71982939a6)
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 
58 static int
59 proc_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 
77 int
78 proc_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 
133 done:
134 	if (stopped)
135 		/* Restart the process if we had to stop it. */
136 		proc_continue(phdl);
137 
138 	return (ret);
139 }
140 
141 int
142 proc_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  */
190 void
191 proc_bkptregadj(unsigned long *pc)
192 {
193 	*pc = *pc - BREAKPOINT_INSTR_SZ;
194 }
195 
196 /*
197  * Step over the breakpoint.
198  */
199 int
200 proc_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