From 591a12a1d4c8843343eb999145d8bcc1efedf408 Mon Sep 17 00:00:00 2001 From: Ulrich Weigand Date: Tue, 4 Feb 2014 18:44:14 +0100 Subject: [PATCH] PowerPC64 ELFv2 ABI: skip global entry point code This patch handles another aspect of the ELFv2 ABI, which unfortunately requires common code changes. In ELFv2, functions may provide both a global and a local entry point. The global entry point (where the function symbol points to) is intended to be used for function-pointer or cross-module (PLT) calls, and requires r12 to be set up to the entry point address itself. The local entry point (which is found at a fixed offset after the global entry point, as defined by bits in the symbol table entries' st_other field), instead expects r2 to be set up to the current TOC. Now, when setting a breakpoint on a function by name, you really want that breakpoint to trigger either way, no matter whether the function is called via its local or global entry point. Since the global entry point will always fall through into the local entry point, the way to achieve that is to simply set the breakpoint at the local entry point. One way to do that would be to have prologue parsing skip the code sequence that makes up the global entry point. Unfortunately, this does not work reliably, since -for optimized code- GDB these days will not actuall invoke the prologue parsing code but instead just set the breakpoint at the symbol address and rely on DWARF being correct at any point throughout the function ... Unfortunately, I don't really see any way to express the notion of local entry points with the current set of gdbarch callbacks. Thus this patch adds a new callback, skip_entrypoint, that is somewhat analogous to skip_prologue, but is called every time GDB needs to determine a function start address, even in those cases where GDB decides to not call skip_prologue. As a side effect, the skip_entrypoint implementation on ppc64 does not need to perform any instruction parsing; it can simply rely on the local entry point flags in the symbol table entry. With this implemented, two test cases would still fail to set the breakpoint correctly, but that's because they use the construct: gdb_test "break *hello" Now, using "*hello" explicitly instructs GDB to set the breakpoint at the numerical value of "hello" treated as function pointer, so it will by definition only hit the global entry point. I think this behaviour is unavoidable, but acceptable -- most people do not use this construct, and if they do, they get what they asked for ... In one of those two test cases, use of this construct is really not appropriate. I think this was added way back when as a means to work around prologue skipping problems on some platforms. These days that shouldn't really be necessary any more ... For the other (step-bt), we really want to make sure backtracing works on the very first instruction of the routine. To enable that test also on powerpc64le-linux, we can modify the code to call the test function via function pointer (which makes it use the global entry point in the ELFv2 ABI). gdb/ChangeLog: * gdbarch.sh (skip_entrypoint): New callback. * gdbarch.c, gdbarch.h: Regenerate. * symtab.c (skip_prologue_sal): Call gdbarch_skip_entrypoint. * infrun.c (fill_in_stop_func): Likewise. * ppc-linux-tdep.c: Include "elf/ppc64.h". (ppc_elfv2_elf_make_msymbol_special): New function. (ppc_elfv2_skip_entrypoint): Likewise. (ppc_linux_init_abi): Install them for ELFv2. gdb/testsuite/ChangeLog: * gdb.base/sigbpt.exp: Do not use "*" when setting breakpoint on a function. * gdb.base/step-bt.c: Call hello via function pointer to make sure its first instruction is executed on powerpc64le-linux. --- gdb/ChangeLog | 11 ++++++ gdb/gdbarch.c | 33 ++++++++++++++++++ gdb/gdbarch.h | 18 ++++++++++ gdb/gdbarch.sh | 13 +++++++ gdb/infrun.c | 4 +++ gdb/ppc-linux-tdep.c | 57 +++++++++++++++++++++++++++++++ gdb/symtab.c | 2 ++ gdb/testsuite/ChangeLog | 7 ++++ gdb/testsuite/gdb.base/sigbpt.exp | 2 +- gdb/testsuite/gdb.base/step-bt.c | 11 +++++- 10 files changed, 156 insertions(+), 2 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 2069aaeac9..9f4636f90a 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,14 @@ +2014-02-04 Ulrich Weigand  + + * gdbarch.sh (skip_entrypoint): New callback. + * gdbarch.c, gdbarch.h: Regenerate. + * symtab.c (skip_prologue_sal): Call gdbarch_skip_entrypoint. + * infrun.c (fill_in_stop_func): Likewise. + * ppc-linux-tdep.c: Include "elf/ppc64.h". + (ppc_elfv2_elf_make_msymbol_special): New function. + (ppc_elfv2_skip_entrypoint): Likewise. + (ppc_linux_init_abi): Install them for ELFv2. + 2014-02-04 Ulrich Weigand  * ppc-sysv-tdep.c (ppc64_aggregate_candidate): New routine. diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c index ee41d5073e..e26681c16b 100644 --- a/gdb/gdbarch.c +++ b/gdb/gdbarch.c @@ -229,6 +229,7 @@ struct gdbarch gdbarch_return_in_first_hidden_param_p_ftype *return_in_first_hidden_param_p; gdbarch_skip_prologue_ftype *skip_prologue; gdbarch_skip_main_prologue_ftype *skip_main_prologue; + gdbarch_skip_entrypoint_ftype *skip_entrypoint; gdbarch_inner_than_ftype *inner_than; gdbarch_breakpoint_from_pc_ftype *breakpoint_from_pc; gdbarch_remote_breakpoint_from_pc_ftype *remote_breakpoint_from_pc; @@ -405,6 +406,7 @@ struct gdbarch startup_gdbarch = default_return_in_first_hidden_param_p, /* return_in_first_hidden_param_p */ 0, /* skip_prologue */ 0, /* skip_main_prologue */ + 0, /* skip_entrypoint */ 0, /* inner_than */ 0, /* breakpoint_from_pc */ default_remote_breakpoint_from_pc, /* remote_breakpoint_from_pc */ @@ -714,6 +716,7 @@ verify_gdbarch (struct gdbarch *gdbarch) if (gdbarch->skip_prologue == 0) fprintf_unfiltered (log, "\n\tskip_prologue"); /* Skip verify of skip_main_prologue, has predicate. */ + /* Skip verify of skip_entrypoint, has predicate. */ if (gdbarch->inner_than == 0) fprintf_unfiltered (log, "\n\tinner_than"); if (gdbarch->breakpoint_from_pc == 0) @@ -1352,6 +1355,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) fprintf_unfiltered (file, "gdbarch_dump: single_step_through_delay = <%s>\n", host_address_to_string (gdbarch->single_step_through_delay)); + fprintf_unfiltered (file, + "gdbarch_dump: gdbarch_skip_entrypoint_p() = %d\n", + gdbarch_skip_entrypoint_p (gdbarch)); + fprintf_unfiltered (file, + "gdbarch_dump: skip_entrypoint = <%s>\n", + host_address_to_string (gdbarch->skip_entrypoint)); fprintf_unfiltered (file, "gdbarch_dump: gdbarch_skip_main_prologue_p() = %d\n", gdbarch_skip_main_prologue_p (gdbarch)); @@ -2702,6 +2711,30 @@ set_gdbarch_skip_main_prologue (struct gdbarch *gdbarch, gdbarch->skip_main_prologue = skip_main_prologue; } +int +gdbarch_skip_entrypoint_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + return gdbarch->skip_entrypoint != NULL; +} + +CORE_ADDR +gdbarch_skip_entrypoint (struct gdbarch *gdbarch, CORE_ADDR ip) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->skip_entrypoint != NULL); + if (gdbarch_debug >= 2) + fprintf_unfiltered (gdb_stdlog, "gdbarch_skip_entrypoint called\n"); + return gdbarch->skip_entrypoint (gdbarch, ip); +} + +void +set_gdbarch_skip_entrypoint (struct gdbarch *gdbarch, + gdbarch_skip_entrypoint_ftype skip_entrypoint) +{ + gdbarch->skip_entrypoint = skip_entrypoint; +} + int gdbarch_inner_than (struct gdbarch *gdbarch, CORE_ADDR lhs, CORE_ADDR rhs) { diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h index b28a80e75b..9698e11187 100644 --- a/gdb/gdbarch.h +++ b/gdb/gdbarch.h @@ -486,6 +486,24 @@ typedef CORE_ADDR (gdbarch_skip_main_prologue_ftype) (struct gdbarch *gdbarch, C extern CORE_ADDR gdbarch_skip_main_prologue (struct gdbarch *gdbarch, CORE_ADDR ip); extern void set_gdbarch_skip_main_prologue (struct gdbarch *gdbarch, gdbarch_skip_main_prologue_ftype *skip_main_prologue); +/* On some platforms, a single function may provide multiple entry points, + e.g. one that is used for function-pointer calls and a different one + that is used for direct function calls. + In order to ensure that breakpoints set on the function will trigger + no matter via which entry point the function is entered, a platform + may provide the skip_entrypoint callback. It is called with IP set + to the main entry point of a function (as determined by the symbol table), + and should return the address of the innermost entry point, where the + actual breakpoint needs to be set. Note that skip_entrypoint is used + by GDB common code even when debugging optimized code, where skip_prologue + is not used. */ + +extern int gdbarch_skip_entrypoint_p (struct gdbarch *gdbarch); + +typedef CORE_ADDR (gdbarch_skip_entrypoint_ftype) (struct gdbarch *gdbarch, CORE_ADDR ip); +extern CORE_ADDR gdbarch_skip_entrypoint (struct gdbarch *gdbarch, CORE_ADDR ip); +extern void set_gdbarch_skip_entrypoint (struct gdbarch *gdbarch, gdbarch_skip_entrypoint_ftype *skip_entrypoint); + typedef int (gdbarch_inner_than_ftype) (CORE_ADDR lhs, CORE_ADDR rhs); extern int gdbarch_inner_than (struct gdbarch *gdbarch, CORE_ADDR lhs, CORE_ADDR rhs); extern void set_gdbarch_inner_than (struct gdbarch *gdbarch, gdbarch_inner_than_ftype *inner_than); diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh index 36dff570c6..e785999d3e 100755 --- a/gdb/gdbarch.sh +++ b/gdb/gdbarch.sh @@ -530,6 +530,19 @@ m:int:return_in_first_hidden_param_p:struct type *type:type::default_return_in_f m:CORE_ADDR:skip_prologue:CORE_ADDR ip:ip:0:0 M:CORE_ADDR:skip_main_prologue:CORE_ADDR ip:ip +# On some platforms, a single function may provide multiple entry points, +# e.g. one that is used for function-pointer calls and a different one +# that is used for direct function calls. +# In order to ensure that breakpoints set on the function will trigger +# no matter via which entry point the function is entered, a platform +# may provide the skip_entrypoint callback. It is called with IP set +# to the main entry point of a function (as determined by the symbol table), +# and should return the address of the innermost entry point, where the +# actual breakpoint needs to be set. Note that skip_entrypoint is used +# by GDB common code even when debugging optimized code, where skip_prologue +# is not used. +M:CORE_ADDR:skip_entrypoint:CORE_ADDR ip:ip + f:int:inner_than:CORE_ADDR lhs, CORE_ADDR rhs:lhs, rhs:0:0 m:const gdb_byte *:breakpoint_from_pc:CORE_ADDR *pcptr, int *lenptr:pcptr, lenptr::0: # Return the adjusted address and kind to use for Z0/Z1 packets. diff --git a/gdb/infrun.c b/gdb/infrun.c index 71d9615582..c0df124717 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -3159,6 +3159,10 @@ fill_in_stop_func (struct gdbarch *gdbarch, ecs->stop_func_start += gdbarch_deprecated_function_start_offset (gdbarch); + if (gdbarch_skip_entrypoint_p (gdbarch)) + ecs->stop_func_start = gdbarch_skip_entrypoint (gdbarch, + ecs->stop_func_start); + ecs->stop_func_filled_in = 1; } } diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c index 25008c010b..6d3b2d8d4a 100644 --- a/gdb/ppc-linux-tdep.c +++ b/gdb/ppc-linux-tdep.c @@ -44,6 +44,7 @@ #include "observer.h" #include "auxv.h" #include "elf/common.h" +#include "elf/ppc64.h" #include "exceptions.h" #include "arch-utils.h" #include "spu-tdep.h" @@ -876,6 +877,55 @@ ppc_linux_core_read_description (struct gdbarch *gdbarch, } } + +/* Implementation of `gdbarch_elf_make_msymbol_special', as defined in + gdbarch.h. This implementation is used for the ELFv2 ABI only. */ + +static void +ppc_elfv2_elf_make_msymbol_special (asymbol *sym, struct minimal_symbol *msym) +{ + elf_symbol_type *elf_sym = (elf_symbol_type *)sym; + + /* If the symbol is marked as having a local entry point, set a target + flag in the msymbol. We currently only support local entry point + offsets of 8 bytes, which is the only entry point offset ever used + by current compilers. If/when other offsets are ever used, we will + have to use additional target flag bits to store them. */ + switch (PPC64_LOCAL_ENTRY_OFFSET (elf_sym->internal_elf_sym.st_other)) + { + default: + break; + case 8: + MSYMBOL_TARGET_FLAG_1 (msym) = 1; + break; + } +} + +/* Implementation of `gdbarch_skip_entrypoint', as defined in + gdbarch.h. This implementation is used for the ELFv2 ABI only. */ + +static CORE_ADDR +ppc_elfv2_skip_entrypoint (struct gdbarch *gdbarch, CORE_ADDR pc) +{ + struct bound_minimal_symbol fun; + int local_entry_offset = 0; + + fun = lookup_minimal_symbol_by_pc (pc); + if (fun.minsym == NULL) + return pc; + + /* See ppc_elfv2_elf_make_msymbol_special for how local entry point + offset values are encoded. */ + if (MSYMBOL_TARGET_FLAG_1 (fun.minsym)) + local_entry_offset = 8; + + if (SYMBOL_VALUE_ADDRESS (fun.minsym) <= pc + && pc < SYMBOL_VALUE_ADDRESS (fun.minsym) + local_entry_offset) + return SYMBOL_VALUE_ADDRESS (fun.minsym) + local_entry_offset; + + return pc; +} + /* Implementation of `gdbarch_stap_is_single_operand', as defined in gdbarch.h. */ @@ -1349,6 +1399,13 @@ ppc_linux_init_abi (struct gdbarch_info info, set_gdbarch_elf_make_msymbol_special (gdbarch, ppc64_elf_make_msymbol_special); } + else + { + set_gdbarch_elf_make_msymbol_special + (gdbarch, ppc_elfv2_elf_make_msymbol_special); + + set_gdbarch_skip_entrypoint (gdbarch, ppc_elfv2_skip_entrypoint); + } /* Shared library handling. */ set_gdbarch_skip_trampoline_code (gdbarch, ppc64_skip_trampoline_code); diff --git a/gdb/symtab.c b/gdb/symtab.c index 97b85b84ac..50c09c1090 100644 --- a/gdb/symtab.c +++ b/gdb/symtab.c @@ -2950,6 +2950,8 @@ skip_prologue_sal (struct symtab_and_line *sal) /* Skip "first line" of function (which is actually its prologue). */ pc += gdbarch_deprecated_function_start_offset (gdbarch); + if (gdbarch_skip_entrypoint_p (gdbarch)) + pc = gdbarch_skip_entrypoint (gdbarch, pc); if (skip) pc = gdbarch_skip_prologue (gdbarch, pc); diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 7378bb24e5..a6c52144ad 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,10 @@ +2014-02-04 Ulrich Weigand + + * gdb.base/sigbpt.exp: Do not use "*" when setting breakpoint + on a function. + * gdb.base/step-bt.c: Call hello via function pointer to make + sure its first instruction is executed on powerpc64le-linux. + 2014-02-04 Ulrich Weigand * gdb.arch/powerpc-d128-regs.exp: Enable on powerpc64*-*. diff --git a/gdb/testsuite/gdb.base/sigbpt.exp b/gdb/testsuite/gdb.base/sigbpt.exp index 3d0db070f6..142e739dc5 100644 --- a/gdb/testsuite/gdb.base/sigbpt.exp +++ b/gdb/testsuite/gdb.base/sigbpt.exp @@ -76,7 +76,7 @@ gdb_test "break keeper" set bowler_addrs bowler set segv_addr none gdb_test {display/i $pc} -gdb_test "advance *bowler" "bowler.*" "advance to the bowler" +gdb_test "advance bowler" "bowler.*" "advance to the bowler" set test "stepping to fault" set signame "SIGSEGV" gdb_test_multiple "stepi" "$test" { diff --git a/gdb/testsuite/gdb.base/step-bt.c b/gdb/testsuite/gdb.base/step-bt.c index e772a8eb9d..2551ebea15 100644 --- a/gdb/testsuite/gdb.base/step-bt.c +++ b/gdb/testsuite/gdb.base/step-bt.c @@ -23,10 +23,19 @@ hello (void) printf ("Hello world.\n"); } +/* The test case uses "break *hello" to make sure to step at the very + first instruction of the function. This causes a problem running + the test on powerpc64le-linux, since the first instruction belongs + to the global entry point prologue, which is skipped when doing a + local direct function call. To make sure that first instruction is + indeed being executed and the breakpoint hits, we make sure to call + the routine via an indirect call. */ +void (*ptr) (void) = hello; + int main (void) { - hello (); + ptr (); return 0; }