This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[RFA] amd64 displaced stepping support
- From: dje at google dot com (Doug Evans)
- To: gdb-patches at sourceware dot org
- Date: Mon, 26 Jan 2009 15:00:12 -0800 (PST)
- Subject: [RFA] amd64 displaced stepping support
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);
}
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];
+}
+
/* Register classes as defined in the psABI. */
@@ -666,7 +703,647 @@ amd64_push_dummy_call (struct gdbarch *g
return sp + 16;
}
+/* 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));
+ }
+}
+
/* 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"