[libc] Enable ifunc support in static startup (#182841)
Resolves ifunc targets before `main()` runs in static libc This enables static binaries to use ifunc-based dispatch during early process startup, so optimized implementations can be selected based on CPU features. Without this relocation step in startup, those targets are not ready when program code begins executing. This change: - adds IRELATIVE relocation handling for x86_64, AArch64, ARMv7 and RISC-V, - reads `AT_HWCAP` / `AT_HWCAP2` from auxv and passes them to resolvers where required (notably AArch64), - runs IRELATIVE processing after base-address discovery and before TLS setup, - adds integration tests for both the ifunc path and the no-ifunc path, - Changed the load bias type for ptrdiff_t to intptr_t to align with IRELATIVE handling, which uses intptr_t for load bias calculations.
This commit is contained in:
@@ -266,6 +266,15 @@ add_proxy_header_library(
|
||||
libc.include.stdint
|
||||
)
|
||||
|
||||
add_proxy_header_library(
|
||||
elf_macros
|
||||
HDRS
|
||||
elf_macros.h
|
||||
FULL_BUILD_DEPENDS
|
||||
libc.include.llvm-libc-macros.elf_macros
|
||||
libc.include.elf
|
||||
)
|
||||
|
||||
add_gen_header(
|
||||
elf_proxy
|
||||
YAML_FILE
|
||||
|
||||
@@ -440,6 +440,15 @@ macros:
|
||||
macro_value: '0xc0000002'
|
||||
- macro_name: GNU_PROPERTY_X86_FEATURE_1_SHSTK
|
||||
macro_value: '0x00000002'
|
||||
# Architecture-specific IRELATIVE relocation types
|
||||
- macro_name: R_AARCH64_IRELATIVE
|
||||
macro_value: 1032
|
||||
- macro_name: R_ARM_IRELATIVE
|
||||
macro_value: 160
|
||||
- macro_name: R_RISCV_IRELATIVE
|
||||
macro_value: 58
|
||||
- macro_name: R_X86_64_IRELATIVE
|
||||
macro_value: 37
|
||||
types:
|
||||
- type_name: Elf32_Addr
|
||||
- type_name: Elf32_Chdr
|
||||
|
||||
@@ -107,6 +107,7 @@ add_object_library(
|
||||
do_start.cpp
|
||||
HDRS
|
||||
do_start.h
|
||||
irelative.h
|
||||
DEPENDS
|
||||
libc.config.app_h
|
||||
libc.hdr.elf_proxy
|
||||
@@ -117,6 +118,8 @@ add_object_library(
|
||||
libc.src.__support.OSUtil.linux.auxv
|
||||
libc.src.__support.OSUtil.osutil
|
||||
libc.src.__support.threads.thread
|
||||
libc.src.__support.macros.config
|
||||
libc.startup.linux.${LIBC_TARGET_ARCHITECTURE}.irelative
|
||||
libc.src.stdlib.exit
|
||||
libc.src.stdlib.atexit
|
||||
libc.src.unistd.environ
|
||||
@@ -131,6 +134,7 @@ merge_relocatable_object(
|
||||
crt1
|
||||
.${LIBC_TARGET_ARCHITECTURE}.start
|
||||
.${LIBC_TARGET_ARCHITECTURE}.tls
|
||||
.${LIBC_TARGET_ARCHITECTURE}.irelative
|
||||
.do_start
|
||||
.gnu_property_section
|
||||
)
|
||||
|
||||
@@ -23,3 +23,20 @@ add_startup_object(
|
||||
-fno-omit-frame-pointer
|
||||
-ffreestanding # To avoid compiler warnings about calling the main function.
|
||||
)
|
||||
|
||||
add_startup_object(
|
||||
irelative
|
||||
SRC
|
||||
irelative.cpp
|
||||
DEPENDS
|
||||
libc.hdr.link_macros
|
||||
libc.hdr.elf_macros
|
||||
libc.hdr.elf_proxy
|
||||
libc.hdr.stdint_proxy
|
||||
libc.src.__support.macros.config
|
||||
COMPILE_OPTIONS
|
||||
-fno-stack-protector
|
||||
-fno-omit-frame-pointer
|
||||
-ffreestanding
|
||||
-fno-builtin
|
||||
)
|
||||
|
||||
40
libc/startup/linux/aarch64/irelative.cpp
Normal file
40
libc/startup/linux/aarch64/irelative.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//===-- Implementation of apply_irelative_relocs (AArch64) ----------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "startup/linux/irelative.h"
|
||||
#include "hdr/elf_macros.h"
|
||||
#include "hdr/elf_proxy.h"
|
||||
#include "hdr/link_macros.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
void apply_irelative_relocs(intptr_t base, unsigned long hwcap,
|
||||
unsigned long hwcap2) {
|
||||
for (const ElfW(Rela) *rela = __rela_iplt_start; rela != __rela_iplt_end;
|
||||
++rela) {
|
||||
if (ELF64_R_TYPE(rela->r_info) != R_AARCH64_IRELATIVE)
|
||||
continue;
|
||||
|
||||
// AArch64 resolvers receive hwcap and hwcap2.
|
||||
// Use unsigned arithmetic to avoid undefined behavior on signed overflow,
|
||||
// which can occur with very large binaries or high load addresses.
|
||||
uintptr_t resolver_addr =
|
||||
static_cast<uintptr_t>(base) + static_cast<uintptr_t>(rela->r_addend);
|
||||
auto resolver =
|
||||
reinterpret_cast<uintptr_t (*)(unsigned long, unsigned long)>(
|
||||
resolver_addr);
|
||||
uintptr_t result = resolver(hwcap, hwcap2);
|
||||
|
||||
// Write the resolved function pointer to the target location.
|
||||
uintptr_t target_addr = static_cast<uintptr_t>(base) + rela->r_offset;
|
||||
*reinterpret_cast<uintptr_t *>(target_addr) = result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "src/stdlib/exit.h"
|
||||
#include "src/unistd/environ.h"
|
||||
#include "startup/linux/gnu_property_section.h"
|
||||
#include "startup/linux/irelative.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
@@ -86,6 +87,8 @@ static TLSDescriptor tls;
|
||||
// denoted by an AT_NULL entry.
|
||||
ElfW(Phdr) *program_hdr_table = nullptr;
|
||||
uintptr_t program_hdr_count = 0;
|
||||
unsigned long hwcap = 0;
|
||||
unsigned long hwcap2 = 0;
|
||||
auxv::Vector::initialize_unsafe(
|
||||
reinterpret_cast<const auxv::Entry *>(env_end_marker + 1));
|
||||
auxv::Vector auxvec;
|
||||
@@ -100,12 +103,18 @@ static TLSDescriptor tls;
|
||||
case AT_PAGESZ:
|
||||
app.page_size = aux_entry.val;
|
||||
break;
|
||||
case AT_HWCAP:
|
||||
hwcap = aux_entry.val;
|
||||
break;
|
||||
case AT_HWCAP2:
|
||||
hwcap2 = aux_entry.val;
|
||||
break;
|
||||
default:
|
||||
break; // TODO: Read other useful entries from the aux vector.
|
||||
}
|
||||
}
|
||||
|
||||
ptrdiff_t base = 0;
|
||||
intptr_t base = 0;
|
||||
app.tls.size = 0;
|
||||
ElfW(Phdr) *tls_phdr = nullptr;
|
||||
[[maybe_unused]] ElfW(Phdr) *gnu_property_phdr = nullptr;
|
||||
@@ -113,9 +122,9 @@ static TLSDescriptor tls;
|
||||
for (uintptr_t i = 0; i < program_hdr_count; ++i) {
|
||||
ElfW(Phdr) &phdr = program_hdr_table[i];
|
||||
if (phdr.p_type == PT_PHDR)
|
||||
base = reinterpret_cast<ptrdiff_t>(program_hdr_table) - phdr.p_vaddr;
|
||||
base = reinterpret_cast<intptr_t>(program_hdr_table) - phdr.p_vaddr;
|
||||
if (phdr.p_type == PT_DYNAMIC && _DYNAMIC)
|
||||
base = reinterpret_cast<ptrdiff_t>(_DYNAMIC) - phdr.p_vaddr;
|
||||
base = reinterpret_cast<intptr_t>(_DYNAMIC) - phdr.p_vaddr;
|
||||
if (phdr.p_type == PT_TLS)
|
||||
tls_phdr = &phdr;
|
||||
if (phdr.p_type == PT_GNU_PROPERTY)
|
||||
@@ -123,6 +132,12 @@ static TLSDescriptor tls;
|
||||
// TODO: adjust PT_GNU_STACK
|
||||
}
|
||||
|
||||
// Process IRELATIVE relocations (ifunc resolvers).
|
||||
// Skips when no ifuncs are present in the binary.
|
||||
if (reinterpret_cast<uintptr_t>(__rela_iplt_start) !=
|
||||
reinterpret_cast<uintptr_t>(__rela_iplt_end))
|
||||
apply_irelative_relocs(base, hwcap, hwcap2);
|
||||
|
||||
app.tls.address = tls_phdr->p_vaddr + base;
|
||||
app.tls.size = tls_phdr->p_memsz;
|
||||
app.tls.init_size = tls_phdr->p_filesz;
|
||||
|
||||
34
libc/startup/linux/irelative.h
Normal file
34
libc/startup/linux/irelative.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//===-- Implementation header for IRELATIVE relocations -------- *- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIBC_STARTUP_LINUX_IRELATIVE_H
|
||||
#define LLVM_LIBC_STARTUP_LINUX_IRELATIVE_H
|
||||
|
||||
#include "hdr/link_macros.h"
|
||||
#include "hdr/stdint_proxy.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
extern "C" {
|
||||
[[gnu::weak, gnu::visibility("hidden")]] extern const ElfW(Rela)
|
||||
__rela_iplt_start[]; // NOLINT
|
||||
[[gnu::weak, gnu::visibility("hidden")]] extern const ElfW(Rela)
|
||||
__rela_iplt_end[]; // NOLINT
|
||||
}
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
// Process IRELATIVE relocations (ifunc resolvers).
|
||||
// base is the load bias (actual load address − link-time address). It is
|
||||
// intptr_t (signed) because it is a difference; it is negative if the binary
|
||||
// loaded below its link address. (unlikely but possible in principle)
|
||||
void apply_irelative_relocs(intptr_t base, unsigned long hwcap,
|
||||
unsigned long hwcap2);
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
|
||||
#endif // LLVM_LIBC_STARTUP_LINUX_IRELATIVE_H
|
||||
@@ -24,3 +24,20 @@ add_startup_object(
|
||||
-fno-omit-frame-pointer
|
||||
-ffreestanding # To avoid compiler warnings about calling the main function.
|
||||
)
|
||||
|
||||
add_startup_object(
|
||||
irelative
|
||||
SRC
|
||||
irelative.cpp
|
||||
DEPENDS
|
||||
libc.hdr.link_macros
|
||||
libc.hdr.elf_macros
|
||||
libc.hdr.elf_proxy
|
||||
libc.hdr.stdint_proxy
|
||||
libc.src.__support.macros.config
|
||||
COMPILE_OPTIONS
|
||||
-fno-stack-protector
|
||||
-fno-omit-frame-pointer
|
||||
-ffreestanding
|
||||
-fno-builtin
|
||||
)
|
||||
|
||||
47
libc/startup/linux/riscv/irelative.cpp
Normal file
47
libc/startup/linux/riscv/irelative.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
//===-- Implementation of apply_irelative_relocs (RISC-V) -----------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "startup/linux/irelative.h"
|
||||
#include "hdr/elf_macros.h"
|
||||
#include "hdr/elf_proxy.h"
|
||||
#include "hdr/link_macros.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
// RISC-V may be 32-bit or 64-bit. ElfW(Rela) handles the struct type,
|
||||
// but we need the correct R_TYPE extraction macro.
|
||||
static LIBC_INLINE constexpr unsigned get_r_type(uintptr_t info) {
|
||||
#ifdef __LP64__
|
||||
return ELF64_R_TYPE(info);
|
||||
#else
|
||||
return ELF32_R_TYPE(info);
|
||||
#endif
|
||||
}
|
||||
|
||||
void apply_irelative_relocs(intptr_t base, unsigned long /*hwcap*/,
|
||||
unsigned long /*hwcap2*/) {
|
||||
for (const ElfW(Rela) *rela = __rela_iplt_start; rela != __rela_iplt_end;
|
||||
++rela) {
|
||||
if (get_r_type(rela->r_info) != R_RISCV_IRELATIVE)
|
||||
continue;
|
||||
|
||||
// RISC-V resolvers take no arguments (same as x86_64).
|
||||
// Use unsigned arithmetic to avoid undefined behavior on signed overflow,
|
||||
// which can occur with very large binaries or high load addresses.
|
||||
uintptr_t resolver_addr =
|
||||
static_cast<uintptr_t>(base) + static_cast<uintptr_t>(rela->r_addend);
|
||||
auto resolver = reinterpret_cast<uintptr_t (*)(void)>(resolver_addr);
|
||||
uintptr_t result = resolver();
|
||||
|
||||
uintptr_t target_addr = static_cast<uintptr_t>(base) + rela->r_offset;
|
||||
*reinterpret_cast<uintptr_t *>(target_addr) = result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
@@ -28,3 +28,20 @@ add_startup_object(
|
||||
-ffreestanding
|
||||
-fno-builtin
|
||||
)
|
||||
|
||||
add_startup_object(
|
||||
irelative
|
||||
SRC
|
||||
irelative.cpp
|
||||
DEPENDS
|
||||
libc.hdr.link_macros
|
||||
libc.hdr.elf_macros
|
||||
libc.hdr.elf_proxy
|
||||
libc.hdr.stdint_proxy
|
||||
libc.src.__support.macros.config
|
||||
COMPILE_OPTIONS
|
||||
-fno-stack-protector
|
||||
-fno-omit-frame-pointer
|
||||
-ffreestanding
|
||||
-fno-builtin
|
||||
)
|
||||
|
||||
37
libc/startup/linux/x86_64/irelative.cpp
Normal file
37
libc/startup/linux/x86_64/irelative.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
//===-- Implementation of apply_irelative_relocs (x86_64) -----------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "startup/linux/irelative.h"
|
||||
#include "hdr/elf_macros.h"
|
||||
#include "hdr/elf_proxy.h"
|
||||
#include "hdr/link_macros.h"
|
||||
#include "src/__support/macros/config.h"
|
||||
|
||||
namespace LIBC_NAMESPACE_DECL {
|
||||
|
||||
void apply_irelative_relocs(intptr_t base, unsigned long /*hwcap*/,
|
||||
unsigned long /*hwcap2*/) {
|
||||
for (const ElfW(Rela) *rela = __rela_iplt_start; rela != __rela_iplt_end;
|
||||
++rela) {
|
||||
if (ELF64_R_TYPE(rela->r_info) != R_X86_64_IRELATIVE)
|
||||
continue;
|
||||
|
||||
// x86_64 resolvers take no arguments.
|
||||
// Use unsigned arithmetic to avoid undefined behavior on signed overflow,
|
||||
// which can occur with very large binaries or high load addresses.
|
||||
uintptr_t resolver_addr =
|
||||
static_cast<uintptr_t>(base) + static_cast<uintptr_t>(rela->r_addend);
|
||||
auto resolver = reinterpret_cast<uintptr_t (*)(void)>(resolver_addr);
|
||||
uintptr_t result = resolver();
|
||||
|
||||
uintptr_t target_addr = static_cast<uintptr_t>(base) + rela->r_offset;
|
||||
*reinterpret_cast<uintptr_t *>(target_addr) = result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LIBC_NAMESPACE_DECL
|
||||
@@ -49,3 +49,17 @@ add_integration_test(
|
||||
SRCS
|
||||
init_fini_array_test.cpp
|
||||
)
|
||||
|
||||
add_integration_test(
|
||||
startup_irelative_test
|
||||
SUITE libc-startup-tests
|
||||
SRCS
|
||||
irelative_test.cpp
|
||||
)
|
||||
|
||||
add_integration_test(
|
||||
startup_irelative_no_ifunc_test
|
||||
SUITE libc-startup-tests
|
||||
SRCS
|
||||
irelative_no_ifunc_test.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//===-- Implementation of apply_irelative_relocs (no ifunc) test ----------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "startup/linux/irelative.h"
|
||||
#include "test/IntegrationTest/test.h"
|
||||
|
||||
TEST_MAIN() {
|
||||
ASSERT_EQ(reinterpret_cast<uintptr_t>(__rela_iplt_start),
|
||||
reinterpret_cast<uintptr_t>(__rela_iplt_end));
|
||||
return 0;
|
||||
}
|
||||
48
libc/test/integration/startup/linux/irelative_test.cpp
Normal file
48
libc/test/integration/startup/linux/irelative_test.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//===-- Implementation of apply_irelative_relocs test ---------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "test/IntegrationTest/test.h"
|
||||
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_attribute(ifunc)
|
||||
|
||||
// Two trivial implementations that return different constants.
|
||||
static int impl_a() { return 42; }
|
||||
static int impl_b() { return 84; }
|
||||
|
||||
// The resolver function. On x86_64 this takes no arguments; on AArch64 it
|
||||
// receives (hwcap, hwcap2). We use extern "C" to ensure C linkage for the
|
||||
// ifunc attribute.
|
||||
extern "C" {
|
||||
// Declare the resolver to return a generic function pointer.
|
||||
// For testing, unconditionally select impl_a.
|
||||
void *my_ifunc_resolver() {
|
||||
(void)impl_b; // Suppress unused warning.
|
||||
return reinterpret_cast<void *>(impl_a);
|
||||
}
|
||||
}
|
||||
|
||||
// Declare the ifunc. The compiler and linker will generate an IRELATIVE
|
||||
// relocation that calls my_ifunc_resolver at startup.
|
||||
extern "C" int my_ifunc() __attribute__((ifunc("my_ifunc_resolver")));
|
||||
|
||||
TEST_MAIN() {
|
||||
// If IRELATIVE processing works correctly, my_ifunc() should call impl_a
|
||||
// and return 42.
|
||||
ASSERT_EQ(my_ifunc(), 42);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
TEST_MAIN() { return 0; }
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user