summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshit Yadav <valium7171@gmail.com>2026-06-13 20:51:56 +0530
committerMichael Tokarev <mjt@tls.msk.ru>2026-06-24 16:36:05 +0300
commita34cc9750b4812d877267f73cc636f6d09486273 (patch)
tree5e7ceea59cb2a6876e494267e8c7b29a7ae8de71
parentd55463da56708db05aee2793d71ea128a20e4a09 (diff)
downloadqemu-a34cc9750b4812d877267f73cc636f6d09486273.tar.gz
qemu-a34cc9750b4812d877267f73cc636f6d09486273.zip
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> (cherry picked from commit 156e536a7b9700018aaa2437072c14e3376340a7) Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
-rw-r--r--linux-user/elfload.c21
-rw-r--r--linux-user/qemu.h1
2 files changed, 21 insertions, 1 deletions
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index a791396ead..fdc84de4ba 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -2627,7 +2627,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));
@@ -3398,6 +3398,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;
@@ -3453,6 +3459,19 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
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 f2b16ef54b..a8ead75074 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -25,6 +25,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;