Re: ppc32 debugging ppc64, part 1

Richard Henderson
2005-09-18T02:02:16Z

On Sat, Sep 17, 2005 at 05:24:03PM -0400, Daniel Jacobowitz wrote:
> You don't need this; PTRACE_TYPE_RET should work OK. We get that from
> autoconf (even if the autoconf bits are a bit broken on Linux at the
> moment, they'll choose long anyway).

I've purged PTRACE_XFER_TYPE from the file entirely.

> I think it's safe to do away with the syscalls; the current userspace
> ptrace interface where glibc has to special-case PEEKTEXT/PEEKDATA/PEEKUSER
> is nasty enough, so I'm willing to declare glibc broken if it is ever
> "fixed" to handle the PEEK*_3264 operations specially.

What about the argument that it causes source uglification because
PEEKUSER and PEEKUSER_3464 have different calling sequences in the
libc funtion, but not the syscall?

For the record, my current source follows.

I'll get rid of the use of the syscall if required, but my 2 cents
say it looks cleaner this way.

r~

Index: ppc-linux-nat.c
===================================================================
RCS file: /cvs/src/src/gdb/ppc-linux-nat.c,v
retrieving revision 1.55
diff -u -p -r1.55 ppc-linux-nat.c
--- ppc-linux-nat.c 10 Sep 2005 18:11:04 -0000 1.55
+++ ppc-linux-nat.c 18 Sep 2005 01:56:58 -0000
@@ -39,6 +39,7 @@
#include <fcntl.h>
#include <sys/procfs.h>
#include <sys/ptrace.h>
+#include <sys/syscall.h>

/* Prototypes for supply_gregset etc. */
#include "gregset.h"
@@ -51,10 +52,6 @@
#define PT_WRITE_U PTRACE_POKEUSR
#endif

-/* Default the type of the ptrace transfer to int. */
-#ifndef PTRACE_XFER_TYPE
-#define PTRACE_XFER_TYPE int
-#endif

/* Glibc's headers don't define PTRACE_GETVRREGS so we cannot use a
configure time check. Some older glibc's (for instance 2.2.1)
@@ -180,7 +177,15 @@ ppc_register_u_addr (int regno)
struct gdbarch_tdep *tdep = gdbarch_tdep (current_gdbarch);
/* NOTE: cagney/2003-11-25: This is the word size used by the ptrace
interface, and not the wordsize of the program's ABI. */
- int wordsize = sizeof (PTRACE_XFER_TYPE);
+ /* NOTE: rth/2005-09-12: Which is complicated by the fact that there
+ are in fact two ptrace interfaces: the normal one, and a different
+ one for 32-bit gdb debugging 64-bit target. */
+ int wordsize;
+
+ if (sizeof (PTRACE_TYPE_RET) == 8)
+ wordsize = sizeof (PTRACE_TYPE_RET);
+ else
+ wordsize = tdep->wordsize;

/* General purpose registers occupy 1 slot each in the buffer */
if (regno >= tdep->ppc_gp0_regnum
@@ -336,15 +341,27 @@ fetch_spe_register (int tid, int regno)
&evrregs.spefscr);
}

-static void
+/* A helper function for fetch_register to avoid too much casting. Note
+ that glibc through 2.3.5 treats PTRACE_PEEKUSR specially, but does not
+ treat PPC_PTRACE_PEEKUSR_3264 the same. To avoid depending on glibc
+ fixing this (or not), and to reduce code alternatives, bypass glibc
+ entirely and go straight to the kernel. */
+
+static inline void
+ptrace_read_u (int cmd, int tid, CORE_ADDR regaddr, PTRACE_TYPE_RET *buf)
+{
+ syscall (SYS_ptrace, cmd, tid, (PTRACE_TYPE_ARG3) regaddr, buf);
+}
+
+static inline void
fetch_register (int tid, int regno)
{
struct gdbarch_tdep *tdep = gdbarch_tdep (current_gdbarch);
/* This isn't really an address. But ptrace thinks of it as one. */
CORE_ADDR regaddr = ppc_register_u_addr (regno);
+ int regsize = register_size (current_gdbarch, regno);
+ PTRACE_TYPE_RET buf[MAX_REGISTER_SIZE / sizeof(PTRACE_TYPE_RET)];
int bytes_transferred;
- unsigned int offset; /* Offset of registers within the u area. */
- char buf[MAX_REGISTER_SIZE];

if (altivec_register_p (regno))
{
@@ -367,36 +384,48 @@ fetch_register (int tid, int regno)
return;
}

+ /* Supply zeroes for unknown registers. */
if (regaddr == -1)
{
- memset (buf, '\0', register_size (current_gdbarch, regno)); /* Supply zeroes */
+ memset (buf, '\0', sizeof (buf));
regcache_raw_supply (current_regcache, regno, buf);
return;
}

- /* Read the raw register using PTRACE_XFER_TYPE sized chunks. On a
- 32-bit platform, 64-bit floating-point registers will require two
- transfers. */
- for (bytes_transferred = 0;
- bytes_transferred < register_size (current_gdbarch, regno);
- bytes_transferred += sizeof (PTRACE_XFER_TYPE))
+ /* We need to distinguish between 32/64-bit gdb and 32/64-bit target. */
+
+ errno = 0;
+ if (regsize <= sizeof (PTRACE_TYPE_RET))
{
- errno = 0;
- *(PTRACE_XFER_TYPE *) & buf[bytes_transferred]
- = ptrace (PT_READ_U, tid, (PTRACE_ARG3_TYPE) regaddr, 0);
- regaddr += sizeof (PTRACE_XFER_TYPE);
- if (errno != 0)
- {
- char message[128];
- sprintf (message, "reading register %s (#%d)",
- REGISTER_NAME (regno), regno);
- perror_with_name (message);
- }
+ ptrace_read_u (PT_READ_U, tid, regaddr, buf);
+ bytes_transferred = sizeof (PTRACE_TYPE_RET);
+ }
+ else
+ {
+ int cmd;
+
+ gdb_assert (sizeof (PTRACE_TYPE_RET) == 4 && regsize == 8);
+ bytes_transferred = 2 * sizeof (PTRACE_TYPE_RET);
+
+ if (tdep->wordsize == 8)
+ cmd = PPC_PTRACE_PEEKUSR_3264;
+ else
+ cmd = PT_READ_U;
+
+ ptrace_read_u (cmd, tid, regaddr, buf);
+ ptrace_read_u (cmd, tid, regaddr + 4, buf + 1);
+ }
+ if (errno != 0)
+ {
+ char message[128];
+ sprintf (message, "reading register %s (#%d)",
+ REGISTER_NAME (regno), regno);
+ perror_with_name (message);
}

/* Now supply the register. Keep in mind that the regcache's idea
of the register's size may not be a multiple of sizeof
- (PTRACE_XFER_TYPE). */
+ (PTRACE_TYPE_RET). */
if (gdbarch_byte_order (current_gdbarch) == BFD_ENDIAN_LITTLE)
{
/* Little-endian values are always found at the left end of the
@@ -407,9 +436,9 @@ fetch_register (int tid, int regno)
{
/* Big-endian values are found at the right end of the bytes
transferred. */
- size_t padding = (bytes_transferred
- - register_size (current_gdbarch, regno));
- regcache_raw_supply (current_regcache, regno, buf + padding);
+ size_t padding = bytes_transferred - regsize;
+ regcache_raw_supply (current_regcache, regno,
+ (char *)buf + padding);
}
else
internal_error (__FILE__, __LINE__,
@@ -466,11 +495,13 @@ fetch_ppc_registers (int tid)
int i;
struct gdbarch_tdep *tdep = gdbarch_tdep (current_gdbarch);

+ /* ??? Use PPC_PTRACE_GETREGS / PPC_PTRACE_GETFPREGS. */
for (i = 0; i < ppc_num_gprs; i++)
fetch_register (tid, tdep->ppc_gp0_regnum + i);
if (tdep->ppc_fp0_regnum >= 0)
for (i = 0; i < ppc_num_fprs; i++)
fetch_register (tid, tdep->ppc_fp0_regnum + i);
+
fetch_register (tid, PC_REGNUM);
if (tdep->ppc_ps_regnum != -1)
fetch_register (tid, tdep->ppc_ps_regnum);
@@ -632,15 +663,23 @@ store_spe_register (int tid, int regno)
set_spe_registers (tid, &evrregs);
}

+/* A helper function for store_register to avoid too much casting. */
+
+static inline void
+ptrace_write_u (int cmd, int tid, CORE_ADDR regaddr, PTRACE_TYPE_RET *buf)
+{
+ ptrace (cmd, tid, (PTRACE_TYPE_ARG3) regaddr, *buf);
+}
+
static void
store_register (int tid, int regno)
{
struct gdbarch_tdep *tdep = gdbarch_tdep (current_gdbarch);
/* This isn't really an address. But ptrace thinks of it as one. */
CORE_ADDR regaddr = ppc_register_u_addr (regno);
- int i;
- size_t bytes_to_transfer;
- char buf[MAX_REGISTER_SIZE];
+ int regsize = register_size (current_gdbarch, regno);
+ PTRACE_TYPE_RET buf[MAX_REGISTER_SIZE / sizeof(PTRACE_TYPE_RET)];
+ int bytes_to_transfer;

if (altivec_register_p (regno))
{
@@ -658,10 +697,9 @@ store_register (int tid, int regno)

/* First collect the register. Keep in mind that the regcache's
idea of the register's size may not be a multiple of sizeof
- (PTRACE_XFER_TYPE). */
- memset (buf, 0, sizeof buf);
- bytes_to_transfer = align_up (register_size (current_gdbarch, regno),
- sizeof (PTRACE_XFER_TYPE));
+ (PTRACE_TYPE_RET). */
+ memset (buf, '\0', sizeof (buf));
+ bytes_to_transfer = align_up (regsize, sizeof (PTRACE_TYPE_RET));
if (TARGET_BYTE_ORDER == BFD_ENDIAN_LITTLE)
{
/* Little-endian values always sit at the left end of the buffer. */
@@ -670,32 +708,37 @@ store_register (int tid, int regno)
else if (TARGET_BYTE_ORDER == BFD_ENDIAN_BIG)
{
/* Big-endian values sit at the right end of the buffer. */
- size_t padding = (bytes_to_transfer
- - register_size (current_gdbarch, regno));
- regcache_raw_collect (current_regcache, regno, buf + padding);
+ size_t padding = bytes_to_transfer - regsize;
+ regcache_raw_collect (current_regcache, regno, (char *)buf + padding);
}

- for (i = 0; i < bytes_to_transfer; i += sizeof (PTRACE_XFER_TYPE))
+ /* We need to distinguish between 32/64-bit gdb and 32/64-bit target. */
+
+ errno = 0;
+ if (regsize <= sizeof (PTRACE_TYPE_RET))
+ ptrace_write_u (PT_WRITE_U, tid, regaddr, buf);
+ else
{
- errno = 0;
- ptrace (PT_WRITE_U, tid, (PTRACE_ARG3_TYPE) regaddr,
- *(PTRACE_XFER_TYPE *) & buf[i]);
- regaddr += sizeof (PTRACE_XFER_TYPE);
+ int cmd;

- if (errno == EIO
- && regno == tdep->ppc_fpscr_regnum)
- {
- /* Some older kernel versions don't allow fpscr to be written. */
- continue;
- }
+ gdb_assert (sizeof (PTRACE_TYPE_RET) == 4 && regsize == 8);

- if (errno != 0)
- {
- char message[128];
- sprintf (message, "writing register %s (#%d)",
- REGISTER_NAME (regno), regno);
- perror_with_name (message);
- }
+ if (regsize == tdep->wordsize)
+ cmd = PPC_PTRACE_POKEUSR_3264;
+ else
+ cmd = PT_WRITE_U;
+
+ ptrace_write_u (cmd, tid, regaddr, buf);
+ ptrace_write_u (cmd, tid, regaddr + 4, buf + 1);
+ }
+ if (errno == EIO && regno == tdep->ppc_fpscr_regnum)
+ /* Some older kernel versions don't allow fpscr to be written. */;
+ else if (errno != 0)
+ {
+ char message[128];
+ sprintf (message, "writing register %s (#%d)",
+ REGISTER_NAME (regno), regno);
+ perror_with_name (message);
}
}

@@ -750,11 +793,13 @@ store_ppc_registers (int tid)
int i;
struct gdbarch_tdep *tdep = gdbarch_tdep (current_gdbarch);

+ /* ??? Use PPC_PTRACE_SETREGS / PPC_PTRACE_SETFPREGS. */
for (i = 0; i < ppc_num_gprs; i++)
store_register (tid, tdep->ppc_gp0_regnum + i);
if (tdep->ppc_fp0_regnum >= 0)
for (i = 0; i < ppc_num_fprs; i++)
store_register (tid, tdep->ppc_fp0_regnum + i);
+
store_register (tid, PC_REGNUM);
if (tdep->ppc_ps_regnum != -1)
store_register (tid, tdep->ppc_ps_regnum);
@@ -798,7 +843,7 @@ supply_gregset (gdb_gregset_t *gregsetp)
{
/* NOTE: cagney/2003-11-25: This is the word size used by the ptrace
interface, and not the wordsize of the program's ABI. */
- int wordsize = sizeof (PTRACE_XFER_TYPE);
+ int wordsize = sizeof (PTRACE_TYPE_RET);
ppc_linux_supply_gregset (current_regcache, -1, gregsetp,
sizeof (gdb_gregset_t), wordsize);
}
@@ -808,7 +853,7 @@ right_fill_reg (int regnum, void *reg)
{
/* NOTE: cagney/2003-11-25: This is the word size used by the ptrace
interface, and not the wordsize of the program's ABI. */
- int wordsize = sizeof (PTRACE_XFER_TYPE);
+ int wordsize = sizeof (PTRACE_TYPE_RET);
/* Right fill the register. */
regcache_raw_collect (current_regcache, regnum,
((bfd_byte *) reg
@@ -886,6 +931,103 @@ fill_fpregset (gdb_fpregset_t *fpregsetp
}
}

+/* Helper functions for ppc32_linux_xfer_partial to avoid too much casting.
+ Note that glibc through 2.3.5 treats PTRACE_PEEKDATA specially, but does
+ not treat PPC_PTRACE_PEEKDATA_3264 the same. To avoid depending on glibc
+ fixing this (or not), bypass glibc entirely. */
+
+static inline void
+ptrace_read64_d (int tid, CORE_ADDR addr, PTRACE_TYPE_RET *buf)
+{
+ syscall (SYS_ptrace, PPC_PTRACE_PEEKDATA_3264, tid,
+ (PTRACE_TYPE_ARG3) &addr, buf);
+}
+
+static inline void
+ptrace_write64_d (int tid, CORE_ADDR addr, PTRACE_TYPE_RET *buf)
+{
+ ptrace (PPC_PTRACE_POKEDATA_3264, tid, (PTRACE_TYPE_ARG3) &addr, *buf);
+}
+
+/* Hold a pointer to inf_ptrace_xfer_partial. */
+static LONGEST (*default_xfer_partial) (struct target_ops *ops,
+ enum target_object object,
+ const char *annex, gdb_byte *readbuf,
+ const gdb_byte *writebuf,
+ ULONGEST offset, LONGEST len);
+
+static LONGEST
+ppc32_linux_xfer_partial (struct target_ops *ops, enum target_object object,
+ const char *annex, gdb_byte *readbuf,
+ const gdb_byte *writebuf,
+ ULONGEST offset, LONGEST len)
+{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (current_gdbarch);
+ pid_t pid;
+ CORE_ADDR rounded_offset;
+ LONGEST partial_len;
+ PTRACE_TYPE_RET buf;
+
+ /* If the target is also 32-bit, we can avoid double indirection by
+ using the default implementation. Plus, the PTRACE_*_3264 commands
+ are only present on 64-bit kernels. */
+ if (tdep->wordsize == 4)
+ return default_xfer_partial (ops, object, annex, readbuf,
+ writebuf, offset, len);
+
+ if (object != TARGET_OBJECT_MEMORY)
+ return -1;
+
+ gdb_assert (sizeof (CORE_ADDR) == 8);
+
+ pid = ptid_get_pid (inferior_ptid);
+
+ /* Round the start offset down to the next long word boundary. */
+ rounded_offset = offset & -(ULONGEST) sizeof (PTRACE_TYPE_RET);
+
+ /* Since ptrace will transfer a single word starting at that
+ rounded_offset the partial_len needs to be adjusted down to
+ that (remember this function only does a single transfer).
+ Should the required length be even less, adjust it down again. */
+ partial_len = rounded_offset + sizeof (PTRACE_TYPE_RET) - offset;
+ if (partial_len > len)
+ partial_len = len;
+
+ if (writebuf)
+ {
+ /* If OFFSET:PARTIAL_LEN is smaller than ROUNDED_OFFSET:WORDSIZE
+ then a read/modify write will be needed. Read in the entire word. */
+ if (rounded_offset < offset
+ || (offset + partial_len
+ < rounded_offset + sizeof (PTRACE_TYPE_RET)))
+ {
+ /* Need part of initial word -- fetch it. */
+ ptrace_read64_d (pid, rounded_offset, &buf);
+ }
+
+ /* Copy data to be written over corresponding part of buffer. */
+ memcpy ((char *)&buf + offset - rounded_offset, writebuf, partial_len);
+
+ errno = 0;
+ ptrace_write64_d (pid, rounded_offset, &buf);
+ if (errno)
+ return 0;
+ }
+
+ if (readbuf)
+ {
+ errno = 0;
+ ptrace_read64_d (pid, rounded_offset, &buf);
+ if (errno)
+ return 0;
+
+ /* Copy appropriate bytes out of the buffer. */
+ memcpy (readbuf, (char *)&buf + offset - rounded_offset, partial_len);
+ }
+
+ return partial_len;
+}
+
void _initialize_ppc_linux_nat (void);

void
@@ -900,6 +1042,14 @@ _initialize_ppc_linux_nat (void)
t->to_fetch_registers = ppc_linux_fetch_inferior_registers;
t->to_store_registers = ppc_linux_store_inferior_registers;

+ /* We need a special version of xfer_partial to handle 32-bit gdb
+ debugging a 64-bit process. */
+ if (sizeof (PTRACE_TYPE_RET) == 4)
+ {
+ default_xfer_partial = t->to_xfer_partial;
+ t->to_xfer_partial = ppc32_linux_xfer_partial;
+ }
+
/* Register the target. */
add_target (t);
}