From: Jose E. Marchesi Date: Mon, 18 Nov 2013 12:37:05 +0000 (-0800) Subject: sparc: support single-stepping over longjmp calls. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=d0b5971ae7b8709c28a7a1638413426d8749d976;p=binutils-gdb.git sparc: support single-stepping over longjmp calls. 2013-11-18 Jose E. Marchesi * sparc-tdep.c (sparc_is_annulled_branch_insn): New function. * sparc-tdep.h: And its prototype. * sparc64-linux-tdep.c (sparc64_linux_get_longjmp_target): New function. (sparc64_linux_init_abi): Register the get_longjmp_target hook. --- diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 7fc09e0cb67..2042e49b7f9 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,12 @@ +2013-11-18 Jose E. Marchesi + + * sparc-tdep.c (sparc_is_annulled_branch_insn): New function. + * sparc-tdep.h: And its prototype. + + * sparc64-linux-tdep.c (sparc64_linux_get_longjmp_target): New + function. + (sparc64_linux_init_abi): Register the get_longjmp_target hook. + 2013-11-18 Pedro Alves * dwarf2-frame.c (read_addr_from_reg): Remove stale comment and diff --git a/gdb/sparc-tdep.c b/gdb/sparc-tdep.c index 53014a0e558..1a200e0e49b 100644 --- a/gdb/sparc-tdep.c +++ b/gdb/sparc-tdep.c @@ -121,6 +121,37 @@ sparc_is_unimp_insn (CORE_ADDR pc) return ((insn & 0xc1c00000) == 0); } +/* Return non-zero if the instruction corresponding to PC is an + "annulled" branch, i.e. the annul bit is set. */ + +int +sparc_is_annulled_branch_insn (CORE_ADDR pc) +{ + /* The branch instructions featuring an annul bit can be identified + by the following bit patterns: + + OP=0 + OP2=1: Branch on Integer Condition Codes with Prediction (BPcc). + OP2=2: Branch on Integer Condition Codes (Bcc). + OP2=5: Branch on FP Condition Codes with Prediction (FBfcc). + OP2=6: Branch on FP Condition Codes (FBcc). + OP2=3 && Bit28=0: + Branch on Integer Register with Prediction (BPr). + + This leaves out ILLTRAP (OP2=0), SETHI/NOP (OP2=4) and the V8 + coprocessor branch instructions (Op2=7). */ + + const unsigned long insn = sparc_fetch_instruction (pc); + const unsigned op2 = X_OP2 (insn); + + if ((X_OP (insn) == 0) + && ((op2 == 1) || (op2 == 2) || (op2 == 5) || (op2 == 6) + || ((op2 == 3) && ((insn & 0x10000000) == 0)))) + return X_A (insn); + else + return 0; +} + /* OpenBSD/sparc includes StackGhost, which according to the author's website http://stackghost.cerias.purdue.edu "... transparently and automatically protects applications' stack frames; more diff --git a/gdb/sparc-tdep.h b/gdb/sparc-tdep.h index cafc627e4f1..6fd76415ea4 100644 --- a/gdb/sparc-tdep.h +++ b/gdb/sparc-tdep.h @@ -220,6 +220,8 @@ extern void sparc32_collect_fpregset (const struct sparc_fpregset *fpregset, const struct regcache *regcache, int regnum, void *fpregs); +extern int sparc_is_annulled_branch_insn (CORE_ADDR pc); + /* Functions and variables exported from sparc-sol2-tdep.c. */ /* Register offsets for Solaris 2. */ diff --git a/gdb/sparc64-linux-tdep.c b/gdb/sparc64-linux-tdep.c index 3f53f6c1acf..f6ddff0f34b 100644 --- a/gdb/sparc64-linux-tdep.c +++ b/gdb/sparc64-linux-tdep.c @@ -232,6 +232,50 @@ sparc64_linux_get_syscall_number (struct gdbarch *gdbarch, return ret; } + +/* Implement the "get_longjmp_target" gdbarch method. */ + +static int +sparc64_linux_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc) +{ + struct gdbarch *gdbarch = get_frame_arch (frame); + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + CORE_ADDR jb_addr; + gdb_byte buf[8]; + + jb_addr = get_frame_register_unsigned (frame, SPARC_O0_REGNUM); + + /* setjmp and longjmp in SPARC64 are implemented in glibc using the + setcontext and getcontext system calls respectively. These + system calls operate on ucontext_t structures, which happen to + partially have the same structure than jmp_buf. However the + ucontext returned by getcontext, and thus the jmp_buf structure + returned by setjmp, contains the context of the trap instruction + in the glibc __[sig]setjmp wrapper, not the context of the user + code calling setjmp. + + %o7 in the jmp_buf structure is stored at offset 18*8 in the + mc_gregs array, which is itself located at offset 32 into + jmp_buf. See bits/setjmp.h. This register contains the address + of the 'call setjmp' instruction in user code. + + In order to determine the longjmp target address in the + initiating frame we need to examine the call instruction itself, + in particular whether the annul bit is set. If it is not set + then we need to jump over the instruction at the delay slot. */ + + if (target_read_memory (jb_addr + 32 + (18 * 8), buf, 8)) + return 0; + + *pc = extract_unsigned_integer (buf, 8, gdbarch_byte_order (gdbarch)); + + if (!sparc_is_annulled_branch_insn (*pc)) + *pc += 4; /* delay slot insn */ + *pc += 4; /* call insn */ + + return 1; +} + static void @@ -272,6 +316,9 @@ sparc64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) /* Make sure we can single-step over signal return system calls. */ tdep->step_trap = sparc64_linux_step_trap; + /* Make sure we can single-step over longjmp calls. */ + set_gdbarch_get_longjmp_target (gdbarch, sparc64_linux_get_longjmp_target); + set_gdbarch_write_pc (gdbarch, sparc64_linux_write_pc); /* Functions for 'catch syscall'. */