[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.