376 lines
15 KiB
Diff
376 lines
15 KiB
Diff
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.
|
||
|
||
### a/gdb/ChangeLog
|
||
### b/gdb/ChangeLog
|
||
## -1,5 +1,16 @@
|
||
2014-02-04 Ulrich Weigand <uweigand@de.ibm.com>
|
||
|
||
+ * 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 <uweigand@de.ibm.com>
|
||
+
|
||
* ppc-sysv-tdep.c (ppc64_aggregate_candidate): New routine.
|
||
(ppc64_elfv2_abi_homogeneous_aggregate): Likewise.
|
||
(ppc64_sysv_abi_push_param): Handle ELFv2 homogeneous structs.
|
||
--- 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)
|
||
@@ -1353,6 +1356,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *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));
|
||
fprintf_unfiltered (file,
|
||
@@ -2703,6 +2712,30 @@ set_gdbarch_skip_main_prologue (struct gdbarch *gdbarch,
|
||
}
|
||
|
||
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);
|
||
--- 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);
|
||
--- 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.
|
||
--- 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;
|
||
}
|
||
}
|
||
--- 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);
|
||
--- 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);
|
||
|
||
### a/gdb/testsuite/ChangeLog
|
||
### b/gdb/testsuite/ChangeLog
|
||
## -1,5 +1,12 @@
|
||
2014-02-04 Ulrich Weigand <uweigand@de.ibm.com>
|
||
|
||
+ * 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 <uweigand@de.ibm.com>
|
||
+
|
||
* gdb.arch/powerpc-d128-regs.exp: Enable on powerpc64*-*.
|
||
|
||
2014-02-04 Ulrich Weigand <uweigand@de.ibm.com>
|
||
--- 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" {
|
||
--- 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;
|
||
}
|