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.
358 lines
15 KiB
358 lines
15 KiB
6 years ago
|
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;
|
||
|
}
|