| 1 | |
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 | #include <linux/kernel.h>
|
| 9 | #include <linux/sched.h>
|
| 10 | #include <linux/sched/task_stack.h>
|
| 11 | #include <linux/mm.h>
|
| 12 | #include <linux/smp.h>
|
| 13 | #include <linux/errno.h>
|
| 14 | #include <linux/ptrace.h>
|
| 15 | #include <linux/user.h>
|
| 16 | #include <linux/security.h>
|
| 17 | #include <linux/signal.h>
|
| 18 | #include <linux/audit.h>
|
| 19 | #include <linux/seccomp.h>
|
| 20 | #include <asm/syscall.h>
|
| 21 |
|
| 22 | #include <linux/uaccess.h>
|
| 23 | #include <asm/fpu.h>
|
| 24 |
|
| 25 | #include "proto.h"
|
| 26 | #include <linux/uio.h>
|
| 27 | #include <linux/regset.h>
|
| 28 |
|
| 29 | #define DEBUG DBG_MEM
|
| 30 | #undef DEBUG
|
| 31 |
|
| 32 | #ifndef NT_FPREGSET
|
| 33 | #define NT_FPREGSET NT_PRFPREG
|
| 34 | #endif
|
| 35 |
|
| 36 | #ifdef DEBUG
|
| 37 | enum {
|
| 38 | DBG_MEM = (1<<0),
|
| 39 | DBG_BPT = (1<<1),
|
| 40 | DBG_MEM_ALL = (1<<2)
|
| 41 | };
|
| 42 | #define DBG(fac,args) {if ((fac) & DEBUG) printk args;}
|
| 43 | #else
|
| 44 | #define DBG(fac,args)
|
| 45 | #endif
|
| 46 |
|
| 47 | #define BREAKINST 0x00000080
|
| 48 |
|
| 49 | |
| 50 | * does not yet catch signals sent when the child dies.
|
| 51 | * in exit.c or in signal.c.
|
| 52 |
|
| 53 |
|
| 54 | |
| 55 | * Processes always block with the following stack-layout:
|
| 56 | *
|
| 57 | * +================================+ <---- task + 2*PAGE_SIZE
|
| 58 | * | PALcode saved frame (ps, pc, | ^
|
| 59 | * | gp, a0, a1, a2) | |
|
| 60 | * +================================+ | struct pt_regs
|
| 61 | * | | |
|
| 62 | * | frame generated by SAVE_ALL | |
|
| 63 | * | | v
|
| 64 | * +================================+
|
| 65 | * | | ^
|
| 66 | * | frame saved by do_switch_stack | | struct switch_stack
|
| 67 | * | | v
|
| 68 | * +================================+
|
| 69 |
|
| 70 |
|
| 71 | |
| 72 | * The following table maps a register index into the stack offset at
|
| 73 | * which the register is saved. Register indices are 0-31 for integer
|
| 74 | * regs, 32-63 for fp regs, and 64 for the pc. Notice that sp and
|
| 75 | * zero have no stack-slot and need to be treated specially (see
|
| 76 | * get_reg/put_reg below).
|
| 77 |
|
| 78 | enum {
|
| 79 | REG_R0 = 0, REG_F0 = 32, REG_FPCR = 63, REG_PC = 64
|
| 80 | };
|
| 81 |
|
| 82 | #define PT_REG(reg) \
|
| 83 | (PAGE_SIZE*2 - sizeof(struct pt_regs) + offsetof(struct pt_regs, reg))
|
| 84 |
|
| 85 | #define SW_REG(reg) \
|
| 86 | (PAGE_SIZE*2 - sizeof(struct pt_regs) - sizeof(struct switch_stack) \
|
| 87 | + offsetof(struct switch_stack, reg))
|
| 88 |
|
| 89 | #define FP_REG(reg) (offsetof(struct thread_info, reg))
|
| 90 |
|
| 91 | static int regoff[] = {
|
| 92 | PT_REG( r0), PT_REG( r1), PT_REG( r2), PT_REG( r3),
|
| 93 | PT_REG( r4), PT_REG( r5), PT_REG( r6), PT_REG( r7),
|
| 94 | PT_REG( r8), SW_REG( r9), SW_REG( r10), SW_REG( r11),
|
| 95 | SW_REG( r12), SW_REG( r13), SW_REG( r14), SW_REG( r15),
|
| 96 | PT_REG( r16), PT_REG( r17), PT_REG( r18), PT_REG( r19),
|
| 97 | PT_REG( r20), PT_REG( r21), PT_REG( r22), PT_REG( r23),
|
| 98 | PT_REG( r24), PT_REG( r25), PT_REG( r26), PT_REG( r27),
|
| 99 | PT_REG( r28), PT_REG( gp), -1, -1,
|
| 100 | FP_REG(fp[ 0]), FP_REG(fp[ 1]), FP_REG(fp[ 2]), FP_REG(fp[ 3]),
|
| 101 | FP_REG(fp[ 4]), FP_REG(fp[ 5]), FP_REG(fp[ 6]), FP_REG(fp[ 7]),
|
| 102 | FP_REG(fp[ 8]), FP_REG(fp[ 9]), FP_REG(fp[10]), FP_REG(fp[11]),
|
| 103 | FP_REG(fp[12]), FP_REG(fp[13]), FP_REG(fp[14]), FP_REG(fp[15]),
|
| 104 | FP_REG(fp[16]), FP_REG(fp[17]), FP_REG(fp[18]), FP_REG(fp[19]),
|
| 105 | FP_REG(fp[20]), FP_REG(fp[21]), FP_REG(fp[22]), FP_REG(fp[23]),
|
| 106 | FP_REG(fp[24]), FP_REG(fp[25]), FP_REG(fp[26]), FP_REG(fp[27]),
|
| 107 | FP_REG(fp[28]), FP_REG(fp[29]), FP_REG(fp[30]), FP_REG(fp[31]),
|
| 108 | PT_REG( pc)
|
| 109 | };
|
| 110 |
|
| 111 | static unsigned long zero;
|
| 112 |
|
| 113 | |
| 114 | * Get address of register REGNO in task TASK.
|
| 115 |
|
| 116 | static unsigned long *
|
| 117 | get_reg_addr(struct task_struct * task, unsigned long regno)
|
| 118 | {
|
| 119 | unsigned long *addr;
|
| 120 |
|
| 121 | if (regno == 30) {
|
| 122 | addr = &task_thread_info(task)->pcb.usp;
|
| 123 | } else if (regno == 65) {
|
| 124 | addr = &task_thread_info(task)->pcb.unique;
|
| 125 | } else if (regno == 31 || regno > 65) {
|
| 126 | zero = 0;
|
| 127 | addr = &zero;
|
| 128 | } else {
|
| 129 | addr = task_stack_page(task) + regoff[regno];
|
| 130 | }
|
| 131 | return addr;
|
| 132 | }
|
| 133 |
|
| 134 | |
| 135 | * Get contents of register REGNO in task TASK.
|
| 136 |
|
| 137 |
|
| 138 | static bool
|
| 139 | valid_regno(unsigned long regno)
|
| 140 | {
|
| 141 | return regno <= 65;
|
| 142 | }
|
| 143 |
|
| 144 | static long
|
| 145 | get_reg(struct task_struct *task, unsigned long regno)
|
| 146 | {
|
| 147 | unsigned long *addr;
|
| 148 |
|
| 149 | if (!valid_regno(regno))
|
| 150 | return -EIO;
|
| 151 |
|
| 152 | |
| 153 | * Special hack for fpcr -- combine hardware and software bits.
|
| 154 |
|
| 155 | if (regno == 63) {
|
| 156 | unsigned long fpcr;
|
| 157 | unsigned long swcr;
|
| 158 |
|
| 159 | addr = get_reg_addr(task, regno);
|
| 160 | if (!addr)
|
| 161 | return -EIO;
|
| 162 |
|
| 163 | fpcr = *addr;
|
| 164 | swcr = task_thread_info(task)->ieee_state & IEEE_SW_MASK;
|
| 165 | swcr = swcr_update_status(swcr, fpcr);
|
| 166 |
|
| 167 | return fpcr | swcr;
|
| 168 | }
|
| 169 |
|
| 170 | addr = get_reg_addr(task, regno);
|
| 171 | if (!addr)
|
| 172 | return -EIO;
|
| 173 |
|
| 174 | return *addr;
|
| 175 | }
|
| 176 |
|
| 177 | static void alpha_elf_fpregs_get(struct task_struct *target,
|
| 178 | elf_fpreg_t *fpregs)
|
| 179 | {
|
| 180 | memcpy(fpregs, task_thread_info(target)->fp, sizeof(elf_fpregset_t));
|
| 181 | }
|
| 182 |
|
| 183 | static void alpha_elf_fpregs_set(struct task_struct *target,
|
| 184 | const elf_fpreg_t *fpregs,
|
| 185 | size_t nwords)
|
| 186 | {
|
| 187 | size_t n = min_t(size_t, nwords, ELF_NFPREG);
|
| 188 |
|
| 189 | memcpy(task_thread_info(target)->fp, fpregs, n * sizeof(elf_fpreg_t));
|
| 190 | }
|
| 191 |
|
| 192 | static void alpha_elf_gregs_set(struct task_struct *child,
|
| 193 | const elf_greg_t *src,
|
| 194 | size_t nwords)
|
| 195 | {
|
| 196 | struct pt_regs *pt = task_pt_regs(child);
|
| 197 | struct thread_info *ti = task_thread_info(child);
|
| 198 | struct switch_stack *sw = ((struct switch_stack *)pt) - 1;
|
| 199 |
|
| 200 |
|
| 201 | if (nwords > 0)
|
| 202 | pt->r0 = src[0];
|
| 203 | if (nwords > 1)
|
| 204 | pt->r1 = src[1];
|
| 205 | if (nwords > 2)
|
| 206 | pt->r2 = src[2];
|
| 207 | if (nwords > 3)
|
| 208 | pt->r3 = src[3];
|
| 209 | if (nwords > 4)
|
| 210 | pt->r4 = src[4];
|
| 211 | if (nwords > 5)
|
| 212 | pt->r5 = src[5];
|
| 213 | if (nwords > 6)
|
| 214 | pt->r6 = src[6];
|
| 215 | if (nwords > 7)
|
| 216 | pt->r7 = src[7];
|
| 217 | if (nwords > 8)
|
| 218 | pt->r8 = src[8];
|
| 219 |
|
| 220 |
|
| 221 | if (nwords > 9)
|
| 222 | sw->r9 = src[9];
|
| 223 | if (nwords > 10)
|
| 224 | sw->r10 = src[10];
|
| 225 | if (nwords > 11)
|
| 226 | sw->r11 = src[11];
|
| 227 | if (nwords > 12)
|
| 228 | sw->r12 = src[12];
|
| 229 | if (nwords > 13)
|
| 230 | sw->r13 = src[13];
|
| 231 | if (nwords > 14)
|
| 232 | sw->r14 = src[14];
|
| 233 | if (nwords > 15)
|
| 234 | sw->r15 = src[15];
|
| 235 |
|
| 236 |
|
| 237 | if (nwords > 16)
|
| 238 | pt->r16 = src[16];
|
| 239 | if (nwords > 17)
|
| 240 | pt->r17 = src[17];
|
| 241 | if (nwords > 18)
|
| 242 | pt->r18 = src[18];
|
| 243 | if (nwords > 19)
|
| 244 | pt->r19 = src[19];
|
| 245 | if (nwords > 20)
|
| 246 | pt->r20 = src[20];
|
| 247 | if (nwords > 21)
|
| 248 | pt->r21 = src[21];
|
| 249 | if (nwords > 22)
|
| 250 | pt->r22 = src[22];
|
| 251 | if (nwords > 23)
|
| 252 | pt->r23 = src[23];
|
| 253 | if (nwords > 24)
|
| 254 | pt->r24 = src[24];
|
| 255 | if (nwords > 25)
|
| 256 | pt->r25 = src[25];
|
| 257 | if (nwords > 26)
|
| 258 | pt->r26 = src[26];
|
| 259 | if (nwords > 27)
|
| 260 | pt->r27 = src[27];
|
| 261 | if (nwords > 28)
|
| 262 | pt->r28 = src[28];
|
| 263 |
|
| 264 |
|
| 265 | if (nwords > 29)
|
| 266 | pt->gp = src[29];
|
| 267 |
|
| 268 | if (nwords > 30) {
|
| 269 | ti->pcb.usp = src[30];
|
| 270 | |
| 271 | * If someone ever does this to current (rare), keep the
|
| 272 | * hardware usp consistent.
|
| 273 |
|
| 274 | if (child == current)
|
| 275 | wrusp(src[30]);
|
| 276 | }
|
| 277 |
|
| 278 | if (nwords > 31)
|
| 279 | pt->pc = src[31];
|
| 280 |
|
| 281 | if (nwords > 32)
|
| 282 | ti->pcb.unique = src[32];
|
| 283 |
|
| 284 | |
| 285 | * PTRACE_SETREGSET can be used at a syscall-entry stop to skip the
|
| 286 | * syscall by setting the syscall number to -1. The seccomp/ptrace
|
| 287 | * selftests use this to synthesize errno returns.
|
| 288 | *
|
| 289 | * Alpha uses r19/a3 as the error flag, so a skipped syscall with a
|
| 290 | * small positive r0 and a clear r19 must be normalized to an error
|
| 291 | * return.
|
| 292 |
|
| 293 | if (pt->r1 == (unsigned long)-1 &&
|
| 294 | pt->r19 == 0 &&
|
| 295 | pt->r0 > 0 &&
|
| 296 | pt->r0 < MAX_ERRNO)
|
| 297 | pt->r19 = 1;
|
| 298 | }
|
| 299 |
|
| 300 | static int
|
| 301 | put_reg(struct task_struct *task, unsigned long regno, unsigned long data)
|
| 302 | {
|
| 303 | struct pt_regs *regs = task_pt_regs(task);
|
| 304 | unsigned long *addr;
|
| 305 | unsigned long old_r0 = regs->r0;
|
| 306 |
|
| 307 | if (regno == 31)
|
| 308 | return 0;
|
| 309 | if (!valid_regno(regno))
|
| 310 | return -EIO;
|
| 311 |
|
| 312 | if (regno == 63) {
|
| 313 | task_thread_info(task)->ieee_state
|
| 314 | = ((task_thread_info(task)->ieee_state & ~IEEE_SW_MASK)
|
| 315 | | (data & IEEE_SW_MASK));
|
| 316 | data = (data & FPCR_DYN_MASK) | ieee_swcr_to_fpcr(data);
|
| 317 | }
|
| 318 |
|
| 319 | addr = get_reg_addr(task, regno);
|
| 320 | if (!addr)
|
| 321 | return -EIO;
|
| 322 |
|
| 323 | *addr = data;
|
| 324 |
|
| 325 | |
| 326 | * Alpha historically exposes r0/v0 as the syscall number at a
|
| 327 | * syscall-entry stop. The generic-entry conversion keeps the
|
| 328 | * mutable syscall number in regs->r1.
|
| 329 |
|
| 330 |
|
| 331 | if (regno == 0 && regs->r1 == old_r0 &&
|
| 332 | (data == (unsigned long)-1 ||
|
| 333 | (regs->r19 == 0 && data < NR_syscalls))) {
|
| 334 | regs->r1 = data;
|
| 335 |
|
| 336 | |
| 337 | * Keep the skip path looking like a clean entry-side syscall
|
| 338 | * rewrite. Do not touch r19 for ordinary syscall substitution.
|
| 339 |
|
| 340 | if (data == (unsigned long)-1)
|
| 341 | regs->r19 = 0;
|
| 342 | }
|
| 343 | return 0;
|
| 344 | }
|
| 345 |
|
| 346 | static inline int
|
| 347 | read_int(struct task_struct *task, unsigned long addr, int * data)
|
| 348 | {
|
| 349 | int copied = access_process_vm(task, addr, data, sizeof(int),
|
| 350 | FOLL_FORCE);
|
| 351 | return (copied == sizeof(int)) ? 0 : -EIO;
|
| 352 | }
|
| 353 |
|
| 354 | static inline int
|
| 355 | write_int(struct task_struct *task, unsigned long addr, int data)
|
| 356 | {
|
| 357 | int copied = access_process_vm(task, addr, &data, sizeof(int),
|
| 358 | FOLL_FORCE | FOLL_WRITE);
|
| 359 | return (copied == sizeof(int)) ? 0 : -EIO;
|
| 360 | }
|
| 361 |
|
| 362 | |
| 363 | * Set breakpoint.
|
| 364 |
|
| 365 | int
|
| 366 | ptrace_set_bpt(struct task_struct * child)
|
| 367 | {
|
| 368 | int displ, i, res, reg_b, nsaved = 0;
|
| 369 | unsigned int insn, op_code;
|
| 370 | unsigned long pc;
|
| 371 |
|
| 372 | pc = get_reg(child, REG_PC);
|
| 373 | res = read_int(child, pc, (int *) &insn);
|
| 374 | if (res < 0)
|
| 375 | return res;
|
| 376 |
|
| 377 | op_code = insn >> 26;
|
| 378 | if (op_code >= 0x30) {
|
| 379 | |
| 380 | * It's a branch: instead of trying to figure out
|
| 381 | * whether the branch will be taken or not, we'll put
|
| 382 | * a breakpoint at either location. This is simpler,
|
| 383 | * more reliable, and probably not a whole lot slower
|
| 384 | * than the alternative approach of emulating the
|
| 385 | * branch (emulation can be tricky for fp branches).
|
| 386 |
|
| 387 | displ = ((s32)(insn << 11)) >> 9;
|
| 388 | task_thread_info(child)->bpt_addr[nsaved++] = pc + 4;
|
| 389 | if (displ)
|
| 390 | task_thread_info(child)->bpt_addr[nsaved++]
|
| 391 | = pc + 4 + displ;
|
| 392 | DBG(DBG_BPT, ("execing branch\n"));
|
| 393 | } else if (op_code == 0x1a) {
|
| 394 | reg_b = (insn >> 16) & 0x1f;
|
| 395 | task_thread_info(child)->bpt_addr[nsaved++] = get_reg(child, reg_b);
|
| 396 | DBG(DBG_BPT, ("execing jump\n"));
|
| 397 | } else {
|
| 398 | task_thread_info(child)->bpt_addr[nsaved++] = pc + 4;
|
| 399 | DBG(DBG_BPT, ("execing normal insn\n"));
|
| 400 | }
|
| 401 |
|
| 402 |
|
| 403 | for (i = 0; i < nsaved; ++i) {
|
| 404 | res = read_int(child, task_thread_info(child)->bpt_addr[i],
|
| 405 | (int *) &insn);
|
| 406 | if (res < 0)
|
| 407 | return res;
|
| 408 | task_thread_info(child)->bpt_insn[i] = insn;
|
| 409 | DBG(DBG_BPT, (" -> next_pc=%lx\n",
|
| 410 | task_thread_info(child)->bpt_addr[i]));
|
| 411 | res = write_int(child, task_thread_info(child)->bpt_addr[i],
|
| 412 | BREAKINST);
|
| 413 | if (res < 0)
|
| 414 | return res;
|
| 415 | }
|
| 416 | task_thread_info(child)->bpt_nsaved = nsaved;
|
| 417 | return 0;
|
| 418 | }
|
| 419 |
|
| 420 | |
| 421 | * Ensure no single-step breakpoint is pending. Returns non-zero
|
| 422 | * value if child was being single-stepped.
|
| 423 |
|
| 424 | int
|
| 425 | ptrace_cancel_bpt(struct task_struct * child)
|
| 426 | {
|
| 427 | int i, nsaved = task_thread_info(child)->bpt_nsaved;
|
| 428 |
|
| 429 | task_thread_info(child)->bpt_nsaved = 0;
|
| 430 |
|
| 431 | if (nsaved > 2) {
|
| 432 | printk("ptrace_cancel_bpt: bogus nsaved: %d!\n", nsaved);
|
| 433 | nsaved = 2;
|
| 434 | }
|
| 435 |
|
| 436 | for (i = 0; i < nsaved; ++i) {
|
| 437 | write_int(child, task_thread_info(child)->bpt_addr[i],
|
| 438 | task_thread_info(child)->bpt_insn[i]);
|
| 439 | }
|
| 440 | return (nsaved != 0);
|
| 441 | }
|
| 442 |
|
| 443 | |
| 444 | * PALcode keeps the user stack pointer in the PCB, not the trap frame.
|
| 445 | * A user-mode pt_regs sits at the top of its task's stack, so the
|
| 446 | * thread_info is at the base.
|
| 447 |
|
| 448 | unsigned long user_stack_pointer(struct pt_regs *regs)
|
| 449 | {
|
| 450 | struct thread_info *ti = (void *)((unsigned long)regs & -THREAD_SIZE);
|
| 451 |
|
| 452 | return ti == current_thread_info() ? rdusp() : ti->pcb.usp;
|
| 453 | }
|
| 454 |
|
| 455 | void user_enable_single_step(struct task_struct *child)
|
| 456 | {
|
| 457 |
|
| 458 | task_thread_info(child)->bpt_nsaved = -1;
|
| 459 | }
|
| 460 |
|
| 461 | void user_disable_single_step(struct task_struct *child)
|
| 462 | {
|
| 463 | ptrace_cancel_bpt(child);
|
| 464 | }
|
| 465 |
|
| 466 | |
| 467 | * Called by kernel/ptrace.c when detaching..
|
| 468 | *
|
| 469 | * Make sure the single step bit is not set.
|
| 470 |
|
| 471 | void ptrace_disable(struct task_struct *child)
|
| 472 | {
|
| 473 | user_disable_single_step(child);
|
| 474 | }
|
| 475 |
|
| 476 | long arch_ptrace(struct task_struct *child, long request,
|
| 477 | unsigned long addr, unsigned long data)
|
| 478 | {
|
| 479 | unsigned long tmp;
|
| 480 | size_t copied;
|
| 481 | long ret;
|
| 482 |
|
| 483 | switch (request) {
|
| 484 |
|
| 485 | case PTRACE_PEEKTEXT:
|
| 486 | case PTRACE_PEEKDATA:
|
| 487 | copied = ptrace_access_vm(child, addr, &tmp, sizeof(tmp),
|
| 488 | FOLL_FORCE);
|
| 489 | ret = -EIO;
|
| 490 | if (copied != sizeof(tmp))
|
| 491 | break;
|
| 492 | force_successful_syscall_return();
|
| 493 | ret = tmp;
|
| 494 | break;
|
| 495 |
|
| 496 | case PTRACE_PEEKUSR:
|
| 497 | ret = get_reg(child, addr);
|
| 498 | if (ret == -EIO)
|
| 499 | break;
|
| 500 |
|
| 501 | force_successful_syscall_return();
|
| 502 | break;
|
| 503 |
|
| 504 | case PTRACE_POKETEXT:
|
| 505 | case PTRACE_POKEDATA:
|
| 506 | ret = generic_ptrace_pokedata(child, addr, data);
|
| 507 | break;
|
| 508 |
|
| 509 | case PTRACE_POKEUSR:
|
| 510 | DBG(DBG_MEM, ("poke $%lu<-%#lx\n", addr, data));
|
| 511 | ret = put_reg(child, addr, data);
|
| 512 | break;
|
| 513 | default:
|
| 514 | ret = ptrace_request(child, request, addr, data);
|
| 515 | break;
|
| 516 | }
|
| 517 | return ret;
|
| 518 | }
|
| 519 |
|
| 520 | |
| 521 | * Minimal regset support for Alpha.
|
| 522 | *
|
| 523 | * Alpha-specific notes:
|
| 524 | * - Do NOT use ELF_CORE_COPY_REGS(): it uses current_thread_info(),
|
| 525 | * which is wrong for non-current tasks.
|
| 526 | * - dump_elf_task() returns 1 unconditionally in this tree, while
|
| 527 | * regset_get should return 0 on success. So call dump_elf_thread()
|
| 528 | * directly and return membuf_write()'s result.
|
| 529 |
|
| 530 | static int alpha_regset_set(struct task_struct *target,
|
| 531 | const struct user_regset *regset,
|
| 532 | unsigned int pos, unsigned int count,
|
| 533 | const void *kbuf,
|
| 534 | const void __user *ubuf)
|
| 535 | {
|
| 536 | elf_gregset_t gregs;
|
| 537 | unsigned int nwords;
|
| 538 |
|
| 539 | if (pos + count > sizeof(gregs))
|
| 540 | return -EIO;
|
| 541 |
|
| 542 | |
| 543 | * Preserve registers outside the written range.
|
| 544 |
|
| 545 | dump_elf_thread(gregs, task_pt_regs(target),
|
| 546 | task_thread_info(target));
|
| 547 |
|
| 548 | if (user_regset_copyin(&pos, &count, &kbuf, &ubuf,
|
| 549 | gregs, 0, sizeof(gregs)))
|
| 550 | return -EFAULT;
|
| 551 |
|
| 552 | nwords = sizeof(gregs) / sizeof(elf_greg_t);
|
| 553 | alpha_elf_gregs_set(target, gregs, nwords);
|
| 554 |
|
| 555 | return 0;
|
| 556 | }
|
| 557 |
|
| 558 | static int alpha_fpregset_set(struct task_struct *target,
|
| 559 | const struct user_regset *regset,
|
| 560 | unsigned int pos, unsigned int count,
|
| 561 | const void *kbuf,
|
| 562 | const void __user *ubuf)
|
| 563 | {
|
| 564 | elf_fpregset_t fpregs;
|
| 565 | unsigned int nwords;
|
| 566 |
|
| 567 | if (pos + count > sizeof(fpregs))
|
| 568 | return -EIO;
|
| 569 |
|
| 570 | alpha_elf_fpregs_get(target, fpregs);
|
| 571 |
|
| 572 | if (user_regset_copyin(&pos, &count, &kbuf, &ubuf,
|
| 573 | fpregs, 0, sizeof(fpregs)))
|
| 574 | return -EFAULT;
|
| 575 |
|
| 576 | nwords = sizeof(fpregs) / sizeof(elf_fpreg_t);
|
| 577 | alpha_elf_fpregs_set(target, fpregs, nwords);
|
| 578 |
|
| 579 | return 0;
|
| 580 | }
|
| 581 |
|
| 582 | static int alpha_regset_get(struct task_struct *target,
|
| 583 | const struct user_regset *regset,
|
| 584 | struct membuf to)
|
| 585 | {
|
| 586 | struct pt_regs *pt = task_pt_regs(target);
|
| 587 | struct thread_info *ti = task_thread_info(target);
|
| 588 | elf_gregset_t gregs;
|
| 589 |
|
| 590 | dump_elf_thread(gregs, pt, ti);
|
| 591 | return membuf_write(&to, gregs, sizeof(gregs));
|
| 592 | }
|
| 593 |
|
| 594 | static int alpha_fpregset_get(struct task_struct *target,
|
| 595 | const struct user_regset *regset,
|
| 596 | struct membuf to)
|
| 597 | {
|
| 598 | elf_fpregset_t fpregs;
|
| 599 |
|
| 600 | alpha_elf_fpregs_get(target, fpregs);
|
| 601 | return membuf_write(&to, fpregs, sizeof(fpregs));
|
| 602 | }
|
| 603 |
|
| 604 | enum alpha_regset {
|
| 605 | REGSET_GPR,
|
| 606 | REGSET_FPR,
|
| 607 | };
|
| 608 |
|
| 609 | static const struct user_regset alpha_user_regsets[] = {
|
| 610 | [REGSET_GPR] = {
|
| 611 | .core_note_type = NT_PRSTATUS,
|
| 612 | .n = ELF_NGREG,
|
| 613 | .size = sizeof(elf_greg_t),
|
| 614 | .align = sizeof(elf_greg_t),
|
| 615 | .regset_get = alpha_regset_get,
|
| 616 | .set = alpha_regset_set,
|
| 617 | },
|
| 618 | [REGSET_FPR] = {
|
| 619 | .core_note_type = NT_PRFPREG,
|
| 620 | .core_note_name = "CORE",
|
| 621 | .n = ELF_NFPREG,
|
| 622 | .size = sizeof(elf_fpreg_t),
|
| 623 | .align = sizeof(elf_fpreg_t),
|
| 624 | .regset_get = alpha_fpregset_get,
|
| 625 | .set = alpha_fpregset_set,
|
| 626 | },
|
| 627 | };
|
| 628 |
|
| 629 | static const struct user_regset_view user_alpha_view = {
|
| 630 | .name = "alpha",
|
| 631 | .e_machine = EM_ALPHA,
|
| 632 | .ei_osabi = ELF_OSABI,
|
| 633 | .regsets = alpha_user_regsets,
|
| 634 | .n = ARRAY_SIZE(alpha_user_regsets),
|
| 635 | };
|
| 636 |
|
| 637 | const struct user_regset_view *task_user_regset_view(struct task_struct *task)
|
| 638 | {
|
| 639 | return &user_alpha_view;
|
| 640 | }
|