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