Commit 156e536a7b for qemu.org

commit 156e536a7b9700018aaa2437072c14e3376340a7
Author: Akshit Yadav <valium7171@gmail.com>
Date:   Sat Jun 13 20:51:56 2026 +0530

    linux-user: Fix AT_PHDR when program headers are relocated into their own segment

    When a binary is patched or relocated such that the program header table is
    moved into a separate PT_LOAD segment (rather than sitting at the start of the
    first loadable segment), QEMU's AT_PHDR auxv entry becomes incorrect. The
    loader was computing AT_PHDR as load_addr + e_phoff, which assumes the headers
    are mapped 1:1 from file offset 0. This breaks when the headers are elsewhere.

    The Linux kernel instead locates the PT_LOAD segment that contains e_phoff,
    then computes the in-memory address as p_vaddr + (e_phoff - p_offset). This
    correctly handles relocated headers.

    Fix by:
    1. Add phdr_addr field to image_info to cache the resolved address.
    2. Initialize to load_addr + e_phoff (fallback for headers outside any PT_LOAD).
    3. In the PT_LOAD mapping loop, detect if the segment contains e_phoff and
       override with the segment-relative address.
    4. Use info->phdr_addr for AT_PHDR instead of the incorrect formula.

    Signed-off-by: Akshit Yadav <valium7171@gmail.com>
    Reviewed-by: Helge Deller <deller@gmx.de>

diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index b05b8b0c6b..8049c8ae62 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -699,7 +699,7 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc,
     /* There must be exactly DLINFO_ITEMS entries here, or the assert
      * on info->auxv_len will trigger.
      */
-    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->load_addr + exec->e_phoff));
+    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->phdr_addr));
     NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr)));
     NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum));
     NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE));
@@ -1469,6 +1469,12 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
     info->data_offset = load_bias;
     info->load_addr = load_addr;
     info->entry = ehdr->e_entry + load_bias;
+    /*
+     * Fallback for AT_PHDR if the program headers do not fall within
+     * any PT_LOAD segment (see the loop below, which overrides this with
+     * the correct in-memory address when a containing segment is found).
+     */
+    info->phdr_addr = load_addr + ehdr->e_phoff;
     info->start_code = -1;
     info->end_code = 0;
     info->start_data = -1;
@@ -1523,6 +1529,19 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
             vaddr_ef = vaddr + eppnt->p_filesz;
             vaddr_em = vaddr + eppnt->p_memsz;

+            /*
+             * If this segment contains the program headers, record their
+             * in-memory address for AT_PHDR. This matches the kernel, which
+             * locates the headers via the containing PT_LOAD rather than
+             * assuming load_addr + e_phoff (false when the phdrs are not
+             * mapped 1:1 from file offset 0, e.g. relocated into their own
+             * segment by a binary patcher).
+             */
+            if (eppnt->p_offset <= ehdr->e_phoff &&
+                ehdr->e_phoff < eppnt->p_offset + eppnt->p_filesz) {
+                info->phdr_addr = vaddr + (ehdr->e_phoff - eppnt->p_offset);
+            }
+
             /*
              * Some segments may be completely empty, with a non-zero p_memsz
              * but no backing file segment.
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 07fe801628..2268493141 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -26,6 +26,7 @@
 struct image_info {
         abi_ulong       load_bias;
         abi_ulong       load_addr;
+        abi_ulong       phdr_addr;
         abi_ulong       start_code;
         abi_ulong       end_code;
         abi_ulong       start_data;