From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 24186 invoked by alias); 20 Jan 2009 22:14:26 -0000 Received: (qmail 24169 invoked by uid 22791); 20 Jan 2009 22:14:21 -0000 X-SWARE-Spam-Status: No, hits=-1.2 required=5.0 tests=AWL,BAYES_20,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; Tue, 20 Jan 2009 22:14:12 +0000 Received: (qmail 16399 invoked from network); 20 Jan 2009 22:14:02 -0000 Received: from unknown (HELO rex.config) (julian@127.0.0.2) by mail.codesourcery.com with ESMTPA; 20 Jan 2009 22:14:02 -0000 Date: Tue, 20 Jan 2009 22:14:00 -0000 From: Julian Brown To: gdb-patches@sourceware.org Cc: pedro@codesourcery.com Subject: [PATCH] Displaced stepping (non-stop debugging) support for ARM Linux Message-ID: <20090120221355.46ac23e6@rex.config> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="MP_/=xcLUPMm0tPj=7RdcupsiZ/" 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-01/txt/msg00430.txt.bz2 --MP_/=xcLUPMm0tPj=7RdcupsiZ/ Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Content-Disposition: inline Content-length: 3586 Hi, These patches provide an implementation of displaced stepping support for ARM (Linux only for now), using the generic hooks provided by GDB. ARM support is relatively tricky compared to some other architectures, because there's no hardware single-stepping support. However we can fake it by making sure that displaced instructions don't modify control flow, and placing a software breakpoint after each displaced instruction. Also registers are rewritten to handle instructions which might read/write the PC. We must of course take care that the cleanup routine puts things back in the correct places. As a side-effect of the lack of h/w single-stepping support, we've enabled displaced stepping in all cases, not just when stepping over breakpoints (a patch of Pedro Alves's, attached, but mangled by me to apply to mainline). I'm not sure if that's the most sensible approach (for displaced stepping, we only care about not *removing* breakpoints which might be hit by other threads. We can still add temporary breakpoints for the purpose of software single-stepping). Only the traditional ARM instruction set is covered by this patch -- there's no support for Thumb or Thumb-2. For ARM instructions, the coverage is pretty good though I think. Note that though this implementation is loosely inspired by the Linux kernel's kprobes implementation, no code has been taken from there. Regression tested using an x86 host and a remote target running gdbserver, with (possibly) no regressions -- although several tests seem to fluctuate randomly between passing/failing for me (with timeouts) with or without the patch. I'm not sure how normal that is. Also tested with "GDBFLAGS=-ex 'set displaced-stepping on'", which seemed OK, and of course with hand-written spot-checks. OK to apply, or any comments? Cheers, Julian ChangeLog (always use displaced stepping) 2008-11-19 Pedro Alves * infrun.c (displaced_step_fixup): If this is a software single-stepping arch, don't tell the target to single-step. (resume): If this is a software single-stepping arch, and displaced-stepping is enabled, use it for all single-step requests. ChangeLog (ARM displaced stepping) gdb/ * arm-linux-tdep.c (arch-utils.h): Include file. (arm_linux_init_abi): Initialise displaced stepping callbacks. * arm-tdep.c (DISPLACED_TEMPS, DISPLACED_MODIFIED_INSNS): New macros. (struct displaced_step_closure): Define. (displaced_read_reg, displaced_write_reg, copy_unmodified) (copy_preload, copy_preload_reg, copy_copro_load_store) (copy_b_bl_blx, copy_bx_blx_reg, copy_dp_imm, copy_dp_reg) (copy_dp_shifted_reg, modify_store_pc, copy_extra_ld_st) (copy_ldr_str_ldrb_strb, copy_block_xfer, copy_svc, copy_undef) (copy_unpred): New. (cleanup_branch, cleanup_dp_imm, cleanup_dp_reg) (cleanup_dp_shifted_reg, cleanup_load, cleanup_store) (cleanup_block_xfer, cleanup_svc, cleanup_kernel_helper_return) (cleanup_preload, cleanup_copro_load_store): New functions (with forward declarations). (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_catch_kernel_helper_return, arm_displaced_step_copy_insn) (arm_displaced_step_fixup): New. (arm_gdbarch_init): Initialise max insn length field. * arm-tdep.h (arm_displaced_step_copy_insn) (arm_displaced_step_fixup): Add prototypes. --MP_/=xcLUPMm0tPj=7RdcupsiZ/ Content-Type: text/x-patch; name=fsf-arm-displaced-stepping-1.diff Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=fsf-arm-displaced-stepping-1.diff Content-length: 46164 --- .pc/arm-displaced-stepping/gdb/arm-linux-tdep.c 2009-01-20 13:22:42.000000000 -0800 +++ gdb/arm-linux-tdep.c 2009-01-20 13:23:59.000000000 -0800 @@ -37,6 +37,7 @@ #include "arm-tdep.h" #include "arm-linux-tdep.h" #include "glibc-tdep.h" +#include "arch-utils.h" #include "gdb_string.h" @@ -647,6 +648,14 @@ arm_linux_init_abi (struct gdbarch_info /* Core file support. */ set_gdbarch_regset_from_core_section (gdbarch, arm_linux_regset_from_core_section); + + /* Displaced stepping. */ + set_gdbarch_displaced_step_copy_insn (gdbarch, + arm_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); } void --- .pc/arm-displaced-stepping/gdb/arm-tdep.c 2009-01-20 13:22:42.000000000 -0800 +++ gdb/arm-tdep.c 2009-01-20 13:33:10.000000000 -0800 @@ -2175,6 +2175,1456 @@ arm_software_single_step (struct frame_i return 1; } +/* Displaced stepping support. */ + +/* The maximum number of temporaries available for displaced instructions. */ +#define DISPLACED_TEMPS 5 +/* The maximum number of modified instructions generated for one single-stepped + instruction. */ +#define DISPLACED_MODIFIED_INSNS 5 + +struct displaced_step_closure +{ + ULONGEST tmp[DISPLACED_TEMPS]; + int rd; + int wrote_to_pc; + union + { + struct + { + int xfersize; + int rn; /* Writeback register. */ + unsigned int immed : 1; /* Offset is immediate. */ + unsigned int writeback : 1; /* Perform base-register writeback. */ + } ldst; + + struct + { + unsigned long dest; + unsigned int link : 1; + unsigned int exchange : 1; + } branch; + + struct + { + unsigned int regmask; + int rn; + CORE_ADDR xfer_addr; + unsigned int load : 1; + unsigned int user : 1; + unsigned int increment : 1; + unsigned int before : 1; + unsigned int writeback : 1; + } block; + + struct + { + unsigned int immed : 1; + } preload; + } u; + unsigned long modinsn[DISPLACED_MODIFIED_INSNS]; + int numinsns; + CORE_ADDR insn_addr; + void (*cleanup) (struct regcache *, struct displaced_step_closure *); +}; + +static void cleanup_branch (struct regcache *, struct displaced_step_closure *); +static void cleanup_dp_imm (struct regcache *, struct displaced_step_closure *); +static void cleanup_dp_reg (struct regcache *, struct displaced_step_closure *); +static void cleanup_dp_shifted_reg (struct regcache *, + struct displaced_step_closure *); +static void cleanup_load (struct regcache *, struct displaced_step_closure *); +static void cleanup_store (struct regcache *, struct displaced_step_closure *); +static void cleanup_block_xfer (struct regcache *, + struct displaced_step_closure *); +static void cleanup_svc (struct regcache *, struct displaced_step_closure *); +static void cleanup_kernel_helper_return (struct regcache *, + struct displaced_step_closure *); +static void cleanup_preload (struct regcache *, + struct displaced_step_closure *); +static void cleanup_copro_load_store (struct regcache *, + struct displaced_step_closure *); + +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 void arm_write_pc (struct regcache *, CORE_ADDR); + +void +displaced_write_reg (struct regcache *regs, struct displaced_step_closure *dsc, + int regno, ULONGEST val) +{ + if (regno == 15) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: writing pc %.8lx\n", + (unsigned long) val); + arm_write_pc (regs, val); + 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); + } +} + +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'\n", insn, iname); + + dsc->modinsn[0] = insn; + + return 0; +} + +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 (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); + + dsc->u.preload.immed = 1; + + dsc->modinsn[0] = insn & 0xfff0ffff; + + dsc->cleanup = &cleanup_preload; + + return 0; +} + +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 (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); + displaced_write_reg (regs, dsc, 1, rm_val); + + dsc->u.preload.immed = 0; + + dsc->modinsn[0] = (insn & 0xfff0fff0) | 0x1; + + dsc->cleanup = &cleanup_preload; + + return 0; +} + +static void +cleanup_preload (struct regcache *regs, struct displaced_step_closure *dsc) +{ + displaced_write_reg (regs, dsc, 0, dsc->tmp[0]); + if (!dsc->u.preload.immed) + displaced_write_reg (regs, dsc, 1, dsc->tmp[1]); +} + +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 (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); + + 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; +} + +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]); + + if (dsc->u.ldst.writeback) + displaced_write_reg (regs, dsc, dsc->u.ldst.rn, rn_val); +} + +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); + + offset = bits (insn, 0, 23) << 2; + if (bit (offset, 25)) + offset = offset | ~0x3ffffff; + + /* Implement "BL