Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCHi v1] PowerPC: Create call stubs for compiled modules
@ 2026-08-13  9:28 Abhay Kandpal
  2026-08-18 12:19 ` Ulrich Weigand
  0 siblings, 1 reply; 5+ messages in thread
From: Abhay Kandpal @ 2026-08-13  9:28 UTC (permalink / raw)
  To: gdb-patches; +Cc: Ulrich.Weigand, cel, abhay.k, Abhay Kandpal

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


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCHi v1] PowerPC: Create call stubs for compiled modules
  2026-08-13  9:28 [PATCHi v1] PowerPC: Create call stubs for compiled modules Abhay Kandpal
@ 2026-08-18 12:19 ` Ulrich Weigand
  2026-08-18 17:47   ` Abhay Kandpal
  0 siblings, 1 reply; 5+ messages in thread
From: Ulrich Weigand @ 2026-08-18 12:19 UTC (permalink / raw)
  To: gdb-patches, abhay; +Cc: Abhay Kandpal, cel

Abhay Kandpal <abhay@linux.ibm.com> wrote:

>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.

On other platforms, the way this is supposed to work is to use a
set of compiler command-line options that result in code that does
not require PLTs for external calls.  Typically, this means to use
-mcmodel=large.

However, it seems that on PowerPC, while that option exists, it
generates code that still needs PLTs.  There is another option
-mlongcall that should avoid this, however.

I'm wondering if we were to just add -mlongcall to the platform-
specific compiler options for PowerPC, we could fix this issue
without having to reimplement a full PLT solution in GDB ...

Bye,
Ulrich

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCHi v1] PowerPC: Create call stubs for compiled modules
  2026-08-18 12:19 ` Ulrich Weigand
@ 2026-08-18 17:47   ` Abhay Kandpal
  2026-08-19 12:39     ` Ulrich Weigand
  0 siblings, 1 reply; 5+ messages in thread
From: Abhay Kandpal @ 2026-08-18 17:47 UTC (permalink / raw)
  To: Ulrich Weigand, gdb-patches; +Cc: Abhay Kandpal, cel

[-- Attachment #1: Type: text/plain, Size: 3422 bytes --]

Hi Ulrich,

Thanks for the suggestion. I tried|-mlongcall| and it doesn't work on PowerPC,
though not for the reason I expected.

gcc does generate the right calling sequence with it - saves r2,
loads the target into r12, uses mtctr/bctrl, restores r2:

   24:    std     r2,24(r1)
   30:    addis   r12,r2,0
             30: R_PPC64_PLT16_HA    _setjmp
   34:    ld      r12,0(r12)
             34: R_PPC64_PLT16_LO_DS    _setjmp
   80:    mtctr   r12
             80: R_PPC64_PLTSEQ    longjmp
   84:    bctrl
             84: R_PPC64_PLTCALL    longjmp
   88:    ld      r2,24(r1)

But it obtains the target address from a PLT slot addressed off r2,
so the calls need R_PPC64_PLT16_HA / R_PPC64_PLT16_LO_DS, which BFD's generic linker rejects:

warning: Compiled module "/tmp/gdbobj-6tZVEd/out1.o" section ".text": dangerous relocation: generic linker can't handle R_PPC64_PLT16_HA
warning: Compiled module "/tmp/gdbobj-6tZVEd/out1.o" section ".text": dangerous relocation: generic linker can't handle R_PPC64_PLT16_LO_DS

|-mlongcall| also converts the intra-module call to a PLT call,
so it fails earlier than before - in|_gdb_expr| rather than in the callee.
Same result with|-mcmodel=large -mlongcall| and with|-fno-plt -mlongcall| (|-mno-plt| is not recognised on PowerPC).

So on PowerPC|-mlongcall| gives the correct convention but still requires a PLT,
which is the one thing GDB can't supply. Resolving PLT16 would mean building a table
within +-32KB of the module's TOC and computing slot offsets - more machinery than the stub, not less.

With the patch, GDB builds the target address as immediates instead, needing no table:

call site:
   bl      <stub>
   ld      r2,24(r1)          ; the nop, rewritten
stub:
   std     r2,24(r1)
   lis     r12,target@highest
   ori     r12,r12,target@higher
   rldicr  r12,r12,32,31
   oris    r12,r12,target@h
   ori     r12,r12,target@l
   mtctr   r12
   bctr

At entry to|_setjmp|, r12 holds the callee's entry address and r2 the correct TOC;
before the patch r2 pointed past the end of libc, which is the SIGSEGV.

The patch applies cleanly to master and gives 526 passes, 0 failures in gdb.compile on powerpc64le.

Thanks
Abhay


On 18/08/26 17:49, Ulrich Weigand wrote:
> Abhay Kandpal<abhay@linux.ibm.com> wrote:
>
>> 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.
> On other platforms, the way this is supposed to work is to use a
> set of compiler command-line options that result in code that does
> not require PLTs for external calls.  Typically, this means to use
> -mcmodel=large.
>
> However, it seems that on PowerPC, while that option exists, it
> generates code that still needs PLTs.  There is another option
> -mlongcall that should avoid this, however.
>
> I'm wondering if we were to just add -mlongcall to the platform-
> specific compiler options for PowerPC, we could fix this issue
> without having to reimplement a full PLT solution in GDB ...
>
> Bye,
> Ulrich

[-- Attachment #2: Type: text/html, Size: 6790 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCHi v1] PowerPC: Create call stubs for compiled modules
  2026-08-18 17:47   ` Abhay Kandpal
@ 2026-08-19 12:39     ` Ulrich Weigand
  2026-08-19 18:39       ` Abhay Kandpal
  0 siblings, 1 reply; 5+ messages in thread
From: Ulrich Weigand @ 2026-08-19 12:39 UTC (permalink / raw)
  To: gdb-patches, abhay; +Cc: Abhay Kandpal, cel

Abhay Kandpal <abhay@linux.ibm.com> wrote:

>   24:    std     r2,24(r1)
>  30:    addis   r12,r2,0
>            30: R_PPC64_PLT16_HA    _setjmp
>  34:    ld      r12,0(r12)
>            34: R_PPC64_PLT16_LO_DS    _setjmp
> 80:    mtctr   r12
>            80: R_PPC64_PLTSEQ    longjmp
> 84:    bctrl
>            84: R_PPC64_PLTCALL    longjmp
>  88:    ld      r2,24(r1)

I think you also need the -mno-pltseq option to get rid of
this inlined PLT sequence.
> 
> 

Bye,
Ulrich

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCHi v1] PowerPC: Create call stubs for compiled modules
  2026-08-19 12:39     ` Ulrich Weigand
@ 2026-08-19 18:39       ` Abhay Kandpal
  0 siblings, 0 replies; 5+ messages in thread
From: Abhay Kandpal @ 2026-08-19 18:39 UTC (permalink / raw)
  To: Ulrich Weigand, gdb-patches; +Cc: Abhay Kandpal, cel

[-- Attachment #1: Type: text/plain, Size: 1053 bytes --]

Hi Ulrich,

That was it, thanks.|-mlongcall -mno-pltseq| fixes the problem.
GCC now loads the target from the module's own .toc via TOC16_HA/TOC16_LO_DS,
which BFD resolves normally, and emits the r2 save and restore around the call itself.

I have created a new patch which adds these to the PowerPC compile options;
I've dropped the call stub patch. Tested on powerpc64le-linux,
no unexpected failures in the full testsuite.

Thanks,
Abhay


On 19/08/26 18:09, Ulrich Weigand wrote:
> Abhay Kandpal<abhay@linux.ibm.com> wrote:
>
>>    24:    std     r2,24(r1)
>>    30:    addis   r12,r2,0
>>              30: R_PPC64_PLT16_HA    _setjmp
>>    34:    ld      r12,0(r12)
>>              34: R_PPC64_PLT16_LO_DS    _setjmp
>> 80:    mtctr   r12
>>              80: R_PPC64_PLTSEQ    longjmp
>> 84:    bctrl
>>              84: R_PPC64_PLTCALL    longjmp
>>    88:    ld      r2,24(r1)
> I think you also need the -mno-pltseq option to get rid of
> this inlined PLT sequence.
>>
> Bye,
> Ulrich

[-- Attachment #2: Type: text/html, Size: 2280 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-19 18:40 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-13  9:28 [PATCHi v1] PowerPC: Create call stubs for compiled modules Abhay Kandpal
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox