diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 23 | ||||
-rw-r--r-- | gdb/amd64-linux-tdep.c | 10 | ||||
-rw-r--r-- | gdb/amd64-tdep.c | 729 | ||||
-rw-r--r-- | gdb/amd64-tdep.h | 8 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 7 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/amd64-disp-step.S | 153 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/amd64-disp-step.exp | 219 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/i386-disp-step.S | 64 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/i386-disp-step.exp | 112 |
9 files changed, 1306 insertions, 19 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 2d96f6e..474dbc2 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,26 @@ +2009-01-28 Doug Evans <dje@google.com> + + * amd64-tdep.h (amd64_displaced_step_copy_insn): Declare. + (amd64_displaced_step_fixup): Declare. + * amd64-tdep.c: #include opcode/i386.h, dis-asm.h. + (amd64_arch_regmap): Move out of amd64_analyze_stack_align + and make static global. + (amd64_arch_regmap_len): New static global. + (amd64_arch_reg_to_regnum): New function. + (struct amd64_insn): New struct. + (struct displaced_step_closure): New struct. + (onebyte_has_modrm,twobyte_has_modrm): New static globals. + (rex_prefix_p,skip_prefixes) + (amd64_insn_length_fprintf,amd64_insn_length_init_dis) + (amd64_insn_length,amd64_get_unused_input_int_reg) + (amd64_get_insn_details,fixup_riprel,fixup_displaced_copy) + (amd64_displaced_step_copy_insn) + (amd64_absolute_jmp_p,amd64_absolute_call_p,amd64_ret_p) + (amd64_call_p,amd64_breakpoint_p,amd64_syscall_p) + (amd64_displaced_step_fixup): New functions. + * amd64-linux-tdep.c: #include arch-utils.h. + (amd64_linux_init_abi): Install displaced stepping support. + 2009-01-28 Pedro Alves <pedro@codesourcery.com> * corefile.c (generic_search): Delete disabled code. diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index 1d99cd4..b8e779e 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "defs.h" +#include "arch-utils.h" #include "frame.h" #include "gdbcore.h" #include "regcache.h" @@ -286,6 +287,15 @@ amd64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) /* Enable TLS support. */ set_gdbarch_fetch_tls_load_module_address (gdbarch, svr4_fetch_objfile_link_map); + + /* Displaced stepping. */ + set_gdbarch_displaced_step_copy_insn (gdbarch, + amd64_displaced_step_copy_insn); + set_gdbarch_displaced_step_fixup (gdbarch, amd64_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); } diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c index bc690c3..ad26493 100644 --- a/gdb/amd64-tdep.c +++ b/gdb/amd64-tdep.c @@ -21,6 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "defs.h" +#include "opcode/i386.h" +#include "dis-asm.h" #include "arch-utils.h" #include "block.h" #include "dummy-frame.h" @@ -200,6 +202,42 @@ amd64_dwarf_reg_to_regnum (struct gdbarch *gdbarch, int reg) return regnum; } +/* Map architectural register numbers to gdb register numbers. */ + +static const int amd64_arch_regmap[16] = +{ + AMD64_RAX_REGNUM, /* %rax */ + AMD64_RCX_REGNUM, /* %rcx */ + AMD64_RDX_REGNUM, /* %rdx */ + AMD64_RBX_REGNUM, /* %rbx */ + AMD64_RSP_REGNUM, /* %rsp */ + AMD64_RBP_REGNUM, /* %rbp */ + AMD64_RSI_REGNUM, /* %rsi */ + AMD64_RDI_REGNUM, /* %rdi */ + AMD64_R8_REGNUM, /* %r8 */ + AMD64_R9_REGNUM, /* %r9 */ + AMD64_R10_REGNUM, /* %r10 */ + AMD64_R11_REGNUM, /* %r11 */ + AMD64_R12_REGNUM, /* %r12 */ + AMD64_R13_REGNUM, /* %r13 */ + AMD64_R14_REGNUM, /* %r14 */ + AMD64_R15_REGNUM /* %r15 */ +}; + +static const int amd64_arch_regmap_len = + (sizeof (amd64_arch_regmap) / sizeof (amd64_arch_regmap[0])); + +/* Convert architectural register number REG to the appropriate register + number used by GDB. */ + +static int +amd64_arch_reg_to_regnum (int reg) +{ + gdb_assert (reg >= 0 && reg < amd64_arch_regmap_len); + + return amd64_arch_regmap[reg]; +} + /* Register classes as defined in the psABI. */ @@ -666,7 +704,678 @@ amd64_push_dummy_call (struct gdbarch *gdbarch, struct value *function, return sp + 16; } +/* Displaced instruction handling. */ + +/* A partially decoded instruction. + This contains enough details for displaced stepping purposes. */ + +struct amd64_insn +{ + /* The number of opcode bytes. */ + int opcode_len; + /* The offset of the rex prefix or -1 if not present. */ + int rex_offset; + /* The offset to the first opcode byte. */ + int opcode_offset; + /* The offset to the modrm byte or -1 if not present. */ + int modrm_offset; + + /* The raw instruction. */ + gdb_byte *raw_insn; +}; + +struct displaced_step_closure +{ + /* For rip-relative insns, saved copy of the reg we use instead of %rip. */ + int tmp_used; + int tmp_regno; + ULONGEST tmp_save; + + /* Details of the instruction. */ + struct amd64_insn insn_details; + + /* Amount of space allocated to insn_buf. */ + int max_len; + + /* The possibly modified insn. + This is a variable-length field. */ + gdb_byte insn_buf[1]; +}; + +/* WARNING: Keep onebyte_has_modrm, twobyte_has_modrm in sync with + ../opcodes/i386-dis.c (until libopcodes exports them, or an alternative, + at which point delete these in favor of libopcodes' versions). */ + +static const unsigned char onebyte_has_modrm[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 00 */ + /* 10 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 10 */ + /* 20 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 20 */ + /* 30 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 30 */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40 */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 50 */ + /* 60 */ 0,0,1,1,0,0,0,0,0,1,0,1,0,0,0,0, /* 60 */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 70 */ + /* 80 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 80 */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 90 */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* a0 */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* b0 */ + /* c0 */ 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0, /* c0 */ + /* d0 */ 1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1, /* d0 */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* e0 */ + /* f0 */ 0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1 /* f0 */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static const unsigned char twobyte_has_modrm[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,1, /* 0f */ + /* 10 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 1f */ + /* 20 */ 1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, /* 3f */ + /* 40 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 4f */ + /* 50 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 5f */ + /* 60 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 6f */ + /* 70 */ 1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 9f */ + /* a0 */ 0,0,0,1,1,1,1,1,0,0,0,1,1,1,1,1, /* af */ + /* b0 */ 1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1, /* bf */ + /* c0 */ 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* df */ + /* e0 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* ef */ + /* f0 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static int amd64_syscall_p (const struct amd64_insn *insn, int *lengthp); + +static int +rex_prefix_p (gdb_byte pfx) +{ + return REX_PREFIX_P (pfx); +} + +/* Skip the legacy instruction prefixes in INSN. + We assume INSN is properly sentineled so we don't have to worry + about falling off the end of the buffer. */ + +static gdb_byte * +skip_prefixes (gdb_byte *insn) +{ + while (1) + { + switch (*insn) + { + case DATA_PREFIX_OPCODE: + case ADDR_PREFIX_OPCODE: + case CS_PREFIX_OPCODE: + case DS_PREFIX_OPCODE: + case ES_PREFIX_OPCODE: + case FS_PREFIX_OPCODE: + case GS_PREFIX_OPCODE: + case SS_PREFIX_OPCODE: + case LOCK_PREFIX_OPCODE: + case REPE_PREFIX_OPCODE: + case REPNE_PREFIX_OPCODE: + ++insn; + continue; + default: + break; + } + break; + } + + return insn; +} + +/* fprintf-function for amd64_insn_length. + This function is a nop, we don't want to print anything, we just want to + compute the length of the insn. */ + +static int ATTR_FORMAT (printf, 2, 3) +amd64_insn_length_fprintf (void *stream, const char *format, ...) +{ + return 0; +} + +/* Initialize a struct disassemble_info for amd64_insn_length. */ + +static void +amd64_insn_length_init_dis (struct gdbarch *gdbarch, + struct disassemble_info *di, + const gdb_byte *insn, int max_len, + CORE_ADDR addr) +{ + init_disassemble_info (di, NULL, amd64_insn_length_fprintf); + + /* init_disassemble_info installs buffer_read_memory, etc. + so we don't need to do that here. + The cast is necessary until disassemble_info is const-ified. */ + di->buffer = (gdb_byte *) insn; + di->buffer_length = max_len; + di->buffer_vma = addr; + + di->arch = gdbarch_bfd_arch_info (gdbarch)->arch; + di->mach = gdbarch_bfd_arch_info (gdbarch)->mach; + di->endian = gdbarch_byte_order (gdbarch); + di->endian_code = gdbarch_byte_order_for_code (gdbarch); + + disassemble_init_for_target (di); +} +/* Return the length in bytes of INSN. + MAX_LEN is the size of the buffer containing INSN. + libopcodes currently doesn't export a utility to compute the + instruction length, so use the disassembler until then. */ + +static int +amd64_insn_length (struct gdbarch *gdbarch, + const gdb_byte *insn, int max_len, CORE_ADDR addr) +{ + struct disassemble_info di; + + amd64_insn_length_init_dis (gdbarch, &di, insn, max_len, addr); + + return gdbarch_print_insn (gdbarch, addr, &di); +} + +/* Return an integer register (other than RSP) that is unused as an input + operand in INSN. + In order to not require adding a rex prefix if the insn doesn't already + have one, the result is restricted to RAX ... RDI, sans RSP. + The register numbering of the result follows architecture ordering, + e.g. RDI = 7. */ + +static int +amd64_get_unused_input_int_reg (const struct amd64_insn *details) +{ + /* 1 bit for each reg */ + int used_regs_mask = 0; + + /* There can be at most 3 int regs used as inputs in an insn, and we have + 7 to choose from (RAX ... RDI, sans RSP). + This allows us to take a conservative approach and keep things simple. + E.g. By avoiding RAX, we don't have to specifically watch for opcodes + that implicitly specify RAX. */ + + /* Avoid RAX. */ + used_regs_mask |= 1 << EAX_REG_NUM; + /* Similarily avoid RDX, implicit operand in divides. */ + used_regs_mask |= 1 << EDX_REG_NUM; + /* Avoid RSP. */ + used_regs_mask |= 1 << ESP_REG_NUM; + + /* If the opcode is one byte long and there's no ModRM byte, + assume the opcode specifies a register. */ + if (details->opcode_len == 1 && details->modrm_offset == -1) + used_regs_mask |= 1 << (details->raw_insn[details->opcode_offset] & 7); + + /* Mark used regs in the modrm/sib bytes. */ + if (details->modrm_offset != -1) + { + int modrm = details->raw_insn[details->modrm_offset]; + int mod = MODRM_MOD_FIELD (modrm); + int reg = MODRM_REG_FIELD (modrm); + int rm = MODRM_RM_FIELD (modrm); + int have_sib = mod != 3 && rm == 4; + + /* Assume the reg field of the modrm byte specifies a register. */ + used_regs_mask |= 1 << reg; + + if (have_sib) + { + int base = SIB_BASE_FIELD (details->raw_insn[details->modrm_offset + 1]); + int index = SIB_INDEX_FIELD (details->raw_insn[details->modrm_offset + 1]); + used_regs_mask |= 1 << base; + used_regs_mask |= 1 << index; + } + else + { + used_regs_mask |= 1 << rm; + } + } + + gdb_assert (used_regs_mask < 256); + gdb_assert (used_regs_mask != 255); + + /* Finally, find a free reg. */ + { + int i; + + for (i = 0; i < 8; ++i) + { + if (! (used_regs_mask & (1 << i))) + return i; + } + + /* We shouldn't get here. */ + internal_error (__FILE__, __LINE__, _("unable to find free reg")); + } +} + +/* Extract the details of INSN that we need. */ + +static void +amd64_get_insn_details (gdb_byte *insn, struct amd64_insn *details) +{ + gdb_byte *start = insn; + int need_modrm; + + details->raw_insn = insn; + + details->opcode_len = -1; + details->rex_offset = -1; + details->opcode_offset = -1; + details->modrm_offset = -1; + + /* Skip legacy instruction prefixes. */ + insn = skip_prefixes (insn); + + /* Skip REX instruction prefix. */ + if (rex_prefix_p (*insn)) + { + details->rex_offset = insn - start; + ++insn; + } + + details->opcode_offset = insn - start; + + if (*insn == TWO_BYTE_OPCODE_ESCAPE) + { + /* Two or three-byte opcode. */ + ++insn; + need_modrm = twobyte_has_modrm[*insn]; + + /* Check for three-byte opcode. */ + if (*insn == 0x38 || *insn == 0x3a) + { + ++insn; + details->opcode_len = 3; + } + else + details->opcode_len = 2; + } + else + { + /* One-byte opcode. */ + need_modrm = onebyte_has_modrm[*insn]; + details->opcode_len = 1; + } + + if (need_modrm) + { + ++insn; + details->modrm_offset = insn - start; + } +} + +/* Update %rip-relative addressing in INSN. + + %rip-relative addressing only uses a 32-bit displacement. + 32 bits is not enough to be guaranteed to cover the distance between where + the real instruction is and where its copy is. + Convert the insn to use base+disp addressing. + We set base = pc + insn_length so we can leave disp unchanged. */ + +static void +fixup_riprel (struct gdbarch *gdbarch, struct displaced_step_closure *dsc, + CORE_ADDR from, CORE_ADDR to, struct regcache *regs) +{ + const struct amd64_insn *insn_details = &dsc->insn_details; + int modrm_offset = insn_details->modrm_offset; + gdb_byte *insn = insn_details->raw_insn + modrm_offset; + CORE_ADDR rip_base; + int32_t disp; + int insn_length; + int arch_tmp_regno, tmp_regno; + ULONGEST orig_value; + + /* %rip+disp32 addressing mode, displacement follows ModRM byte. */ + ++insn; + + /* Compute the rip-relative address. */ + disp = extract_signed_integer (insn, sizeof (int32_t)); + insn_length = amd64_insn_length (gdbarch, dsc->insn_buf, dsc->max_len, from); + rip_base = from + insn_length; + + /* We need a register to hold the address. + Pick one not used in the insn. + NOTE: arch_tmp_regno uses architecture ordering, e.g. RDI = 7. */ + arch_tmp_regno = amd64_get_unused_input_int_reg (insn_details); + tmp_regno = amd64_arch_reg_to_regnum (arch_tmp_regno); + + /* REX.B should be unset as we were using rip-relative addressing, + but ensure it's unset anyway, tmp_regno is not r8-r15. */ + if (insn_details->rex_offset != -1) + dsc->insn_buf[insn_details->rex_offset] &= ~REX_B; + + regcache_cooked_read_unsigned (regs, tmp_regno, &orig_value); + dsc->tmp_regno = tmp_regno; + dsc->tmp_save = orig_value; + dsc->tmp_used = 1; + + /* Convert the ModRM field to be base+disp. */ + dsc->insn_buf[modrm_offset] &= ~0xc7; + dsc->insn_buf[modrm_offset] |= 0x80 + arch_tmp_regno; + + regcache_cooked_write_unsigned (regs, tmp_regno, rip_base); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: %%rip-relative addressing used.\n" + "displaced: using temp reg %d, old value 0x%s, new value 0x%s\n", + dsc->tmp_regno, paddr_nz (dsc->tmp_save), + paddr_nz (rip_base)); +} + +static void +fixup_displaced_copy (struct gdbarch *gdbarch, + struct displaced_step_closure *dsc, + CORE_ADDR from, CORE_ADDR to, struct regcache *regs) +{ + const struct amd64_insn *details = &dsc->insn_details; + + if (details->modrm_offset != -1) + { + gdb_byte modrm = details->raw_insn[details->modrm_offset]; + + if ((modrm & 0xc7) == 0x05) + { + /* The insn uses rip-relative addressing. + Deal with it. */ + fixup_riprel (gdbarch, dsc, from, to, regs); + } + } +} + +struct displaced_step_closure * +amd64_displaced_step_copy_insn (struct gdbarch *gdbarch, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs) +{ + int len = gdbarch_max_insn_length (gdbarch); + /* Extra space for sentinels so fixup_{riprel,displaced_copy don't have to + continually watch for running off the end of the buffer. */ + int fixup_sentinel_space = len; + struct displaced_step_closure *dsc = + xmalloc (sizeof (*dsc) + len + fixup_sentinel_space); + gdb_byte *buf = &dsc->insn_buf[0]; + struct amd64_insn *details = &dsc->insn_details; + + dsc->tmp_used = 0; + dsc->max_len = len + fixup_sentinel_space; + + read_memory (from, buf, len); + + /* Set up the sentinel space so we don't have to worry about running + off the end of the buffer. An excessive number of leading prefixes + could otherwise cause this. */ + memset (buf + len, 0, fixup_sentinel_space); + + amd64_get_insn_details (buf, details); + + /* GDB may get control back after the insn after the syscall. + Presumably this is a kernel bug. + If this is a syscall, make sure there's a nop afterwards. */ + { + int syscall_length; + + if (amd64_syscall_p (details, &syscall_length)) + buf[details->opcode_offset + syscall_length] = NOP_OPCODE; + } + + /* Modify the insn to cope with the address where it will be executed from. + In particular, handle any rip-relative addressing. */ + fixup_displaced_copy (gdbarch, dsc, from, to, regs); + + write_memory (to, buf, len); + + if (debug_displaced) + { + fprintf_unfiltered (gdb_stdlog, "displaced: copy 0x%s->0x%s: ", + paddr_nz (from), paddr_nz (to)); + displaced_step_dump_bytes (gdb_stdlog, buf, len); + } + + return dsc; +} + +static int +amd64_absolute_jmp_p (const struct amd64_insn *details) +{ + const gdb_byte *insn = &details->raw_insn[details->opcode_offset]; + + if (insn[0] == 0xff) + { + /* jump near, absolute indirect (/4) */ + if ((insn[1] & 0x38) == 0x20) + return 1; + + /* jump far, absolute indirect (/5) */ + if ((insn[1] & 0x38) == 0x28) + return 1; + } + + return 0; +} + +static int +amd64_absolute_call_p (const struct amd64_insn *details) +{ + const gdb_byte *insn = &details->raw_insn[details->opcode_offset]; + + if (insn[0] == 0xff) + { + /* Call near, absolute indirect (/2) */ + if ((insn[1] & 0x38) == 0x10) + return 1; + + /* Call far, absolute indirect (/3) */ + if ((insn[1] & 0x38) == 0x18) + return 1; + } + + return 0; +} + +static int +amd64_ret_p (const struct amd64_insn *details) +{ + /* NOTE: gcc can emit "repz ; ret". */ + const gdb_byte *insn = &details->raw_insn[details->opcode_offset]; + + switch (insn[0]) + { + case 0xc2: /* ret near, pop N bytes */ + case 0xc3: /* ret near */ + case 0xca: /* ret far, pop N bytes */ + case 0xcb: /* ret far */ + case 0xcf: /* iret */ + return 1; + + default: + return 0; + } +} + +static int +amd64_call_p (const struct amd64_insn *details) +{ + const gdb_byte *insn = &details->raw_insn[details->opcode_offset]; + + if (amd64_absolute_call_p (details)) + return 1; + + /* call near, relative */ + if (insn[0] == 0xe8) + return 1; + + return 0; +} + +static int +amd64_breakpoint_p (const struct amd64_insn *details) +{ + const gdb_byte *insn = &details->raw_insn[details->opcode_offset]; + + return insn[0] == 0xcc; /* int 3 */ +} + +/* Return non-zero if INSN is a system call, and set *LENGTHP to its + length in bytes. Otherwise, return zero. */ + +static int +amd64_syscall_p (const struct amd64_insn *details, int *lengthp) +{ + const gdb_byte *insn = &details->raw_insn[details->opcode_offset]; + + if (insn[0] == 0x0f && insn[1] == 0x05) + { + *lengthp = 2; + return 1; + } + + return 0; +} + +/* Fix up the state of registers and memory after having single-stepped + a displaced instruction. */ + +void +amd64_displaced_step_fixup (struct gdbarch *gdbarch, + struct displaced_step_closure *dsc, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs) +{ + /* The offset we applied to the instruction's address. */ + ULONGEST insn_offset = to - from; + gdb_byte *insn = dsc->insn_buf; + const struct amd64_insn *insn_details = &dsc->insn_details; + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: fixup (0x%s, 0x%s), " + "insn = 0x%02x 0x%02x ...\n", + paddr_nz (from), paddr_nz (to), insn[0], insn[1]); + + /* If we used a tmp reg, restore it. */ + + if (dsc->tmp_used) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: restoring reg %d to 0x%s\n", + dsc->tmp_regno, paddr_nz (dsc->tmp_save)); + regcache_cooked_write_unsigned (regs, dsc->tmp_regno, dsc->tmp_save); + } + + /* The list of issues to contend with here is taken from + resume_execution in arch/x86/kernel/kprobes.c, Linux 2.6.28. + Yay for Free Software! */ + + /* Relocate the %rip back to the program's instruction stream, + if necessary. */ + + /* Except in the case of absolute or indirect jump or call + instructions, or a return instruction, the new rip is relative to + the displaced instruction; make it relative to the original insn. + Well, signal handler returns don't need relocation either, but we use the + value of %rip to recognize those; see below. */ + if (! amd64_absolute_jmp_p (insn_details) + && ! amd64_absolute_call_p (insn_details) + && ! amd64_ret_p (insn_details)) + { + ULONGEST orig_rip; + int insn_len; + + regcache_cooked_read_unsigned (regs, AMD64_RIP_REGNUM, &orig_rip); + + /* A signal trampoline system call changes the %rip, resuming + execution of the main program after the signal handler has + returned. That makes them like 'return' instructions; we + shouldn't relocate %rip. + + But most system calls don't, and we do need to relocate %rip. + + Our heuristic for distinguishing these cases: if stepping + over the system call instruction left control directly after + the instruction, the we relocate --- control almost certainly + doesn't belong in the displaced copy. Otherwise, we assume + the instruction has put control where it belongs, and leave + it unrelocated. Goodness help us if there are PC-relative + system calls. */ + if (amd64_syscall_p (insn_details, &insn_len) + && orig_rip != to + insn_len + /* GDB can get control back after the insn after the syscall. + Presumably this is a kernel bug. + Fixup ensures its a nop, we add one to the length for it. */ + && orig_rip != to + insn_len + 1) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: syscall changed %%rip; " + "not relocating\n"); + } + else + { + ULONGEST rip = orig_rip - insn_offset; + + /* If we have stepped over a breakpoint, set %rip to + point at the breakpoint instruction itself. + + (gdbarch_decr_pc_after_break was never something the core + of GDB should have been concerned with; arch-specific + code should be making PC values consistent before + presenting them to GDB.) */ + if (amd64_breakpoint_p (insn_details)) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: stepped breakpoint\n"); + rip--; + } + + regcache_cooked_write_unsigned (regs, AMD64_RIP_REGNUM, rip); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: " + "relocated %%rip from 0x%s to 0x%s\n", + paddr_nz (orig_rip), paddr_nz (rip)); + } + } + + /* If the instruction was PUSHFL, then the TF bit will be set in the + pushed value, and should be cleared. We'll leave this for later, + since GDB already messes up the TF flag when stepping over a + pushfl. */ + + /* If the instruction was a call, the return address now atop the + stack is the address following the copied instruction. We need + to make it the address following the original instruction. */ + if (amd64_call_p (insn_details)) + { + ULONGEST rsp; + ULONGEST retaddr; + const ULONGEST retaddr_len = 8; + + regcache_cooked_read_unsigned (regs, AMD64_RSP_REGNUM, &rsp); + retaddr = read_memory_unsigned_integer (rsp, retaddr_len); + retaddr = (retaddr - insn_offset) & 0xffffffffUL; + write_memory_unsigned_integer (rsp, retaddr_len, retaddr); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: relocated return addr at 0x%s " + "to 0x%s\n", + paddr_nz (rsp), + paddr_nz (retaddr)); + } +} + /* The maximum number of saved registers. This should include %rip. */ #define AMD64_NUM_SAVED_REGS AMD64_NUM_GREGS @@ -756,24 +1465,6 @@ amd64_analyze_stack_align (CORE_ADDR pc, CORE_ADDR current_pc, gdb_byte buf[18]; int reg, r; int offset, offset_and; - static int regnums[16] = { - AMD64_RAX_REGNUM, /* %rax */ - AMD64_RCX_REGNUM, /* %rcx */ - AMD64_RDX_REGNUM, /* %rdx */ - AMD64_RBX_REGNUM, /* %rbx */ - AMD64_RSP_REGNUM, /* %rsp */ - AMD64_RBP_REGNUM, /* %rbp */ - AMD64_RSI_REGNUM, /* %rsi */ - AMD64_RDI_REGNUM, /* %rdi */ - AMD64_R8_REGNUM, /* %r8 */ - AMD64_R9_REGNUM, /* %r9 */ - AMD64_R10_REGNUM, /* %r10 */ - AMD64_R11_REGNUM, /* %r11 */ - AMD64_R12_REGNUM, /* %r12 */ - AMD64_R13_REGNUM, /* %r13 */ - AMD64_R14_REGNUM, /* %r14 */ - AMD64_R15_REGNUM, /* %r15 */ - }; if (target_read_memory (pc, buf, sizeof buf)) return pc; @@ -889,7 +1580,7 @@ amd64_analyze_stack_align (CORE_ADDR pc, CORE_ADDR current_pc, return pc; if (current_pc > pc + offset_and) - cache->saved_sp_reg = regnums[reg]; + cache->saved_sp_reg = amd64_arch_reg_to_regnum (reg); return min (pc + offset + 2, current_pc); } diff --git a/gdb/amd64-tdep.h b/gdb/amd64-tdep.h index 6b5d04e..121d225 100644 --- a/gdb/amd64-tdep.h +++ b/gdb/amd64-tdep.h @@ -67,6 +67,14 @@ enum amd64_regnum /* Number of general purpose registers. */ #define AMD64_NUM_GREGS 24 +extern struct displaced_step_closure *amd64_displaced_step_copy_insn + (struct gdbarch *gdbarch, CORE_ADDR from, CORE_ADDR to, + struct regcache *regs); +extern void amd64_displaced_step_fixup (struct gdbarch *gdbarch, + struct displaced_step_closure *closure, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs); + extern void amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch); /* Functions from amd64-tdep.c which may be needed on architectures diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 23dd304..e4ec632 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,10 @@ +2009-01-28 Doug Evans <dje@google.com> + + * gdb.arch/amd64-disp-step.S: New file. + * gdb.arch/amd64-disp-step.exp: New file. + * gdb.arch/i386-disp-step.S: New file. + * gdb.arch/i386-disp-step.exp: New file. + 2009-01-27 Pierre Muller <muller@ics.u-strasbg.fr> * gdb.base/find.exp: Set newline variable diff --git a/gdb/testsuite/gdb.arch/amd64-disp-step.S b/gdb/testsuite/gdb.arch/amd64-disp-step.S new file mode 100644 index 0000000..45eeb9b --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-disp-step.S @@ -0,0 +1,153 @@ +/* Copyright 2009 Free Software Foundation, Inc. + + 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 3 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, see <http://www.gnu.org/licenses/>. + + This file is part of the gdb testsuite. + It tests displaced stepping over various insns that require special + handling. */ + + .text + + .global main +main: + nop + +/* test call/ret */ + + .global test_call +test_call: + call test_call_subr + nop + .global test_ret_end +test_ret_end: + nop + +/* test abs-jmp/rep-ret */ + +test_abs_jmp_setup: + mov $test_abs_jmp_return,%rdx + push %rdx + mov $test_abs_jmp_subr,%rdx + .global test_abs_jmp +test_abs_jmp: + jmp *%rdx +test_abs_jmp_return: + nop + .global test_rep_ret_end +test_rep_ret_end: + nop + +/* test syscall */ + + .global test_syscall + mov $0x27,%eax /* getpid */ +test_syscall: + syscall + nop +test_syscall_end: + nop + +/* test rip-relative + GDB picks a spare register to hold the rip-relative address. + Exercise all the possibilities (rax-rdi, sans rsp). */ + + .global test_rip_rax +test_rip_rax: + add answer(%rip),%rax + .global test_rip_rax_end +test_rip_rax_end: + nop + + .global test_rip_rbx +test_rip_rbx: + add answer(%rip),%rbx + .global test_rip_rbx_end +test_rip_rbx_end: + nop + + .global test_rip_rcx +test_rip_rcx: + add answer(%rip),%rcx + .global test_rip_rcx_end +test_rip_rcx_end: + nop + + .global test_rip_rdx +test_rip_rdx: + add answer(%rip),%rdx + .global test_rip_rdx_end +test_rip_rdx_end: + nop + + .global test_rip_rbp +test_rip_rbp: + add answer(%rip),%rbp + .global test_rip_rbp_end +test_rip_rbp_end: + nop + + .global test_rip_rsi +test_rip_rsi: + add answer(%rip),%rsi + .global test_rip_rsi_end +test_rip_rsi_end: + nop + + .global test_rip_rdi +test_rip_rdi: + add answer(%rip),%rdi + .global test_rip_rdi_end +test_rip_rdi_end: + nop + + /* skip over test data */ + jmp done + +/* test rip-relative data */ + +answer: .8byte 42 + +/* all done */ + +done: + mov $0,%rdi + call exit + hlt + +/***********************************************/ + +/* subroutine to help test call/ret */ + +test_call_subr: + nop + .global test_call_end +test_call_end: + nop + + .global test_ret +test_ret: + ret + +/* subroutine to help test abs-jmp/rep-ret */ + +test_abs_jmp_subr: + nop + .global test_abs_jmp_end +test_abs_jmp_end: + nop + + .global test_rep_ret +test_rep_ret: + repz + ret diff --git a/gdb/testsuite/gdb.arch/amd64-disp-step.exp b/gdb/testsuite/gdb.arch/amd64-disp-step.exp new file mode 100644 index 0000000..26ebe59 --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-disp-step.exp @@ -0,0 +1,219 @@ +# Copyright 2009 Free Software Foundation, Inc. + +# 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 3 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, see <http://www.gnu.org/licenses/>. + +# This file is part of the gdb testsuite. + +# Test amd64 displaced stepping. + +if $tracelevel { + strace $tracelevel +} + +set prms_id 0 +set bug_id 0 + +if ![istarget "x86_64-*-linux*"] then { + verbose "Skipping x86_64 displaced stepping tests." + return +} + +set newline "\[\r\n\]*" + +set testfile "amd64-disp-step" +set srcfile ${testfile}.S +set binfile ${objdir}/${subdir}/${testfile} + +set additional_flags "-Wa,-g" + +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable [list debug $additional_flags]] != "" } { + untested amd64-disp-step.exp + return -1 +} + +# Get things started. + +gdb_exit +gdb_start +gdb_reinitialize_dir $srcdir/$subdir +gdb_load ${binfile} + +gdb_test "set displaced-stepping on" "" +gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*" + +if ![runto_main] then { + fail "Can't run to main" + return 0 +} + +########################################## + +# Test call/ret. + +gdb_test "break test_call" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_call" +gdb_test "break test_call_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_call_end" + +gdb_test "break test_ret" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_ret" +gdb_test "break test_ret_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_ret_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_call ().*" \ + "continue to test_call" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_call_end ().*" \ + "continue to test_call_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_ret ().*" \ + "continue to test_ret" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_ret_end ().*" \ + "continue to test_ret_end" + +########################################## + +# Test abs-jmp/rep-ret. + +gdb_test "break test_abs_jmp" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_abs_jmp" +gdb_test "break test_abs_jmp_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_abs_jmp_end" + +gdb_test "break test_rep_ret" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_rep_ret" +gdb_test "break test_rep_ret_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_rep_ret_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_abs_jmp ().*" \ + "continue to test_abs_jmp" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_abs_jmp_end ().*" \ + "continue to test_abs_jmp_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_rep_ret ().*" \ + "continue to test_rep_ret" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_rep_ret_end ().*" \ + "continue to test_rep_ret_end" + +########################################## + +# Test syscall. + +gdb_test "break test_syscall" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_syscall" +gdb_test "break test_syscall_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_syscall_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_syscall ().*" \ + "continue to test_syscall" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_syscall_end ().*" \ + "continue to test_syscall_end" + +########################################## + +# Test rip-relative. +# GDB picks a spare register to hold the rip-relative address. +# Exercise all the possibilities (rax-rdi, sans rsp). + +# The order must much the order in srcfile. +set rip_regs { "rax" "rbx" "rcx" "rdx" "rbp" "rsi" "rdi" } + +# Assign val to all specified regs. + +proc set_regs { regs val } { + global gdb_prompt + + foreach reg ${regs} { + # Use send_gdb/gdb_expect so that these aren't logged as pass/fail. + send_gdb "set \$${reg} = ${val}\n" + gdb_expect 10 { + -re "$gdb_prompt $" { + verbose "Setting ${reg} to ${val}." 2 + } + timeout { + warning "Couldn't set ${reg} to ${val}." + } + } + } +} + +# Verify all REGS equal VAL, except REG which equals REG_VAL. + +proc verify_regs { test_name regs val except_reg except_reg_val } { + global newline + + foreach reg ${regs} { + set expected ${val} + if { "${reg}" == "${except_reg}" } { + set expected ${except_reg_val} + } + # The cast to (int) is because RBP is printed as a pointer. + gdb_test "p (int) \$${reg}" " = ${expected}${newline}" "${test_name} ${reg} expected value" + } +} + +proc rip_test { reg } { + global srcfile rip_regs + + set test_start_label "test_rip_${reg}" + set test_end_label "test_rip_${reg}_end" + + gdb_test "break ${test_start_label}" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break ${test_start_label}" + gdb_test "break ${test_end_label}" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break ${test_end_label}" + + gdb_test "continue" \ + "Continuing.*Breakpoint.*, ${test_start_label} ().*" \ + "continue to ${test_start_label}" + + set_regs ${rip_regs} 0 + + gdb_test "continue" \ + "Continuing.*Breakpoint.*, ${test_end_label} ().*" \ + "continue to ${test_end_label}" + + verify_regs "test rip w/${reg}" ${rip_regs} 0 ${reg} 42 +} + +foreach reg ${rip_regs} { + rip_test $reg +} + +########################################## + +# Done, run program to exit. + +gdb_continue_to_end "amd64-disp-step" diff --git a/gdb/testsuite/gdb.arch/i386-disp-step.S b/gdb/testsuite/gdb.arch/i386-disp-step.S new file mode 100644 index 0000000..a56ff1c --- /dev/null +++ b/gdb/testsuite/gdb.arch/i386-disp-step.S @@ -0,0 +1,64 @@ +/* Copyright 2009 Free Software Foundation, Inc. + + 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 3 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, see <http://www.gnu.org/licenses/>. + + This file is part of the gdb testsuite. + It tests displaced stepping over various insns that require special + handling. */ + + .text + + .global main +main: + nop + +/* test call/ret */ + + .global test_call +test_call: + call test_call_subr + nop + .global test_ret_end +test_ret_end: + nop + +/* test syscall */ + + .global test_syscall + mov $0x14,%eax /* getpid */ +test_syscall: + int $0x80 + nop +test_syscall_end: + nop + +/* all done */ + + pushl $0 + call exit + hlt + +/***********************************************/ + +/* subroutine to help test call/ret */ + +test_call_subr: + nop + .global test_call_end +test_call_end: + nop + + .global test_ret +test_ret: + ret diff --git a/gdb/testsuite/gdb.arch/i386-disp-step.exp b/gdb/testsuite/gdb.arch/i386-disp-step.exp new file mode 100644 index 0000000..06c5fb2 --- /dev/null +++ b/gdb/testsuite/gdb.arch/i386-disp-step.exp @@ -0,0 +1,112 @@ +# Copyright 2009 Free Software Foundation, Inc. + +# 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 3 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, see <http://www.gnu.org/licenses/>. + +# This file is part of the gdb testsuite. + +# Test i386 displaced stepping. + +if $tracelevel { + strace $tracelevel +} + +set prms_id 0 +set bug_id 0 + +if ![istarget "i?86-*-linux*"] then { + verbose "Skipping x86 displaced stepping tests." + return +} + +set testfile "i386-disp-step" +set srcfile ${testfile}.S +set binfile ${objdir}/${subdir}/${testfile} + +set additional_flags "-Wa,-g" + +if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable [list debug $additional_flags]] != "" } { + untested i386-disp-step.exp + return -1 +} + +# Get things started. + +gdb_exit +gdb_start +gdb_reinitialize_dir $srcdir/$subdir +gdb_load ${binfile} + +gdb_test "set displaced-stepping on" "" +gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*" + +if ![runto_main] then { + fail "Can't run to main" + return 0 +} + +########################################## + +# Test call/ret. + +gdb_test "break test_call" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_call" +gdb_test "break test_call_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_call_end" + +gdb_test "break test_ret" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_ret" +gdb_test "break test_ret_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_ret_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_call ().*" \ + "continue to test_call" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_call_end ().*" \ + "continue to test_call_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_ret ().*" \ + "continue to test_ret" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_ret_end ().*" \ + "continue to test_ret_end" + +########################################## + +# Test syscall. + +gdb_test "break test_syscall" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_syscall" +gdb_test "break test_syscall_end" \ + "Breakpoint.*at.* file .*$srcfile, line.*" \ + "break test_syscall_end" + +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_syscall ().*" \ + "continue to test_syscall" +gdb_test "continue" \ + "Continuing.*Breakpoint.*, test_syscall_end ().*" \ + "continue to test_syscall_end" + +########################################## + +# Done, run program to exit. + +gdb_continue_to_end "i386-disp-step" |