aboutsummaryrefslogtreecommitdiff
path: root/gdb/arm-linux-nat.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/arm-linux-nat.c')
-rw-r--r--gdb/arm-linux-nat.c547
1 files changed, 547 insertions, 0 deletions
diff --git a/gdb/arm-linux-nat.c b/gdb/arm-linux-nat.c
new file mode 100644
index 0000000..f738050
--- /dev/null
+++ b/gdb/arm-linux-nat.c
@@ -0,0 +1,547 @@
+/* GNU/Linux on ARM native support.
+ Copyright 1999 Free Software Foundation, Inc.
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+#include "defs.h"
+#include "inferior.h"
+#include "gdbcore.h"
+#include "gdb_string.h"
+
+#include <sys/user.h>
+#include <sys/ptrace.h>
+#include <sys/utsname.h>
+
+extern int arm_apcs_32;
+
+#define typeNone 0x00
+#define typeSingle 0x01
+#define typeDouble 0x02
+#define typeExtended 0x03
+#define FPWORDS 28
+#define CPSR_REGNUM 16
+
+typedef union tagFPREG
+ {
+ unsigned int fSingle;
+ unsigned int fDouble[2];
+ unsigned int fExtended[3];
+ }
+FPREG;
+
+typedef struct tagFPA11
+ {
+ FPREG fpreg[8]; /* 8 floating point registers */
+ unsigned int fpsr; /* floating point status register */
+ unsigned int fpcr; /* floating point control register */
+ unsigned char fType[8]; /* type of floating point value held in
+ floating point registers. */
+ int initflag; /* NWFPE initialization flag. */
+ }
+FPA11;
+
+/* The following variables are used to determine the version of the
+ underlying Linux operating system. Examples:
+
+ Linux 2.0.35 Linux 2.2.12
+ os_version = 0x00020023 os_version = 0x0002020c
+ os_major = 2 os_major = 2
+ os_minor = 0 os_minor = 2
+ os_release = 35 os_release = 12
+
+ Note: os_version = (os_major << 16) | (os_minor << 8) | os_release
+
+ These are initialized using get_linux_version() from
+ _initialize_arm_linux_nat(). */
+
+static unsigned int os_version, os_major, os_minor, os_release;
+
+static void
+fetch_nw_fpe_single (unsigned int fn, FPA11 * fpa11, unsigned int *pmem)
+{
+ unsigned int mem[3];
+
+ mem[0] = fpa11->fpreg[fn].fSingle;
+ mem[1] = 0;
+ mem[2] = 0;
+ supply_register (F0_REGNUM + fn, (char *) &mem[0]);
+}
+
+static void
+fetch_nw_fpe_double (unsigned int fn, FPA11 * fpa11, unsigned int *pmem)
+{
+ unsigned int mem[3];
+
+ mem[0] = fpa11->fpreg[fn].fDouble[1];
+ mem[1] = fpa11->fpreg[fn].fDouble[0];
+ mem[2] = 0;
+ supply_register (F0_REGNUM + fn, (char *) &mem[0]);
+}
+
+static void
+fetch_nw_fpe_none (unsigned int fn, FPA11 * fpa11, unsigned int *pmem)
+{
+ unsigned int mem[3] =
+ {0, 0, 0};
+
+ supply_register (F0_REGNUM + fn, (char *) &mem[0]);
+}
+
+static void
+fetch_nw_fpe_extended (unsigned int fn, FPA11 * fpa11, unsigned int *pmem)
+{
+ unsigned int mem[3];
+
+ mem[0] = fpa11->fpreg[fn].fExtended[0]; /* sign & exponent */
+ mem[1] = fpa11->fpreg[fn].fExtended[2]; /* ls bits */
+ mem[2] = fpa11->fpreg[fn].fExtended[1]; /* ms bits */
+ supply_register (F0_REGNUM + fn, (char *) &mem[0]);
+}
+
+static void
+store_nw_fpe_single (unsigned int fn, FPA11 * fpa11)
+{
+ unsigned int mem[3];
+
+ read_register_gen (F0_REGNUM + fn, (char *) &mem[0]);
+ fpa11->fpreg[fn].fSingle = mem[0];
+ fpa11->fType[fn] = typeSingle;
+}
+
+static void
+store_nw_fpe_double (unsigned int fn, FPA11 * fpa11)
+{
+ unsigned int mem[3];
+
+ read_register_gen (F0_REGNUM + fn, (char *) &mem[0]);
+ fpa11->fpreg[fn].fDouble[1] = mem[0];
+ fpa11->fpreg[fn].fDouble[0] = mem[1];
+ fpa11->fType[fn] = typeDouble;
+}
+
+void
+store_nw_fpe_extended (unsigned int fn, FPA11 * fpa11)
+{
+ unsigned int mem[3];
+
+ read_register_gen (F0_REGNUM + fn, (char *) &mem[0]);
+ fpa11->fpreg[fn].fExtended[0] = mem[0]; /* sign & exponent */
+ fpa11->fpreg[fn].fExtended[2] = mem[1]; /* ls bits */
+ fpa11->fpreg[fn].fExtended[1] = mem[2]; /* ms bits */
+ fpa11->fType[fn] = typeDouble;
+}
+
+/* Get the whole floating point state of the process and store the
+ floating point stack into registers[]. */
+
+static void
+fetch_fpregs (void)
+{
+ int ret, regno;
+ FPA11 fp;
+
+ /* Read the floating point state. */
+ ret = ptrace (PT_GETFPREGS, inferior_pid, 0, &fp);
+ if (ret < 0)
+ {
+ warning ("Unable to fetch the floating point state.");
+ return;
+ }
+
+ /* Fetch fpsr. */
+ supply_register (FPS_REGNUM, (char *) &fp.fpsr);
+
+ /* Fetch the floating point registers. */
+ for (regno = F0_REGNUM; regno <= F7_REGNUM; regno++)
+ {
+ int fn = regno - F0_REGNUM;
+ unsigned int *p = (unsigned int *) &registers[REGISTER_BYTE (regno)];
+
+ switch (fp.fType[fn])
+ {
+ case typeSingle:
+ fetch_nw_fpe_single (fn, &fp, p);
+ break;
+
+ case typeDouble:
+ fetch_nw_fpe_double (fn, &fp, p);
+ break;
+
+ case typeExtended:
+ fetch_nw_fpe_extended (fn, &fp, p);
+ break;
+
+ default:
+ fetch_nw_fpe_none (fn, &fp, p);
+ }
+ }
+}
+
+/* Save the whole floating point state of the process using
+ the contents from registers[]. */
+
+static void
+store_fpregs (void)
+{
+ int ret, regno;
+ unsigned int mem[3];
+ FPA11 fp;
+
+ /* Store fpsr. */
+ if (register_valid[FPS_REGNUM])
+ read_register_gen (FPS_REGNUM, (char *) &fp.fpsr);
+
+ /* Store the floating point registers. */
+ for (regno = F0_REGNUM; regno <= F7_REGNUM; regno++)
+ {
+ if (register_valid[regno])
+ {
+ unsigned int fn = regno - F0_REGNUM;
+ switch (fp.fType[fn])
+ {
+ case typeSingle:
+ store_nw_fpe_single (fn, &fp);
+ break;
+
+ case typeDouble:
+ store_nw_fpe_double (fn, &fp);
+ break;
+
+ case typeExtended:
+ store_nw_fpe_extended (fn, &fp);
+ break;
+ }
+ }
+ }
+
+ ret = ptrace (PTRACE_SETFPREGS, inferior_pid, 0, &fp);
+ if (ret < 0)
+ {
+ warning ("Unable to store floating point state.");
+ return;
+ }
+}
+
+/* Fetch all general registers of the process and store into
+ registers[]. */
+
+static void
+fetch_regs (void)
+{
+ int ret, regno;
+ struct pt_regs regs;
+
+ ret = ptrace (PTRACE_GETREGS, inferior_pid, 0, &regs);
+ if (ret < 0)
+ {
+ warning ("Unable to fetch general registers.");
+ return;
+ }
+
+ for (regno = A1_REGNUM; regno < PC_REGNUM; regno++)
+ supply_register (regno, (char *) &regs.uregs[regno]);
+
+ if (arm_apcs_32)
+ supply_register (PS_REGNUM, (char *) &regs.uregs[CPSR_REGNUM]);
+ else
+ supply_register (PS_REGNUM, (char *) &regs.uregs[PC_REGNUM]);
+
+ regs.uregs[PC_REGNUM] = ADDR_BITS_REMOVE (regs.uregs[PC_REGNUM]);
+ supply_register (PC_REGNUM, (char *) &regs.uregs[PC_REGNUM]);
+}
+
+/* Store all general registers of the process from the values in
+ registers[]. */
+
+static void
+store_regs (void)
+{
+ int ret, regno;
+ struct pt_regs regs;
+
+ ret = ptrace (PTRACE_GETREGS, inferior_pid, 0, &regs);
+ if (ret < 0)
+ {
+ warning ("Unable to fetch general registers.");
+ return;
+ }
+
+ for (regno = A1_REGNUM; regno <= PC_REGNUM; regno++)
+ {
+ if (register_valid[regno])
+ read_register_gen (regno, (char *) &regs.uregs[regno]);
+ }
+
+ ret = ptrace (PTRACE_SETREGS, inferior_pid, 0, &regs);
+
+ if (ret < 0)
+ {
+ warning ("Unable to store general registers.");
+ return;
+ }
+}
+
+/* Fetch registers from the child process. Fetch all registers if
+ regno == -1, otherwise fetch all general registers or all floating
+ point registers depending upon the value of regno. */
+
+void
+fetch_inferior_registers (int regno)
+{
+ if ((regno < F0_REGNUM) || (regno > FPS_REGNUM))
+ fetch_regs ();
+
+ if (((regno >= F0_REGNUM) && (regno <= FPS_REGNUM)) || (regno == -1))
+ fetch_fpregs ();
+}
+
+/* Store registers back into the inferior. Store all registers if
+ regno == -1, otherwise store all general registers or all floating
+ point registers depending upon the value of regno. */
+
+void
+store_inferior_registers (int regno)
+{
+ if ((regno < F0_REGNUM) || (regno > FPS_REGNUM))
+ store_regs ();
+
+ if (((regno >= F0_REGNUM) && (regno <= FPS_REGNUM)) || (regno == -1))
+ store_fpregs ();
+}
+
+#ifdef GET_LONGJMP_TARGET
+
+/* Figure out where the longjmp will land. We expect that we have
+ just entered longjmp and haven't yet altered r0, r1, so the
+ arguments are still in the registers. (A1_REGNUM) points at the
+ jmp_buf structure from which we extract the pc (JB_PC) that we will
+ land at. The pc is copied into ADDR. This routine returns true on
+ success. */
+
+#define LONGJMP_TARGET_SIZE sizeof(int)
+#define JB_ELEMENT_SIZE sizeof(int)
+#define JB_SL 18
+#define JB_FP 19
+#define JB_SP 20
+#define JB_PC 21
+
+int
+arm_get_longjmp_target (CORE_ADDR * pc)
+{
+ CORE_ADDR jb_addr;
+ char buf[LONGJMP_TARGET_SIZE];
+
+ jb_addr = read_register (A1_REGNUM);
+
+ if (target_read_memory (jb_addr + JB_PC * JB_ELEMENT_SIZE, buf,
+ LONGJMP_TARGET_SIZE))
+ return 0;
+
+ *pc = extract_address (buf, LONGJMP_TARGET_SIZE);
+ return 1;
+}
+
+#endif /* GET_LONGJMP_TARGET */
+
+/*
+ Dynamic Linking on ARM Linux
+ ----------------------------
+
+ Note: PLT = procedure linkage table
+ GOT = global offset table
+
+ As much as possible, ELF dynamic linking defers the resolution of
+ jump/call addresses until the last minute. The technique used is
+ inspired by the i386 ELF design, and is based on the following
+ constraints.
+
+ 1) The calling technique should not force a change in the assembly
+ code produced for apps; it MAY cause changes in the way assembly
+ code is produced for position independent code (i.e. shared
+ libraries).
+
+ 2) The technique must be such that all executable areas must not be
+ modified; and any modified areas must not be executed.
+
+ To do this, there are three steps involved in a typical jump:
+
+ 1) in the code
+ 2) through the PLT
+ 3) using a pointer from the GOT
+
+ When the executable or library is first loaded, each GOT entry is
+ initialized to point to the code which implements dynamic name
+ resolution and code finding. This is normally a function in the
+ program interpreter (on ARM Linux this is usually ld-linux.so.2,
+ but it does not have to be). On the first invocation, the function
+ is located and the GOT entry is replaced with the real function
+ address. Subsequent calls go through steps 1, 2 and 3 and end up
+ calling the real code.
+
+ 1) In the code:
+
+ b function_call
+ bl function_call
+
+ This is typical ARM code using the 26 bit relative branch or branch
+ and link instructions. The target of the instruction
+ (function_call is usually the address of the function to be called.
+ In position independent code, the target of the instruction is
+ actually an entry in the PLT when calling functions in a shared
+ library. Note that this call is identical to a normal function
+ call, only the target differs.
+
+ 2) In the PLT:
+
+ The PLT is a synthetic area, created by the linker. It exists in
+ both executables and libraries. It is an array of stubs, one per
+ imported function call. It looks like this:
+
+ PLT[0]:
+ str lr, [sp, #-4]! @push the return address (lr)
+ ldr lr, [pc, #16] @load from 6 words ahead
+ add lr, pc, lr @form an address for GOT[0]
+ ldr pc, [lr, #8]! @jump to the contents of that addr
+
+ The return address (lr) is pushed on the stack and used for
+ calculations. The load on the second line loads the lr with
+ &GOT[3] - . - 20. The addition on the third leaves:
+
+ lr = (&GOT[3] - . - 20) + (. + 8)
+ lr = (&GOT[3] - 12)
+ lr = &GOT[0]
+
+ On the fourth line, the pc and lr are both updated, so that:
+
+ pc = GOT[2]
+ lr = &GOT[0] + 8
+ = &GOT[2]
+
+ NOTE: PLT[0] borrows an offset .word from PLT[1]. This is a little
+ "tight", but allows us to keep all the PLT entries the same size.
+
+ PLT[n+1]:
+ ldr ip, [pc, #4] @load offset from gotoff
+ add ip, pc, ip @add the offset to the pc
+ ldr pc, [ip] @jump to that address
+ gotoff: .word GOT[n+3] - .
+
+ The load on the first line, gets an offset from the fourth word of
+ the PLT entry. The add on the second line makes ip = &GOT[n+3],
+ which contains either a pointer to PLT[0] (the fixup trampoline) or
+ a pointer to the actual code.
+
+ 3) In the GOT:
+
+ The GOT contains helper pointers for both code (PLT) fixups and
+ data fixups. The first 3 entries of the GOT are special. The next
+ M entries (where M is the number of entries in the PLT) belong to
+ the PLT fixups. The next D (all remaining) entries belong to
+ various data fixups. The actual size of the GOT is 3 + M + D.
+
+ The GOT is also a synthetic area, created by the linker. It exists
+ in both executables and libraries. When the GOT is first
+ initialized , all the GOT entries relating to PLT fixups are
+ pointing to code back at PLT[0].
+
+ The special entries in the GOT are:
+
+ GOT[0] = linked list pointer used by the dynamic loader
+ GOT[1] = pointer to the reloc table for this module
+ GOT[2] = pointer to the fixup/resolver code
+
+ The first invocation of function call comes through and uses the
+ fixup/resolver code. On the entry to the fixup/resolver code:
+
+ ip = &GOT[n+3]
+ lr = &GOT[2]
+ stack[0] = return address (lr) of the function call
+ [r0, r1, r2, r3] are still the arguments to the function call
+
+ This is enough information for the fixup/resolver code to work
+ with. Before the fixup/resolver code returns, it actually calls
+ the requested function and repairs &GOT[n+3]. */
+
+CORE_ADDR
+arm_skip_solib_resolver (CORE_ADDR pc)
+{
+ /* FIXME */
+ return 0;
+}
+
+int
+arm_linux_register_u_addr (int blockend, int regnum)
+{
+ return blockend + REGISTER_BYTE (regnum);
+}
+
+int
+arm_linux_kernel_u_size (void)
+{
+ return (sizeof (struct user));
+}
+
+/* Extract from an array REGBUF containing the (raw) register state
+ a function return value of type TYPE, and copy that, in virtual format,
+ into VALBUF. */
+
+void
+arm_linux_extract_return_value (struct type *type,
+ char regbuf[REGISTER_BYTES],
+ char *valbuf)
+{
+ /* ScottB: This needs to be looked at to handle the different
+ floating point emulators on ARM Linux. Right now the code
+ assumes that fetch inferior registers does the right thing for
+ GDB. I suspect this won't handle NWFPE registers correctly, nor
+ will the default ARM version (arm_extract_return_value()). */
+
+ int regnum = (TYPE_CODE_FLT == TYPE_CODE (type)) ? F0_REGNUM : A1_REGNUM;
+ memcpy (valbuf, &regbuf[REGISTER_BYTE (regnum)], TYPE_LENGTH (type));
+}
+
+static unsigned int
+get_linux_version (unsigned int *vmajor,
+ unsigned int *vminor,
+ unsigned int *vrelease)
+{
+ struct utsname info;
+ char *pmajor, *pminor, *prelease, *tail;
+
+ if (-1 == uname (&info))
+ {
+ warning ("Unable to determine Linux version.");
+ return -1;
+ }
+
+ pmajor = strtok (info.release, ".");
+ pminor = strtok (NULL, ".");
+ prelease = strtok (NULL, ".");
+
+ *vmajor = (unsigned int) strtoul (pmajor, &tail, 0);
+ *vminor = (unsigned int) strtoul (pminor, &tail, 0);
+ *vrelease = (unsigned int) strtoul (prelease, &tail, 0);
+
+ return ((*vmajor << 16) | (*vminor << 8) | *vrelease);
+}
+
+void
+_initialize_arm_linux_nat (void)
+{
+ os_version = get_linux_version (&os_major, &os_minor, &os_release);
+}