/* SPU native-dependent code for GDB, the GNU debugger. Copyright (C) 2006 Free Software Foundation, Inc. Contributed by Ulrich Weigand . This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "defs.h" #include "gdbcore.h" #include "gdb_string.h" #include "target.h" #include "inferior.h" #include "inf-ptrace.h" #include "regcache.h" #include "symfile.h" #include "gdb_wait.h" #include #include #include #include #include "spu-tdep.h" /* PPU side system calls. */ #define INSTR_SC 0x44000002 #define NR_spu_run 0x0116 /* Fetch PPU register REGNO. */ static CORE_ADDR fetch_ppc_register (int regno) { PTRACE_TYPE_RET res; int tid = TIDGET (inferior_ptid); if (tid == 0) tid = PIDGET (inferior_ptid); #ifndef __powerpc64__ /* If running as a 32-bit process on a 64-bit system, we attempt to get the full 64-bit register content of the target process. If the PPC special ptrace call fails, we're on a 32-bit system; just fall through to the regular ptrace call in that case. */ { gdb_byte buf[8]; errno = 0; ptrace (PPC_PTRACE_PEEKUSR_3264, tid, (PTRACE_TYPE_ARG3) (regno * 8), buf); if (errno == 0) ptrace (PPC_PTRACE_PEEKUSR_3264, tid, (PTRACE_TYPE_ARG3) (regno * 8 + 4), buf + 4); if (errno == 0) return (CORE_ADDR) *(unsigned long long *)buf; } #endif errno = 0; res = ptrace (PT_READ_U, tid, (PTRACE_TYPE_ARG3) (regno * sizeof (PTRACE_TYPE_RET)), 0); if (errno != 0) { char mess[128]; xsnprintf (mess, sizeof mess, "reading PPC register #%d", regno); perror_with_name (_(mess)); } return (CORE_ADDR) (unsigned long) res; } /* Fetch WORD from PPU memory at (aligned) MEMADDR in thread TID. */ static int fetch_ppc_memory_1 (int tid, CORE_ADDR memaddr, PTRACE_TYPE_RET *word) { errno = 0; #ifndef __powerpc64__ if (memaddr >> 32) { unsigned long long addr_8 = (unsigned long long) memaddr; ptrace (PPC_PTRACE_PEEKTEXT_3264, tid, (PTRACE_TYPE_ARG3) &addr_8, word); } else #endif *word = ptrace (PT_READ_I, tid, (PTRACE_TYPE_ARG3) (size_t) memaddr, 0); return errno; } /* Store WORD into PPU memory at (aligned) MEMADDR in thread TID. */ static int store_ppc_memory_1 (int tid, CORE_ADDR memaddr, PTRACE_TYPE_RET word) { errno = 0; #ifndef __powerpc64__ if (memaddr >> 32) { unsigned long long addr_8 = (unsigned long long) memaddr; ptrace (PPC_PTRACE_POKEDATA_3264, tid, (PTRACE_TYPE_ARG3) &addr_8, word); } else #endif ptrace (PT_WRITE_D, tid, (PTRACE_TYPE_ARG3) (size_t) memaddr, word); return errno; } /* Fetch LEN bytes of PPU memory at MEMADDR to MYADDR. */ static int fetch_ppc_memory (CORE_ADDR memaddr, gdb_byte *myaddr, int len) { int i, ret; CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_TYPE_RET); int count = ((((memaddr + len) - addr) + sizeof (PTRACE_TYPE_RET) - 1) / sizeof (PTRACE_TYPE_RET)); PTRACE_TYPE_RET *buffer; int tid = TIDGET (inferior_ptid); if (tid == 0) tid = PIDGET (inferior_ptid); buffer = (PTRACE_TYPE_RET *) alloca (count * sizeof (PTRACE_TYPE_RET)); for (i = 0; i < count; i++, addr += sizeof (PTRACE_TYPE_RET)) if ((ret = fetch_ppc_memory_1 (tid, addr, &buffer[i])) != 0) return ret; memcpy (myaddr, (char *) buffer + (memaddr & (sizeof (PTRACE_TYPE_RET) - 1)), len); return 0; } /* Store LEN bytes from MYADDR to PPU memory at MEMADDR. */ static int store_ppc_memory (CORE_ADDR memaddr, const gdb_byte *myaddr, int len) { int i, ret; CORE_ADDR addr = memaddr & -(CORE_ADDR) sizeof (PTRACE_TYPE_RET); int count = ((((memaddr + len) - addr) + sizeof (PTRACE_TYPE_RET) - 1) / sizeof (PTRACE_TYPE_RET)); PTRACE_TYPE_RET *buffer; int tid = TIDGET (inferior_ptid); if (tid == 0) tid = PIDGET (inferior_ptid); buffer = (PTRACE_TYPE_RET *) alloca (count * sizeof (PTRACE_TYPE_RET)); if (addr != memaddr || len < (int) sizeof (PTRACE_TYPE_RET)) if ((ret = fetch_ppc_memory_1 (tid, addr, &buffer[0])) != 0) return ret; if (count > 1) if ((ret = fetch_ppc_memory_1 (tid, addr + (count - 1) * sizeof (PTRACE_TYPE_RET), &buffer[count - 1])) != 0) return ret; memcpy ((char *) buffer + (memaddr & (sizeof (PTRACE_TYPE_RET) - 1)), myaddr, len); for (i = 0; i < count; i++, addr += sizeof (PTRACE_TYPE_RET)) if ((ret = store_ppc_memory_1 (tid, addr, buffer[i])) != 0) return ret; return 0; } /* If the PPU thread is currently stopped on a spu_run system call, return to FD and ADDR the file handle and NPC parameter address used with the system call. Return non-zero if successful. */ static int parse_spufs_run (int *fd, CORE_ADDR *addr) { gdb_byte buf[4]; CORE_ADDR pc = fetch_ppc_register (32); /* nip */ /* Fetch instruction preceding current NIP. */ if (fetch_ppc_memory (pc-4, buf, 4) != 0) return 0; /* It should be a "sc" instruction. */ if (extract_unsigned_integer (buf, 4) != INSTR_SC) return 0; /* System call number should be NR_spu_run. */ if (fetch_ppc_register (0) != NR_spu_run) return 0; /* Register 3 contains fd, register 4 the NPC param pointer. */ *fd = fetch_ppc_register (34); /* orig_gpr3 */ *addr = fetch_ppc_register (4); return 1; } /* Copy LEN bytes at OFFSET in spufs file ANNEX into/from READBUF or WRITEBUF, using the /proc file system. */ static LONGEST spu_proc_xfer_spu (const char *annex, gdb_byte *readbuf, const gdb_byte *writebuf, ULONGEST offset, LONGEST len) { char buf[128]; int fd = 0; int ret = -1; int pid = PIDGET (inferior_ptid); if (!annex) return 0; xsnprintf (buf, sizeof buf, "/proc/%d/fd/%s", pid, annex); fd = open (buf, writebuf? O_WRONLY : O_RDONLY); if (fd <= 0) return -1; if (offset != 0 && lseek (fd, (off_t) offset, SEEK_SET) != (off_t) offset) { close (fd); return -1; } if (writebuf) ret = write (fd, writebuf, (size_t) len); else if (readbuf) ret = read (fd, readbuf, (size_t) len); close (fd); return ret; } /* Inferior memory should contain an SPE executable image at location ADDR. Allocate a BFD representing that executable. Return NULL on error. */ static void * spu_bfd_iovec_open (struct bfd *nbfd, void *open_closure) { return open_closure; } static int spu_bfd_iovec_close (struct bfd *nbfd, void *stream) { xfree (stream); return 1; } static file_ptr spu_bfd_iovec_pread (struct bfd *abfd, void *stream, void *buf, file_ptr nbytes, file_ptr offset) { CORE_ADDR addr = *(CORE_ADDR *)stream; if (fetch_ppc_memory (addr + offset, buf, nbytes) != 0) { bfd_set_error (bfd_error_invalid_operation); return -1; } return nbytes; } static bfd * spu_bfd_open (CORE_ADDR addr) { struct bfd *nbfd; CORE_ADDR *open_closure = xmalloc (sizeof (CORE_ADDR)); *open_closure = addr; nbfd = bfd_openr_iovec (xstrdup (""), "elf32-spu", spu_bfd_iovec_open, open_closure, spu_bfd_iovec_pread, spu_bfd_iovec_close); if (!nbfd) return NULL; if (!bfd_check_format (nbfd, bfd_object)) { bfd_close (nbfd); return NULL; } return nbfd; } /* INFERIOR_FD is a file handle passed by the inferior to the spu_run system call. Assuming the SPE context was allocated by the libspe library, try to retrieve the main SPE executable file from its copy within the target process. */ static void spu_symbol_file_add_from_memory (int inferior_fd) { CORE_ADDR addr; struct bfd *nbfd; char id[128]; char annex[32]; int len; /* Read object ID. */ xsnprintf (annex, sizeof annex, "%d/object-id", inferior_fd); len = spu_proc_xfer_spu (annex, id, NULL, 0, sizeof id); if (len <= 0 || len >= sizeof id) return; id[len] = 0; if (sscanf (id, "0x%llx", &addr) != 1) return; /* Open BFD representing SPE executable and read its symbols. */ nbfd = spu_bfd_open (addr); if (nbfd) symbol_file_add_from_bfd (nbfd, 0, NULL, 1, 0); } /* Override the post_startup_inferior routine to continue running the inferior until the first spu_run system call. */ static void spu_child_post_startup_inferior (ptid_t ptid) { int fd; CORE_ADDR addr; int tid = TIDGET (ptid); if (tid == 0) tid = PIDGET (ptid); while (!parse_spufs_run (&fd, &addr)) { ptrace (PT_SYSCALL, tid, (PTRACE_TYPE_ARG3) 0, 0); waitpid (tid, NULL, __WALL | __WNOTHREAD); } } /* Override the post_attach routine to try load the SPE executable file image from its copy inside the target process. */ static void spu_child_post_attach (int pid) { int fd; CORE_ADDR addr; /* Like child_post_startup_inferior, if we happened to attach to the inferior while it wasn't currently in spu_run, continue running it until we get back there. */ while (!parse_spufs_run (&fd, &addr)) { ptrace (PT_SYSCALL, pid, (PTRACE_TYPE_ARG3) 0, 0); waitpid (pid, NULL, __WALL | __WNOTHREAD); } /* If the user has not provided an executable file, try to extract the image from inside the target process. */ if (!get_exec_file (0)) spu_symbol_file_add_from_memory (fd); } /* Wait for child PTID to do something. Return id of the child, minus_one_ptid in case of error; store status into *OURSTATUS. */ static ptid_t spu_child_wait (ptid_t ptid, struct target_waitstatus *ourstatus) { int save_errno; int status; pid_t pid; do { set_sigint_trap (); /* Causes SIGINT to be passed on to the attached process. */ set_sigio_trap (); pid = waitpid (PIDGET (ptid), &status, 0); if (pid == -1 && errno == ECHILD) /* Try again with __WCLONE to check cloned processes. */ pid = waitpid (PIDGET (ptid), &status, __WCLONE); save_errno = errno; /* Make sure we don't report an event for the exit of the original program, if we've detached from it. */ if (pid != -1 && !WIFSTOPPED (status) && pid != PIDGET (inferior_ptid)) { pid = -1; save_errno = EINTR; } clear_sigio_trap (); clear_sigint_trap (); } while (pid == -1 && save_errno == EINTR); if (pid == -1) { warning ("Child process unexpectedly missing: %s", safe_strerror (save_errno)); /* Claim it exited with unknown signal. */ ourstatus->kind = TARGET_WAITKIND_SIGNALLED; ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN; return minus_one_ptid; } store_waitstatus (ourstatus, status); return pid_to_ptid (pid); } /* Override the fetch_inferior_register routine. */ static void spu_fetch_inferior_registers (int regno) { int fd; CORE_ADDR addr; /* We must be stopped on a spu_run system call. */ if (!parse_spufs_run (&fd, &addr)) return; /* The ID register holds the spufs file handle. */ if (regno == -1 || regno == SPU_ID_REGNUM) { char buf[4]; store_unsigned_integer (buf, 4, fd); regcache_raw_supply (current_regcache, SPU_ID_REGNUM, buf); } /* The NPC register is found at ADDR. */ if (regno == -1 || regno == SPU_PC_REGNUM) { gdb_byte buf[4]; if (fetch_ppc_memory (addr, buf, 4) == 0) regcache_raw_supply (current_regcache, SPU_PC_REGNUM, buf); } /* The GPRs are found in the "regs" spufs file. */ if (regno == -1 || (regno >= 0 && regno < SPU_NUM_GPRS)) { gdb_byte buf[16 * SPU_NUM_GPRS]; char annex[32]; int i; xsnprintf (annex, sizeof annex, "%d/regs", fd); if (spu_proc_xfer_spu (annex, buf, NULL, 0, sizeof buf) == sizeof buf) for (i = 0; i < SPU_NUM_GPRS; i++) regcache_raw_supply (current_regcache, i, buf + i*16); } } /* Override the store_inferior_register routine. */ static void spu_store_inferior_registers (int regno) { int fd; CORE_ADDR addr; /* We must be stopped on a spu_run system call. */ if (!parse_spufs_run (&fd, &addr)) return; /* The NPC register is found at ADDR. */ if (regno == -1 || regno == SPU_PC_REGNUM) { gdb_byte buf[4]; regcache_raw_collect (current_regcache, SPU_PC_REGNUM, buf); store_ppc_memory (addr, buf, 4); } /* The GPRs are found in the "regs" spufs file. */ if (regno == -1 || (regno >= 0 && regno < SPU_NUM_GPRS)) { gdb_byte buf[16 * SPU_NUM_GPRS]; char annex[32]; int i; for (i = 0; i < SPU_NUM_GPRS; i++) regcache_raw_collect (current_regcache, i, buf + i*16); xsnprintf (annex, sizeof annex, "%d/regs", fd); spu_proc_xfer_spu (annex, NULL, buf, 0, sizeof buf); } } /* Override the to_xfer_partial routine. */ static LONGEST spu_xfer_partial (struct target_ops *ops, enum target_object object, const char *annex, gdb_byte *readbuf, const gdb_byte *writebuf, ULONGEST offset, LONGEST len) { if (object == TARGET_OBJECT_MEMORY) { int fd; CORE_ADDR addr; char mem_annex[32]; /* We must be stopped on a spu_run system call. */ if (!parse_spufs_run (&fd, &addr)) return 0; /* Use the "mem" spufs file to access SPU local store. */ xsnprintf (mem_annex, sizeof mem_annex, "%d/mem", fd); return spu_proc_xfer_spu (mem_annex, readbuf, writebuf, offset, len); } return 0; } /* Override the to_can_use_hw_breakpoint routine. */ static int spu_can_use_hw_breakpoint (int type, int cnt, int othertype) { return 0; } /* Initialize SPU native target. */ void _initialize_spu_nat (void) { /* Generic ptrace methods. */ struct target_ops *t; t = inf_ptrace_target (); /* Add SPU methods. */ t->to_post_attach = spu_child_post_attach; t->to_post_startup_inferior = spu_child_post_startup_inferior; t->to_wait = spu_child_wait; t->to_fetch_registers = spu_fetch_inferior_registers; t->to_store_registers = spu_store_inferior_registers; t->to_xfer_partial = spu_xfer_partial; t->to_can_use_hw_breakpoint = spu_can_use_hw_breakpoint; /* Register SPU target. */ add_target (t); }