[PATCHi v1] PowerPC: Create call stubs for compiled modules
Abhay Kandpal <[email protected]>
| Newsgroups | gmane.comp.gdb.patches |
|---|---|
| Message-ID | <[email protected]> |
The compile command loads a module into inferior memory and relocates
it itself, without a linker. For R_PPC64_REL24 it patches the branch
to point directly at the target. On ELFv2 that is not a valid call to
another module: the callee derives its TOC pointer from r12, which only
a PLT-style call stub sets up, and the caller's TOC pointer is never
restored because the nop following the bl is left alone.
Any call leaving a compiled module therefore runs with a wrong TOC
pointer. gdb.compile/compile-setjmp.exp is where this shows up: the
injected code calls setjmp, whose global entry point computes r2 from a
stale r12, and the first TOC-relative load segfaults.
Add two gdbarch hooks. compile_stub_area_size tells the loader how much
executable inferior memory to reserve for stubs; compile_fixup_section
lets the architecture rewrite the relocated section contents before they
are written to the inferior. Both are installed only for ELFv2.
ppc64_compile_fixup_section redirects each call to an external symbol
through a stub which saves r2, loads the target into r12 and branches to
it, and rewrites the following nop to ld r2,24(r1). Symbol values are
left untouched, so a function address used as data stays correct.
The symbol table is passed through the hook rather than re-read by the
architecture code, so the arch sees the same symbols compile_object_load
resolved.
Tested on powerpc64le-linux; the two compile-setjmp.exp failures now
pass with no regressions in gdb.compile.
---
This patch is reg tested.
gdb/compile/compile-object-load.c | 40 +++++++++-
gdb/gdbarch-gen.c | 54 +++++++++++++
gdb/gdbarch-gen.h | 18 +++++
gdb/gdbarch_components.py | 33 ++++++++
gdb/ppc-linux-tdep.c | 3 +
gdb/ppc64-tdep.c | 123 ++++++++++++++++++++++++++++++
gdb/ppc64-tdep.h | 13 ++++
7 files changed, 282 insertions(+), 2 deletions(-)
diff --git a/gdb/compile/compile-object-load.c b/gdb/compile/compile-object-load.c
index a70282c4e0c..4ed9ebdf25f 100644
--- a/gdb/compile/compile-object-load.c
+++ b/gdb/compile/compile-object-load.c
@@ -333,7 +333,9 @@ struct link_hash_table_cleanup_data
static void
copy_section (bfd *abfd, asection *sect,
- gdb::array_view<asymbol *> symbol_table)
+ gdb::array_view<asymbol *> symbol_table,
+ CORE_ADDR stub_area, CORE_ADDR stub_area_size,
+ CORE_ADDR *stub_next)
{
bfd_byte *sect_data_got;
struct bfd_link_info link_info;
@@ -382,6 +384,15 @@ copy_section (bfd *abfd, asection *sect,
bfd_errmsg (bfd_get_error ()));
gdb_assert (sect_data_got == sect_data.get ());
+ /* Some architectures cannot express a call to another module in the
+ relocated contents alone; give the architecture a chance to rewrite
+ the calls before the section reaches inferior memory. */
+ gdbarch *gdbarch = current_inferior ()->arch ();
+ if (gdbarch_compile_fixup_section_p (gdbarch))
+ gdbarch_compile_fixup_section (gdbarch, abfd, sect, sect_data.get (),
+ symbol_table.data (), stub_area,
+ stub_area_size, stub_next);
+
inferior_addr = bfd_section_vma (sect);
if (0 != target_write_memory (inferior_addr, sect_data.get (),
bfd_section_size (sect)))
@@ -635,6 +646,30 @@ compile_object_load (const compile_file_names &file_names,
setup_sections_data.setup_one_section (sect);
setup_sections_data.setup_one_section (nullptr);
+ /* Executable inferior memory the architecture may need for call stubs,
+ and the next unused address within it. */
+ CORE_ADDR stub_area = 0, stub_next = 0;
+ CORE_ADDR stub_area_size = 0;
+
+ if (gdbarch_compile_fixup_section_p (current_inferior ()->arch ()))
+ stub_area_size
+ = gdbarch_compile_stub_area_size (current_inferior ()->arch ());
+
+ if (stub_area_size != 0)
+ {
+ stub_area = gdbarch_infcall_mmap (current_inferior ()->arch (),
+ stub_area_size,
+ GDB_MMAP_PROT_READ | GDB_MMAP_PROT_EXEC);
+ setup_sections_data.munmap_list.add (stub_area, stub_area_size);
+ stub_next = stub_area;
+
+ if (compile_debug)
+ gdb_printf (gdb_stdlog,
+ "allocated %s bytes at %s for call stubs\n",
+ paddress (current_inferior ()->arch (), stub_area_size),
+ paddress (current_inferior ()->arch (), stub_area));
+ }
+
/* SYMFILE_VERBOSE is not passed even if FROM_TTY, user is not interested in
"Reading symbols from ..." message for automatically generated file. */
scoped_objfile_unlinker objfile_holder (symbol_file_add_from_bfd
@@ -789,7 +824,8 @@ compile_object_load (const compile_file_names &file_names,
error (_("%ld symbols were missing, cannot continue."), missing_symbols);
for (asection *sect : gdb_bfd_sections (abfd.get ()))
- copy_section (abfd.get (), sect, symbol_table);
+ copy_section (abfd.get (), sect, symbol_table, stub_area,
+ stub_area_size, &stub_next);
regs_type = get_regs_type (func_sym, objfile);
if (regs_type == NULL)
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 6008003466c..042c4f308c6 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -241,6 +241,8 @@ struct gdbarch
gdbarch_infcall_mmap_ftype *infcall_mmap = default_infcall_mmap;
gdbarch_infcall_munmap_ftype *infcall_munmap = default_infcall_munmap;
gdbarch_gcc_target_options_ftype *gcc_target_options = default_gcc_target_options;
+ ULONGEST compile_stub_area_size = 0;
+ gdbarch_compile_fixup_section_ftype *compile_fixup_section = nullptr;
gdbarch_gnu_triplet_regexp_ftype *gnu_triplet_regexp = default_gnu_triplet_regexp;
gdbarch_addressable_memory_unit_size_ftype *addressable_memory_unit_size = default_addressable_memory_unit_size;
const char *disassembler_options_implicit = nullptr;
@@ -501,6 +503,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* Skip verify of infcall_mmap, invalid_p == 0. */
/* Skip verify of infcall_munmap, invalid_p == 0. */
/* Skip verify of gcc_target_options, invalid_p == 0. */
+ /* Skip verify of compile_stub_area_size, invalid_p == 0. */
+ /* Skip verify of compile_fixup_section, has predicate. */
/* Skip verify of gnu_triplet_regexp, invalid_p == 0. */
/* Skip verify of addressable_memory_unit_size, invalid_p == 0. */
/* Skip verify of disassembler_options_implicit, invalid_p == 0. */
@@ -1300,6 +1304,15 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
gdb_printf (file,
"gdbarch_dump: gcc_target_options = <%s>\n",
host_address_to_string (gdbarch->gcc_target_options));
+ gdb_printf (file,
+ "gdbarch_dump: compile_stub_area_size = %s\n",
+ plongest (gdbarch->compile_stub_area_size));
+ gdb_printf (file,
+ "gdbarch_dump: gdbarch_compile_fixup_section_p() = %d\n",
+ gdbarch_compile_fixup_section_p (gdbarch));
+ gdb_printf (file,
+ "gdbarch_dump: compile_fixup_section = <%s>\n",
+ host_address_to_string (gdbarch->compile_fixup_section));
gdb_printf (file,
"gdbarch_dump: gnu_triplet_regexp = <%s>\n",
host_address_to_string (gdbarch->gnu_triplet_regexp));
@@ -5076,6 +5089,47 @@ set_gdbarch_gcc_target_options (struct gdbarch *gdbarch,
gdbarch->gcc_target_options = gcc_target_options;
}
+ULONGEST
+gdbarch_compile_stub_area_size (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != nullptr);
+ /* Skip verify of compile_stub_area_size, invalid_p == 0. */
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_compile_stub_area_size called\n");
+ return gdbarch->compile_stub_area_size;
+}
+
+void
+set_gdbarch_compile_stub_area_size (struct gdbarch *gdbarch,
+ ULONGEST compile_stub_area_size)
+{
+ gdbarch->compile_stub_area_size = compile_stub_area_size;
+}
+
+bool
+gdbarch_compile_fixup_section_p (struct gdbarch *gdbarch)
+{
+ gdb_assert (gdbarch != nullptr);
+ return gdbarch->compile_fixup_section != nullptr;
+}
+
+void
+gdbarch_compile_fixup_section (struct gdbarch *gdbarch, bfd *abfd, asection *sect, gdb_byte *sect_data, asymbol **symbol_table, CORE_ADDR stub_area, CORE_ADDR stub_area_size, CORE_ADDR *stub_next)
+{
+ gdb_assert (gdbarch != nullptr);
+ gdb_assert (gdbarch->compile_fixup_section != nullptr);
+ if (gdbarch_debug >= 2)
+ gdb_printf (gdb_stdlog, "gdbarch_compile_fixup_section called\n");
+ gdbarch->compile_fixup_section (gdbarch, abfd, sect, sect_data, symbol_table, stub_area, stub_area_size, stub_next);
+}
+
+void
+set_gdbarch_compile_fixup_section (struct gdbarch *gdbarch,
+ gdbarch_compile_fixup_section_ftype compile_fixup_section)
+{
+ gdbarch->compile_fixup_section = compile_fixup_section;
+}
+
const char *
gdbarch_gnu_triplet_regexp (struct gdbarch *gdbarch)
{
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 6eda8693d58..d88b167a489 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1656,6 +1656,24 @@ using gdbarch_gcc_target_options_ftype = std::string (struct gdbarch *gdbarch);
std::string gdbarch_gcc_target_options (struct gdbarch *gdbarch);
void set_gdbarch_gcc_target_options (struct gdbarch *gdbarch, gdbarch_gcc_target_options_ftype *gcc_target_options);
+/* Size in bytes of executable inferior memory to reserve for call stubs
+ when loading a module for the 'compile' command, or 0 if none is needed. */
+
+ULONGEST gdbarch_compile_stub_area_size (struct gdbarch *gdbarch);
+void set_gdbarch_compile_stub_area_size (struct gdbarch *gdbarch, ULONGEST compile_stub_area_size);
+
+/* Rewrite the relocated contents SECT_DATA of section SECT of the compiled
+ module ABFD before it is written to inferior memory. Used on targets
+ where a call to another module needs a linkage stub. STUB_AREA and
+ STUB_AREA_SIZE describe the reserved stub memory; *STUB_NEXT is the next
+ free address within it and is updated as stubs are written. */
+
+bool gdbarch_compile_fixup_section_p (struct gdbarch *gdbarch);
+
+using gdbarch_compile_fixup_section_ftype = void (struct gdbarch *gdbarch, bfd *abfd, asection *sect, gdb_byte *sect_data, asymbol **symbol_table, CORE_ADDR stub_area, CORE_ADDR stub_area_size, CORE_ADDR *stub_next);
+void gdbarch_compile_fixup_section (struct gdbarch *gdbarch, bfd *abfd, asection *sect, gdb_byte *sect_data, asymbol **symbol_table, CORE_ADDR stub_area, CORE_ADDR stub_area_size, CORE_ADDR *stub_next);
+void set_gdbarch_compile_fixup_section (struct gdbarch *gdbarch, gdbarch_compile_fixup_section_ftype *compile_fixup_section);
+
/* Return a regular expression that matches names used by this
architecture in GNU configury triplets. The result is statically
allocated and must not be freed. The default implementation simply
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index d8b2d114909..b2a717aef0d 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2627,6 +2627,39 @@ they can override it.
invalid=False,
)
+Value(
+ comment="""
+Size in bytes of executable inferior memory to reserve for call stubs
+when loading a module for the 'compile' command, or 0 if none is needed.
+""",
+ type="ULONGEST",
+ name="compile_stub_area_size",
+ predefault="0",
+ invalid=False,
+)
+
+Method(
+ comment="""
+Rewrite the relocated contents SECT_DATA of section SECT of the compiled
+module ABFD before it is written to inferior memory. Used on targets
+where a call to another module needs a linkage stub. STUB_AREA and
+STUB_AREA_SIZE describe the reserved stub memory; *STUB_NEXT is the next
+free address within it and is updated as stubs are written.
+""",
+ type="void",
+ name="compile_fixup_section",
+ params=[
+ ("bfd *", "abfd"),
+ ("asection *", "sect"),
+ ("gdb_byte *", "sect_data"),
+ ("asymbol **", "symbol_table"),
+ ("CORE_ADDR", "stub_area"),
+ ("CORE_ADDR", "stub_area_size"),
+ ("CORE_ADDR *", "stub_next"),
+ ],
+ predicate=True,
+)
+
Method(
comment="""
Return a regular expression that matches names used by this
diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
index 8a55ff5d4af..de0e1d79b2b 100644
--- a/gdb/ppc-linux-tdep.c
+++ b/gdb/ppc-linux-tdep.c
@@ -2304,6 +2304,9 @@ ppc_linux_init_abi (struct gdbarch_info info,
(gdbarch, ppc_elfv2_elf_make_msymbol_special);
set_gdbarch_skip_entrypoint (gdbarch, ppc_elfv2_skip_entrypoint);
+ set_gdbarch_compile_stub_area_size (gdbarch, PPC64_STUB_AREA_SIZE);
+ set_gdbarch_compile_fixup_section (gdbarch,
+ ppc64_compile_fixup_section);
}
/* Shared library handling. */
diff --git a/gdb/ppc64-tdep.c b/gdb/ppc64-tdep.c
index 9011a354612..5f5e48db641 100644
--- a/gdb/ppc64-tdep.c
+++ b/gdb/ppc64-tdep.c
@@ -24,6 +24,8 @@
#include "ppc-tdep.h"
#include "ppc64-tdep.h"
#include "elf-bfd.h"
+#include "elf/ppc64.h"
+#include <unordered_map>
/* Macros for matching instructions. Note that, since all the
operands are masked off before they're or-ed into the instruction,
@@ -788,6 +790,127 @@ ppc64_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
return addr;
}
+/* Size of one call stub written by ppc64_write_call_stub. */
+
+#define PPC64_STUB_SIZE (8 * 4)
+
+/* Write a PLT-style call stub at STUB_ADDR in inferior memory which
+ transfers control to TARGET. ELFv2 requires the caller to save its
+ own TOC pointer in the ABI-reserved stack slot, and to pass TARGET in
+ r12 so that TARGET's global entry point sequence can derive its own
+ TOC pointer from it. A direct branch can do neither, hence this stub.
+ Returns STUB_ADDR. */
+
+static CORE_ADDR
+ppc64_write_call_stub (struct gdbarch *gdbarch, CORE_ADDR stub_addr,
+ CORE_ADDR target)
+{
+ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+ gdb_byte buf[PPC64_STUB_SIZE];
+ uint32_t insns[8] = {
+ /* std r2,24(r1) */
+ 0xf8410018,
+ /* lis r12,target@highest */
+ (uint32_t) (0x3d800000 | ((target >> 48) & 0xffff)),
+ /* ori r12,r12,target@higher */
+ (uint32_t) (0x618c0000 | ((target >> 32) & 0xffff)),
+ /* rldicr r12,r12,32,31 */
+ 0x798c07c6,
+ /* oris r12,r12,target@h */
+ (uint32_t) (0x658c0000 | ((target >> 16) & 0xffff)),
+ /* ori r12,r12,target@l */
+ (uint32_t) (0x618c0000 | (target & 0xffff)),
+ /* mtctr r12 */
+ 0x7d8903a6,
+ /* bctr */
+ 0x4e800420
+ };
+
+ for (int i = 0; i < 8; i++)
+ store_unsigned_integer (buf + i * 4, 4, byte_order, insns[i]);
+
+ write_memory (stub_addr, buf, sizeof (buf));
+ return stub_addr;
+}
+
+/* See ppc64-tdep.h. */
+
+void
+ppc64_compile_fixup_section (struct gdbarch *gdbarch, bfd *abfd,
+ asection *sect, gdb_byte *sect_data,
+ asymbol **symbol_table, CORE_ADDR stub_area,
+ CORE_ADDR stub_area_size, CORE_ADDR *stub_next)
+{
+ long relsize = bfd_get_reloc_upper_bound (abfd, sect);
+
+ if (relsize <= 0)
+ return;
+
+ gdb::unique_xmalloc_ptr<arelent *> relocs ((arelent **) xmalloc (relsize));
+ long relcount = bfd_canonicalize_reloc (abfd, sect, relocs.get (),
+ symbol_table);
+
+ /* Stubs already written for this section, keyed by target address, so
+ that repeated calls to one function share a single stub. */
+ std::unordered_map<CORE_ADDR, CORE_ADDR> stubs;
+
+ for (long i = 0; i < relcount; i++)
+ {
+ arelent *rel = relocs.get ()[i];
+
+ if (rel->howto == NULL || rel->howto->type != R_PPC64_REL24)
+ continue;
+
+ /* Symbols resolved outside this module were parked in the absolute
+ section by compile_object_load; those are the calls needing a
+ stub. Calls staying within the module are left alone. They are
+ relocated to the global entry point, so the callee recomputes r2
+ from r12, which still holds the address of the function GDB
+ called; resolving them to the local entry point instead would be
+ more correct. A compiled module reaches its data through
+ absolute addresses rather than its TOC, so this has no effect in
+ practice. */
+ asymbol *sym = *rel->sym_ptr_ptr;
+ if (!bfd_is_abs_section (sym->section))
+ continue;
+
+ CORE_ADDR target = sym->value + rel->addend;
+ CORE_ADDR stub;
+
+ auto iter = stubs.find (target);
+ if (iter != stubs.end ())
+ stub = iter->second;
+ else
+ {
+ if (*stub_next + PPC64_STUB_SIZE > stub_area + stub_area_size)
+ error (_("No room left for compiled module call stubs."));
+
+ stub = ppc64_write_call_stub (gdbarch, *stub_next, target);
+ *stub_next += PPC64_STUB_SIZE;
+ stubs[target] = stub;
+ }
+
+ CORE_ADDR at = bfd_section_vma (sect) + rel->address;
+ LONGEST disp = (LONGEST) stub - (LONGEST) at;
+
+ if (disp < -(1 << 25) || disp >= (1 << 25) || (disp & 3) != 0)
+ error (_("Compiled module call stub at %s is out of reach of the "
+ "call at %s."),
+ paddress (gdbarch, stub), paddress (gdbarch, at));
+
+ /* Branch to the stub rather than to the target. */
+ uint32_t insn = bfd_get_32 (abfd, sect_data + rel->address);
+ insn = (insn & ~0x03fffffc) | (((uint32_t) disp) & 0x03fffffc);
+ bfd_put_32 (abfd, insn, sect_data + rel->address);
+
+ /* The compiler left a nop after the call for the TOC pointer
+ reload; fill it in, as a linker would. */
+ if (rel->address + 8 <= bfd_section_size (sect)
+ && bfd_get_32 (abfd, sect_data + rel->address + 4) == 0x60000000)
+ bfd_put_32 (abfd, 0xe8410018, sect_data + rel->address + 4);
+ }
+}
+
/* A synthetic 'dot' symbols on ppc64 has the udata.p entry pointing
back to the original ELF symbol it was derived from. Get the size
from that symbol. */
diff --git a/gdb/ppc64-tdep.h b/gdb/ppc64-tdep.h
index ae12a70d053..2b8498a4989 100644
--- a/gdb/ppc64-tdep.h
+++ b/gdb/ppc64-tdep.h
@@ -24,6 +24,11 @@ struct gdbarch;
class frame_info_ptr;
struct target_ops;
+/* Inferior memory reserved for the call stubs of one module loaded by
+ the "compile" command; enough for 128 stubs. */
+
+#define PPC64_STUB_AREA_SIZE 4096
+
extern CORE_ADDR ppc64_skip_trampoline_code (const frame_info_ptr &frame,
CORE_ADDR pc);
@@ -33,4 +38,12 @@ extern CORE_ADDR ppc64_convert_from_func_ptr_addr (struct gdbarch *gdbarch,
extern void ppc64_elf_make_msymbol_special (const asymbol *,
struct minimal_symbol *);
+
+extern void ppc64_compile_fixup_section (struct gdbarch *gdbarch, bfd *abfd,
+ asection *sect, gdb_byte *sect_data,
+ asymbol **symbol_table,
+ CORE_ADDR stub_area,
+ CORE_ADDR stub_area_size,
+ CORE_ADDR *stub_next);
+
#endif /* GDB_PPC64_TDEP_H */
--
2.52.0