From nobody Mon Feb 9 13:02:00 2026 Received: from mail-pg1-f169.google.com (mail-pg1-f169.google.com [209.85.215.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 176A133BBC5 for ; Sat, 31 Jan 2026 07:44:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769845491; cv=none; b=VcLqWEPmxwnCnWc7FrEnPJlgPUOwyJxwgj3uhUWtP8sc1iaVukPCymYZReZLNaNcKUQh/67dmLdIWuH8rO7VnQB/E5eSIspakyRYCD+ZiPXhJAEvsFVJd13+eDoimuTwej1YZSPD7UGKkiugr2mSMhlUYHqzSSt46Ixk/xJFqlM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769845491; c=relaxed/simple; bh=mgKFzv8GyQWv+DM+glA4YkSuf52sB+vH3aeiG7Kk/pw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RB4mRCgees0W6V346TATegM2V6WthemiIYWjXRF4RswzSKOvvdKmVjZ3/egnwxuj8xU/SJa24ZzOT8CHPWXnO7oH0R6ZwDSVTWwudGwxRqkoHAM8X5jFE39QaRDpSg2wBJuel9uMO2LsfneX0+YdDHV4ZyvRE9NtzuXdM7f2LR4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=thingy.jp; spf=pass smtp.mailfrom=0x0f.com; dkim=pass (1024-bit key) header.d=thingy.jp header.i=@thingy.jp header.b=TDEagTz5; arc=none smtp.client-ip=209.85.215.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=thingy.jp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=0x0f.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=thingy.jp header.i=@thingy.jp header.b="TDEagTz5" Received: by mail-pg1-f169.google.com with SMTP id 41be03b00d2f7-c5e051a47ddso1622078a12.1 for ; Fri, 30 Jan 2026 23:44:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=thingy.jp; s=google; t=1769845489; x=1770450289; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=1edsEnhpG6Bk5mALRqZqGFyuqmJB3nWoL6pBChctvlI=; b=TDEagTz5/JNETm6HZ3TgmKGnKV8nqk/Lbh2T8YBQ8hWPgz/lLxbeCHSiw6pADCMP6U B4I9VN/MIOlNFfCz48xZiXrz6l01e96avxjR7sKj0J2zV84hyUhvLHBFdqj55oP+hH/w AJ2TJ852UP0/0kw+w268zAJuQCkD0kEmZppbY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769845489; x=1770450289; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=1edsEnhpG6Bk5mALRqZqGFyuqmJB3nWoL6pBChctvlI=; b=tiAOh9k+4SNrG8j8HTRdT9ZYuS3EviqmGYdjENP+7wtB1QbLBhMYUxQyajiyBtd17J 2gsZzlo47FY4P72w1QJEbpO4NABM/qYK0X/ou0u6Ucku2x3hfDkpcwAPaz3N0osCkK7j O2Ii6l4gI4SLh+GEJQrgNx09s9I8xvLRU97uxGBhxKpQfAbhr+zYZHhWk4Fv4MO1BaEe oCitIarbxOvCGBOgGSWhL8GSf3AAkx6s7PWfIKAiD+eOTHTrj8Pk9X6T4ym+l+eVZ93j Bjbi8L9lNdSjRHJY1oAx+mzQNlNMUYIPtxZlafmwiCfs22gvxxBPdYN1MyAzgs4ohauV S77g== X-Forwarded-Encrypted: i=1; AJvYcCUBtQzLbVc/qzoANx/29Z2FA5NpoieltE98huIlpDzId9I1z6/2KCCzo0AoE/H4fIDsQKEunBWutlGIMx8=@vger.kernel.org X-Gm-Message-State: AOJu0Yy7oQu2kgyFek4OuvYXNM+8YqFBKWXOLYJAP1Gk0jd5umGb6cfO 8m4UgxOMJBfRZHeBsvZ63zd+WQUsq3krTDZQfnC7pBXCvi7EEXJiuwVXd8tr76PNxpc= X-Gm-Gg: AZuq6aJlojFF5taugBN5lE3Hmxj9amS3At2Sl0VBQFrB0BvxVw0mKobh49yOqnvICAn fadmUOeXdFIm5+JrIQO5yMv964BO3EUBXaQIK0eLBITi+b8D628gCUVIAIjnKK3V69J4KcBhHu5 X1qqvB6caMR3ELQ7q/tU3sQCGaRMa7LSUfPgpMShdqbmjx6DgNb9L7FTxPz8LW5RTHklt5S81IG GJ/RAK55SO1Z2II8vItlBQgyfuSoNOHm57i5gLblnQEoHTS0DlIjU0S3A6W0asUR7yN7OPU5fwV tIuWYwWl4mdlwNXfusiZ6FZ53KTZotjQ3hMeu2cOZdF1WzssjT96a/KmNKTMSjoMouaC+EMz7HC X2KrOXD5bAQlRu30+k3/xklL1DPn/eAvK0XAFnhKqQgLF1s7e4eR3w2WrrXCiGFP4j/ealzTewu PpZ+BmygoaQC1izZyM3RFIugnj2IEydkIjzE0FCHq5gtogiEA39aRLWFl/1f/iRkwaAapyBKeAm YTQG/fCIIkqr9c= X-Received: by 2002:a05:6a20:568b:b0:38b:ebaa:c167 with SMTP id adf61e73a8af0-392e003bfebmr5713531637.20.1769845489298; Fri, 30 Jan 2026 23:44:49 -0800 (PST) Received: from kinako.work.home.arpa (p1490223-ipxg00b01sizuokaden.shizuoka.ocn.ne.jp. [153.223.134.223]) by smtp.googlemail.com with ESMTPSA id 41be03b00d2f7-c642a9f539dsm8866928a12.26.2026.01.30.23.44.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Jan 2026 23:44:49 -0800 (PST) From: Daniel Palmer To: linux@weissschuh.net, w@1wt.eu Cc: kees@kernel.org, linux-kernel@vger.kernel.org, Daniel Palmer Subject: [RFC PATCH 3/9] tools/nolibc: Add basic ELF self-relocation support for static PIE Date: Sat, 31 Jan 2026 16:44:34 +0900 Message-ID: <20260131074440.732588-4-daniel@thingy.jp> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260131074440.732588-1-daniel@thingy.jp> References: <20260131074440.732588-1-daniel@thingy.jp> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Currently nolibc programs cannot be compiled with -static-pie. Which is basically no shared libraries, no interpreter, but contain relocation information in the ELF to allow the program to be fixed up to run at the address that the kernel loaded it to. There might be use cases for static PIE but mine is for nommu. The ELF FDPIC loader can actually load normal ELFs is long as they can be relocated. This very basic implementation does the following: - Works out if we are PIE and need to be relocated. ELF type =3D=3D ET_DYN - Works out if we are static PIE, have no interpreter, and need to relocate ourselves. - Calculates the base address using the location of the program headers. This is probably not correct. - Finds the ELF relocation data. - Calls an arch specific function to handle each of the relocations. Note that from testing a lot of archs don't produce static PIE binaries with the -static-pie option and you need to compile with -pie -Wl,--no-dynamic-linker to get a static PIE binary. Currently REL and RELA formats are supported. Signed-off-by: Daniel Palmer --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/crt.h | 7 + tools/include/nolibc/reloc.h | 240 ++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 tools/include/nolibc/reloc.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 8118e22844f1..2b968a097854 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -38,6 +38,7 @@ all_files :=3D \ math.h \ nolibc.h \ poll.h \ + reloc.h \ sched.h \ signal.h \ stackprotector.h \ diff --git a/tools/include/nolibc/crt.h b/tools/include/nolibc/crt.h index 899062c00fb7..3c1c8d738ac7 100644 --- a/tools/include/nolibc/crt.h +++ b/tools/include/nolibc/crt.h @@ -10,6 +10,7 @@ #ifndef NOLIBC_NO_RUNTIME =20 #include "compiler.h" +#include "reloc.h" =20 char **environ __attribute__((weak)); const unsigned long *_auxv __attribute__((weak)); @@ -100,6 +101,12 @@ void __no_stack_protector _start_c(long *sp) for (auxv =3D (void *)envp; *auxv++;) ; =20 + /* + * Do relocation if required and supported, this must happen before any + * global variables are updated or used. + */ + _relocate(auxv); + __start_c(argc, argv, envp, auxv); } =20 diff --git a/tools/include/nolibc/reloc.h b/tools/include/nolibc/reloc.h new file mode 100644 index 000000000000..98c42af6f845 --- /dev/null +++ b/tools/include/nolibc/reloc.h @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * Self relocation support for NOLIBC + * Copyright (C) 2026 Daniel Palmer + * + * This allows a PIE compiled nolibc binary relocate itself + * instead of relying on a dynamic linker. So called "static PIE". + * + * With some care binaries produced with this relocation code + * can run via the FDPIC ELF loader on nommu systems. + * + * I am not expert in all of the different options for GCC but + * this works for me for x86: + * gcc -nostdlib -fpie -Os -include \ + * -static-pie -o helloworld helloworld.c + * + * For some targets -static-pie doesn't work but setting PIE and + * then disabling the linker results in a static-pie: + * gcc -nostdlib -fpie -Os -include \ + * -Wl,--no-dynamic-linker -pie -o helloworld helloworld.c + */ + +#ifndef _NOLIBC_RELOC_H +#define _NOLIBC_RELOC_H + +#ifdef NOLIBC_ARCH_HAS_RELOC +#include "elf.h" +#include + +#ifdef NOLIBC_ARCH_ELF32 +#define elf_ehdr Elf32_Ehdr +#define elf_phdr Elf32_Phdr +/* 32bit ARM, x86 uses REL instead of RELA */ +#ifdef NOLIBC_ARCH_ELF_REL +#define elf_rel Elf32_Rel +#else +#define elf_rela Elf32_Rela +#endif +#define elf_dyn Elf32_Dyn +#define elf_addr Elf32_Addr +#define elf_r_type(_x) ELF32_R_TYPE(_x) +#else +#define elf_ehdr Elf64_Ehdr +#define elf_phdr Elf64_Phdr +#define elf_dyn Elf64_Dyn +#define elf_rela Elf64_Rela +#define elf_addr Elf64_Addr +#define elf_r_type(_x) ELF64_R_TYPE(_x) +#endif + +#ifdef NOLIBC_ARCH_ELF_REL +/* + * Your arch needs to provide this to actually handle doing each of the + * relocations if it uses the REL format. + */ +static int __relocate_rel(unsigned long base, elf_rel *entry); + +/* Generic implementation of R_x_RELATIVE for REL */ +#define __relocate_rel_relative(_base, _entry) \ + do { \ + elf_addr *_addr; \ + int addend; \ + \ + _addr =3D (elf_addr *)(_base + _entry->r_offset); \ + addend =3D *_addr; \ + *_addr =3D _base + addend; \ + } while (0) + +static int __relocate(unsigned long base, + unsigned long rel_off, + unsigned long rel_count) +{ + elf_rel *rel =3D (elf_rel *)(base + rel_off); + unsigned long i; + + for (i =3D 0; i < rel_count; i++) { + if (__relocate_rel(base, &rel[i])) + return -1; + } + + return 0; +} +#else +/* + * Your arch needs to provide this to actually handle doing each of the + * relocations if it uses the RELA format. + */ +static int __relocate_rela(unsigned long base, elf_rela *entry); + +/* Generic implementation of R_x_RELATIVE for RELA */ +#define __relocate_rela_relative(_base, _entry) \ + do { \ + elf_addr *_addr; \ + \ + _addr =3D (elf_addr *)(_base + _entry->r_offset); \ + *_addr =3D (elf_addr) (_base + _entry->r_addend); \ + } while (0) + +static int __relocate(unsigned long base, + unsigned long rela_off, + unsigned long rela_count) +{ + elf_rela *rela =3D (elf_rela *)(base + rela_off); + unsigned long i; + + for (i =3D 0; i < rela_count; i++) { + if (__relocate_rela(base, &rela[i])) + return -1; + } + + return 0; +} +#endif + +static void _relocate(const unsigned long *auxv) +{ + unsigned long rel_rela_count =3D 0; + unsigned long rel_rela_off =3D 0; + unsigned long phdr_addr =3D 0; + unsigned long phdr_num =3D 0; + unsigned long phdr_sz =3D 0; + elf_phdr *phdr_dyn =3D NULL; + unsigned long base; + unsigned long i; + int remaining; + elf_ehdr *ehdr; + elf_dyn *dyn; + + for (remaining =3D 3; remaining; ) { + if (!auxv[0] && !auxv[1]) + break; + + switch (auxv[0]) { + case AT_NOTELF: + return; + + case AT_PHDR: + phdr_addr =3D auxv[1]; + remaining--; + break; + + case AT_PHNUM: + phdr_num =3D auxv[1]; + remaining--; + break; + + /* + * Not sure if this is even needed, should match + * the size of the program header type? + */ + case AT_PHENT: + phdr_sz =3D auxv[1]; + remaining--; + break; + } + + auxv +=3D 2; + } + + if (remaining) + goto failed; + + /* + * Everything I could find said that the way to find the base for relocat= ion + * should be done by searching for the first PT_LOAD and then using the o= fset + * of that against the adressed of the program headers. So FIXME. + */ + base =3D phdr_addr - sizeof(elf_ehdr); + + /* Check that we are PIE */ + ehdr =3D (elf_ehdr *) base; + if (ehdr->e_type !=3D ET_DYN) + return; + + for (i =3D 0, remaining =3D 1; (i < phdr_num) && remaining; i++) { + elf_phdr *phdr =3D (elf_phdr *)(phdr_addr + (phdr_sz * i)); + + switch (phdr->p_type) { + case PT_INTERP: + /* Interp was set, we were relocated already?, return */ + return; + case PT_DYNAMIC: + phdr_dyn =3D phdr; + remaining--; + break; + } + } + + if (!phdr_dyn) + goto failed; + + dyn =3D (elf_dyn *)(base + phdr_dyn->p_offset); + for (; dyn->d_tag !=3D DT_NULL; dyn++) { + switch (dyn->d_tag) { +#ifdef NOLIBC_ARCH_ELF_REL + case DT_REL: + rel_rela_off =3D dyn->d_un.d_ptr; + break; + case DT_RELCOUNT: + rel_rela_count =3D dyn->d_un.d_val; + break; + } +#else + case DT_RELA: + rel_rela_off =3D dyn->d_un.d_ptr; + break; + case DT_RELACOUNT: + rel_rela_count =3D dyn->d_un.d_val; + break; + } +#endif + + /* Got what we came for, exit loop */ + if (rel_rela_off && rel_rela_count) + break; + } + + if (!rel_rela_off || !rel_rela_count) + goto failed; + + if (__relocate(base, rel_rela_off, rel_rela_count)) + goto failed; + + return; + +failed: + __builtin_trap(); +} +#else +static void _relocate(const unsigned long *auxv __attribute__((unused))) +{ + /* + * Maybe if you build a program that needs relocation + * but it's not supported detect that and trap here. + * But for now trust that people know what they are doing. + */ +} +#endif /* NOLIBC_ARCH_HAS_RELOC */ + +#endif /* _NOLIBC_RELOC_H */ --=20 2.51.0