From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 950 invoked by alias); 16 May 2009 18:19:39 -0000 Received: (qmail 935 invoked by uid 22791); 16 May 2009 18:19:32 -0000 X-SWARE-Spam-Status: No, hits=-0.8 required=5.0 tests=AWL,BAYES_50,SPF_PASS X-Spam-Check-By: sourceware.org Received: from mail.codesourcery.com (HELO mail.codesourcery.com) (65.74.133.4) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Sat, 16 May 2009 18:19:19 +0000 Received: (qmail 11938 invoked from network); 16 May 2009 18:19:16 -0000 Received: from unknown (HELO rex.config) (julian@127.0.0.2) by mail.codesourcery.com with ESMTPA; 16 May 2009 18:19:16 -0000 Date: Sat, 16 May 2009 18:19:00 -0000 From: Julian Brown To: Daniel Jacobowitz Cc: gdb-patches@sourceware.org, pedro@codesourcery.com Subject: Re: [PATCH] Displaced stepping (non-stop debugging) support for ARM Linux Message-ID: <20090516191910.14820805@rex.config> In-Reply-To: <20090202200107.GA26459@caradoc.them.org> References: <20090120221355.46ac23e6@rex.config> <20090202200107.GA26459@caradoc.them.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="MP_/LBSRa93K3+h/hpbvwvOOZ4u" X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2009-05/txt/msg00342.txt.bz2 --MP_/LBSRa93K3+h/hpbvwvOOZ4u Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Content-Disposition: inline Content-length: 10243 Hi, This is a new version of the patch to support displaced stepping on ARM. Many things are fixed from the last version posted previously (January 20th), though we're probably not 100% of the way there yet. Pedro Alves wrote: > Right, you may end up with a temporary breakpoint over another > breakpoint, though. It would be better to use the standard software > single-stepping (set temp break at next pc, continue, remove break) > for standard stepping requests, and use displaced stepping only for > stepping over breakpoints. Unfortunately, you don't get that for > free --- infrun.c and friends don't know how to handle multiple > simultaneous software single-stepping requests, and that is required > in non-stop mode. I'm not sure what the status is here now. For testing purposes, I've (still) been using a local patch which uses displaced stepping for all single-step operations. Daniel Jacobowitz wrote: > * What's the point of executing mov on the target for BL? > At that point it seems like we ought to skip the target step entirely; > just simulate the instruction. We've already got a function to check > conditions (condition_true). I'm now using NOP instructions and condition_true, because the current displaced stepping support wants to execute "something" rather than nothing. > * Using arm_write_pc is a bit dodgy here; I don't think it's what we > want. That function updates the CPSR based on a number of things > including symbol tables. We know exactly what is supposed to happen > to CPSR for a given instruction and should honor it. An example of > why this matters: people regularly get a blx in Cortex-M3 code by use > of bad libraries, untyped ELF symbols, or other such circumstances. > That blx had better update the CPSR even when we step over it. Fixed, I think. > > +/* FIXME: This should depend on the arch version. */ > > + > > +static ULONGEST > > +modify_store_pc (ULONGEST pc) > > +{ > > + return pc + 4; > > +} > > This one we might not be able to fix in current GDB but we can at > least expand the comment... if I remember right the +4 is correct for > everything since ARMv5 and most ARMv4? I've removed this function. Stores of PC now read back the offset, so should be architecture-version independent (the strategy is slightly different for STR vs. STM: see below). > Yes, we just can't emulate loads or stores. Anything that could cause > an exception that won't be delayed till the next instruction, I think. LDM and STM are handled substantially differently now: STM instructions are let through unmodified, and when PC is in the register list the cleanup routine reads back the stored value and calculates the proper offset for PC writes. The true (non-displaced) PC value (plus offset) is then written to the appropriate memory location. LDM instructions shuffle registers downwards into a contiguous list (to avoid loading PC directly), then fix up register contents afterwards in the cleanup routine. The case with a fully-populated register list is still emulated, for now. > > +static int > > +copy_svc (unsigned long insn, CORE_ADDR to, struct regcache *regs, > > + struct displaced_step_closure *dsc) > > +{ > > + CORE_ADDR from = dsc->insn_addr; > > + > > + if (debug_displaced) > > + fprintf_unfiltered (gdb_stdlog, "displaced: copying svc insn > > %.8lx\n", > > + insn); > > + > > + /* Preparation: tmp[0] <- to. > > + Insn: unmodified svc. > > + Cleanup: if (pc == +4) pc <- insn_addr + 4; > > + else leave PC alone. */ > > What about the saved PC? Don't really want the OS service routine to > return to the scratchpad. > > > + /* FIXME: What can we do about signal trampolines? */ > > Maybe this is referring to the same question I asked above? > > If so, I think you get to unwind and if you find the scratchpad, > update the saved PC. I've tried to figure this out, and have totally drawn a blank so far. AFAICT, the problem we're trying to solve runs as follows: sometimes, a signal may be delivered to a process whilst it is executing a system call. In that case, the kernel writes a signal trampoline to the user program's stack space, and rewrites the state so that the trampoline is executed when the system call returns. Now: if we single-step that signal trampoline, we will see a system call ("sigreturn") which does not return to the caller: rather, it returns to a handler (in the user program) for the signal in question. So, the expected result at present is that if displaced stepping is used to single-step the sigreturn call, the debugger will lose control of the debugged program. Unfortunately I've been unable to figure out if the above is true, and I can't quite figure out the mechanism in enough detail to know if there's really anything we can do about it if so. My test program (stolen from the internet and tweaked) runs as follows: /* * signal.c - A signal-catching test program */ #include #include #include void func (int, siginfo_t *, void *); void func2 (int, siginfo_t *, void *); int main (int argc, char **argv) { struct sigaction sa; printf ("Starting execution\n"); sa.sa_sigaction = func; sigemptyset (&sa.sa_mask); sa.sa_flags = SA_SIGINFO | SA_RESETHAND; if (sigaction (SIGHUP, &sa, NULL)) perror ("sigaction() failed"); sa.sa_sigaction = func2; if (sigaction (SIGINT, &sa, NULL)) perror ("sigaction() failed"); printf ("sigaction() successful. Now sleeping\n"); while (1) sleep (600); printf ("I should not come here\n"); return 0; } void func (int sig, siginfo_t *sinf, void *foo) { printf ("Signal Handler: sig=%d scp=%p\n", sig, sinf); if (sinf) { printf ("siginfo.si_signo=%d\n", sinf->si_signo); printf ("siginfo.si_errno=%d\n", sinf->si_errno); printf ("siginfo.si_code=%d\n", sinf->si_code); } pause (); printf ("func() exiting\n"); sleep (2); } void func2 (int sig, siginfo_t *sinf, void *foo) { printf ("Signal Handler: sig=%d scp=%p\n", sig, sinf); if (sinf) { printf ("siginfo.si_signo=%d\n", sinf->si_signo); printf ("siginfo.si_errno=%d\n", sinf->si_errno); printf ("siginfo.si_code=%d\n", sinf->si_code); } printf ("func2() exiting\n"); } Without the debugger, this can be run, then sent signal 1 (which prints the messages from func(), and then sent signal 2 (which prints the messages from func2() -- presumably after running a signal trampoline, though I'm not entirely certain of that), then sleeps. But with the debugger, the program never gets beyond func(): and that's where I got stuck. > > +struct displaced_step_closure * > > +arm_displaced_step_copy_insn (struct gdbarch *gdbarch, > > + CORE_ADDR from, CORE_ADDR to, > > + struct regcache *regs) > > +{ > > + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); > > + const size_t len = 4; > > + gdb_byte *buf = xmalloc (len); > > + struct displaced_step_closure *dsc; > > + unsigned long insn; > > + int i; > > + > > + /* A linux-specific hack. Detect when we've entered > > (inaccessible by GDB) > > + kernel helpers, and stop at the return location. */ > > + if (gdbarch_osabi (gdbarch) == GDB_OSABI_LINUX && from > > > 0xffff0000) > > + { > > + if (debug_displaced) > > + fprintf_unfiltered (gdb_stdlog, "displaced: detected > > kernel helper " > > + "at %.8lx\n", (unsigned long) from); > > + > > + dsc = arm_catch_kernel_helper_return (from, to, regs); > > + } > > + else > > + { > > + insn = read_memory_unsigned_integer (from, len); > > + > > + if (debug_displaced) > > + fprintf_unfiltered (gdb_stdlog, "displaced: stepping insn > > %.8lx " > > + "at %.8lx\n", insn, (unsigned long) > > from); + > > + dsc = arm_process_displaced_insn (insn, from, to, regs); > > + } > > Can the Linux-specific hack go in arm-linux-tdep.c? Shouldn't have to > make many functions global to do that. Moved. Other points you (Dan) raised have been dealt with, I think. I've hit some problems testing this patch, mainly because I can't seem to get a reliable baseline run with my current test setup. AFAICT, there should be no affect on behaviour unless displaced stepping is in use (differences in passes/failures with my patch only seem to be in "unreliable" tests, after running baseline testing three times), and of course displaced stepping isn't present for ARM without this patch anyway. OK to apply? Thanks, Julian ChangeLog gdb/ * arm-linux-tdep.c (arch-utils.h, inferior.h): Include files. (cleanup_kernel_helper_return, arm_catch_kernel_helper_return): New. (arm_linux_displaced_step_copy_insn): New. (arm_linux_init_abi): Initialise displaced stepping callbacks. * arm-tdep.c (DISPLACED_STEPPING_ARCH_VERSION): New macro. (ARM_NOP): New. (displaced_read_reg, displaced_in_arm_mode, branch_write_pc) (bx_write_pc, load_write_pc, alu_write_pc, displaced_write_reg) (insn_references_pc, copy_unmodified, cleanup_preload, copy_preload) (copy_preload_reg, cleanup_copro_load_store, copy_copro_load_store) (cleanup_branch, copy_b_bl_blx, copy_bx_blx_reg, cleanup_alu_imm) (copy_alu_imm, cleanup_alu_reg, copy_alu_reg) (cleanup_alu_shifted_reg, copy_alu_shifted_reg, cleanup_load) (cleanup_store, copy_extra_ld_st, copy_ldr_str_ldrb_strb) (cleanup_block_load_all, cleanup_block_store_pc) (cleanup_block_load_pc, copy_block_xfer, cleanup_svc, copy_svc) (copy_undef, copy_unpred): New. (decode_misc_memhint_neon, decode_unconditional) (decode_miscellaneous, decode_dp_misc, decode_ld_st_word_ubyte) (decode_media, decode_b_bl_ldmstm, decode_ext_reg_ld_st) (decode_svc_copro, arm_process_displaced_insn) (arm_displaced_init_closure, arm_displcaed_step_copy_insn) (arm_displaced_step_fixup): New. (arm_gdbarch_init): Initialise max insn length field. * arm-tdep.h (DISPLACED_TEMPS, DISPLACED_MODIFIED_INSNS): New macros. (displaced_step_closure, pc_write_style): New. (arm_displaced_init_closure, displaced_read_reg) (displaced_write_reg, arm_displaced_step_copy_insn) (arm_displaced_step_fixup): Add prototypes. --MP_/LBSRa93K3+h/hpbvwvOOZ4u Content-Type: text/x-patch; name=fsf-arm-displaced-stepping-6.diff Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=fsf-arm-displaced-stepping-6.diff Content-length: 64527 --- .pc/displaced-stepping/gdb/arm-linux-tdep.c 2009-05-15 16:05:07.000000000 -0700 +++ gdb/arm-linux-tdep.c 2009-05-16 10:16:52.000000000 -0700 @@ -38,6 +38,8 @@ #include "arm-linux-tdep.h" #include "linux-tdep.h" #include "glibc-tdep.h" +#include "arch-utils.h" +#include "inferior.h" #include "gdb_string.h" @@ -590,6 +592,77 @@ arm_linux_software_single_step (struct f return 1; } +/* The following two functions implement single-stepping over calls to Linux + kernel helper routines, which perform e.g. atomic operations on architecture + variants which don't support them natively. We call the helper out-of-line + and place a breakpoint at the return address (in our scratch space). */ + +static void +cleanup_kernel_helper_return (struct regcache *regs, + struct displaced_step_closure *dsc) +{ + displaced_write_reg (regs, dsc, ARM_LR_REGNUM, dsc->tmp[0], CANNOT_WRITE_PC); + displaced_write_reg (regs, dsc, ARM_PC_REGNUM, dsc->tmp[0], BRANCH_WRITE_PC); +} + +static struct displaced_step_closure * +arm_catch_kernel_helper_return (CORE_ADDR from, CORE_ADDR to, + struct regcache *regs) +{ + struct displaced_step_closure *dsc + = xmalloc (sizeof (struct displaced_step_closure)); + + dsc->numinsns = 1; + dsc->insn_addr = from; + dsc->cleanup = &cleanup_kernel_helper_return; + /* Say we wrote to the PC, else cleanup will set PC to the next + instruction in the helper, which isn't helpful. */ + dsc->wrote_to_pc = 1; + + /* Preparation: tmp[0] <- r14 + r14 <- +4 + *(+8) <- from + Insn: ldr pc, [r14, #4] + Cleanup: r14 <- tmp[0], pc <- tmp[0]. */ + + dsc->tmp[0] = displaced_read_reg (regs, from, ARM_LR_REGNUM); + displaced_write_reg (regs, dsc, ARM_LR_REGNUM, (ULONGEST) to + 4, + CANNOT_WRITE_PC); + write_memory_unsigned_integer (to + 8, 4, from); + + dsc->modinsn[0] = 0xe59ef004; /* ldr pc, [lr, #4]. */ + + return dsc; +} + +/* Linux-specific displaced step instruction copying function. Detects when + the program has stepped into a Linux kernel helper routine (which must be + handled as a special case), falling back to arm_displaced_step_copy_insn() + if it hasn't. */ + +static struct displaced_step_closure * +arm_linux_displaced_step_copy_insn (struct gdbarch *gdbarch, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs) +{ + /* Detect when we enter an (inaccessible by GDB) Linux kernel helper, and + stop at the return location. */ + if (from > 0xffff0000) + { + struct displaced_step_closure *dsc; + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: detected kernel helper " + "at %.8lx\n", (unsigned long) from); + + dsc = arm_catch_kernel_helper_return (from, to, regs); + + return arm_displaced_init_closure (gdbarch, from, to, dsc); + } + else + return arm_displaced_step_copy_insn (gdbarch, from, to, regs); +} + static void arm_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) @@ -650,6 +723,14 @@ arm_linux_init_abi (struct gdbarch_info arm_linux_regset_from_core_section); set_gdbarch_get_siginfo_type (gdbarch, linux_get_siginfo_type); + + /* Displaced stepping. */ + set_gdbarch_displaced_step_copy_insn (gdbarch, + arm_linux_displaced_step_copy_insn); + set_gdbarch_displaced_step_fixup (gdbarch, arm_displaced_step_fixup); + set_gdbarch_displaced_step_free_closure (gdbarch, + simple_displaced_step_free_closure); + set_gdbarch_displaced_step_location (gdbarch, displaced_step_at_entry_point); } /* Provide a prototype to silence -Wmissing-prototypes. */ --- .pc/displaced-stepping/gdb/arm-tdep.c 2009-05-15 16:05:07.000000000 -0700 +++ gdb/arm-tdep.c 2009-05-16 10:16:52.000000000 -0700 @@ -241,6 +241,11 @@ struct arm_prologue_cache struct trad_frame_saved_reg *saved_regs; }; +/* Architecture version for displaced stepping. This effects the behaviour of + certain instructions, and really should not be hard-wired. */ + +#define DISPLACED_STEPPING_ARCH_VERSION 5 + /* Addresses for calling Thumb functions have the bit 0 set. Here are some macros to test, set, or clear bit 0 of addresses. */ #define IS_THUMB_ADDR(addr) ((addr) & 1) @@ -2175,6 +2180,1828 @@ arm_software_single_step (struct frame_i return 1; } +/* ARM displaced stepping support. + + Generally ARM displaced stepping works as follows: + + 1. When an instruction is to be single-stepped, it is first decoded by + arm_process_displaced_insn (called from arm_displaced_step_copy_insn). + Depending on the type of instruction, it is then copied to a scratch + location, possibly in a modified form. The copy_* set of functions + performs such modification, as necessary. A breakpoint is placed after + the modified instruction in the scratch space to return control to GDB. + Note in particular that instructions which modify the PC will no longer + do so after modification. + + 2. The instruction is single-stepped. + + 3. A cleanup function (cleanup_*) is called corresponding to the copy_* + function used for the current instruction. This function's job is to + put the CPU/memory state back to what it would have been if the + instruction had been executed unmodified in its original location. */ + +/* NOP instruction (mov r0, r0). */ +#define ARM_NOP 0xe1a00000 + +/* Helper for register reads for displaced stepping. In particular, this + returns the PC as it would be seen by the instruction at its original + location. */ + +ULONGEST +displaced_read_reg (struct regcache *regs, CORE_ADDR from, int regno) +{ + ULONGEST ret; + + if (regno == 15) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: read pc value %.8lx\n", + (unsigned long) from + 8); + return (ULONGEST) from + 8; /* Pipeline offset. */ + } + else + { + regcache_cooked_read_unsigned (regs, regno, &ret); + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: read r%d value %.8lx\n", + regno, (unsigned long) ret); + return ret; + } +} + +static int +displaced_in_arm_mode (struct regcache *regs) +{ + ULONGEST ps; + + regcache_cooked_read_unsigned (regs, ARM_PS_REGNUM, &ps); + + return (ps & CPSR_T) == 0; +} + +/* Write to the PC as from a branch instruction. */ + +static void +branch_write_pc (struct regcache *regs, ULONGEST val) +{ + if (displaced_in_arm_mode (regs)) + /* Note: If bits 0/1 are set, this branch would be unpredictable for + architecture versions < 6. */ + regcache_cooked_write_unsigned (regs, ARM_PC_REGNUM, val & ~(ULONGEST) 0x3); + else + regcache_cooked_write_unsigned (regs, ARM_PC_REGNUM, val & ~(ULONGEST) 0x1); +} + +/* Write to the PC as from a branch-exchange instruction. */ + +static void +bx_write_pc (struct regcache *regs, ULONGEST val) +{ + ULONGEST ps; + + regcache_cooked_read_unsigned (regs, ARM_PS_REGNUM, &ps); + + if ((val & 1) == 1) + { + regcache_cooked_write_unsigned (regs, ARM_PS_REGNUM, ps | CPSR_T); + regcache_cooked_write_unsigned (regs, ARM_PC_REGNUM, val & 0xfffffffe); + } + else if ((val & 2) == 0) + { + regcache_cooked_write_unsigned (regs, ARM_PS_REGNUM, + ps & ~(ULONGEST) CPSR_T); + regcache_cooked_write_unsigned (regs, ARM_PC_REGNUM, val); + } + else + /* Unpredictable behaviour. */ + warning (_("Single-stepping BX to non-word-aligned ARM instruction.")); +} + +/* Write to the PC as if from a load instruction. */ + +static void +load_write_pc (struct regcache *regs, ULONGEST val) +{ + if (DISPLACED_STEPPING_ARCH_VERSION >= 5) + bx_write_pc (regs, val); + else + branch_write_pc (regs, val); +} + +/* Write to the PC as if from an ALU instruction. */ + +static void +alu_write_pc (struct regcache *regs, ULONGEST val) +{ + if (DISPLACED_STEPPING_ARCH_VERSION >= 7 && displaced_in_arm_mode (regs)) + bx_write_pc (regs, val); + else + branch_write_pc (regs, val); +} + +/* Helper for writing to registers for displaced stepping. Writing to the PC + has a varying effects depending on the instruction which does the write: + this is controlled by the WRITE_PC argument. */ + +void +displaced_write_reg (struct regcache *regs, struct displaced_step_closure *dsc, + int regno, ULONGEST val, enum pc_write_style write_pc) +{ + if (regno == 15) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: writing pc %.8lx\n", + (unsigned long) val); + switch (write_pc) + { + case BRANCH_WRITE_PC: + branch_write_pc (regs, val); + break; + + case BX_WRITE_PC: + bx_write_pc (regs, val); + break; + + case LOAD_WRITE_PC: + load_write_pc (regs, val); + break; + + case ALU_WRITE_PC: + alu_write_pc (regs, val); + break; + + case CANNOT_WRITE_PC: + warning (_("Instruction wrote to PC in an unexpected way when " + "single-stepping")); + break; + + default: + abort (); + } + + dsc->wrote_to_pc = 1; + } + else + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: writing r%d value %.8lx\n", + regno, (unsigned long) val); + regcache_cooked_write_unsigned (regs, regno, val); + } +} + +/* This function is used to concisely determine if an instruction INSN + references PC. Register fields of interest in INSN should have the + corresponding fields of BITMASK set to 0b1111. The function returns return 1 + if any of these fields in INSN reference the PC (also 0b1111, r15), else it + returns 0. */ + +static int +insn_references_pc (unsigned long insn, unsigned long bitmask) +{ + unsigned long lowbit = 1; + + while (bitmask != 0) + { + unsigned long mask; + + for (; lowbit && (bitmask & lowbit) == 0; lowbit <<= 1) + ; + + if (!lowbit) + break; + + mask = lowbit * 0xf; + + if ((insn & mask) == mask) + return 1; + + bitmask &= ~mask; + } + + return 0; +} + +/* The simplest copy function. Many instructions have the same effect no + matter what address they are executed at: in those cases, use this. */ + +static int +copy_unmodified (unsigned long insn, const char *iname, + struct displaced_step_closure *dsc) +{ + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: copying insn %.8lx, " + "opcode/class '%s' unmodified\n", insn, iname); + + dsc->modinsn[0] = insn; + + return 0; +} + +/* Preload instructions with immediate offset. */ + +static void +cleanup_preload (struct regcache *regs, struct displaced_step_closure *dsc) +{ + displaced_write_reg (regs, dsc, 0, dsc->tmp[0], CANNOT_WRITE_PC); + if (!dsc->u.preload.immed) + displaced_write_reg (regs, dsc, 1, dsc->tmp[1], CANNOT_WRITE_PC); +} + +static int +copy_preload (unsigned long insn, struct regcache *regs, + struct displaced_step_closure *dsc) +{ + unsigned int rn = bits (insn, 16, 19); + ULONGEST rn_val; + CORE_ADDR from = dsc->insn_addr; + + if (!insn_references_pc (insn, 0x000f0000ul)) + return copy_unmodified (insn, "preload", dsc); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: copying preload insn %.8lx\n", + insn); + + /* Preload instructions: + + {pli/pld} [rn, #+/-imm] + -> + {pli/pld} [r0, #+/-imm]. */ + + dsc->tmp[0] = displaced_read_reg (regs, from, 0); + rn_val = displaced_read_reg (regs, from, rn); + displaced_write_reg (regs, dsc, 0, rn_val, CANNOT_WRITE_PC); + + dsc->u.preload.immed = 1; + + dsc->modinsn[0] = insn & 0xfff0ffff; + + dsc->cleanup = &cleanup_preload; + + return 0; +} + +/* Preload instructions with register offset. */ + +static int +copy_preload_reg (unsigned long insn, struct regcache *regs, + struct displaced_step_closure *dsc) +{ + unsigned int rn = bits (insn, 16, 19); + unsigned int rm = bits (insn, 0, 3); + ULONGEST rn_val, rm_val; + CORE_ADDR from = dsc->insn_addr; + + if (!insn_references_pc (insn, 0x000f000ful)) + return copy_unmodified (insn, "preload reg", dsc); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: copying preload insn %.8lx\n", + insn); + + /* Preload register-offset instructions: + + {pli/pld} [rn, rm {, shift}] + -> + {pli/pld} [r0, r1 {, shift}]. */ + + dsc->tmp[0] = displaced_read_reg (regs, from, 0); + dsc->tmp[1] = displaced_read_reg (regs, from, 1); + rn_val = displaced_read_reg (regs, from, rn); + rm_val = displaced_read_reg (regs, from, rm); + displaced_write_reg (regs, dsc, 0, rn_val, CANNOT_WRITE_PC); + displaced_write_reg (regs, dsc, 1, rm_val, CANNOT_WRITE_PC); + + dsc->u.preload.immed = 0; + + dsc->modinsn[0] = (insn & 0xfff0fff0) | 0x1; + + dsc->cleanup = &cleanup_preload; + + return 0; +} + +/* Copy/cleanup coprocessor load and store instructions. */ + +static void +cleanup_copro_load_store (struct regcache *regs, + struct displaced_step_closure *dsc) +{ + ULONGEST rn_val = displaced_read_reg (regs, dsc->insn_addr, 0); + + displaced_write_reg (regs, dsc, 0, dsc->tmp[0], CANNOT_WRITE_PC); + + if (dsc->u.ldst.writeback) + displaced_write_reg (regs, dsc, dsc->u.ldst.rn, rn_val, LOAD_WRITE_PC); +} + +static int +copy_copro_load_store (unsigned long insn, struct regcache *regs, + struct displaced_step_closure *dsc) +{ + unsigned int rn = bits (insn, 16, 19); + ULONGEST rn_val; + CORE_ADDR from = dsc->insn_addr; + + if (!insn_references_pc (insn, 0x000f0000ul)) + return copy_unmodified (insn, "copro load/store", dsc); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: copying coprocessor " + "load/store insn %.8lx\n", insn); + + /* Coprocessor load/store instructions: + + {stc/stc2} [, #+/-imm] (and other immediate addressing modes) + -> + {stc/stc2} [r0, #+/-imm]. + + ldc/ldc2 are handled identically. */ + + dsc->tmp[0] = displaced_read_reg (regs, from, 0); + rn_val = displaced_read_reg (regs, from, rn); + displaced_write_reg (regs, dsc, 0, rn_val, CANNOT_WRITE_PC); + + dsc->u.ldst.writeback = bit (insn, 25); + dsc->u.ldst.rn = rn; + + dsc->modinsn[0] = insn & 0xfff0ffff; + + dsc->cleanup = &cleanup_copro_load_store; + + return 0; +} + +/* Clean up branch instructions (actually perform the branch, by setting + PC). */ + +static void +cleanup_branch (struct regcache *regs, struct displaced_step_closure *dsc) +{ + ULONGEST from = dsc->insn_addr; + unsigned long status = displaced_read_reg (regs, from, ARM_PS_REGNUM); + int branch_taken = condition_true (dsc->u.branch.cond, status); + enum pc_write_style write_pc = dsc->u.branch.exchange + ? BX_WRITE_PC : BRANCH_WRITE_PC; + + if (!branch_taken) + return; + + if (dsc->u.branch.link) + { + ULONGEST pc = displaced_read_reg (regs, from, 15); + displaced_write_reg (regs, dsc, 14, pc - 4, CANNOT_WRITE_PC); + } + + displaced_write_reg (regs, dsc, 15, dsc->u.branch.dest, write_pc); +} + +/* Copy B/BL/BLX instructions with immediate destinations. */ + +static int +copy_b_bl_blx (unsigned long insn, struct regcache *regs, + struct displaced_step_closure *dsc) +{ + unsigned int cond = bits (insn, 28, 31); + int exchange = (cond == 0xf); + int link = exchange || bit (insn, 24); + CORE_ADDR from = dsc->insn_addr; + long offset; + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: copying %s immediate insn " + "%.8lx\n", (exchange) ? "blx" : (link) ? "bl" : "b", + insn); + + /* Implement "BL