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