You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
15 KiB
357 lines
15 KiB
commit 591a12a1d4c8843343eb999145d8bcc1efedf408 |
|
Author: Ulrich Weigand <ulrich.weigand@de.ibm.com> |
|
Date: Tue Feb 4 18:44:14 2014 +0100 |
|
|
|
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. |
|
|
|
Index: gdb-7.6.1/gdb/gdbarch.c |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/gdbarch.c |
|
+++ gdb-7.6.1/gdb/gdbarch.c |
|
@@ -200,6 +200,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; |
|
@@ -371,6 +372,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 */ |
|
@@ -672,6 +674,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) |
|
@@ -1285,6 +1288,12 @@ gdbarch_dump (struct gdbarch *gdbarch, s |
|
"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)); |
|
fprintf_unfiltered (file, |
|
@@ -2635,6 +2644,30 @@ set_gdbarch_skip_main_prologue (struct g |
|
} |
|
|
|
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) |
|
{ |
|
gdb_assert (gdbarch != NULL); |
|
Index: gdb-7.6.1/gdb/gdbarch.h |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/gdbarch.h |
|
+++ gdb-7.6.1/gdb/gdbarch.h |
|
@@ -487,6 +487,24 @@ typedef CORE_ADDR (gdbarch_skip_main_pro |
|
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); |
|
Index: gdb-7.6.1/gdb/gdbarch.sh |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/gdbarch.sh |
|
+++ gdb-7.6.1/gdb/gdbarch.sh |
|
@@ -527,6 +527,19 @@ m:int:return_in_first_hidden_param_p:str |
|
|
|
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. |
|
Index: gdb-7.6.1/gdb/infrun.c |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/infrun.c |
|
+++ gdb-7.6.1/gdb/infrun.c |
|
@@ -3162,6 +3162,10 @@ fill_in_stop_func (struct gdbarch *gdbar |
|
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; |
|
} |
|
} |
|
Index: gdb-7.6.1/gdb/ppc-linux-tdep.c |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/ppc-linux-tdep.c |
|
+++ gdb-7.6.1/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" |
|
@@ -875,6 +876,55 @@ ppc_linux_core_read_description (struct |
|
} |
|
} |
|
|
|
+ |
|
+/* 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 minimal_symbol *fun; |
|
+ int local_entry_offset = 0; |
|
+ |
|
+ fun = lookup_minimal_symbol_by_pc (pc); |
|
+ if (fun == 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)) |
|
+ local_entry_offset = 8; |
|
+ |
|
+ if (SYMBOL_VALUE_ADDRESS (fun) <= pc |
|
+ && pc < SYMBOL_VALUE_ADDRESS (fun) + local_entry_offset) |
|
+ return SYMBOL_VALUE_ADDRESS (fun) + local_entry_offset; |
|
+ |
|
+ return pc; |
|
+} |
|
+ |
|
/* Implementation of `gdbarch_stap_is_single_operand', as defined in |
|
gdbarch.h. */ |
|
|
|
@@ -1341,6 +1391,13 @@ ppc_linux_init_abi (struct gdbarch_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); |
|
Index: gdb-7.6.1/gdb/symtab.c |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/symtab.c |
|
+++ gdb-7.6.1/gdb/symtab.c |
|
@@ -2872,6 +2872,8 @@ skip_prologue_sal (struct symtab_and_lin |
|
|
|
/* 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); |
|
|
|
Index: gdb-7.6.1/gdb/testsuite/gdb.base/sigbpt.exp |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/testsuite/gdb.base/sigbpt.exp |
|
+++ gdb-7.6.1/gdb/testsuite/gdb.base/sigbpt.exp |
|
@@ -82,7 +82,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" { |
|
Index: gdb-7.6.1/gdb/testsuite/gdb.base/step-bt.c |
|
=================================================================== |
|
--- gdb-7.6.1.orig/gdb/testsuite/gdb.base/step-bt.c |
|
+++ gdb-7.6.1/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; |
|
}
|
|
|