| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 1 | #include "config.h" |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 2 | |
| 3 | #include <sys/types.h> |
| 4 | #include <sys/wait.h> |
| 5 | #include <signal.h> |
| 6 | #include <sys/ptrace.h> |
| 7 | #include <asm/ptrace.h> |
| Ian Wienand | 9a2ad35 | 2006-02-20 22:44:45 +0100 | [diff] [blame] | 8 | #include <elf.h> |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 9 | #include <errno.h> |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 10 | #include <string.h> |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 11 | |
| Petr Machata | 366c2f4 | 2012-02-09 19:34:36 +0100 | [diff] [blame] | 12 | #include "proc.h" |
| Juan Cespedes | f728123 | 2009-06-25 16:11:21 +0200 | [diff] [blame] | 13 | #include "common.h" |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 14 | #include "ptrace.h" |
| Petr Machata | 9e1e969 | 2012-04-19 02:29:38 +0200 | [diff] [blame] | 15 | #include "breakpoint.h" |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 16 | |
| 17 | #if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR)) |
| 18 | # define PTRACE_PEEKUSER PTRACE_PEEKUSR |
| 19 | #endif |
| 20 | |
| 21 | #if (!defined(PTRACE_POKEUSER) && defined(PTRACE_POKEUSR)) |
| 22 | # define PTRACE_POKEUSER PTRACE_POKEUSR |
| 23 | #endif |
| 24 | |
| Juan Cespedes | f135052 | 2008-12-16 18:19:58 +0100 | [diff] [blame] | 25 | void |
| Juan Cespedes | a8909f7 | 2009-04-28 20:02:41 +0200 | [diff] [blame] | 26 | get_arch_dep(Process *proc) { |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 27 | if (proc->arch_ptr == NULL) { |
| 28 | proc->arch_ptr = malloc(sizeof(proc_archdep)); |
| Ian Wienand | 9a2ad35 | 2006-02-20 22:44:45 +0100 | [diff] [blame] | 29 | #ifdef __powerpc64__ |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 30 | proc->mask_32bit = (proc->e_machine == EM_PPC); |
| Ian Wienand | 9a2ad35 | 2006-02-20 22:44:45 +0100 | [diff] [blame] | 31 | #endif |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 32 | } |
| 33 | |
| 34 | proc_archdep *a = (proc_archdep *) (proc->arch_ptr); |
| 35 | a->valid = (ptrace(PTRACE_GETREGS, proc->pid, 0, &a->regs) >= 0) |
| 36 | && (ptrace(PTRACE_GETFPREGS, proc->pid, 0, &a->fpregs) >= 0); |
| Juan Cespedes | 5c3fe06 | 2004-06-14 18:08:37 +0200 | [diff] [blame] | 37 | } |
| 38 | |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 39 | #define SYSCALL_INSN 0x44000002 |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 40 | |
| 41 | unsigned int greg = 3; |
| 42 | unsigned int freg = 1; |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 43 | |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 44 | /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ |
| Juan Cespedes | f135052 | 2008-12-16 18:19:58 +0100 | [diff] [blame] | 45 | int |
| Juan Cespedes | a8909f7 | 2009-04-28 20:02:41 +0200 | [diff] [blame] | 46 | syscall_p(Process *proc, int status, int *sysnum) { |
| Ian Wienand | 2d45b1a | 2006-02-20 22:48:07 +0100 | [diff] [blame] | 47 | if (WIFSTOPPED(status) |
| 48 | && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { |
| Ian Wienand | 9a2ad35 | 2006-02-20 22:44:45 +0100 | [diff] [blame] | 49 | long pc = (long)get_instruction_pointer(proc); |
| Ian Wienand | 2d45b1a | 2006-02-20 22:48:07 +0100 | [diff] [blame] | 50 | int insn = |
| 51 | (int)ptrace(PTRACE_PEEKTEXT, proc->pid, pc - sizeof(long), |
| 52 | 0); |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 53 | |
| 54 | if (insn == SYSCALL_INSN) { |
| Ian Wienand | 2d45b1a | 2006-02-20 22:48:07 +0100 | [diff] [blame] | 55 | *sysnum = |
| 56 | (int)ptrace(PTRACE_PEEKUSER, proc->pid, |
| 57 | sizeof(long) * PT_R0, 0); |
| Juan Cespedes | 3e94cbf | 2009-05-22 19:12:07 +0200 | [diff] [blame] | 58 | if (proc->callstack_depth > 0 && |
| 59 | proc->callstack[proc->callstack_depth - 1].is_syscall && |
| 60 | proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) { |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 61 | return 2; |
| 62 | } |
| Juan Cespedes | 5bfb061 | 2002-03-31 20:01:28 +0200 | [diff] [blame] | 63 | return 1; |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 64 | } |
| 65 | } |
| 66 | return 0; |
| 67 | } |
| 68 | |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 69 | static long |
| 70 | gimme_arg_regset(enum tof type, Process *proc, int arg_num, arg_type_info *info, |
| 71 | gregset_t *regs, fpregset_t *fpregs) |
| 72 | { |
| 73 | union { long val; float fval; double dval; } cvt; |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 74 | |
| 75 | if (info->type == ARGTYPE_FLOAT || info->type == ARGTYPE_DOUBLE) { |
| 76 | if (freg <= 13 || (proc->mask_32bit && freg <= 8)) { |
| Petr Machata | 87e1a5c | 2010-11-18 11:19:09 +0100 | [diff] [blame] | 77 | double val = GET_FPREG(*fpregs, freg); |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 78 | |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 79 | if (info->type == ARGTYPE_FLOAT) |
| 80 | cvt.fval = val; |
| 81 | else |
| 82 | cvt.dval = val; |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 83 | |
| 84 | freg++; |
| 85 | greg++; |
| 86 | |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 87 | return cvt.val; |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 88 | } |
| 89 | } |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 90 | else if (greg <= 10) |
| 91 | return (*regs)[greg++]; |
| Andreas Schwab | e276cf4 | 2011-05-08 16:25:41 +0200 | [diff] [blame] | 92 | else { |
| 93 | #ifdef __powerpc64__ |
| 94 | if (proc->mask_32bit) |
| 95 | return ptrace (PTRACE_PEEKDATA, proc->pid, |
| 96 | proc->stack_pointer + 8 + |
| 97 | sizeof (int) * (arg_num - 8), 0) >> 32; |
| 98 | else |
| 99 | return ptrace (PTRACE_PEEKDATA, proc->pid, |
| 100 | proc->stack_pointer + 112 + |
| 101 | sizeof (long) * (arg_num - 8), 0); |
| 102 | #else |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 103 | return ptrace (PTRACE_PEEKDATA, proc->pid, |
| Andreas Schwab | e276cf4 | 2011-05-08 16:25:41 +0200 | [diff] [blame] | 104 | proc->stack_pointer + 8 + |
| 105 | sizeof (long) * (arg_num - 8), 0); |
| 106 | #endif |
| 107 | } |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 108 | |
| Juan Cespedes | f1bfe20 | 2002-03-27 00:22:23 +0100 | [diff] [blame] | 109 | return 0; |
| 110 | } |
| Juan Cespedes | 5c3fe06 | 2004-06-14 18:08:37 +0200 | [diff] [blame] | 111 | |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 112 | static long |
| 113 | gimme_retval(Process *proc, int arg_num, arg_type_info *info, |
| 114 | gregset_t *regs, fpregset_t *fpregs) |
| 115 | { |
| 116 | union { long val; float fval; double dval; } cvt; |
| 117 | if (info->type == ARGTYPE_FLOAT || info->type == ARGTYPE_DOUBLE) { |
| Petr Machata | 87e1a5c | 2010-11-18 11:19:09 +0100 | [diff] [blame] | 118 | double val = GET_FPREG(*fpregs, 1); |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 119 | |
| 120 | if (info->type == ARGTYPE_FLOAT) |
| 121 | cvt.fval = val; |
| 122 | else |
| 123 | cvt.dval = val; |
| 124 | |
| 125 | return cvt.val; |
| 126 | } |
| 127 | else |
| 128 | return (*regs)[3]; |
| 129 | } |
| 130 | |
| 131 | /* Grab functions arguments based on the PPC64 ABI. */ |
| 132 | long |
| 133 | gimme_arg(enum tof type, Process *proc, int arg_num, arg_type_info *info) |
| 134 | { |
| 135 | proc_archdep *arch = (proc_archdep *)proc->arch_ptr; |
| 136 | if (arch == NULL || !arch->valid) |
| 137 | return -1; |
| 138 | |
| 139 | /* Check if we're entering a new function call to list parameters. If |
| 140 | so, initialize the register control variables to keep track of where |
| 141 | the parameters were stored. */ |
| 142 | if ((type == LT_TOF_FUNCTION || type == LT_TOF_FUNCTIONR) |
| 143 | && arg_num == 0) { |
| 144 | /* Initialize the set of registrers for parameter passing. */ |
| 145 | greg = 3; |
| 146 | freg = 1; |
| 147 | } |
| 148 | |
| 149 | |
| 150 | if (type == LT_TOF_FUNCTIONR) { |
| 151 | if (arg_num == -1) |
| 152 | return gimme_retval(proc, arg_num, info, |
| 153 | &arch->regs, &arch->fpregs); |
| 154 | else |
| 155 | return gimme_arg_regset(type, proc, arg_num, info, |
| 156 | &arch->regs_copy, |
| 157 | &arch->fpregs_copy); |
| 158 | } |
| 159 | else |
| 160 | return gimme_arg_regset(type, proc, arg_num, info, |
| 161 | &arch->regs, &arch->fpregs); |
| 162 | } |
| 163 | |
| Juan Cespedes | f135052 | 2008-12-16 18:19:58 +0100 | [diff] [blame] | 164 | void |
| Juan Cespedes | a8909f7 | 2009-04-28 20:02:41 +0200 | [diff] [blame] | 165 | save_register_args(enum tof type, Process *proc) { |
| Petr Machata | fac99be | 2010-10-25 13:39:28 +0200 | [diff] [blame] | 166 | proc_archdep *arch = (proc_archdep *)proc->arch_ptr; |
| 167 | if (arch == NULL || !arch->valid) |
| 168 | return; |
| 169 | |
| 170 | memcpy(&arch->regs_copy, &arch->regs, sizeof(arch->regs)); |
| 171 | memcpy(&arch->fpregs_copy, &arch->fpregs, sizeof(arch->fpregs)); |
| Juan Cespedes | 5c3fe06 | 2004-06-14 18:08:37 +0200 | [diff] [blame] | 172 | } |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 173 | |
| 174 | /* Read a single long from the process's memory address 'addr'. */ |
| Juan Cespedes | f135052 | 2008-12-16 18:19:58 +0100 | [diff] [blame] | 175 | int |
| Juan Cespedes | a8909f7 | 2009-04-28 20:02:41 +0200 | [diff] [blame] | 176 | arch_umovelong (Process *proc, void *addr, long *result, arg_type_info *info) { |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 177 | long pointed_to; |
| 178 | |
| 179 | errno = 0; |
| 180 | |
| 181 | pointed_to = ptrace (PTRACE_PEEKTEXT, proc->pid, addr, 0); |
| 182 | |
| 183 | if (pointed_to == -1 && errno) |
| 184 | return -errno; |
| 185 | |
| Michael K. Edwards | 9bc4a9b | 2011-03-06 17:20:11 +0000 | [diff] [blame] | 186 | #if SIZEOF_LONG == 8 |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 187 | /* Since int's are 4-bytes (long is 8-bytes) in length for ppc64, we |
| 188 | need to shift the long values returned by ptrace to end up with |
| 189 | the correct value. */ |
| 190 | |
| 191 | if (info) { |
| 192 | if (info->type == ARGTYPE_INT || (proc->mask_32bit && (info->type == ARGTYPE_POINTER |
| 193 | || info->type == ARGTYPE_STRING))) { |
| Michael K. Edwards | 9bc4a9b | 2011-03-06 17:20:11 +0000 | [diff] [blame] | 194 | pointed_to = (long) (((unsigned long) pointed_to) >> 32); |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 195 | } |
| 196 | } |
| Michael K. Edwards | 9bc4a9b | 2011-03-06 17:20:11 +0000 | [diff] [blame] | 197 | #endif |
| Luis Machado | 55c5feb | 2008-03-12 15:56:01 +0100 | [diff] [blame] | 198 | |
| 199 | *result = pointed_to; |
| 200 | return 0; |
| 201 | } |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 202 | |
| 203 | /* The atomic skip code is mostly taken from GDB. */ |
| 204 | |
| 205 | /* Instruction masks used during single-stepping of atomic |
| 206 | * sequences. This was lifted from GDB. */ |
| 207 | #define LWARX_MASK 0xfc0007fe |
| 208 | #define LWARX_INSTRUCTION 0x7c000028 |
| 209 | #define LDARX_INSTRUCTION 0x7c0000A8 |
| 210 | #define STWCX_MASK 0xfc0007ff |
| 211 | #define STWCX_INSTRUCTION 0x7c00012d |
| 212 | #define STDCX_INSTRUCTION 0x7c0001ad |
| 213 | #define BC_MASK 0xfc000000 |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 214 | #define BC_INSN 0x40000000 |
| 215 | #define BRANCH_MASK 0xfc000000 |
| 216 | |
| 217 | /* In plt.h. XXX make this official interface. */ |
| 218 | int read_target_4(struct Process *proc, target_address_t addr, uint32_t *lp); |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 219 | |
| 220 | int |
| Petr Machata | 9e1e969 | 2012-04-19 02:29:38 +0200 | [diff] [blame] | 221 | arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 222 | int (*add_cb)(void *addr, void *data), |
| 223 | void *add_cb_data) |
| 224 | { |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 225 | target_address_t ip = get_instruction_pointer(proc); |
| 226 | struct breakpoint *other = address2bpstruct(proc->leader, ip); |
| 227 | |
| 228 | debug(1, "arch_atomic_singlestep pid=%d addr=%p %s(%p)", |
| 229 | proc->pid, ip, breakpoint_name(sbp), sbp->addr); |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 230 | |
| 231 | /* If the original instruction was lwarx/ldarx, we can't |
| 232 | * single-step over it, instead we have to execute the whole |
| 233 | * atomic block at once. */ |
| 234 | union { |
| 235 | uint32_t insn; |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 236 | char buf[BREAKPOINT_LENGTH]; |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 237 | } u; |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 238 | if (other != NULL) { |
| 239 | memcpy(u.buf, sbp->orig_value, BREAKPOINT_LENGTH); |
| 240 | } else if (read_target_4(proc, ip, &u.insn) < 0) { |
| 241 | fprintf(stderr, "couldn't read instruction at IP %p\n", ip); |
| 242 | /* Do the normal singlestep. */ |
| 243 | return 1; |
| 244 | } |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 245 | |
| 246 | if ((u.insn & LWARX_MASK) != LWARX_INSTRUCTION |
| 247 | && (u.insn & LWARX_MASK) != LDARX_INSTRUCTION) |
| 248 | return 1; |
| 249 | |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 250 | debug(1, "singlestep over atomic block at %p", ip); |
| 251 | |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 252 | int insn_count; |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 253 | target_address_t addr = ip; |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 254 | for (insn_count = 0; ; ++insn_count) { |
| 255 | addr += 4; |
| 256 | unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0); |
| 257 | if (l == (unsigned long)-1 && errno) |
| 258 | return -1; |
| 259 | uint32_t insn; |
| 260 | #ifdef __powerpc64__ |
| 261 | insn = l >> 32; |
| 262 | #else |
| 263 | insn = l; |
| 264 | #endif |
| 265 | |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 266 | /* If a conditional branch is found, put a breakpoint |
| 267 | * in its destination address. */ |
| 268 | if ((insn & BRANCH_MASK) == BC_INSN) { |
| 269 | int immediate = ((insn & 0xfffc) ^ 0x8000) - 0x8000; |
| 270 | int absolute = insn & 2; |
| 271 | |
| 272 | /* XXX drop the following casts. */ |
| 273 | target_address_t branch_addr; |
| 274 | if (absolute) |
| 275 | branch_addr = (void *)(uintptr_t)immediate; |
| 276 | else |
| 277 | branch_addr = addr + (uintptr_t)immediate; |
| 278 | |
| 279 | debug(1, "pid=%d, branch in atomic block from %p to %p", |
| 280 | proc->pid, addr, branch_addr); |
| 281 | if (add_cb(branch_addr, add_cb_data) < 0) |
| 282 | return -1; |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 283 | } |
| 284 | |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 285 | /* Assume that the atomic sequence ends with a |
| 286 | * stwcx/stdcx instruction. */ |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 287 | if ((insn & STWCX_MASK) == STWCX_INSTRUCTION |
| 288 | || (insn & STWCX_MASK) == STDCX_INSTRUCTION) { |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 289 | debug(1, "pid=%d, found end of atomic block %p at %p", |
| 290 | proc->pid, ip, addr); |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 291 | break; |
| 292 | } |
| 293 | |
| 294 | /* Arbitrary cut-off. If we didn't find the |
| 295 | * terminating instruction by now, just give up. */ |
| 296 | if (insn_count > 16) { |
| Petr Machata | 42748ac | 2012-04-19 04:24:25 +0200 | [diff] [blame] | 297 | fprintf(stderr, "[%d] couldn't find end of atomic block" |
| 298 | " at %p\n", proc->pid, ip); |
| Petr Machata | a266acb | 2012-04-12 23:50:23 +0200 | [diff] [blame] | 299 | return -1; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | /* Put the breakpoint to the next instruction. */ |
| 304 | addr += 4; |
| 305 | if (add_cb(addr, add_cb_data) < 0) |
| 306 | return -1; |
| 307 | |
| 308 | debug(1, "PTRACE_CONT"); |
| 309 | ptrace(PTRACE_CONT, proc->pid, 0, 0); |
| 310 | return 0; |
| 311 | } |