Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Abhay Kandpal <abhay@linux.ibm.com>
To: gdb-patches@sourceware.org
Cc: Ulrich.Weigand@de.ibm.com, cel@linux.ibm.com, abhay.k@ibm.com,
	Abhay Kandpal <abhay@linux.ibm.com>
Subject: [PATCHi v1] PowerPC: Create call stubs for compiled modules
Date: Thu, 13 Aug 2026 04:28:29 -0500	[thread overview]
Message-ID: <20260813092829.262470-1-abhay@linux.ibm.com> (raw)

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


             reply	other threads:[~2026-08-13  9:29 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-13  9:28 Abhay Kandpal [this message]
2026-08-18 12:19 ` Ulrich Weigand
2026-08-18 17:47   ` Abhay Kandpal
2026-08-19 12:39     ` Ulrich Weigand
2026-08-19 18:39       ` Abhay Kandpal

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260813092829.262470-1-abhay@linux.ibm.com \
    --to=abhay@linux.ibm.com \
    --cc=Ulrich.Weigand@de.ibm.com \
    --cc=abhay.k@ibm.com \
    --cc=cel@linux.ibm.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox