* [RFA] amd64 displaced stepping support
@ 2009-01-27 1:33 Doug Evans
2009-01-27 8:26 ` Stan Shebs
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Doug Evans @ 2009-01-27 1:33 UTC (permalink / raw)
To: gdb-patches
Hi. I took a crack at implementing displaced stepping support for amd64.
Using the disassembler to compute instruction lengths is awkward, I know.
It's needed in order to compute the address of rip-relative addressing.
The address is %rip + address-of-next-insn + displacement,
and the displacement is only 32 bits so it's not guaranteed to be enough
to cover the distance between the original instruction and its copy.
To compensate I compute an unused integer reg, set it to
%rip + address-of-next-insn, and rewrite the insn to use base+disp addressing.
I think the GNU tools need a general-purpose library of ISA-related tools.
Until then, I went with the disassembler. The code is laid out such that
when a better implementation of computing insn lengths comes along, it
can be easily dropped in.
This also includes a testcase! :-)
Plus I added a testcase for the i386 case.
Ok to check in?
2009-01-26 Doug Evans <dje@google.com>
* amd64-tdep.h (amd64_displaced_step_copy_insn): Declare.
(amd64_displaced_step_fixup): Declare.
* amd64-tdep.c: #include 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 fn.
(struct displaced_step_closure): New struct.
(onebyte_has_modrm,twobyte_has_modrm): New static globals.
(rex_prefix_p,amd64_insn_length_fprintf,amd64_insn_length_init_dis,
amd64_insn_length,amd64_get_unused_input_int_reg,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 fns.
* amd64-linux-tdep.c (amd64_linux_init_abi): Install displaced
stepping support.
* 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.
Index: amd64-linux-tdep.c
===================================================================
RCS file: /cvs/src/src/gdb/amd64-linux-tdep.c,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 amd64-linux-tdep.c
--- amd64-linux-tdep.c 3 Jan 2009 05:57:50 -0000 1.21
+++ amd64-linux-tdep.c 26 Jan 2009 22:46:50 -0000
@@ -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_inf
/* 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);
}
\f
Index: amd64-tdep.c
===================================================================
RCS file: /cvs/src/src/gdb/amd64-tdep.c,v
retrieving revision 1.56
diff -u -p -u -p -r1.56 amd64-tdep.c
--- amd64-tdep.c 3 Jan 2009 05:57:50 -0000 1.56
+++ amd64-tdep.c 26 Jan 2009 22:46:50 -0000
@@ -21,6 +21,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "defs.h"
+#include "dis-asm.h" /* FIXME: for amd64_insn_length, sigh */
#include "arch-utils.h"
#include "block.h"
#include "dummy-frame.h"
@@ -200,6 +201,42 @@ amd64_dwarf_reg_to_regnum (struct gdbarc
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];
+}
+
\f
/* Register classes as defined in the psABI. */
@@ -666,7 +703,647 @@ amd64_push_dummy_call (struct gdbarch *g
return sp + 16;
}
\f
+/* Displaced instruction handling. */
+
+static int amd64_syscall_p (gdb_byte *insn, int *lengthp);
+
+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;
+
+ int opcode_len;
+ int rex_offset;
+ int opcode_offset;
+ int modrm_offset;
+
+ /* Size of insn_buf, the maximum length of an insn. */
+ int max_len;
+
+ /* The modified insn.
+ This is a variable-length field. */
+ gdb_byte insn_buf[1];
+};
+
+/* ??? *_has_modrm are copies of the tables in ../opcodes/i386-dis.c.
+ It might be nice if we could use them.
+ ??? This differs from the kernel version, is one of them out of date? */
+
+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
+rex_prefix_p (gdb_byte pfx)
+{
+ return (pfx & 0xf0) == 0x40;
+}
+
+/* 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
+ ??? The GNU tools need a library of basic ISA-related support.
+ Until then we use the disassembler. */
+
+static int
+amd64_insn_length (const gdb_byte *insn, int max_len, CORE_ADDR addr)
+{
+ struct disassemble_info di;
+ struct gdbarch *gdbarch = current_gdbarch;
+
+ 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.
+ MAX_LEN is the size of the buffer containing INSN.
+ OPCODE_OFFSET is the offset of the first opcode byte.
+ OPCODE_LEN is the number of opcode bytes.
+ MODRM_OFFSET is the offset of the ModRM byte or -1 if not present.
+ 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.
+
+ ??? The GNU tools need a library of basic ISA-related support. */
+
+static int
+amd64_get_unused_input_int_reg (const gdb_byte *insn, int max_len,
+ int opcode_offset, int opcode_len,
+ int modrm_offset)
+{
+ /* 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 << 0;
+ /* Similarily avoid RDX, implicit operand in divides. */
+ used_regs_mask |= 1 << 2;
+ /* Avoid RSP. */
+ used_regs_mask |= 1 << 4;
+
+ /* If the opcode is one byte long and there's no ModRM byte,
+ assume the opcode specifies a register. */
+ if (opcode_len == 1 && modrm_offset == -1)
+ used_regs_mask |= 1 << (insn[opcode_offset] & 7);
+
+ /* Mark used regs in the modrm/sib bytes. */
+ if (modrm_offset != -1)
+ {
+ int modrm = insn[modrm_offset];
+ int mod = (modrm >> 6) & 3;
+ int reg = (modrm >> 3) & 7;
+ int rm = modrm & 7;
+ int have_sib = mod != 3 && rm == 4;
+
+ /* Assume the reg field of the modrm byte specifies a register. */
+ used_regs_mask |= 1 << ((modrm >> 3) & 3);
+
+ if (have_sib)
+ {
+ int base = insn[modrm_offset + 1] & 7;
+ int index = (insn[modrm_offset + 1] >> 3) & 7;
+ 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. */
+ gdb_assert (0);
+ }
+}
+
+/* Extra space for sentinels so fixup_{riprel,displaced_copy don't have to
+ continually watch for running off the end of the buffer. */
+/* maximum space needed for opcodes + modrm byte, sans prefixes */
+#define MAX_MODRM_SPACE 4
+/* add in space needed for rip-relative offset */
+#define FIXUP_SENTINEL_SPACE (MAX_MODRM_SPACE + 4)
+
+/* 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 displaced_step_closure *dsc, CORE_ADDR from, CORE_ADDR to,
+ struct regcache *regs)
+{
+ int modrm_offset = dsc->modrm_offset;
+ gdb_byte *insn = dsc->insn_buf + 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 = *(int32_t*) insn; // FIXME: target-fetch-value
+ insn_length = amd64_insn_length (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 (dsc->insn_buf, dsc->max_len,
+ dsc->opcode_offset,
+ dsc->opcode_len,
+ dsc->modrm_offset);
+ 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 (dsc->rex_offset != -1)
+ dsc->insn_buf[dsc->rex_offset] &= ~1;
+
+ 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 displaced_step_closure *dsc,
+ CORE_ADDR from, CORE_ADDR to, struct regcache *regs)
+{
+ gdb_byte *insn = dsc->insn_buf;
+ int len = dsc->max_len;
+ gdb_byte *start = insn;
+ gdb_byte *end = insn + len;
+ int need_modrm;
+
+ /* Skip legacy instruction prefixes.
+ While the kernel's kprobes support can assume the instruction is valid,
+ we can't. Don't crash if we see an invalid insn. */
+
+ while (insn < end)
+ {
+ switch (*insn)
+ {
+ case 0x66:
+ case 0x67:
+ case 0x2e:
+ case 0x3e:
+ case 0x26:
+ case 0x64:
+ case 0x65:
+ case 0x36:
+ case 0xf0:
+ case 0xf3:
+ case 0xf2:
+ ++insn;
+ continue;
+ }
+ break;
+ }
+
+ /* Skip REX instruction prefix. */
+ if (rex_prefix_p (*insn))
+ {
+ dsc->rex_offset = insn - start;
+ ++insn;
+ }
+
+ dsc->opcode_offset = insn - start;
+
+ if (*insn == 0x0f)
+ {
+ /* Two or three-byte opcode. */
+ ++insn;
+ need_modrm = twobyte_has_modrm[*insn];
+
+ /* Check for three-byte opcode. */
+ if (*insn == 0x38 || *insn == 0x3a)
+ {
+ ++insn;
+ dsc->opcode_len = 3;
+ }
+ else
+ dsc->opcode_len = 2;
+ }
+ else
+ {
+ /* One-byte opcode. */
+ need_modrm = onebyte_has_modrm[*insn];
+ dsc->opcode_len = 1;
+ }
+
+ if (need_modrm)
+ {
+ gdb_byte modrm = *++insn;
+
+ dsc->modrm_offset = insn - start;
+
+ if ((modrm & 0xc7) == 0x05)
+ {
+ /* The insn uses rip-relative addressing.
+ Deal with it. */
+ fixup_riprel (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);
+ struct displaced_step_closure *dsc =
+ xmalloc (sizeof (*dsc) + len + FIXUP_SENTINEL_SPACE);
+ gdb_byte *buf = dsc->insn_buf;
+
+ dsc->tmp_used = 0;
+ dsc->max_len = len;
+ dsc->rex_offset = -1;
+ dsc->opcode_offset = -1;
+ dsc->modrm_offset = -1;
+
+ read_memory (from, buf, len);
+
+ /* GDB may get control back after the insn after the syscall.
+ If this is a syscall, make sure there's a nop afterwards. */
+ {
+ int syscall_length;
+ if (amd64_syscall_p (buf, &syscall_length))
+ buf[syscall_length] = 0x90;
+ }
+
+ /* Set up a sentinel so stepping over prefixes is guaranteed to end. */
+ memset (buf + len, 0, FIXUP_SENTINEL_SPACE);
+
+ /* Modify the insn to cope with the address where it will be executed from.
+ In particular, handle any rip-relative addressing. */
+ fixup_displaced_copy (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;
+}
+
+/* ??? Do we need to check for rex prefix in these predicates, or their
+ callers? */
+
+static int
+amd64_absolute_jmp_p (gdb_byte *insn)
+{
+ /* jmp far (absolute address in operand) */
+ /* ??? undefined insn actually */
+ if (insn[0] == 0xea)
+ return 1;
+
+ 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 (gdb_byte *insn)
+{
+ /* call far, absolute */
+ /* ??? undefined insn actually */
+ if (insn[0] == 0x9a)
+ return 1;
+
+ 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 (gdb_byte *insn)
+{
+ /* gcc can emit "repz ; ret" */
+ if (*insn == 0xf3)
+ ++insn;
+
+ 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 (gdb_byte *insn)
+{
+ if (amd64_absolute_call_p (insn))
+ return 1;
+
+ /* call near, relative */
+ if (insn[0] == 0xe8)
+ return 1;
+
+ return 0;
+}
+
+static int
+amd64_breakpoint_p (gdb_byte *insn)
+{
+ 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 (gdb_byte *insn, int *lengthp)
+{
+ 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;
+
+ 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)
+ && ! amd64_absolute_call_p (insn)
+ && ! amd64_ret_p (insn))
+ {
+ 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, &insn_len)
+ && orig_rip != to + insn_len
+ /* GDB can get control back after the insn after the syscall.
+ 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))
+ {
+ 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))
+ {
+ 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));
+ }
+}
+\f
/* The maximum number of saved registers. This should include %rip. */
#define AMD64_NUM_SAVED_REGS AMD64_NUM_GREGS
@@ -756,24 +1433,6 @@ amd64_analyze_stack_align (CORE_ADDR 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 +1548,7 @@ amd64_analyze_stack_align (CORE_ADDR 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);
}
Index: amd64-tdep.h
===================================================================
RCS file: /cvs/src/src/gdb/amd64-tdep.h,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 amd64-tdep.h
--- amd64-tdep.h 3 Jan 2009 05:57:50 -0000 1.13
+++ amd64-tdep.h 26 Jan 2009 22:46:50 -0000
@@ -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
Index: testsuite/gdb.arch/amd64-disp-step.S
===================================================================
RCS file: testsuite/gdb.arch/amd64-disp-step.S
diff -N testsuite/gdb.arch/amd64-disp-step.S
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.arch/amd64-disp-step.S 26 Jan 2009 22:46:50 -0000
@@ -0,0 +1,156 @@
+/* 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/>.
+
+ Please email any bugs, comments, and/or additions to this file to:
+ bug-gdb@gnu.org
+
+ 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:
+ movq answer(%rip),%rax
+ .global test_rip_rax_end
+test_rip_rax_end:
+ nop
+
+ .global test_rip_rbx
+test_rip_rbx:
+ movq answer(%rip),%rbx
+ .global test_rip_rbx_end
+test_rip_rbx_end:
+ nop
+
+ .global test_rip_rcx
+test_rip_rcx:
+ movq answer(%rip),%rcx
+ .global test_rip_rcx_end
+test_rip_rcx_end:
+ nop
+
+ .global test_rip_rdx
+test_rip_rdx:
+ movq answer(%rip),%rdx
+ .global test_rip_rdx_end
+test_rip_rdx_end:
+ nop
+
+ .global test_rip_rbp
+test_rip_rbp:
+ movq answer(%rip),%rbp
+ .global test_rip_rbp_end
+test_rip_rbp_end:
+ nop
+
+ .global test_rip_rsi
+test_rip_rsi:
+ movq answer(%rip),%rsi
+ .global test_rip_rsi_end
+test_rip_rsi_end:
+ nop
+
+ .global test_rip_rdi
+test_rip_rdi:
+ movq 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
Index: testsuite/gdb.arch/amd64-disp-step.exp
===================================================================
RCS file: testsuite/gdb.arch/amd64-disp-step.exp
diff -N testsuite/gdb.arch/amd64-disp-step.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.arch/amd64-disp-step.exp 26 Jan 2009 22:46:50 -0000
@@ -0,0 +1,224 @@
+# 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/>.
+
+# Please email any bugs, comments, and/or additions to this file to:
+# bug-gdb@gnu.org
+
+# 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 non-stop on" ""
+gdb_test "show non-stop" "Controlling the inferior in non-stop mode 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_test "continue" \
+ ".*Program exited normally.*" \
+ "continue to exit"
Index: testsuite/gdb.arch/i386-disp-step.S
===================================================================
RCS file: testsuite/gdb.arch/i386-disp-step.S
diff -N testsuite/gdb.arch/i386-disp-step.S
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.arch/i386-disp-step.S 26 Jan 2009 22:46:50 -0000
@@ -0,0 +1,67 @@
+/* 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/>.
+
+ Please email any bugs, comments, and/or additions to this file to:
+ bug-gdb@gnu.org
+
+ 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
Index: testsuite/gdb.arch/i386-disp-step.exp
===================================================================
RCS file: testsuite/gdb.arch/i386-disp-step.exp
diff -N testsuite/gdb.arch/i386-disp-step.exp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/gdb.arch/i386-disp-step.exp 26 Jan 2009 22:46:50 -0000
@@ -0,0 +1,117 @@
+# 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/>.
+
+# Please email any bugs, comments, and/or additions to this file to:
+# bug-gdb@gnu.org
+
+# 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 non-stop on" ""
+gdb_test "show non-stop" "Controlling the inferior in non-stop mode 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_test "continue" \
+ ".*Program exited normally.*" \
+ "continue to exit"
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [RFA] amd64 displaced stepping support 2009-01-27 1:33 [RFA] amd64 displaced stepping support Doug Evans @ 2009-01-27 8:26 ` Stan Shebs 2009-01-27 17:27 ` Pedro Alves 2009-01-27 20:27 ` Daniel Jacobowitz 2 siblings, 0 replies; 9+ messages in thread From: Stan Shebs @ 2009-01-27 8:26 UTC (permalink / raw) To: Doug Evans; +Cc: gdb-patches Doug Evans wrote: > Hi. I took a crack at implementing displaced stepping support for amd64. > Ooh nice! And fortuitous timing, I was supposed to write this very code next week... > Using the disassembler to compute instruction lengths is awkward, I know. > It's needed in order to compute the address of rip-relative addressing. > The address is %rip + address-of-next-insn + displacement, > and the displacement is only 32 bits so it's not guaranteed to be enough > to cover the distance between the original instruction and its copy. > To compensate I compute an unused integer reg, set it to > %rip + address-of-next-insn, and rewrite the insn to use base+disp addressing. > I think the GNU tools need a general-purpose library of ISA-related tools. > Until then, I went with the disassembler. The code is laid out such that > when a better implementation of computing insn lengths comes along, it > can be easily dropped in. > My kneejerk would be to borrow libopcodes. > This also includes a testcase! :-) > Plus I added a testcase for the i386 case. > > Ok to check in? > I read it and it seems sensible, I'm the not the displaced stepping maven though. Stan ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-27 1:33 [RFA] amd64 displaced stepping support Doug Evans 2009-01-27 8:26 ` Stan Shebs @ 2009-01-27 17:27 ` Pedro Alves 2009-01-28 12:39 ` Doug Evans 2009-01-27 20:27 ` Daniel Jacobowitz 2 siblings, 1 reply; 9+ messages in thread From: Pedro Alves @ 2009-01-27 17:27 UTC (permalink / raw) To: gdb-patches; +Cc: Doug Evans On Monday 26 January 2009 23:00:12, Doug Evans wrote: > Hi. I took a crack at implementing displaced stepping support for amd64. Great! Thank you so much for this. > I think the GNU tools need a general-purpose library of ISA-related tools. Right, opcodes <-> gdb could cooperate better, in several ways. > Until then, I went with the disassembler. The code is laid out such that > when a better implementation of computing insn lengths comes along, it > can be easily dropped in. Err, well, did you consider adding the needed interfaces to libopcodes? It's what we use to implement the disassembler anyway... I don't like these spread around we-should-have-this-super-library references. Please, if not proposing new interfaces yourself, centralize the complain, if you must, and when this goes in, open a PR about it. > This also includes a testcase! :-) > Plus I added a testcase for the i386 case. Yay! > > Ok to check in? Close! > (rex_prefix_p,amd64_insn_length_fprintf,amd64_insn_length_init_dis, > amd64_insn_length,amd64_get_unused_input_int_reg,fixup_riprel, On the nitpicking department, the standard tells us to split these lines as: (rex_prefix_p,amd64_insn_length_fprintf,amd64_insn_length_init_dis) (amd64_insn_length,amd64_get_unused_input_int_reg,fixup_riprel) (foo): Base. Emacs loves this form better too. > amd64_displaced_step_fixup): New fns. Are you really that lazy? :-) Please spell out "fns", and avoid everyone else the extra cycles to expand that. > +/* ??? *_has_modrm are copies of the tables in ../opcodes/i386-dis.c. > + It might be nice if we could use them. > + ??? This differs from the kernel version, is one of them out of date? */ Yes, looks like it. It is safe to assume that the opcodes version is more up to date, as it gets updated to reflect new cpus frequently, by the Intel and AMD folks, at least. In any case, comments like this are no good, as they're doomed to get out of date at some point themselves too. Better say, "please keep this in sync with ... foo". If not addressing this duplication yourself, please do open a PR about it. This will surelly end up out of date at some point... > +/* Return the length in bytes of INSN. > + MAX_LEN is the size of the buffer containing INSN > + ??? The GNU tools need a library of basic ISA-related support. > + Until then we use the disassembler. */ ... Why not say something like "Unfortunately, libopcodes doesn't export a simpler way to query the length of instruction at a given address. We use the disassembler interface to do that." ... > +static int > +amd64_insn_length (const gdb_byte *insn, int max_len, CORE_ADDR addr) > +{ > + struct disassemble_info di; > + struct gdbarch *gdbarch = current_gdbarch; Ouch, CURRENT_GDBARCH red alert. From what I can tell, you have a gdbarch available to use. amd64_displaced_step_copy_insn gets a gdbarch* passed in as parameter, so you can pass it to fixup_displaced_copy, and then to fixup_riprel, which then can pass it here. Can you do that please? > +/* Return an integer register (other than RSP) that is unused as an input > + operand in INSN. > + MAX_LEN is the size of the buffer containing INSN. > + OPCODE_OFFSET is the offset of the first opcode byte. > + OPCODE_LEN is the number of opcode bytes. > + MODRM_OFFSET is the offset of the ModRM byte or -1 if not present. > + 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. > + > + ??? The GNU tools need a library of basic ISA-related support. */ > + /* We shouldn't get here. */ > + gdb_assert (0); Use internal_error instead, please. > +static void > +fixup_riprel (struct displaced_step_closure *dsc, CORE_ADDR from, CORE_ADDR to, > + struct regcache *regs) > +{ > + int modrm_offset = dsc->modrm_offset; > + gdb_byte *insn = dsc->insn_buf + 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 = *(int32_t*) insn; // FIXME: target-fetch-value Err, that will cause unaligned traps on some hosts (this is a tdep file, not a nat file). Please fix that up. > + insn_length = amd64_insn_length (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 (dsc->insn_buf, dsc->max_len, > + dsc->opcode_offset, > + dsc->opcode_len, > + dsc->modrm_offset); > + 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 (dsc->rex_offset != -1) > + dsc->insn_buf[dsc->rex_offset] &= ~1; > + > + 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 displaced_step_closure *dsc, > + CORE_ADDR from, CORE_ADDR to, struct regcache *regs) > +{ > + gdb_byte *insn = dsc->insn_buf; > + int len = dsc->max_len; > + gdb_byte *start = insn; > + gdb_byte *end = insn + len; > + int need_modrm; > + > + /* Skip legacy instruction prefixes. > + While the kernel's kprobes support can assume the instruction is valid, > + we can't. Don't crash if we see an invalid insn. */ Yeah, we'd better warn/error out to the user than to do something silly with instructions we don't know what to do with. Should we be checking if you've reached >= end somewhere? > + > + while (insn < end) > + { > + switch (*insn) > + { > + case 0x66: > + case 0x67: > + case 0x2e: > + case 0x3e: > + case 0x26: > + case 0x64: > + case 0x65: > + case 0x36: > + case 0xf0: > + case 0xf3: > + case 0xf2: > + ++insn; > + continue; > + } > + break; > + } > + > + /* Skip REX instruction prefix. */ > + if (rex_prefix_p (*insn)) > + { > + dsc->rex_offset = insn - start; > + ++insn; > + } > + > + dsc->opcode_offset = insn - start; > + > + if (*insn == 0x0f) > + { > + /* Two or three-byte opcode. */ > + ++insn; > + need_modrm = twobyte_has_modrm[*insn]; > + > + /* Check for three-byte opcode. */ > + if (*insn == 0x38 || *insn == 0x3a) > + { > + ++insn; > + dsc->opcode_len = 3; > + } > + else > + dsc->opcode_len = 2; > + } > + else > + { > + /* One-byte opcode. */ > + need_modrm = onebyte_has_modrm[*insn]; > + dsc->opcode_len = 1; > + } > + > + if (need_modrm) > + { > + gdb_byte modrm = *++insn; > + > + dsc->modrm_offset = insn - start; > + > + if ((modrm & 0xc7) == 0x05) > + { > + /* The insn uses rip-relative addressing. > + Deal with it. */ > + fixup_riprel (dsc, from, to, regs); > + } > + } > +} > + > + /* GDB may get control back after the insn after the syscall. > + If this is a syscall, make sure there's a nop afterwards. */ > + { > + int syscall_length; > > + if (amd64_syscall_p (buf, &syscall_length)) > + buf[syscall_length] = 0x90; > + } Weird. Isn't this a kernel bug? It only happens when single-stepping the syscall insn. E.g., from your testcase, without displaced stepping: 58 test_syscall: 59 syscall 60 nop 61 test_syscall_end: 62 nop (gdb) b amd64-disp-step.S:60 Breakpoint 1 at 0x4004fa: file ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S, line 60. (gdb) r Starting program: /home/pedro/gdb/baseline/build/gdb/testsuite/gdb.arch/amd64-disp-step Breakpoint 1, test_syscall () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:60 60 nop Current language: auto; currently asm But: (gdb) c Continuing. Program exited normally. (gdb) b 59 Breakpoint 2 at 0x4004f8: file ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S, line 59. (gdb) r Starting program: /home/pedro/gdb/baseline/build/gdb/testsuite/gdb.arch/amd64-disp-step Breakpoint 2, test_syscall () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:59 59 syscall (gdb) c Continuing. Program exited normally. (gdb) GDB did a single-step to get over the "syscall", and missed the breakpoint at 0x4004fa. Again, doing a manual stepi: (gdb) r Starting program: /home/pedro/gdb/baseline/build/gdb/testsuite/gdb.arch/amd64-disp-step Breakpoint 2, test_syscall () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:59 59 syscall (gdb) del 2 (gdb) set debug infrun 1 (gdb) stepi infrun: clear_proceed_status_thread (process 4322) infrun: proceed (addr=0xffffffffffffffff, signal=144, step=1) During symbol reading, incomplete CFI data; unspecified registers (e.g., rax) at 0x7ffff781b0f3. infrun: resume (step=1, signal=0), trap_expected=0 infrun: wait_for_inferior (treat_exec_as_sigtrap=0) infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x4004fb infrun: stepi/nexti infrun: stop_stepping test_syscall_end () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:62 62 nop (gdb) stepi test_syscall_end () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:62 62 nop (gdb) p $pc $1 = (void (*)()) 0x4004fb <test_syscall_end> (gdb) If you replace the nop with something wider, e.g., try stepping over this: mov $0x27,%eax /* getpid */ syscall mov $0xff,%eax nop You'll see that the move after the syscall is completely stepped over, and that is was executed correctly. Something in the kernel not restoring the trace flag correctly, and hence stepping one instruction too much? > +/* ??? Do we need to check for rex prefix in these predicates, or their > + callers? */ The caller should (amd64_displaced_step_fixup), I believe? IIUC, you also store the prefixes in dsc->insn_buf. > + > +static int > +amd64_absolute_jmp_p (gdb_byte *insn) > +{ > + /* jmp far (absolute address in operand) */ > + /* ??? undefined insn actually */ > + if (insn[0] == 0xea) > + return 1; Indeed, invalid in 64-bit mode. Any reason not to drop it? > + > + 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 (gdb_byte *insn) > +{ > + /* call far, absolute */ > + /* ??? undefined insn actually */ > + if (insn[0] == 0x9a) > + return 1; > + Same as above. > + 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; > +} > + > Index: testsuite/gdb.arch/amd64-disp-step.S > =================================================================== > RCS file: testsuite/gdb.arch/amd64-disp-step.S > diff -N testsuite/gdb.arch/amd64-disp-step.S > + Please email any bugs, comments, and/or additions to this file to: > + bug-gdb@gnu.org Hmmm, shoudn't we be stopping using this address? > +gdb_test "set non-stop on" "" > +gdb_test "show non-stop" "Controlling the inferior in non-stop mode is on.*" Please use "set displaced-stepping on" instead. > +# Done, run program to exit. > + > +gdb_test "continue" \ > + ".*Program exited normally.*" \ > + "continue to exit" > Use gdb_continue_to_end instead. -- Pedro Alves ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-27 17:27 ` Pedro Alves @ 2009-01-28 12:39 ` Doug Evans 2009-01-28 14:34 ` Pedro Alves 2009-01-28 15:05 ` Daniel Jacobowitz 0 siblings, 2 replies; 9+ messages in thread From: Doug Evans @ 2009-01-28 12:39 UTC (permalink / raw) To: Pedro Alves; +Cc: gdb-patches [-- Attachment #1: Type: text/plain, Size: 15945 bytes --] On Tue, Jan 27, 2009 at 9:15 AM, Pedro Alves <pedro@codesourcery.com> wrote: > On Monday 26 January 2009 23:00:12, Doug Evans wrote: >> Hi. I took a crack at implementing displaced stepping support for amd64. > > Great! Thank you so much for this. > >> I think the GNU tools need a general-purpose library of ISA-related tools. > > Right, opcodes <-> gdb could cooperate better, in several ways. > >> Until then, I went with the disassembler. The code is laid out such that >> when a better implementation of computing insn lengths comes along, it >> can be easily dropped in. > > Err, well, did you consider adding the needed interfaces to libopcodes? > It's what we use to implement the disassembler anyway... I'd like to add the needed interfaces. I'm just not sure how long a process that will be and I'd like to get started on exercising amd64 non-stop functionality. I'll pursue exporting the modrm_bytes arrays and insn-length computation with binutils. > >> (rex_prefix_p,amd64_insn_length_fprintf,amd64_insn_length_init_dis, >> amd64_insn_length,amd64_get_unused_input_int_reg,fixup_riprel, > > On the nitpicking department, the standard tells us to split these lines > as: > > (rex_prefix_p,amd64_insn_length_fprintf,amd64_insn_length_init_dis) > (amd64_insn_length,amd64_get_unused_input_int_reg,fixup_riprel) > (foo): Base. > > Emacs loves this form better too. > >> amd64_displaced_step_fixup): New fns. > > Are you really that lazy? :-) Please spell out "fns", and avoid everyone > else the extra cycles to expand that. ok. > >> +/* ??? *_has_modrm are copies of the tables in ../opcodes/i386-dis.c. >> + It might be nice if we could use them. >> + ??? This differs from the kernel version, is one of them out of date? */ > > Yes, looks like it. It is safe to assume that the opcodes version is more > up to date, as it gets updated to reflect new cpus frequently, by the Intel > and AMD folks, at least. I assumed so, but didn't actually know. > In any case, comments like this are no good, as they're doomed to get out of > date at some point themselves too. Better say, "please keep this in > sync with ... foo". If not addressing this duplication yourself, please > do open a PR about it. This will surelly end up out of date at > some point... I've modified the comment to be a warning. >> +/* Return the length in bytes of INSN. >> + MAX_LEN is the size of the buffer containing INSN >> + ??? The GNU tools need a library of basic ISA-related support. >> + Until then we use the disassembler. */ > > ... Why not say something like "Unfortunately, libopcodes doesn't export > a simpler way to query the length of instruction at a given address. We > use the disassembler interface to do that." ... The attached patch has a different wording. >> +static int >> +amd64_insn_length (const gdb_byte *insn, int max_len, CORE_ADDR addr) >> +{ >> + struct disassemble_info di; >> + struct gdbarch *gdbarch = current_gdbarch; > > Ouch, CURRENT_GDBARCH red alert. From what I can tell, you have a gdbarch > available to use. amd64_displaced_step_copy_insn gets a gdbarch* passed in > as parameter, so you can pass it to fixup_displaced_copy, and then > to fixup_riprel, which then can pass it here. Can you do that please? righto. >> +/* Return an integer register (other than RSP) that is unused as an input >> + operand in INSN. >> + MAX_LEN is the size of the buffer containing INSN. >> + OPCODE_OFFSET is the offset of the first opcode byte. >> + OPCODE_LEN is the number of opcode bytes. >> + MODRM_OFFSET is the offset of the ModRM byte or -1 if not present. >> + 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. >> + >> + ??? The GNU tools need a library of basic ISA-related support. */ > > >> + /* We shouldn't get here. */ >> + gdb_assert (0); > > Use internal_error instead, please. Ah, righto. >> +static void >> +fixup_riprel (struct displaced_step_closure *dsc, CORE_ADDR from, CORE_ADDR to, >> + struct regcache *regs) >> +{ >> + int modrm_offset = dsc->modrm_offset; >> + gdb_byte *insn = dsc->insn_buf + 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 = *(int32_t*) insn; // FIXME: target-fetch-value > > Err, that will cause unaligned traps on some hosts (this is a tdep file, not > a nat file). Please fix that up. Ya, oops. That one I intended to fix but forgot about. Fixed. >> + insn_length = amd64_insn_length (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 (dsc->insn_buf, dsc->max_len, >> + dsc->opcode_offset, >> + dsc->opcode_len, >> + dsc->modrm_offset); >> + 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 (dsc->rex_offset != -1) >> + dsc->insn_buf[dsc->rex_offset] &= ~1; >> + >> + 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 displaced_step_closure *dsc, >> + CORE_ADDR from, CORE_ADDR to, struct regcache *regs) >> +{ >> + gdb_byte *insn = dsc->insn_buf; >> + int len = dsc->max_len; >> + gdb_byte *start = insn; >> + gdb_byte *end = insn + len; >> + int need_modrm; >> + >> + /* Skip legacy instruction prefixes. >> + While the kernel's kprobes support can assume the instruction is valid, >> + we can't. Don't crash if we see an invalid insn. */ > > Yeah, we'd better warn/error out to the user than to do something silly > with instructions we don't know what to do with. Should we be checking > if you've reached >= end somewhere? The code allocates sufficient extra space to obviate having to continually test "are we at the end yet?". A lot of this is copied from i386-tdep.c. If this patch is ok, I'll send patches for i386-tdep.c. > >> + >> + while (insn < end) >> + { >> + switch (*insn) >> + { >> + case 0x66: >> + case 0x67: >> + case 0x2e: >> + case 0x3e: >> + case 0x26: >> + case 0x64: >> + case 0x65: >> + case 0x36: >> + case 0xf0: >> + case 0xf3: >> + case 0xf2: >> + ++insn; >> + continue; >> + } >> + break; >> + } >> + >> + /* Skip REX instruction prefix. */ >> + if (rex_prefix_p (*insn)) >> + { >> + dsc->rex_offset = insn - start; >> + ++insn; >> + } >> + >> + dsc->opcode_offset = insn - start; >> + >> + if (*insn == 0x0f) >> + { >> + /* Two or three-byte opcode. */ >> + ++insn; >> + need_modrm = twobyte_has_modrm[*insn]; >> + >> + /* Check for three-byte opcode. */ >> + if (*insn == 0x38 || *insn == 0x3a) >> + { >> + ++insn; >> + dsc->opcode_len = 3; >> + } >> + else >> + dsc->opcode_len = 2; >> + } >> + else >> + { >> + /* One-byte opcode. */ >> + need_modrm = onebyte_has_modrm[*insn]; >> + dsc->opcode_len = 1; >> + } >> + >> + if (need_modrm) >> + { >> + gdb_byte modrm = *++insn; >> + >> + dsc->modrm_offset = insn - start; >> + >> + if ((modrm & 0xc7) == 0x05) >> + { >> + /* The insn uses rip-relative addressing. >> + Deal with it. */ >> + fixup_riprel (dsc, from, to, regs); >> + } >> + } >> +} >> + > > >> + /* GDB may get control back after the insn after the syscall. >> + If this is a syscall, make sure there's a nop afterwards. */ >> + { >> + int syscall_length; >> >> + if (amd64_syscall_p (buf, &syscall_length)) >> + buf[syscall_length] = 0x90; >> + } > > Weird. Isn't this a kernel bug? It only happens when single-stepping > the syscall insn. E.g., from your testcase, without displaced stepping: > > 58 test_syscall: > 59 syscall > 60 nop > 61 test_syscall_end: > 62 nop > > (gdb) b amd64-disp-step.S:60 > Breakpoint 1 at 0x4004fa: file ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S, line 60. > (gdb) r > Starting program: /home/pedro/gdb/baseline/build/gdb/testsuite/gdb.arch/amd64-disp-step > > Breakpoint 1, test_syscall () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:60 > 60 nop > Current language: auto; currently asm > > But: > > (gdb) c > Continuing. > > Program exited normally. > (gdb) b 59 > Breakpoint 2 at 0x4004f8: file ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S, line 59. > (gdb) r > Starting program: /home/pedro/gdb/baseline/build/gdb/testsuite/gdb.arch/amd64-disp-step > > Breakpoint 2, test_syscall () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:59 > 59 syscall > (gdb) c > Continuing. > > Program exited normally. > (gdb) > > GDB did a single-step to get over the "syscall", and missed the breakpoint > at 0x4004fa. > > Again, doing a manual stepi: > > (gdb) r > Starting program: /home/pedro/gdb/baseline/build/gdb/testsuite/gdb.arch/amd64-disp-step > > Breakpoint 2, test_syscall () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:59 > 59 syscall > (gdb) del 2 > (gdb) set debug infrun 1 > (gdb) stepi > infrun: clear_proceed_status_thread (process 4322) > infrun: proceed (addr=0xffffffffffffffff, signal=144, step=1) > During symbol reading, incomplete CFI data; unspecified registers (e.g., rax) at 0x7ffff781b0f3. > infrun: resume (step=1, signal=0), trap_expected=0 > infrun: wait_for_inferior (treat_exec_as_sigtrap=0) > infrun: infwait_normal_state > infrun: TARGET_WAITKIND_STOPPED > infrun: stop_pc = 0x4004fb > infrun: stepi/nexti > infrun: stop_stepping > test_syscall_end () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:62 > 62 nop > (gdb) stepi > test_syscall_end () at ../../../src/gdb/testsuite/gdb.arch/amd64-disp-step.S:62 > 62 nop > (gdb) p $pc > $1 = (void (*)()) 0x4004fb <test_syscall_end> > (gdb) > > If you replace the nop with something wider, e.g., try stepping over > this: > > mov $0x27,%eax /* getpid */ > syscall > mov $0xff,%eax > nop > > You'll see that the move after the syscall is completely > stepped over, and that is was executed correctly. Ya, this is what I discovered during my experiments to understand this. > Something in the kernel not restoring the trace flag correctly, and > hence stepping one instruction too much? Presumably. > >> +/* ??? Do we need to check for rex prefix in these predicates, or their >> + callers? */ > > The caller should (amd64_displaced_step_fixup), I believe? IIUC, you also > store the prefixes in dsc->insn_buf. Well, that comment refers to the insn predicates like amd64_call_p. Having the caller skip the prefixes muddies their interface. The appended patch does something cleaner. > >> + >> +static int >> +amd64_absolute_jmp_p (gdb_byte *insn) >> +{ >> + /* jmp far (absolute address in operand) */ >> + /* ??? undefined insn actually */ >> + if (insn[0] == 0xea) >> + return 1; > > Indeed, invalid in 64-bit mode. Any reason not to drop it? Dropped. > >> + >> + 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 (gdb_byte *insn) >> +{ >> + /* call far, absolute */ >> + /* ??? undefined insn actually */ >> + if (insn[0] == 0x9a) >> + return 1; >> + > > Same as above. Righto. > >> + 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; >> +} >> + > >> Index: testsuite/gdb.arch/amd64-disp-step.S >> =================================================================== >> RCS file: testsuite/gdb.arch/amd64-disp-step.S >> diff -N testsuite/gdb.arch/amd64-disp-step.S > >> + Please email any bugs, comments, and/or additions to this file to: >> + bug-gdb@gnu.org > > Hmmm, shoudn't we be stopping using this address? cut-n-paste. Deleted. > >> +gdb_test "set non-stop on" "" >> +gdb_test "show non-stop" "Controlling the inferior in non-stop mode is on.*" > > Please use "set displaced-stepping on" instead. Righto. > >> +# Done, run program to exit. >> + >> +gdb_test "continue" \ >> + ".*Program exited normally.*" \ >> + "continue to exit" >> > > Use gdb_continue_to_end instead. Righto. Ok to check in? [Modulo I need to clear the opcode/i386.h additions with binutils. I'm not sure who to get approval from first.] 2009-01-27 Doug Evans <dje@google.com> * opcode/i386.h: Add multiple inclusion protection. (EAX_REG_NUM,ECX_REG_NUM,EDX_REGNUM,EBX_REG_NUM,ESI_REG_NUM) (EDI_REG_NUM): New macros. (MODRM_MOD_FIELD,MODRM_REG_FIELD,MODRM_RM_FIELD): New macros. (SIB_SCALE_FIELD,SIB_INDEX_FIELD,SIB_BASE_FIELD): New macros. (REG_PREFIX_P): New macro. * 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. * 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. [-- Attachment #2: gdb-090128-amd64-disp-step-2.patch.txt --] [-- Type: text/plain, Size: 44135 bytes --] 2009-01-27 Doug Evans <dje@google.com> * opcode/i386.h: Add multiple inclusion protection. (EAX_REG_NUM,ECX_REG_NUM,EDX_REGNUM,EBX_REG_NUM,ESI_REG_NUM) (EDI_REG_NUM): New macros. (MODRM_MOD_FIELD,MODRM_REG_FIELD,MODRM_RM_FIELD): New macros. (SIB_SCALE_FIELD,SIB_INDEX_FIELD,SIB_BASE_FIELD): New macros. (REG_PREFIX_P): New macro. * 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. * 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. Index: include/opcode/i386.h =================================================================== RCS file: /cvs/src/src/include/opcode/i386.h,v retrieving revision 1.79 diff -u -p -u -p -r1.79 i386.h --- include/opcode/i386.h 3 Apr 2008 14:03:20 -0000 1.79 +++ include/opcode/i386.h 28 Jan 2009 08:31:34 -0000 @@ -35,6 +35,9 @@ The affected opcode map is dceX, dcfX, deeX, defX. */ +#ifndef OPCODE_I386_H +#define OPCODE_I386_H + #ifndef SYSV386_COMPAT /* Set non-zero for broken, compatible instructions. Set to zero for non-broken opcodes at your peril. gcc generates SystemV/386 @@ -72,8 +75,14 @@ #define NOP_OPCODE (char) 0x90 /* register numbers */ -#define EBP_REG_NUM 5 +#define EAX_REG_NUM 0 +#define ECX_REG_NUM 1 +#define EDX_REG_NUM 2 +#define EBX_REG_NUM 3 #define ESP_REG_NUM 4 +#define EBP_REG_NUM 5 +#define ESI_REG_NUM 6 +#define EDI_REG_NUM 7 /* modrm_byte.regmem for twobyte escape */ #define ESCAPE_TO_TWO_BYTE_ADDRESSING ESP_REG_NUM @@ -87,9 +96,22 @@ #define REGMEM_FIELD_HAS_REG 0x3/* always = 0x3 */ #define REGMEM_FIELD_HAS_MEM (~REGMEM_FIELD_HAS_REG) +/* Extract fields from the mod/rm byte. */ +#define MODRM_MOD_FIELD(modrm) (((modrm) >> 6) & 3) +#define MODRM_REG_FIELD(modrm) (((modrm) >> 3) & 7) +#define MODRM_RM_FIELD(modrm) (((modrm) >> 0) & 7) + +/* Extract fields from the sib byte. */ +#define SIB_SCALE_FIELD(sib) (((sib) >> 6) & 3) +#define SIB_INDEX_FIELD(sib) (((sib) >> 3) & 7) +#define SIB_BASE_FIELD(sib) (((sib) >> 0) & 7) + /* x86-64 extension prefix. */ #define REX_OPCODE 0x40 +/* Non-zero if OPCODE is the rex prefix. */ +#define REX_PREFIX_P(opcode) (((opcode) & 0xf0) == REX_OPCODE) + /* Indicates 64 bit operand size. */ #define REX_W 8 /* High extension to reg field of modrm byte. */ @@ -113,3 +135,5 @@ /* max size of register name in insn mnemonics. */ #define MAX_REG_NAME_SIZE 8 + +#endif /* OPCODE_I386_H */ Index: gdb/amd64-linux-tdep.c =================================================================== RCS file: /cvs/src/src/gdb/amd64-linux-tdep.c,v retrieving revision 1.21 diff -u -p -u -p -r1.21 amd64-linux-tdep.c --- gdb/amd64-linux-tdep.c 3 Jan 2009 05:57:50 -0000 1.21 +++ gdb/amd64-linux-tdep.c 28 Jan 2009 08:31:34 -0000 @@ -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_inf /* 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); } \f Index: gdb/amd64-tdep.c =================================================================== RCS file: /cvs/src/src/gdb/amd64-tdep.c,v retrieving revision 1.56 diff -u -p -u -p -r1.56 amd64-tdep.c --- gdb/amd64-tdep.c 3 Jan 2009 05:57:50 -0000 1.56 +++ gdb/amd64-tdep.c 28 Jan 2009 08:31:34 -0000 @@ -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 gdbarc 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]; +} + \f /* Register classes as defined in the psABI. */ @@ -666,7 +704,678 @@ amd64_push_dummy_call (struct gdbarch *g return sp + 16; } \f +/* 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 << ((modrm >> 3) & 3); + + 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)); + } +} +\f /* 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, 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, 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); } Index: gdb/amd64-tdep.h =================================================================== RCS file: /cvs/src/src/gdb/amd64-tdep.h,v retrieving revision 1.13 diff -u -p -u -p -r1.13 amd64-tdep.h --- gdb/amd64-tdep.h 3 Jan 2009 05:57:50 -0000 1.13 +++ gdb/amd64-tdep.h 28 Jan 2009 08:31:34 -0000 @@ -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 Index: gdb/testsuite/gdb.arch/amd64-disp-step.S =================================================================== RCS file: gdb/testsuite/gdb.arch/amd64-disp-step.S diff -N gdb/testsuite/gdb.arch/amd64-disp-step.S --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdb/testsuite/gdb.arch/amd64-disp-step.S 28 Jan 2009 08:31:35 -0000 @@ -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 Index: gdb/testsuite/gdb.arch/amd64-disp-step.exp =================================================================== RCS file: gdb/testsuite/gdb.arch/amd64-disp-step.exp diff -N gdb/testsuite/gdb.arch/amd64-disp-step.exp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdb/testsuite/gdb.arch/amd64-disp-step.exp 28 Jan 2009 08:31:35 -0000 @@ -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" Index: gdb/testsuite/gdb.arch/i386-disp-step.S =================================================================== RCS file: gdb/testsuite/gdb.arch/i386-disp-step.S diff -N gdb/testsuite/gdb.arch/i386-disp-step.S --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdb/testsuite/gdb.arch/i386-disp-step.S 28 Jan 2009 08:31:35 -0000 @@ -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 Index: gdb/testsuite/gdb.arch/i386-disp-step.exp =================================================================== RCS file: gdb/testsuite/gdb.arch/i386-disp-step.exp diff -N gdb/testsuite/gdb.arch/i386-disp-step.exp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ gdb/testsuite/gdb.arch/i386-disp-step.exp 28 Jan 2009 08:31:35 -0000 @@ -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" ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-28 12:39 ` Doug Evans @ 2009-01-28 14:34 ` Pedro Alves 2009-01-28 15:05 ` Daniel Jacobowitz 1 sibling, 0 replies; 9+ messages in thread From: Pedro Alves @ 2009-01-28 14:34 UTC (permalink / raw) To: Doug Evans; +Cc: gdb-patches On Wednesday 28 January 2009 08:39:47, Doug Evans wrote: > I'd like to add the needed interfaces. I'm just not sure how long a > process that will be and I'd like to get started on exercising amd64 > non-stop functionality. I'll pursue exporting the modrm_bytes arrays > and insn-length computation with binutils. Ok, thanks a lot. I don't think doing that should be a prerequisite for this patch. I just wanted to know if it's in your radar, and in case it was not, to have it recorded somewhere as a TODO item. > Ok to check in? Looks great to me. > [Modulo I need to clear the opcode/i386.h additions with binutils. > I'm not sure who to get approval from first.] The gdb bits are approved. A suggestion: don't add the macros/defines you were adding in opcode/i386.h just yet, but inline / add them in amd64-tdep.c, and check in the gdb patch (pre-approved). Then, all the opcode changes can be made on top, separately (and incrementally, if desired). Sounds good to you? -- Pedro Alves ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-28 12:39 ` Doug Evans 2009-01-28 14:34 ` Pedro Alves @ 2009-01-28 15:05 ` Daniel Jacobowitz 1 sibling, 0 replies; 9+ messages in thread From: Daniel Jacobowitz @ 2009-01-28 15:05 UTC (permalink / raw) To: Doug Evans; +Cc: Pedro Alves, gdb-patches On Wed, Jan 28, 2009 at 12:39:47AM -0800, Doug Evans wrote: > [Modulo I need to clear the opcode/i386.h additions with binutils. > I'm not sure who to get approval from first.] Easy - post them to the binutils list, say I approved them :-) > 2009-01-27 Doug Evans <dje@google.com> > > * opcode/i386.h: Add multiple inclusion protection. > (EAX_REG_NUM,ECX_REG_NUM,EDX_REGNUM,EBX_REG_NUM,ESI_REG_NUM) Personally I prefer spaces after commas, but I'm not going to make a fuss... -- Daniel Jacobowitz CodeSourcery ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-27 1:33 [RFA] amd64 displaced stepping support Doug Evans 2009-01-27 8:26 ` Stan Shebs 2009-01-27 17:27 ` Pedro Alves @ 2009-01-27 20:27 ` Daniel Jacobowitz 2009-01-28 0:13 ` Doug Evans 2 siblings, 1 reply; 9+ messages in thread From: Daniel Jacobowitz @ 2009-01-27 20:27 UTC (permalink / raw) To: Doug Evans; +Cc: gdb-patches On Mon, Jan 26, 2009 at 03:00:12PM -0800, Doug Evans wrote: > Using the disassembler to compute instruction lengths is awkward, I know. > It's needed in order to compute the address of rip-relative addressing. > The address is %rip + address-of-next-insn + displacement, > and the displacement is only 32 bits so it's not guaranteed to be enough > to cover the distance between the original instruction and its copy. > To compensate I compute an unused integer reg, set it to > %rip + address-of-next-insn, and rewrite the insn to use base+disp addressing. > I think the GNU tools need a general-purpose library of ISA-related tools. > Until then, I went with the disassembler. The code is laid out such that > when a better implementation of computing insn lengths comes along, it > can be easily dropped in. IMO, "the disassembler" means a bit of GDB interface glue, and libopcodes. Libopcodes is the obvious place for a library about opcodes. It can export more information; there's an example of this at the very end of struct disassemble_info, though it probably needs more granularity. -- Daniel Jacobowitz CodeSourcery ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-27 20:27 ` Daniel Jacobowitz @ 2009-01-28 0:13 ` Doug Evans 2009-01-28 14:37 ` Daniel Jacobowitz 0 siblings, 1 reply; 9+ messages in thread From: Doug Evans @ 2009-01-28 0:13 UTC (permalink / raw) To: gdb-patches On Tue, Jan 27, 2009 at 9:27 AM, Daniel Jacobowitz <drow@false.org> wrote: > On Mon, Jan 26, 2009 at 03:00:12PM -0800, Doug Evans wrote: >> Using the disassembler to compute instruction lengths is awkward, I know. >> It's needed in order to compute the address of rip-relative addressing. >> The address is %rip + address-of-next-insn + displacement, >> and the displacement is only 32 bits so it's not guaranteed to be enough >> to cover the distance between the original instruction and its copy. >> To compensate I compute an unused integer reg, set it to >> %rip + address-of-next-insn, and rewrite the insn to use base+disp addressing. >> I think the GNU tools need a general-purpose library of ISA-related tools. >> Until then, I went with the disassembler. The code is laid out such that >> when a better implementation of computing insn lengths comes along, it >> can be easily dropped in. > > IMO, "the disassembler" means a bit of GDB interface glue, and > libopcodes. Libopcodes is the obvious place for a library about > opcodes. It can export more information; there's an example of this > at the very end of struct disassemble_info, though it probably needs > more granularity. I don't disagree that libopcodes is a reasonable place. I'm assuming by "example of this" you're refering to branch_delay_insns, target, target2, etc. Right? This is great stuff, but it's in a struct named "disassemble_info". :-) IWBN if libopcodes could provide a more generic interface, and long term an interface more suitable to general use (i.e. not restricted to the confines of gdb/binutils releases). ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFA] amd64 displaced stepping support 2009-01-28 0:13 ` Doug Evans @ 2009-01-28 14:37 ` Daniel Jacobowitz 0 siblings, 0 replies; 9+ messages in thread From: Daniel Jacobowitz @ 2009-01-28 14:37 UTC (permalink / raw) To: gdb-patches On Tue, Jan 27, 2009 at 03:31:38PM -0800, Doug Evans wrote: > I don't disagree that libopcodes is a reasonable place. > > I'm assuming by "example of this" you're refering to > branch_delay_insns, target, target2, etc. Right? This is great > stuff, but it's in a struct named "disassemble_info". :-) IWBN if > libopcodes could provide a more generic interface, and long term an > interface more suitable to general use (i.e. not restricted to the > confines of gdb/binutils releases). Oh, yes. Completely agreed - although I think it's going to look a lot like a disassembler, anyway. -- Daniel Jacobowitz CodeSourcery ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2009-01-28 14:37 UTC | newest] Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2009-01-27 1:33 [RFA] amd64 displaced stepping support Doug Evans 2009-01-27 8:26 ` Stan Shebs 2009-01-27 17:27 ` Pedro Alves 2009-01-28 12:39 ` Doug Evans 2009-01-28 14:34 ` Pedro Alves 2009-01-28 15:05 ` Daniel Jacobowitz 2009-01-27 20:27 ` Daniel Jacobowitz 2009-01-28 0:13 ` Doug Evans 2009-01-28 14:37 ` Daniel Jacobowitz
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox