├── .gitattributes ├── tests ├── dt-needed.c ├── hello.c ├── libtest.c ├── dlopen.c ├── ld-path-restore.c └── tests.rs ├── src ├── nolibc.c ├── support.rs ├── auxv.rs ├── const_concat.rs ├── sys.rs ├── fixup.rs ├── arch.rs └── main.rs ├── justfile ├── .gitignore ├── .cargo └── config.toml ├── renovate.json ├── .envrc ├── .github ├── dependabot.yml └── workflows │ └── auto-merge.yaml ├── nixpkgs.nix ├── flake.lock ├── vendor ├── nolibc │ ├── signal.h │ ├── time.h │ ├── errno.h │ ├── arch.h │ ├── unistd.h │ ├── std.h │ ├── ctype.h │ ├── nolibc.h │ ├── string.h │ ├── types.h │ ├── arch-s390.h │ ├── stdio.h │ ├── vendor.patch │ ├── arch-aarch64.h │ ├── arch-riscv.h │ ├── arch-i386.h │ ├── arch-x86_64.h │ ├── arch-mips.h │ ├── arch-arm.h │ └── stdlib.h └── nolibc.nix ├── Cargo.toml ├── LICENSE ├── examples ├── masterpdfeditor.nix └── zoom.nix ├── nixos-tests └── default.nix ├── riscv64-shell.nix ├── package.nix ├── scripts └── create-release.sh ├── modules └── nix-ld.nix ├── flake.nix └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | vendor/nolibc/* linguist-vendored 2 | -------------------------------------------------------------------------------- /tests/dt-needed.c: -------------------------------------------------------------------------------- 1 | void print_test(); 2 | 3 | int main() { 4 | print_test(); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | printf("Hello, world!\n"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/libtest.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void print_test() { 4 | printf("Hello from libtest\n"); 5 | } 6 | -------------------------------------------------------------------------------- /src/nolibc.c: -------------------------------------------------------------------------------- 1 | #ifndef __PIE__ 2 | #error Pass -fPIE 3 | #endif 4 | 5 | // HACK 6 | #define static 7 | #include "nolibc.h" 8 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | test *args='': 2 | cargo test {{args}} --target x86_64-unknown-linux-gnu --target i686-unknown-linux-gnu --target aarch64-unknown-linux-gnu 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | .vscode 3 | 4 | result* 5 | 6 | *.o 7 | *.so 8 | *.out 9 | 10 | *~ 11 | *.swp 12 | 13 | 14 | 15 | # Added by cargo 16 | 17 | /target 18 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "relocation-model=pie", "-Z", "plt=yes"] 3 | 4 | [target.'cfg(target_arch = "aarch64")'] 5 | rustflags = [ 6 | "-C", "relocation-model=pie", "-Z", "plt=yes", 7 | ] 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "labels": ["dependencies"], 7 | "lockFileMaintenance": { "enabled": true } 8 | } 9 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | if ! has nix_direnv_version || ! nix_direnv_version 3.0.5; then 2 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w=" 3 | fi 4 | 5 | use_flake 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yaml: -------------------------------------------------------------------------------- 1 | name: Auto Merge Dependency Updates 2 | on: 3 | - pull_request_target 4 | jobs: 5 | auto-merge-dependency-updates: 6 | runs-on: ubuntu-latest 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | concurrency: 11 | group: "auto-merge:${{ github.head_ref }}" 12 | cancel-in-progress: true 13 | steps: 14 | - uses: Mic92/auto-merge@main 15 | -------------------------------------------------------------------------------- /tests/dlopen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char **argv) { 5 | void *handle = dlopen("libtest.so", RTLD_LAZY); 6 | if (!handle) { 7 | fprintf(stderr, "%s: Failed to dlopen libtest.so\n", argv[0]); 8 | } 9 | 10 | void (*print_test)(); 11 | print_test = dlsym(handle, "print_test"); 12 | 13 | if (!print_test) { 14 | fprintf(stderr, "%s: Failed to dlsym print_test\n", argv[0]); 15 | } 16 | 17 | print_test(); 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /nixpkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | lock = builtins.fromJSON (builtins.readFile ../flake.lock); 3 | 4 | flake-compat = builtins.fetchTarball { 5 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | }; 8 | 9 | flake = 10 | (import flake-compat { 11 | src = ./..; 12 | }).defaultNix; 13 | in 14 | import flake.inputs.nixpkgs.outPath { 15 | overlays = [ 16 | flake.overlays.default 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1752480373, 6 | "narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /vendor/nolibc/signal.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * signal function definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_SIGNAL_H 8 | #define _NOLIBC_SIGNAL_H 9 | 10 | #include "std.h" 11 | #include "arch.h" 12 | #include "types.h" 13 | #include "sys.h" 14 | 15 | /* This one is not marked static as it's needed by libgcc for divide by zero */ 16 | __attribute__((weak,unused,section(".text.nolibc_raise"))) 17 | int raise(int signal) 18 | { 19 | return sys_kill(sys_getpid(), signal); 20 | } 21 | 22 | /* make sure to include all global symbols */ 23 | #include "nolibc.h" 24 | 25 | #endif /* _NOLIBC_SIGNAL_H */ 26 | -------------------------------------------------------------------------------- /vendor/nolibc/time.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * time function definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_TIME_H 8 | #define _NOLIBC_TIME_H 9 | 10 | #include "std.h" 11 | #include "arch.h" 12 | #include "types.h" 13 | #include "sys.h" 14 | 15 | static __attribute__((unused)) 16 | time_t time(time_t *tptr) 17 | { 18 | struct timeval tv; 19 | 20 | /* note, cannot fail here */ 21 | sys_gettimeofday(&tv, NULL); 22 | 23 | if (tptr) 24 | *tptr = tv.tv_sec; 25 | return tv.tv_sec; 26 | } 27 | 28 | /* make sure to include all global symbols */ 29 | #include "nolibc.h" 30 | 31 | #endif /* _NOLIBC_TIME_H */ 32 | -------------------------------------------------------------------------------- /vendor/nolibc.nix: -------------------------------------------------------------------------------- 1 | { 2 | linuxPackages_latest, 3 | runCommand, 4 | git, 5 | }: 6 | let 7 | linux = linuxPackages_latest.kernel; 8 | in 9 | runCommand "linux-nolibc-${linux.version}" 10 | { 11 | inherit (linux) src; 12 | nativeBuildInputs = [ git ]; 13 | } 14 | '' 15 | tar xvf $src --wildcards '*/tools/include/nolibc/*.h' --strip-components=3 16 | cp -r nolibc{,.orig} 17 | 18 | # Stores into environ and _auxv in _start break PIE :( 19 | sed -i -E '/".*(_auxv|environ)/s/^\/*/\/\//' nolibc/arch-*.h 20 | 21 | mkdir -p $out 22 | cp -r nolibc $out 23 | 24 | echo '# Vendored from Linux ${linux.version} (${linux.src.url})' >$out/nolibc/vendor.patch 25 | git diff --no-index nolibc.orig nolibc >>$out/nolibc/vendor.patch || true 26 | '' 27 | -------------------------------------------------------------------------------- /vendor/nolibc/errno.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * Minimal errno definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ERRNO_H 8 | #define _NOLIBC_ERRNO_H 9 | 10 | #include 11 | 12 | #ifndef NOLIBC_IGNORE_ERRNO 13 | #define SET_ERRNO(v) do { errno = (v); } while (0) 14 | int errno __attribute__((weak)); 15 | #else 16 | #define SET_ERRNO(v) do { } while (0) 17 | #endif 18 | 19 | 20 | /* errno codes all ensure that they will not conflict with a valid pointer 21 | * because they all correspond to the highest addressable memory page. 22 | */ 23 | #define MAX_ERRNO 4095 24 | 25 | /* make sure to include all global symbols */ 26 | #include "nolibc.h" 27 | 28 | #endif /* _NOLIBC_ERRNO_H */ 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nix-ld" 3 | version = "2.0.6" 4 | edition = "2024" 5 | 6 | [[bin]] 7 | name = "nix-ld" 8 | test = false 9 | 10 | [[test]] 11 | name = "tests" 12 | 13 | [dependencies] 14 | embedded-io = "0.6.1" 15 | goblin = { version = "0.10.4", default-features = false, features = [ 16 | "elf32", 17 | "elf64", 18 | ] } 19 | heapless = "0.9.2" 20 | linux-raw-sys = { version = "0.12.0", default-features = false, features = [ 21 | "no_std", 22 | "general", 23 | "errno", 24 | ] } 25 | log = "0.4.29" 26 | 27 | [build-dependencies] 28 | cc = "1.2.50" 29 | 30 | [dev-dependencies] 31 | cc = "1.2.50" 32 | rstest = { version = "0.26.1", default-features = false } 33 | tempfile = "3.23.0" 34 | 35 | [profile.dev] 36 | panic = "abort" 37 | 38 | [profile.release] 39 | panic = "abort" 40 | lto = true 41 | debug = true 42 | 43 | [features] 44 | default = ["entry_trampoline"] 45 | 46 | # Use a trampoline to revert our changes to LD_LIBRARY_PATH before executing the real program 47 | entry_trampoline = [] 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 Jörg Thalheim, nix-ld contributors 4 | Copyright (c) 2023 Zhaofeng Li 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/masterpdfeditor.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { }, 3 | }: 4 | let 5 | inherit (pkgs) lib stdenv qt5; 6 | in 7 | # run with 8 | # $ nix-shell ./masterpdfeditor.nix 9 | pkgs.mkShell { 10 | NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [ 11 | pkgs.nss 12 | pkgs.sane-backends 13 | pkgs.nspr 14 | pkgs.zlib 15 | pkgs.libglvnd 16 | qt5.qtbase 17 | qt5.qtsvg 18 | qt5.qtdeclarative 19 | qt5.qtwayland 20 | pkgs.pkcs11helper 21 | stdenv.cc.cc 22 | ]; 23 | 24 | NIX_LD = builtins.readFile "${stdenv.cc}/nix-support/dynamic-linker"; 25 | 26 | QT_PLUGIN_PATH = "${qt5.qtbase}/${qt5.qtbase.qtPluginPrefix}:${qt5.qtwayland.bin}/${qt5.qtbase.qtPluginPrefix}"; 27 | QML2_IMPORT_PATH = "${qt5.qtdeclarative.bin}/${qt5.qtbase.qtQmlPrefix}:${qt5.qtwayland.bin}/${qt5.qtbase.qtQmlPrefix}"; 28 | 29 | shellHook = '' 30 | if [ ! -d master-pdf-editor ]; then 31 | echo "unpack master-pdf-editor..." 32 | mkdir master-pdf-editor 33 | tar -C master-pdf-editor \ 34 | --strip-components 1 \ 35 | -xf ${pkgs.masterpdfeditor.src} 36 | fi 37 | echo '$ ./master-pdf-editor/masterpdfeditor5' 38 | ./master-pdf-editor/masterpdfeditor5 39 | ''; 40 | } 41 | -------------------------------------------------------------------------------- /nixos-tests/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | testers, 3 | }: 4 | { 5 | basic = testers.runNixOSTest { 6 | name = "basic"; 7 | nodes.machine = 8 | { pkgs, config, ... }: 9 | { 10 | imports = [ 11 | ../modules/nix-ld.nix 12 | ]; 13 | programs.nix-ld.dev.enable = true; 14 | environment.systemPackages = [ 15 | (pkgs.runCommand "patched-hello" { } '' 16 | install -D -m755 ${pkgs.hello}/bin/hello $out/bin/hello 17 | patchelf $out/bin/hello --set-interpreter $(cat ${config.programs.nix-ld.dev.package}/nix-support/ldpath) 18 | '') 19 | ]; 20 | }; 21 | testScript = 22 | { nodes, ... }: 23 | let 24 | nix-ld = nodes.machine.programs.nix-ld.dev.package; 25 | in 26 | '' 27 | start_all() 28 | machine.succeed("hello") 29 | machine.succeed("ls -la /run/current-system/sw/share/nix-ld/lib/ld.so >&2") 30 | machine.succeed("$(< ${nix-ld}/nix-support/ldpath) --version") 31 | 32 | # test fallback if NIX_LD is not set 33 | machine.succeed("unset NIX_LD; unset NIX_LD_LIBRARY_PATH; $(< ${nix-ld}/nix-support/ldpath) $(which hello)") 34 | ''; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /examples/zoom.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | let 3 | inherit (pkgs) lib stdenv xorg; 4 | src = pkgs.zoom-us.src; 5 | in 6 | pkgs.mkShell { 7 | NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [ 8 | # found by 9 | # $ LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH:$PWD ldd zoom | grep 'not found' 10 | pkgs.alsa-lib 11 | pkgs.atk 12 | pkgs.cairo 13 | pkgs.dbus 14 | pkgs.libGL 15 | pkgs.fontconfig 16 | pkgs.freetype 17 | pkgs.gtk3 18 | pkgs.gdk-pixbuf 19 | pkgs.glib 20 | pkgs.pango 21 | stdenv.cc.cc 22 | pkgs.pulseaudio 23 | pkgs.wayland 24 | xorg.libX11 25 | xorg.libxcb 26 | xorg.libXcomposite 27 | xorg.libXext 28 | pkgs.libxkbcommon 29 | xorg.libXrender 30 | pkgs.zlib 31 | xorg.xcbutilimage 32 | xorg.xcbutilkeysyms 33 | xorg.libXfixes 34 | xorg.libXtst 35 | ]; 36 | NIX_LD = builtins.readFile "${stdenv.cc}/nix-support/dynamic-linker"; 37 | shellHook = '' 38 | if [ ! -d zoom ]; then 39 | echo "unpack zoom..." 40 | mkdir zoom 41 | tar -C zoom \ 42 | -xf ${src} 43 | fi 44 | export LD_LIBRARY_PATH=$PWD/zoom/opt/zoom/ 45 | echo '$ ./zoom/opt/zoom/ZoomLauncher' 46 | ./zoom/opt/zoom/ZoomLauncher 47 | ''; 48 | } 49 | -------------------------------------------------------------------------------- /vendor/nolibc/arch.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * Copyright (C) 2017-2022 Willy Tarreau 4 | */ 5 | 6 | /* Below comes the architecture-specific code. For each architecture, we have 7 | * the syscall declarations and the _start code definition. This is the only 8 | * global part. On all architectures the kernel puts everything in the stack 9 | * before jumping to _start just above us, without any return address (_start 10 | * is not a function but an entry pint). So at the stack pointer we find argc. 11 | * Then argv[] begins, and ends at the first NULL. Then we have envp which 12 | * starts and ends with a NULL as well. So envp=argv+argc+1. 13 | */ 14 | 15 | #ifndef _NOLIBC_ARCH_H 16 | #define _NOLIBC_ARCH_H 17 | 18 | #if defined(__x86_64__) 19 | #include "arch-x86_64.h" 20 | #elif defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) 21 | #include "arch-i386.h" 22 | #elif defined(__ARM_EABI__) 23 | #include "arch-arm.h" 24 | #elif defined(__aarch64__) 25 | #include "arch-aarch64.h" 26 | #elif defined(__mips__) && defined(_ABIO32) 27 | #include "arch-mips.h" 28 | #elif defined(__riscv) 29 | #include "arch-riscv.h" 30 | #elif defined(__s390x__) 31 | #include "arch-s390.h" 32 | #endif 33 | 34 | #endif /* _NOLIBC_ARCH_H */ 35 | -------------------------------------------------------------------------------- /riscv64-shell.nix: -------------------------------------------------------------------------------- 1 | { mkShell 2 | , rustc 3 | , cargo 4 | , cargo-watch 5 | , cargo-bloat 6 | , cargo-nextest 7 | , just 8 | , stdenv 9 | , qemu 10 | }: 11 | 12 | mkShell { 13 | nativeBuildInputs = [ 14 | rustc 15 | cargo 16 | cargo-watch 17 | cargo-bloat 18 | cargo-nextest 19 | just 20 | qemu 21 | ]; 22 | 23 | hardeningDisable = [ "stackprotector" ]; 24 | 25 | RUSTC_BOOTSTRAP = "1"; 26 | 27 | # Cross compilation environment 28 | CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc"; 29 | CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER = "qemu-riscv64 -L /"; 30 | CC_riscv64gc_unknown_linux_gnu = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc"; 31 | NIX_LD_TEST_TARGET = "riscv64gc-unknown-linux-gnu"; 32 | NIX_LD = "${stdenv.cc.libc}/lib/ld-linux-riscv64-lp64d.so.1"; 33 | TARGET_CC = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc"; 34 | 35 | shellHook = '' 36 | echo "RISC-V 64-bit cross-compilation environment" 37 | echo "Target: riscv64gc-unknown-linux-gnu" 38 | echo "Cross compiler: ${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc" 39 | echo "Usage:" 40 | echo " cargo build --target riscv64gc-unknown-linux-gnu" 41 | echo " cargo test --target riscv64gc-unknown-linux-gnu" 42 | ''; 43 | } 44 | -------------------------------------------------------------------------------- /vendor/nolibc/unistd.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * unistd function definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_UNISTD_H 8 | #define _NOLIBC_UNISTD_H 9 | 10 | #include "std.h" 11 | #include "arch.h" 12 | #include "types.h" 13 | #include "sys.h" 14 | 15 | 16 | static __attribute__((unused)) 17 | int msleep(unsigned int msecs) 18 | { 19 | struct timeval my_timeval = { msecs / 1000, (msecs % 1000) * 1000 }; 20 | 21 | if (sys_select(0, 0, 0, 0, &my_timeval) < 0) 22 | return (my_timeval.tv_sec * 1000) + 23 | (my_timeval.tv_usec / 1000) + 24 | !!(my_timeval.tv_usec % 1000); 25 | else 26 | return 0; 27 | } 28 | 29 | static __attribute__((unused)) 30 | unsigned int sleep(unsigned int seconds) 31 | { 32 | struct timeval my_timeval = { seconds, 0 }; 33 | 34 | if (sys_select(0, 0, 0, 0, &my_timeval) < 0) 35 | return my_timeval.tv_sec + !!my_timeval.tv_usec; 36 | else 37 | return 0; 38 | } 39 | 40 | static __attribute__((unused)) 41 | int usleep(unsigned int usecs) 42 | { 43 | struct timeval my_timeval = { usecs / 1000000, usecs % 1000000 }; 44 | 45 | return sys_select(0, 0, 0, 0, &my_timeval); 46 | } 47 | 48 | static __attribute__((unused)) 49 | int tcsetpgrp(int fd, pid_t pid) 50 | { 51 | return ioctl(fd, TIOCSPGRP, &pid); 52 | } 53 | 54 | /* make sure to include all global symbols */ 55 | #include "nolibc.h" 56 | 57 | #endif /* _NOLIBC_UNISTD_H */ 58 | -------------------------------------------------------------------------------- /tests/ld-path-restore.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void print_test(); 6 | 7 | #define CHILD_COMMAND OUT_DIR "/dt-needed" 8 | 9 | int main(int argc, char **argv) { 10 | char *ld_library_path = getenv("LD_LIBRARY_PATH"); 11 | if (ld_library_path) { 12 | fprintf(stderr, "%s: Our LD_LIBRARY_PATH: %s\n", argv[0], ld_library_path); 13 | if (strstr(ld_library_path, "POISON")) { 14 | fprintf(stderr, "%s: Forbidden string exists in LD_LIBRARY_PATH\n", argv[0]); 15 | return 1; 16 | } 17 | if (strstr(ld_library_path, "NEEDLE")) { 18 | fprintf(stderr, "%s: LD_LIBRARY_PATH contains needle\n", argv[0]); 19 | } 20 | } else { 21 | fprintf(stderr, "%s: No LD_LIBRARY_PATH\n", argv[0]); 22 | } 23 | 24 | // On the other hand, NIX_LD_LIBRARY_PATH must exist for 25 | // prebuilt binaries to work as child processes. 26 | char *nix_ld_library_path = getenv("NIX_LD_LIBRARY_PATH"); 27 | if (!nix_ld_library_path) { 28 | fprintf(stderr, "%s: NIX_LD_LIBRARY_PATH doesn't exist\n", argv[0]); 29 | return 1; 30 | } 31 | if (!strstr(nix_ld_library_path, "POISON")) { 32 | fprintf(stderr, "%s: POISON doesn't exist in NIX_LD_LIBRARY_PATH\n", argv[0]); 33 | return 1; 34 | } 35 | 36 | print_test(); 37 | 38 | // Our children must not be polluted by LD_LIBRARY_PATH 39 | unsetenv("NIX_LD_LIBRARY_PATH"); // our child is built with nix-ld as well 40 | fprintf(stderr, "%s: Launching child process\n", argv[0]); 41 | int ret = system(CHILD_COMMAND); 42 | if (ret == 0) { 43 | fprintf(stderr, "%s: Child process unexpectedly succeeded\n", argv[0]); 44 | return 1; 45 | } 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /vendor/nolibc/std.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * Standard definitions and types for NOLIBC 4 | * Copyright (C) 2017-2021 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_STD_H 8 | #define _NOLIBC_STD_H 9 | 10 | /* Declare a few quite common macros and types that usually are in stdlib.h, 11 | * stdint.h, ctype.h, unistd.h and a few other common locations. Please place 12 | * integer type definitions and generic macros here, but avoid OS-specific and 13 | * syscall-specific stuff, as this file is expected to be included very early. 14 | */ 15 | 16 | /* note: may already be defined */ 17 | #ifndef NULL 18 | #define NULL ((void *)0) 19 | #endif 20 | 21 | /* stdint types */ 22 | typedef unsigned char uint8_t; 23 | typedef signed char int8_t; 24 | typedef unsigned short uint16_t; 25 | typedef signed short int16_t; 26 | typedef unsigned int uint32_t; 27 | typedef signed int int32_t; 28 | typedef unsigned long long uint64_t; 29 | typedef signed long long int64_t; 30 | typedef unsigned long size_t; 31 | typedef signed long ssize_t; 32 | typedef unsigned long uintptr_t; 33 | typedef signed long intptr_t; 34 | typedef signed long ptrdiff_t; 35 | 36 | /* those are commonly provided by sys/types.h */ 37 | typedef unsigned int dev_t; 38 | typedef unsigned long ino_t; 39 | typedef unsigned int mode_t; 40 | typedef signed int pid_t; 41 | typedef unsigned int uid_t; 42 | typedef unsigned int gid_t; 43 | typedef unsigned long nlink_t; 44 | typedef signed long off_t; 45 | typedef signed long blksize_t; 46 | typedef signed long blkcnt_t; 47 | typedef signed long time_t; 48 | 49 | #endif /* _NOLIBC_STD_H */ 50 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | pkgs, 5 | rustPlatform, 6 | enableClippy ? false, 7 | }: 8 | 9 | let 10 | libDir = 11 | if 12 | builtins.elem stdenv.system [ 13 | "x86_64-linux" 14 | "mips64-linux" 15 | "powerpc64le-linux" 16 | ] 17 | then 18 | "/lib64" 19 | else 20 | "/lib"; 21 | 22 | nix-ld = rustPlatform.buildRustPackage { 23 | name = "nix-ld"; 24 | 25 | cargoLock.lockFile = ./Cargo.lock; 26 | 27 | src = lib.fileset.toSource { 28 | root = ./.; 29 | fileset = lib.fileset.unions [ 30 | ./src 31 | ./Cargo.toml 32 | ./Cargo.lock 33 | ./build.rs 34 | ./vendor 35 | ./tests 36 | ]; 37 | }; 38 | 39 | hardeningDisable = [ "stackprotector" ]; 40 | 41 | NIX_SYSTEM = stdenv.system; 42 | RUSTC_BOOTSTRAP = "1"; 43 | 44 | preCheck = '' 45 | export NIX_LD=${stdenv.cc.bintools.dynamicLinker} 46 | ''; 47 | 48 | postInstall = '' 49 | mkdir -p $out/libexec 50 | ln -s $out/bin/nix-ld $out/libexec/nix-ld 51 | 52 | mkdir -p $out/nix-support 53 | 54 | ldpath=${libDir}/$(basename ${stdenv.cc.bintools.dynamicLinker}) 55 | echo "$ldpath" > $out/nix-support/ldpath 56 | mkdir -p $out/lib/tmpfiles.d/ 57 | cat > $out/lib/tmpfiles.d/nix-ld.conf </dev/null && pwd )" 6 | cd "$SCRIPT_DIR/.." 7 | 8 | version=${1:-} 9 | if [[ -z "$version" ]]; then 10 | echo "USAGE: $0 version" >&2 11 | exit 1 12 | fi 13 | 14 | if [[ "$(git symbolic-ref --short HEAD)" != "main" ]]; then 15 | echo "must be on main branch" >&2 16 | exit 1 17 | fi 18 | 19 | waitForPr() { 20 | local pr=$1 21 | while true; do 22 | if gh pr view "$pr" | grep -q 'MERGED'; then 23 | break 24 | fi 25 | echo "Waiting for PR to be merged..." 26 | sleep 5 27 | done 28 | } 29 | 30 | # ensure we are up-to-date 31 | uncommitted_changes=$(git diff --compact-summary) 32 | if [[ -n "$uncommitted_changes" ]]; then 33 | echo -e "There are uncommitted changes, exiting:\n${uncommitted_changes}" >&2 34 | exit 1 35 | fi 36 | git pull git@github.com:nix-community/nix-ld main 37 | unpushed_commits=$(git log --format=oneline origin/main..main) 38 | if [[ "$unpushed_commits" != "" ]]; then 39 | echo -e "\nThere are unpushed changes, exiting:\n$unpushed_commits" >&2 40 | exit 1 41 | fi 42 | # make sure tag does not exist 43 | if git tag -l | grep -q "^${version}\$"; then 44 | echo "Tag ${version} already exists, exiting" >&2 45 | exit 1 46 | fi 47 | sed -i -e "s!^version = \".*\"\$!version = \"${version}\"!" Cargo.toml 48 | cargo build 49 | git add Cargo.lock Cargo.toml 50 | git branch -D "release-${version}" || true 51 | git checkout -b "release-${version}" 52 | nix flake check -vL 53 | git commit -m "bump version nix-ld ${version}" 54 | git push origin "release-${version}" 55 | pr_url=$(gh pr create \ 56 | --base main \ 57 | --head "release-${version}" \ 58 | --title "Release ${version}" \ 59 | --body "Release ${version} of nix-ld") 60 | 61 | # Extract PR number from URL 62 | pr_number=$(echo "$pr_url" | grep -oE '[0-9]+$') 63 | 64 | # Enable auto-merge with specific merge method and delete branch 65 | gh pr merge "$pr_number" --auto --merge 66 | git checkout main 67 | 68 | waitForPr "release-${version}" 69 | git pull git@github.com:nix-community/nix-ld main 70 | git tag "${version}" 71 | git push origin "${version}" 72 | gh release create "${version}" --draft --title "${version}" --notes "" 73 | -------------------------------------------------------------------------------- /vendor/nolibc/ctype.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * ctype function definitions for NOLIBC 4 | * Copyright (C) 2017-2021 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_CTYPE_H 8 | #define _NOLIBC_CTYPE_H 9 | 10 | #include "std.h" 11 | 12 | /* 13 | * As much as possible, please keep functions alphabetically sorted. 14 | */ 15 | 16 | static __attribute__((unused)) 17 | int isascii(int c) 18 | { 19 | /* 0x00..0x7f */ 20 | return (unsigned int)c <= 0x7f; 21 | } 22 | 23 | static __attribute__((unused)) 24 | int isblank(int c) 25 | { 26 | return c == '\t' || c == ' '; 27 | } 28 | 29 | static __attribute__((unused)) 30 | int iscntrl(int c) 31 | { 32 | /* 0x00..0x1f, 0x7f */ 33 | return (unsigned int)c < 0x20 || c == 0x7f; 34 | } 35 | 36 | static __attribute__((unused)) 37 | int isdigit(int c) 38 | { 39 | return (unsigned int)(c - '0') < 10; 40 | } 41 | 42 | static __attribute__((unused)) 43 | int isgraph(int c) 44 | { 45 | /* 0x21..0x7e */ 46 | return (unsigned int)(c - 0x21) < 0x5e; 47 | } 48 | 49 | static __attribute__((unused)) 50 | int islower(int c) 51 | { 52 | return (unsigned int)(c - 'a') < 26; 53 | } 54 | 55 | static __attribute__((unused)) 56 | int isprint(int c) 57 | { 58 | /* 0x20..0x7e */ 59 | return (unsigned int)(c - 0x20) < 0x5f; 60 | } 61 | 62 | static __attribute__((unused)) 63 | int isspace(int c) 64 | { 65 | /* \t is 0x9, \n is 0xA, \v is 0xB, \f is 0xC, \r is 0xD */ 66 | return ((unsigned int)c == ' ') || (unsigned int)(c - 0x09) < 5; 67 | } 68 | 69 | static __attribute__((unused)) 70 | int isupper(int c) 71 | { 72 | return (unsigned int)(c - 'A') < 26; 73 | } 74 | 75 | static __attribute__((unused)) 76 | int isxdigit(int c) 77 | { 78 | return isdigit(c) || (unsigned int)(c - 'A') < 6 || (unsigned int)(c - 'a') < 6; 79 | } 80 | 81 | static __attribute__((unused)) 82 | int isalpha(int c) 83 | { 84 | return islower(c) || isupper(c); 85 | } 86 | 87 | static __attribute__((unused)) 88 | int isalnum(int c) 89 | { 90 | return isalpha(c) || isdigit(c); 91 | } 92 | 93 | static __attribute__((unused)) 94 | int ispunct(int c) 95 | { 96 | return isgraph(c) && !isalnum(c); 97 | } 98 | 99 | /* make sure to include all global symbols */ 100 | #include "nolibc.h" 101 | 102 | #endif /* _NOLIBC_CTYPE_H */ 103 | -------------------------------------------------------------------------------- /modules/nix-ld.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | config, 5 | ... 6 | }: 7 | let 8 | cfg = config.programs.nix-ld.dev; 9 | 10 | nix-ld-libraries = pkgs.buildEnv { 11 | name = "lb-library-path"; 12 | pathsToLink = [ "/lib" ]; 13 | paths = map lib.getLib cfg.libraries; 14 | # TODO make glibc here configurable? 15 | postBuild = '' 16 | ln -s ${pkgs.stdenv.cc.bintools.dynamicLinker} $out/share/nix-ld/lib/ld.so 17 | ''; 18 | extraPrefix = "/share/nix-ld"; 19 | ignoreCollisions = true; 20 | }; 21 | 22 | # We currently take all libraries from systemd and nix as the default. 23 | # Is there a better list? 24 | baseLibraries = with pkgs; [ 25 | zlib 26 | zstd 27 | stdenv.cc.cc 28 | curl 29 | openssl 30 | attr 31 | libssh 32 | bzip2 33 | libxml2 34 | acl 35 | libsodium 36 | util-linux 37 | xz 38 | systemd 39 | ]; 40 | in 41 | { 42 | meta.maintainers = [ lib.maintainers.mic92 ]; 43 | 44 | options.programs.nix-ld.dev = { 45 | enable = 46 | lib.mkEnableOption (''nix-ld, Documentation: '') 47 | // { 48 | default = true; 49 | }; 50 | package = lib.mkOption { 51 | type = lib.types.package; 52 | description = "The package to be used for nix-ld."; 53 | default = pkgs.callPackage ../package.nix { }; 54 | }; 55 | libraries = lib.mkOption { 56 | type = lib.types.listOf lib.types.package; 57 | description = "Libraries that automatically become available to all programs. The default set includes common libraries."; 58 | default = baseLibraries; 59 | defaultText = lib.literalExpression "baseLibraries derived from systemd and nix dependencies."; 60 | }; 61 | }; 62 | 63 | config = lib.mkIf config.programs.nix-ld.dev.enable { 64 | assertions = [ 65 | { 66 | assertion = !config.programs.nix-ld.enable; 67 | message = '' 68 | nix-ld.dev cannot be enabled at the same time as nix-ld. 69 | ''; 70 | } 71 | ]; 72 | 73 | environment.ldso = "${cfg.package}/libexec/nix-ld"; 74 | 75 | systemd.tmpfiles.packages = [ cfg.package ]; 76 | 77 | environment.systemPackages = [ nix-ld-libraries ]; 78 | 79 | environment.pathsToLink = [ "/share/nix-ld" ]; 80 | 81 | environment.variables = { 82 | NIX_LD = "/run/current-system/sw/share/nix-ld/lib/ld.so"; 83 | NIX_LD_LIBRARY_PATH = "/run/current-system/sw/share/nix-ld/lib"; 84 | }; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/support.rs: -------------------------------------------------------------------------------- 1 | //! Low-level support. 2 | 3 | use core::fmt::Write; 4 | 5 | use crate::arch::STACK_ALIGNMENT; 6 | use crate::sys; 7 | 8 | pub static LOGGER: StderrLogger = StderrLogger; 9 | 10 | pub struct StderrLogger; 11 | 12 | impl log::Log for StderrLogger { 13 | fn enabled(&self, _metadata: &log::Metadata) -> bool { 14 | true 15 | } 16 | 17 | fn log(&self, record: &log::Record) { 18 | if self.enabled(record.metadata()) { 19 | let mut stderr = sys::stderr(); 20 | writeln!(stderr, "[nix-ld] {:>5}: {}", record.level(), record.args()).unwrap(); 21 | } 22 | } 23 | 24 | fn flush(&self) {} 25 | } 26 | 27 | #[repr(transparent)] 28 | pub struct StackSpace([u8; 1024 * 1024 * 5]); 29 | 30 | impl StackSpace { 31 | pub fn bottom(&self) -> *const u8 { 32 | let end = self.0.as_ptr() as usize + self.0.len(); 33 | let aligned = end & !(STACK_ALIGNMENT - 1); 34 | aligned as *const u8 35 | } 36 | } 37 | 38 | /// Aborts the program because something went terribly wrong. 39 | /// 40 | /// Unlike panic!(), this doesn't trigger the panic-handling 41 | /// or formatting machinery. 42 | #[cold] 43 | pub fn explode(s: &str) -> ! { 44 | let prefix = "[nix-ld] FATAL: "; 45 | 46 | unsafe { 47 | sys::write(2, prefix.as_ptr(), prefix.len()); 48 | sys::write(2, s.as_ptr(), s.len()); 49 | sys::write(2, "\n".as_ptr(), 1); 50 | sys::abort(); 51 | } 52 | } 53 | 54 | #[cfg(not(test))] 55 | #[panic_handler] 56 | fn panic_handler(info: &core::panic::PanicInfo) -> ! { 57 | let mut stderr = sys::stderr(); 58 | writeln!(stderr, "[nix-ld] FATAL: {info}").unwrap(); 59 | 60 | unsafe { 61 | sys::abort(); 62 | } 63 | } 64 | 65 | #[unsafe(no_mangle)] 66 | extern "C" fn __stack_chk_fail() -> ! { 67 | explode("stack smashing detected"); 68 | } 69 | 70 | // Stack canary value for stack protection. In a production system, this would 71 | // be randomized at startup, but for nix-ld's minimal environment, a constant 72 | // suffices to satisfy the linker requirements from libcompiler_builtins. 73 | #[unsafe(no_mangle)] 74 | static __stack_chk_guard: usize = 0x1234567890abcdef_u64 as usize; 75 | 76 | // On 32-bit systems, PIC code uses __stack_chk_fail_local instead of __stack_chk_fail 77 | // as an optimization to avoid going through the PLT 78 | #[cfg(target_pointer_width = "32")] 79 | #[unsafe(no_mangle)] 80 | extern "C" fn __stack_chk_fail_local() -> ! { 81 | explode("stack smashing detected"); 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Run unpatched dynamic binaries on NixOS, but this time with more Rust"; 3 | 4 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 5 | 6 | outputs = 7 | { self, nixpkgs, ... }: 8 | let 9 | # System types to support. 10 | supportedSystems = [ 11 | "i686-linux" 12 | "x86_64-linux" 13 | "aarch64-linux" 14 | "riscv64-linux" 15 | ]; 16 | lib = nixpkgs.lib; 17 | forAllSystems = 18 | f: 19 | nixpkgs.lib.genAttrs supportedSystems ( 20 | system: 21 | f { 22 | pkgs = nixpkgs.legacyPackages.${system}; 23 | inherit system; 24 | } 25 | ); 26 | in 27 | { 28 | packages = forAllSystems ( 29 | { pkgs, system, ... }: 30 | { 31 | nix-ld = pkgs.callPackage ./package.nix { }; 32 | nolibc = pkgs.callPackage ./vendor/nolibc.nix { }; 33 | default = self.packages.${system}.nix-ld; 34 | 35 | # Cross-compiled package for riscv64 (only available on x86_64-linux) 36 | } // lib.optionalAttrs (system == "x86_64-linux") { 37 | nix-ld-riscv64 = pkgs.pkgsCross.riscv64.callPackage ./package.nix { }; 38 | } 39 | ); 40 | 41 | checks = forAllSystems ( 42 | { pkgs, system, ... }: 43 | let 44 | nixosTests = pkgs.callPackage ./nixos-tests { }; 45 | packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self.packages.${system}; 46 | devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self.devShells.${system}; 47 | in 48 | packages 49 | // devShells 50 | // lib.optionalAttrs (system != "i686-linux") { 51 | # test driver is broken on i686-linux 52 | inherit (nixosTests) basic; 53 | } 54 | // { 55 | clippy = self.packages.${system}.nix-ld.override { 56 | enableClippy = true; 57 | }; 58 | } 59 | ); 60 | 61 | devShells = forAllSystems ( 62 | { pkgs, system, ... }: 63 | { 64 | nix-ld = pkgs.mkShell { 65 | nativeBuildInputs = [ 66 | pkgs.rustc 67 | pkgs.cargo 68 | pkgs.cargo-watch 69 | pkgs.cargo-bloat 70 | pkgs.cargo-nextest 71 | pkgs.clippy 72 | pkgs.just 73 | 74 | ]; 75 | 76 | hardeningDisable = [ "stackprotector" ]; 77 | 78 | # For convenience in devShell 79 | DEFAULT_NIX_LD = pkgs.stdenv.cc.bintools.dynamicLinker; 80 | NIX_LD = pkgs.stdenv.cc.bintools.dynamicLinker; 81 | 82 | RUSTC_BOOTSTRAP = "1"; 83 | 84 | shellHook = '' 85 | echo "nix-ld development environment" 86 | '' + lib.optionalString (system == "x86_64-linux") '' 87 | echo "Available cross-compilation shell:" 88 | echo " nix develop .#cross-riscv64 - Cross compile to riscv64" 89 | ''; 90 | }; 91 | 92 | # Default cross shell for current system 93 | default = self.devShells.${system}.nix-ld; 94 | } // lib.optionalAttrs (system == "x86_64-linux") { 95 | # Cross compilation shell for riscv64 (only available on x86_64-linux) 96 | cross-riscv64 = pkgs.pkgsCross.riscv64.callPackage ./riscv64-shell.nix { }; 97 | } 98 | ); 99 | } 100 | // { 101 | nixosModules.nix-ld = import ./modules/nix-ld.nix; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /src/auxv.rs: -------------------------------------------------------------------------------- 1 | //! auxv wrangling. 2 | 3 | use core::ffi::c_void; 4 | use core::marker::PhantomData; 5 | use core::ops::Deref; 6 | 7 | use crate::elf::elf_types::program_header::ProgramHeader; 8 | 9 | pub const AT_PHDR: usize = 3; 10 | pub const AT_PHENT: usize = 4; 11 | pub const AT_PHNUM: usize = 5; 12 | pub const AT_PAGESZ: usize = 6; 13 | pub const AT_BASE: usize = 7; 14 | pub const AT_ENTRY: usize = 9; 15 | 16 | #[derive(Debug, Default)] 17 | pub struct AuxVec { 18 | ptr: Option<*const usize>, 19 | auxvc: Option, 20 | pub at_base: Option>, 21 | pub at_entry: Option>, 22 | pub at_phdr: Option>, 23 | pub at_phent: Option, 24 | pub at_phnum: Option, 25 | pub at_pagesz: Option, 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Entry { 30 | ptr: *const usize, 31 | _phantom: PhantomData, 32 | } 33 | 34 | pub struct BorrowedEntry<'a> { 35 | entry: Entry, 36 | _phantom: PhantomData<&'a ()>, 37 | } 38 | 39 | pub struct AuxVecIter<'a> { 40 | auxv: &'a AuxVec, 41 | index: usize, 42 | } 43 | 44 | impl AuxVec { 45 | pub unsafe fn from_raw(ptr: *const usize) -> Self { 46 | let mut auxv = Self { 47 | ptr: Some(ptr), 48 | ..Self::default() 49 | }; 50 | 51 | let mut at_base = None; 52 | let mut at_entry = None; 53 | let mut at_phdr = None; 54 | let mut at_phent = None; 55 | let mut at_phnum = None; 56 | let mut at_pagesz = None; 57 | let mut auxvc = 0; 58 | 59 | for entry in auxv.iter() { 60 | match entry.key() { 61 | AT_BASE => at_base = Some(entry.steal()), 62 | AT_ENTRY => at_entry = Some(entry.steal()), 63 | AT_PHDR => at_phdr = Some(entry.steal()), 64 | AT_PHENT => at_phent = Some(entry.steal()), 65 | AT_PHNUM => at_phnum = Some(entry.steal()), 66 | AT_PAGESZ => at_pagesz = Some(entry.steal()), 67 | _ => {} 68 | } 69 | auxvc += 1; 70 | } 71 | 72 | auxv.at_base = at_base; 73 | auxv.at_entry = at_entry; 74 | auxv.at_phdr = at_phdr; 75 | auxv.at_phent = at_phent; 76 | auxv.at_phnum = at_phnum; 77 | auxv.at_pagesz = at_pagesz; 78 | auxv.auxvc = Some(auxvc); 79 | auxv 80 | } 81 | 82 | pub fn as_ptr(&self) -> Option<*const usize> { 83 | self.ptr 84 | } 85 | 86 | pub fn count(&self) -> Option { 87 | self.auxvc 88 | } 89 | 90 | pub fn iter(&self) -> AuxVecIter { 91 | AuxVecIter { 92 | auxv: self, 93 | index: 0, 94 | } 95 | } 96 | } 97 | 98 | impl Entry { 99 | pub fn key(&self) -> usize { 100 | unsafe { *self.ptr } 101 | } 102 | 103 | pub fn set(&mut self, value: T) { 104 | unsafe { 105 | let valp = self.value_ptr().cast_mut(); 106 | *valp = value; 107 | } 108 | } 109 | 110 | fn value_ptr(&self) -> *const T { 111 | unsafe { self.ptr.add(1).cast() } 112 | } 113 | 114 | fn steal(&self) -> Entry { 115 | // AuxVec/AuxVecIter only gives out references so lifetime is enforced 116 | Entry { 117 | ptr: self.ptr.cast(), 118 | _phantom: PhantomData, 119 | } 120 | } 121 | } 122 | 123 | impl Entry { 124 | pub fn value(&self) -> T { 125 | unsafe { *self.value_ptr() } 126 | } 127 | } 128 | 129 | impl BorrowedEntry<'_> { 130 | fn new(entry: Entry) -> Self { 131 | Self { 132 | entry, 133 | _phantom: PhantomData, 134 | } 135 | } 136 | } 137 | 138 | impl Deref for BorrowedEntry<'_> { 139 | type Target = Entry; 140 | 141 | fn deref(&self) -> &Self::Target { 142 | &self.entry 143 | } 144 | } 145 | 146 | impl<'a> Iterator for AuxVecIter<'a> { 147 | type Item = BorrowedEntry<'a>; 148 | 149 | fn next(&mut self) -> Option { 150 | let auxvp = self.auxv.ptr?; 151 | let entryp = unsafe { auxvp.add(self.index * 2) }; 152 | if unsafe { *entryp } == 0 { 153 | return None; 154 | } 155 | 156 | let entry = BorrowedEntry::new(Entry { 157 | ptr: entryp, 158 | _phantom: PhantomData, 159 | }); 160 | 161 | self.index += 1; 162 | 163 | Some(entry) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/const_concat.rs: -------------------------------------------------------------------------------- 1 | //! Const concatenation utilities 2 | //! 3 | //! This module provides compile-time string and byte slice concatenation 4 | //! functionality to replace the external `constcat` crate dependency. 5 | 6 | /// Concatenate const string expressions into a static string slice. 7 | /// 8 | /// This macro works similarly to `std::concat!` but supports const variables 9 | /// and expressions, not just literals. 10 | #[macro_export] 11 | macro_rules! const_concat { 12 | ($($expr:expr),* $(,)?) => {{ 13 | const fn concat_str_len(strings: &[&str]) -> usize { 14 | let mut total_len = 0; 15 | let mut i = 0; 16 | while i < strings.len() { 17 | let s = strings[i]; 18 | let mut j = 0; 19 | while j < s.len() { 20 | total_len += 1; 21 | j += 1; 22 | } 23 | i += 1; 24 | } 25 | total_len 26 | } 27 | 28 | const fn concat_str(_strings: &[&str]) -> &'static str { 29 | const STRINGS: &[&str] = &[$($expr),*]; 30 | 31 | const LEN: usize = concat_str_len(STRINGS); 32 | const fn inner() -> [u8; LEN] { 33 | let mut result = [0u8; LEN]; 34 | let mut pos = 0; 35 | let mut i = 0; 36 | while i < STRINGS.len() { 37 | let s = STRINGS[i]; 38 | let bytes = s.as_bytes(); 39 | let mut j = 0; 40 | while j < bytes.len() { 41 | result[pos] = bytes[j]; 42 | pos += 1; 43 | j += 1; 44 | } 45 | i += 1; 46 | } 47 | result 48 | } 49 | const BYTES: [u8; LEN] = inner(); 50 | unsafe { core::str::from_utf8_unchecked(&BYTES) } 51 | } 52 | concat_str(&[$($expr),*]) 53 | }}; 54 | } 55 | 56 | /// Concatenate const byte slice expressions into a static byte slice. 57 | /// 58 | /// This macro concatenates const `&[u8]` expressions and literals into a static 59 | /// byte slice, supporting both literals and const variables. 60 | #[macro_export] 61 | macro_rules! const_concat_slices { 62 | ([$type:ty]: $($expr:expr),* $(,)?) => {{ 63 | const fn concat_slices_len(slices: &[&[T]]) -> usize { 64 | let mut total_len = 0; 65 | let mut i = 0; 66 | while i < slices.len() { 67 | total_len += slices[i].len(); 68 | i += 1; 69 | } 70 | total_len 71 | } 72 | 73 | const fn concat_slices(slices: &[&[T]]) -> [T; N] { 74 | let mut result = [slices[0][0]; N]; // Initialize with first element 75 | let mut pos = 0; 76 | let mut i = 0; 77 | while i < slices.len() { 78 | let slice = slices[i]; 79 | let mut j = 0; 80 | while j < slice.len() { 81 | result[pos] = slice[j]; 82 | pos += 1; 83 | j += 1; 84 | } 85 | i += 1; 86 | } 87 | result 88 | } 89 | 90 | const SLICES: &[&[$type]] = &[$($expr),*]; 91 | const LEN: usize = concat_slices_len(SLICES); 92 | const RESULT: [$type; LEN] = concat_slices::<$type, LEN>(SLICES); 93 | &RESULT 94 | }}; 95 | } 96 | 97 | /// Re-export the macros with constcat-compatible names 98 | pub use const_concat as concat; 99 | pub use const_concat_slices as concat_slices; 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | 104 | 105 | #[test] 106 | fn test_const_concat() { 107 | const PREFIX: &str = "NIX_LD_"; 108 | const SUFFIX: &str = "x86_64_linux"; 109 | const RESULT: &str = const_concat!(PREFIX, SUFFIX); 110 | assert_eq!(RESULT, "NIX_LD_x86_64_linux"); 111 | } 112 | 113 | #[test] 114 | fn test_const_concat_slices() { 115 | const PATH: &[u8] = b"/run/current-system/sw/share/nix-ld/lib/ld.so"; 116 | const NULL: &[u8] = b"\0"; 117 | const RESULT: &[u8] = const_concat_slices!([u8]: PATH, NULL); 118 | assert_eq!(RESULT, b"/run/current-system/sw/share/nix-ld/lib/ld.so\0"); 119 | } 120 | 121 | #[test] 122 | fn test_multiple_concat() { 123 | const A: &str = "NIX_"; 124 | const B: &str = "LD_"; 125 | const C: &str = "LIBRARY_"; 126 | const D: &str = "PATH"; 127 | const RESULT: &str = const_concat!(A, B, C, D); 128 | assert_eq!(RESULT, "NIX_LD_LIBRARY_PATH"); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/sys.rs: -------------------------------------------------------------------------------- 1 | //! System interface. 2 | //! 3 | //! We currently use `nolibc` for the following things: 4 | //! 5 | //! - `_start` code 6 | //! - Thin syscall wrappers like `open()`, `read()`, `write()` (can be 7 | //! replaced by `syscalls`) 8 | //! 9 | //! This dependency may be reduced further. For memory operations, 10 | //! compiler-builtins provides faster implementations. 11 | 12 | use core::ffi::{CStr, c_char, c_int, c_void}; 13 | use core::fmt; 14 | use core::ptr; 15 | use core::slice; 16 | 17 | use embedded_io as eio; 18 | pub use embedded_io::{Read, Write}; 19 | #[rustfmt::skip] 20 | pub use linux_raw_sys::general::{ 21 | O_RDONLY, 22 | PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC, 23 | MAP_PRIVATE, MAP_FIXED, MAP_ANONYMOUS, 24 | }; 25 | use heapless::Vec as ArrayVec; 26 | pub use linux_raw_sys::errno; 27 | 28 | #[link(name = "c_kinda", kind = "static")] 29 | unsafe extern "C" { 30 | pub fn write(fd: i32, buf: *const u8, count: usize) -> isize; 31 | #[must_use] 32 | pub fn mmap( 33 | addr: *mut c_void, 34 | len: usize, 35 | prot: u32, 36 | flags: u32, 37 | fd: i32, 38 | offset: isize, 39 | ) -> *mut c_void; 40 | pub fn munmap(addr: *mut c_void, len: usize) -> c_int; 41 | pub fn open(path: *const c_char, oflag: u32, _: ...) -> c_int; 42 | pub fn read(fd: i32, buf: *mut c_void, count: usize) -> isize; 43 | pub fn close(fd: i32) -> c_int; 44 | pub fn abort() -> !; 45 | pub fn memset(dst: *mut c_void, c: c_int, n: usize) -> *mut c_void; 46 | pub fn execve(prog: *const c_char, argv: *const *const u8, envp: *const *const u8) -> c_int; 47 | 48 | #[link_name = "errno"] 49 | static c_errno: u32; 50 | } 51 | 52 | pub const MAP_FAILED: *mut c_void = !0 as *mut c_void; 53 | 54 | macro_rules! if_ok { 55 | ($ret:ident, $expr:expr) => { 56 | if $ret < 0 { 57 | Err(Error::Posix(errno())) 58 | } else { 59 | Ok($expr) 60 | } 61 | }; 62 | ($ret:ident $($rest:tt)+) => { 63 | if_ok!($ret, $ret $($rest)*) 64 | }; 65 | } 66 | 67 | /// A file. 68 | #[derive(Debug)] 69 | pub struct File(c_int); 70 | 71 | /// An error. 72 | /// 73 | /// TODO: Convert to human-readable form. 74 | #[derive(Debug)] 75 | pub enum Error { 76 | Posix(u32), 77 | PathTooLong, 78 | Unknown, 79 | } 80 | 81 | impl File { 82 | /// Opens a file. 83 | /// 84 | /// This copies the path into a temporary buffer to convert it 85 | /// into a NUL-terminated string. 86 | pub fn open(path: &[u8]) -> Result { 87 | let mut temp = ArrayVec::<_, 100>::from_slice(path).map_err(|_| Error::PathTooLong)?; 88 | temp.push(0).map_err(|_| Error::PathTooLong)?; 89 | let ret = unsafe { open(temp.as_ptr().cast(), O_RDONLY, 0) }; 90 | if_ok!(ret, Self(ret)) 91 | } 92 | 93 | /// Opens a file. 94 | pub fn open_cstr(path: &CStr) -> Result { 95 | let ret = unsafe { open(path.as_ptr(), O_RDONLY, 0) }; 96 | if_ok!(ret, Self(ret)) 97 | } 98 | 99 | /// Returns the underlying file descriptor number. 100 | pub fn as_raw_fd(&self) -> c_int { 101 | self.0 102 | } 103 | } 104 | 105 | impl Drop for File { 106 | fn drop(&mut self) { 107 | if self.0 > 2 { 108 | unsafe { close(self.0) }; 109 | } 110 | } 111 | } 112 | 113 | impl Read for File { 114 | fn read(&mut self, buf: &mut [u8]) -> Result { 115 | let ret = unsafe { read(self.0, buf.as_mut_ptr().cast(), buf.len()) }; 116 | if_ok!(ret as usize) 117 | } 118 | } 119 | 120 | impl Write for File { 121 | fn write(&mut self, buf: &[u8]) -> Result { 122 | let ret = unsafe { write(self.0, buf.as_ptr(), buf.len()) }; 123 | if_ok!(ret as usize) 124 | } 125 | fn flush(&mut self) -> Result<(), Self::Error> { 126 | Ok(()) 127 | } 128 | } 129 | 130 | impl eio::ErrorType for File { 131 | type Error = Error; 132 | } 133 | 134 | impl fmt::Write for File { 135 | fn write_str(&mut self, buf: &str) -> fmt::Result { 136 | eio::Write::write(self, buf.as_bytes()).map_err(|_| fmt::Error)?; 137 | 138 | Ok(()) 139 | } 140 | } 141 | 142 | impl eio::Error for Error { 143 | fn kind(&self) -> embedded_io::ErrorKind { 144 | eio::ErrorKind::Other 145 | } 146 | } 147 | 148 | impl PartialEq for Error { 149 | fn eq(&self, other: &u32) -> bool { 150 | matches!(self, Self::Posix(num) if num == other) 151 | } 152 | } 153 | 154 | pub const fn stderr() -> impl fmt::Write { 155 | File(2) 156 | } 157 | 158 | #[inline(always)] 159 | pub fn errno() -> u32 { 160 | unsafe { c_errno } 161 | } 162 | 163 | pub fn new_slice_leak(size: usize) -> Option<&'static mut [u8]> { 164 | let ptr = unsafe { 165 | mmap( 166 | ptr::null_mut(), 167 | size, 168 | PROT_READ | PROT_WRITE, 169 | MAP_PRIVATE | MAP_ANONYMOUS, 170 | -1, 171 | 0, 172 | ) 173 | }; 174 | 175 | if ptr == MAP_FAILED { 176 | None 177 | } else { 178 | Some(unsafe { slice::from_raw_parts_mut(ptr as *mut u8, size) }) 179 | } 180 | } 181 | 182 | #[cfg(not(test))] 183 | #[lang = "eh_personality"] 184 | pub extern "C" fn rust_eh_personality() {} 185 | -------------------------------------------------------------------------------- /vendor/nolibc/nolibc.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* nolibc.h 3 | * Copyright (C) 2017-2018 Willy Tarreau 4 | */ 5 | 6 | /* 7 | * This file is designed to be used as a libc alternative for minimal programs 8 | * with very limited requirements. It consists of a small number of syscall and 9 | * type definitions, and the minimal startup code needed to call main(). 10 | * All syscalls are declared as static functions so that they can be optimized 11 | * away by the compiler when not used. 12 | * 13 | * Syscalls are split into 3 levels: 14 | * - The lower level is the arch-specific syscall() definition, consisting in 15 | * assembly code in compound expressions. These are called my_syscall0() to 16 | * my_syscall6() depending on the number of arguments. The MIPS 17 | * implementation is limited to 5 arguments. All input arguments are cast 18 | * to a long stored in a register. These expressions always return the 19 | * syscall's return value as a signed long value which is often either a 20 | * pointer or the negated errno value. 21 | * 22 | * - The second level is mostly architecture-independent. It is made of 23 | * static functions called sys_() which rely on my_syscallN() 24 | * depending on the syscall definition. These functions are responsible 25 | * for exposing the appropriate types for the syscall arguments (int, 26 | * pointers, etc) and for setting the appropriate return type (often int). 27 | * A few of them are architecture-specific because the syscalls are not all 28 | * mapped exactly the same among architectures. For example, some archs do 29 | * not implement select() and need pselect6() instead, so the sys_select() 30 | * function will have to abstract this. 31 | * 32 | * - The third level is the libc call definition. It exposes the lower raw 33 | * sys_() calls in a way that looks like what a libc usually does, 34 | * takes care of specific input values, and of setting errno upon error. 35 | * There can be minor variations compared to standard libc calls. For 36 | * example the open() call always takes 3 args here. 37 | * 38 | * The errno variable is declared static and unused. This way it can be 39 | * optimized away if not used. However this means that a program made of 40 | * multiple C files may observe different errno values (one per C file). For 41 | * the type of programs this project targets it usually is not a problem. The 42 | * resulting program may even be reduced by defining the NOLIBC_IGNORE_ERRNO 43 | * macro, in which case the errno value will never be assigned. 44 | * 45 | * Some stdint-like integer types are defined. These are valid on all currently 46 | * supported architectures, because signs are enforced, ints are assumed to be 47 | * 32 bits, longs the size of a pointer and long long 64 bits. If more 48 | * architectures have to be supported, this may need to be adapted. 49 | * 50 | * Some macro definitions like the O_* values passed to open(), and some 51 | * structures like the sys_stat struct depend on the architecture. 52 | * 53 | * The definitions start with the architecture-specific parts, which are picked 54 | * based on what the compiler knows about the target architecture, and are 55 | * completed with the generic code. Since it is the compiler which sets the 56 | * target architecture, cross-compiling normally works out of the box without 57 | * having to specify anything. 58 | * 59 | * Finally some very common libc-level functions are provided. It is the case 60 | * for a few functions usually found in string.h, ctype.h, or stdlib.h. 61 | * 62 | * The nolibc.h file is only a convenient entry point which includes all other 63 | * files. It also defines the NOLIBC macro, so that it is possible for a 64 | * program to check this macro to know if it is being built against and decide 65 | * to disable some features or simply not to include some standard libc files. 66 | * 67 | * A simple static executable may be built this way : 68 | * $ gcc -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \ 69 | * -static -include nolibc.h -o hello hello.c -lgcc 70 | * 71 | * Simple programs meant to be reasonably portable to various libc and using 72 | * only a few common includes, may also be built by simply making the include 73 | * path point to the nolibc directory: 74 | * $ gcc -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \ 75 | * -I../nolibc -o hello hello.c -lgcc 76 | * 77 | * The available standard (but limited) include files are: 78 | * ctype.h, errno.h, signal.h, stdio.h, stdlib.h, string.h, time.h 79 | * 80 | * In addition, the following ones are expected to be provided by the compiler: 81 | * float.h, stdarg.h, stddef.h 82 | * 83 | * The following ones which are part to the C standard are not provided: 84 | * assert.h, locale.h, math.h, setjmp.h, limits.h 85 | * 86 | * A very useful calling convention table may be found here : 87 | * http://man7.org/linux/man-pages/man2/syscall.2.html 88 | * 89 | * This doc is quite convenient though not necessarily up to date : 90 | * https://w3challs.com/syscalls/ 91 | * 92 | */ 93 | #ifndef _NOLIBC_H 94 | #define _NOLIBC_H 95 | 96 | #include "std.h" 97 | #include "arch.h" 98 | #include "types.h" 99 | #include "sys.h" 100 | #include "ctype.h" 101 | #include "signal.h" 102 | #include "stdio.h" 103 | #include "stdlib.h" 104 | #include "string.h" 105 | #include "time.h" 106 | #include "unistd.h" 107 | 108 | /* Used by programs to avoid std includes */ 109 | #define NOLIBC 110 | 111 | #endif /* _NOLIBC_H */ 112 | -------------------------------------------------------------------------------- /src/fixup.rs: -------------------------------------------------------------------------------- 1 | //! Relocation fixups. 2 | //! 3 | //! panic!()/unwrap() cannot be used here. 4 | 5 | use core::mem; 6 | use core::ptr; 7 | use core::slice; 8 | 9 | use crate::arch::R_RELATIVE; 10 | use crate::auxv::AuxVec; 11 | use crate::elf::{ 12 | ProgramHeaders, elf_types, 13 | elf_types::dynamic::{DT_NULL, DT_REL, DT_RELA, DT_RELAENT, DT_RELASZ, DT_RELENT, DT_RELSZ}, 14 | elf_types::header::Header, 15 | elf_types::program_header::PT_DYNAMIC, 16 | }; 17 | use crate::support::explode; 18 | 19 | struct Dynamic { 20 | ptr: *const elf_types::dynamic::Dyn, 21 | load_offset: usize, 22 | } 23 | 24 | impl Dynamic { 25 | fn from_program_headers(phs: &ProgramHeaders, load_offset: usize) -> Option { 26 | phs.iter().find(|ph| ph.p_type == PT_DYNAMIC).map(|ph| { 27 | let ptr = 28 | load_offset.wrapping_add(ph.p_vaddr as usize) as *const elf_types::dynamic::Dyn; 29 | Self { ptr, load_offset } 30 | }) 31 | } 32 | 33 | fn fixup(&self) { 34 | let mut cur = self.ptr; 35 | 36 | let mut rela_data = None; 37 | let mut rela_len = None; 38 | 39 | let mut rel_data = None; 40 | let mut rel_len = None; 41 | 42 | loop { 43 | let entry = unsafe { &*cur }; 44 | 45 | #[allow(clippy::unnecessary_cast)] // it's necessary with ELF32 46 | match entry.d_tag as u64 { 47 | DT_NULL => break, 48 | 49 | // DT_RELA 50 | DT_RELA => { 51 | rela_data = Some(entry.d_val.wrapping_add(self.load_offset as _) 52 | as *const elf_types::reloc::Rela); 53 | } 54 | DT_RELASZ => { 55 | rela_len = 56 | Some(entry.d_val as usize / mem::size_of::()); 57 | } 58 | DT_RELAENT => { 59 | let actual_size = entry.d_val as usize; 60 | if actual_size != mem::size_of::() { 61 | explode("DT_RELAENT has unsupported size"); 62 | } 63 | } 64 | 65 | // DT_REL 66 | DT_REL => { 67 | rel_data = Some(entry.d_val.wrapping_add(self.load_offset as _) 68 | as *const elf_types::reloc::Rel); 69 | } 70 | DT_RELSZ => { 71 | rel_len = Some(entry.d_val as usize / mem::size_of::()); 72 | } 73 | DT_RELENT => { 74 | let actual_size = entry.d_val as usize; 75 | if actual_size != mem::size_of::() { 76 | explode("DT_RELENT has unsupported size"); 77 | } 78 | } 79 | 80 | _ => {} 81 | } 82 | 83 | cur = unsafe { cur.add(1) }; 84 | } 85 | 86 | if let (Some(rela_data), Some(rela_len)) = (rela_data, rela_len) { 87 | let rela = unsafe { slice::from_raw_parts(rela_data, rela_len) }; 88 | for reloc in rela { 89 | let r_type = elf_types::reloc::r_type(reloc.r_info); 90 | if r_type != R_RELATIVE { 91 | explode("Unsupported relocation type"); 92 | } 93 | 94 | let ptr = self.load_offset.wrapping_add(reloc.r_offset as usize) as *mut usize; 95 | unsafe { 96 | *ptr = self.load_offset.wrapping_add(reloc.r_addend as usize); 97 | } 98 | } 99 | } 100 | 101 | if let (Some(rel_data), Some(rel_len)) = (rel_data, rel_len) { 102 | let rel = unsafe { slice::from_raw_parts(rel_data, rel_len) }; 103 | for reloc in rel { 104 | let r_type = elf_types::reloc::r_type(reloc.r_info); 105 | if r_type != R_RELATIVE { 106 | explode("Unsupported relocation type"); 107 | } 108 | 109 | let ptr = self.load_offset.wrapping_add(reloc.r_offset as usize) as *mut usize; 110 | unsafe { 111 | let addend = *ptr; 112 | let relocated = self.load_offset.wrapping_add(addend); 113 | *ptr = relocated; 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | pub unsafe fn fixup_relocs(envp: *const *const u8) { 121 | unsafe { 122 | // Reference: 123 | let auxv = find_auxv(envp); 124 | let auxv = AuxVec::from_raw(auxv); 125 | 126 | let at_base = auxv.at_base.as_ref().map_or_else(ptr::null, |v| v.value()); 127 | let (load_offset, phs) = if at_base.is_null() { 128 | // We were executed directly 129 | if let (Some(phdr), Some(phent), Some(phnum)) = 130 | (&auxv.at_phdr, &auxv.at_phent, &auxv.at_phnum) 131 | { 132 | if phdr.value().is_null() { 133 | explode("AT_PHDR is null"); 134 | } 135 | let phs = ProgramHeaders::from_raw(phdr.value(), phent.value(), phnum.value()); 136 | let load_offset = phdr.value() as usize - mem::size_of::
(); 137 | (load_offset, phs) 138 | } else { 139 | explode("AT_PHDR, AT_PHENT, AT_PHNUM must exist"); 140 | } 141 | } else { 142 | // We are the loader 143 | let ehdr: *const Header = at_base.cast(); 144 | let header: &Header = &*ehdr; 145 | 146 | if &header.e_ident[..4] != b"\x7fELF".as_slice() { 147 | explode("We are not an ELF ourselves"); 148 | } 149 | 150 | let phdr = ehdr.add(1).cast(); 151 | 152 | let phs = ProgramHeaders::from_raw( 153 | phdr, 154 | header.e_phentsize as usize, 155 | header.e_phnum as usize, 156 | ); 157 | (at_base as usize, phs) 158 | }; 159 | 160 | let dynamic = if let Some(dynamic) = Dynamic::from_program_headers(&phs, load_offset) { 161 | dynamic 162 | } else { 163 | explode("No dynamic section in own executable"); 164 | }; 165 | 166 | dynamic.fixup(); 167 | } 168 | } 169 | 170 | unsafe fn find_auxv(envp: *const *const u8) -> *const usize { 171 | unsafe { 172 | let mut cur = envp; 173 | while !(*cur).is_null() { 174 | cur = cur.add(1); 175 | } 176 | cur.add(1) as *const usize 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /vendor/nolibc/string.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * string function definitions for NOLIBC 4 | * Copyright (C) 2017-2021 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_STRING_H 8 | #define _NOLIBC_STRING_H 9 | 10 | #include "std.h" 11 | 12 | static void *malloc(size_t len); 13 | 14 | /* 15 | * As much as possible, please keep functions alphabetically sorted. 16 | */ 17 | 18 | static __attribute__((unused)) 19 | int memcmp(const void *s1, const void *s2, size_t n) 20 | { 21 | size_t ofs = 0; 22 | int c1 = 0; 23 | 24 | while (ofs < n && !(c1 = ((unsigned char *)s1)[ofs] - ((unsigned char *)s2)[ofs])) { 25 | ofs++; 26 | } 27 | return c1; 28 | } 29 | 30 | static __attribute__((unused)) 31 | void *_nolibc_memcpy_up(void *dst, const void *src, size_t len) 32 | { 33 | size_t pos = 0; 34 | 35 | while (pos < len) { 36 | ((char *)dst)[pos] = ((const char *)src)[pos]; 37 | pos++; 38 | } 39 | return dst; 40 | } 41 | 42 | static __attribute__((unused)) 43 | void *_nolibc_memcpy_down(void *dst, const void *src, size_t len) 44 | { 45 | while (len) { 46 | len--; 47 | ((char *)dst)[len] = ((const char *)src)[len]; 48 | } 49 | return dst; 50 | } 51 | 52 | /* might be ignored by the compiler without -ffreestanding, then found as 53 | * missing. 54 | */ 55 | __attribute__((weak,unused,section(".text.nolibc_memmove"))) 56 | void *memmove(void *dst, const void *src, size_t len) 57 | { 58 | size_t dir, pos; 59 | 60 | pos = len; 61 | dir = -1; 62 | 63 | if (dst < src) { 64 | pos = -1; 65 | dir = 1; 66 | } 67 | 68 | while (len) { 69 | pos += dir; 70 | ((char *)dst)[pos] = ((const char *)src)[pos]; 71 | len--; 72 | } 73 | return dst; 74 | } 75 | 76 | /* must be exported, as it's used by libgcc on ARM */ 77 | __attribute__((weak,unused,section(".text.nolibc_memcpy"))) 78 | void *memcpy(void *dst, const void *src, size_t len) 79 | { 80 | return _nolibc_memcpy_up(dst, src, len); 81 | } 82 | 83 | /* might be ignored by the compiler without -ffreestanding, then found as 84 | * missing. 85 | */ 86 | __attribute__((weak,unused,section(".text.nolibc_memset"))) 87 | void *memset(void *dst, int b, size_t len) 88 | { 89 | char *p = dst; 90 | 91 | while (len--) { 92 | /* prevent gcc from recognizing memset() here */ 93 | asm volatile(""); 94 | *(p++) = b; 95 | } 96 | return dst; 97 | } 98 | 99 | static __attribute__((unused)) 100 | char *strchr(const char *s, int c) 101 | { 102 | while (*s) { 103 | if (*s == (char)c) 104 | return (char *)s; 105 | s++; 106 | } 107 | return NULL; 108 | } 109 | 110 | static __attribute__((unused)) 111 | int strcmp(const char *a, const char *b) 112 | { 113 | unsigned int c; 114 | int diff; 115 | 116 | while (!(diff = (unsigned char)*a++ - (c = (unsigned char)*b++)) && c) 117 | ; 118 | return diff; 119 | } 120 | 121 | static __attribute__((unused)) 122 | char *strcpy(char *dst, const char *src) 123 | { 124 | char *ret = dst; 125 | 126 | while ((*dst++ = *src++)); 127 | return ret; 128 | } 129 | 130 | /* this function is only used with arguments that are not constants or when 131 | * it's not known because optimizations are disabled. Note that gcc 12 132 | * recognizes an strlen() pattern and replaces it with a jump to strlen(), 133 | * thus itself, hence the asm() statement below that's meant to disable this 134 | * confusing practice. 135 | */ 136 | static __attribute__((unused)) 137 | size_t strlen(const char *str) 138 | { 139 | size_t len; 140 | 141 | for (len = 0; str[len]; len++) 142 | asm(""); 143 | return len; 144 | } 145 | 146 | /* do not trust __builtin_constant_p() at -O0, as clang will emit a test and 147 | * the two branches, then will rely on an external definition of strlen(). 148 | */ 149 | #if defined(__OPTIMIZE__) 150 | #define nolibc_strlen(x) strlen(x) 151 | #define strlen(str) ({ \ 152 | __builtin_constant_p((str)) ? \ 153 | __builtin_strlen((str)) : \ 154 | nolibc_strlen((str)); \ 155 | }) 156 | #endif 157 | 158 | static __attribute__((unused)) 159 | size_t strnlen(const char *str, size_t maxlen) 160 | { 161 | size_t len; 162 | 163 | for (len = 0; (len < maxlen) && str[len]; len++); 164 | return len; 165 | } 166 | 167 | static __attribute__((unused)) 168 | char *strdup(const char *str) 169 | { 170 | size_t len; 171 | char *ret; 172 | 173 | len = strlen(str); 174 | ret = malloc(len + 1); 175 | if (__builtin_expect(ret != NULL, 1)) 176 | memcpy(ret, str, len + 1); 177 | 178 | return ret; 179 | } 180 | 181 | static __attribute__((unused)) 182 | char *strndup(const char *str, size_t maxlen) 183 | { 184 | size_t len; 185 | char *ret; 186 | 187 | len = strnlen(str, maxlen); 188 | ret = malloc(len + 1); 189 | if (__builtin_expect(ret != NULL, 1)) { 190 | memcpy(ret, str, len); 191 | ret[len] = '\0'; 192 | } 193 | 194 | return ret; 195 | } 196 | 197 | static __attribute__((unused)) 198 | size_t strlcat(char *dst, const char *src, size_t size) 199 | { 200 | size_t len; 201 | char c; 202 | 203 | for (len = 0; dst[len]; len++) 204 | ; 205 | 206 | for (;;) { 207 | c = *src; 208 | if (len < size) 209 | dst[len] = c; 210 | if (!c) 211 | break; 212 | len++; 213 | src++; 214 | } 215 | 216 | return len; 217 | } 218 | 219 | static __attribute__((unused)) 220 | size_t strlcpy(char *dst, const char *src, size_t size) 221 | { 222 | size_t len; 223 | char c; 224 | 225 | for (len = 0;;) { 226 | c = src[len]; 227 | if (len < size) 228 | dst[len] = c; 229 | if (!c) 230 | break; 231 | len++; 232 | } 233 | return len; 234 | } 235 | 236 | static __attribute__((unused)) 237 | char *strncat(char *dst, const char *src, size_t size) 238 | { 239 | char *orig = dst; 240 | 241 | while (*dst) 242 | dst++; 243 | 244 | while (size && (*dst = *src)) { 245 | src++; 246 | dst++; 247 | size--; 248 | } 249 | 250 | *dst = 0; 251 | return orig; 252 | } 253 | 254 | static __attribute__((unused)) 255 | int strncmp(const char *a, const char *b, size_t size) 256 | { 257 | unsigned int c; 258 | int diff = 0; 259 | 260 | while (size-- && 261 | !(diff = (unsigned char)*a++ - (c = (unsigned char)*b++)) && c) 262 | ; 263 | 264 | return diff; 265 | } 266 | 267 | static __attribute__((unused)) 268 | char *strncpy(char *dst, const char *src, size_t size) 269 | { 270 | size_t len; 271 | 272 | for (len = 0; len < size; len++) 273 | if ((dst[len] = *src)) 274 | src++; 275 | return dst; 276 | } 277 | 278 | static __attribute__((unused)) 279 | char *strrchr(const char *s, int c) 280 | { 281 | const char *ret = NULL; 282 | 283 | while (*s) { 284 | if (*s == (char)c) 285 | ret = s; 286 | s++; 287 | } 288 | return (char *)ret; 289 | } 290 | 291 | /* make sure to include all global symbols */ 292 | #include "nolibc.h" 293 | 294 | #endif /* _NOLIBC_STRING_H */ 295 | -------------------------------------------------------------------------------- /vendor/nolibc/types.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * Special types used by various syscalls for NOLIBC 4 | * Copyright (C) 2017-2021 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_TYPES_H 8 | #define _NOLIBC_TYPES_H 9 | 10 | #include "std.h" 11 | #include 12 | 13 | 14 | /* Only the generic macros and types may be defined here. The arch-specific 15 | * ones such as the O_RDONLY and related macros used by fcntl() and open(), or 16 | * the layout of sys_stat_struct must not be defined here. 17 | */ 18 | 19 | /* stat flags (WARNING, octal here) */ 20 | #define S_IFDIR 0040000 21 | #define S_IFCHR 0020000 22 | #define S_IFBLK 0060000 23 | #define S_IFREG 0100000 24 | #define S_IFIFO 0010000 25 | #define S_IFLNK 0120000 26 | #define S_IFSOCK 0140000 27 | #define S_IFMT 0170000 28 | 29 | #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) 30 | #define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) 31 | #define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) 32 | #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) 33 | #define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) 34 | #define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) 35 | #define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) 36 | 37 | /* dirent types */ 38 | #define DT_UNKNOWN 0x0 39 | #define DT_FIFO 0x1 40 | #define DT_CHR 0x2 41 | #define DT_DIR 0x4 42 | #define DT_BLK 0x6 43 | #define DT_REG 0x8 44 | #define DT_LNK 0xa 45 | #define DT_SOCK 0xc 46 | 47 | /* commonly an fd_set represents 256 FDs */ 48 | #ifndef FD_SETSIZE 49 | #define FD_SETSIZE 256 50 | #endif 51 | 52 | /* PATH_MAX and MAXPATHLEN are often used and found with plenty of different 53 | * values. 54 | */ 55 | #ifndef PATH_MAX 56 | #define PATH_MAX 4096 57 | #endif 58 | 59 | #ifndef MAXPATHLEN 60 | #define MAXPATHLEN (PATH_MAX) 61 | #endif 62 | 63 | /* Special FD used by all the *at functions */ 64 | #ifndef AT_FDCWD 65 | #define AT_FDCWD (-100) 66 | #endif 67 | 68 | /* whence values for lseek() */ 69 | #define SEEK_SET 0 70 | #define SEEK_CUR 1 71 | #define SEEK_END 2 72 | 73 | /* cmd for reboot() */ 74 | #define LINUX_REBOOT_MAGIC1 0xfee1dead 75 | #define LINUX_REBOOT_MAGIC2 0x28121969 76 | #define LINUX_REBOOT_CMD_HALT 0xcdef0123 77 | #define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc 78 | #define LINUX_REBOOT_CMD_RESTART 0x01234567 79 | #define LINUX_REBOOT_CMD_SW_SUSPEND 0xd000fce2 80 | 81 | /* Macros used on waitpid()'s return status */ 82 | #define WEXITSTATUS(status) (((status) & 0xff00) >> 8) 83 | #define WIFEXITED(status) (((status) & 0x7f) == 0) 84 | 85 | /* waitpid() flags */ 86 | #define WNOHANG 1 87 | 88 | /* standard exit() codes */ 89 | #define EXIT_SUCCESS 0 90 | #define EXIT_FAILURE 1 91 | 92 | #define FD_SETIDXMASK (8 * sizeof(unsigned long)) 93 | #define FD_SETBITMASK (8 * sizeof(unsigned long)-1) 94 | 95 | /* for select() */ 96 | typedef struct { 97 | unsigned long fds[(FD_SETSIZE + FD_SETBITMASK) / FD_SETIDXMASK]; 98 | } fd_set; 99 | 100 | #define FD_CLR(fd, set) do { \ 101 | fd_set *__set = (set); \ 102 | int __fd = (fd); \ 103 | if (__fd >= 0) \ 104 | __set->fds[__fd / FD_SETIDXMASK] &= \ 105 | ~(1U << (__fd & FX_SETBITMASK)); \ 106 | } while (0) 107 | 108 | #define FD_SET(fd, set) do { \ 109 | fd_set *__set = (set); \ 110 | int __fd = (fd); \ 111 | if (__fd >= 0) \ 112 | __set->fds[__fd / FD_SETIDXMASK] |= \ 113 | 1 << (__fd & FD_SETBITMASK); \ 114 | } while (0) 115 | 116 | #define FD_ISSET(fd, set) ({ \ 117 | fd_set *__set = (set); \ 118 | int __fd = (fd); \ 119 | int __r = 0; \ 120 | if (__fd >= 0) \ 121 | __r = !!(__set->fds[__fd / FD_SETIDXMASK] & \ 122 | 1U << (__fd & FD_SET_BITMASK)); \ 123 | __r; \ 124 | }) 125 | 126 | #define FD_ZERO(set) do { \ 127 | fd_set *__set = (set); \ 128 | int __idx; \ 129 | int __size = (FD_SETSIZE+FD_SETBITMASK) / FD_SETIDXMASK;\ 130 | for (__idx = 0; __idx < __size; __idx++) \ 131 | __set->fds[__idx] = 0; \ 132 | } while (0) 133 | 134 | /* for poll() */ 135 | #define POLLIN 0x0001 136 | #define POLLPRI 0x0002 137 | #define POLLOUT 0x0004 138 | #define POLLERR 0x0008 139 | #define POLLHUP 0x0010 140 | #define POLLNVAL 0x0020 141 | 142 | struct pollfd { 143 | int fd; 144 | short int events; 145 | short int revents; 146 | }; 147 | 148 | /* for getdents64() */ 149 | struct linux_dirent64 { 150 | uint64_t d_ino; 151 | int64_t d_off; 152 | unsigned short d_reclen; 153 | unsigned char d_type; 154 | char d_name[]; 155 | }; 156 | 157 | /* needed by wait4() */ 158 | struct rusage { 159 | struct timeval ru_utime; 160 | struct timeval ru_stime; 161 | long ru_maxrss; 162 | long ru_ixrss; 163 | long ru_idrss; 164 | long ru_isrss; 165 | long ru_minflt; 166 | long ru_majflt; 167 | long ru_nswap; 168 | long ru_inblock; 169 | long ru_oublock; 170 | long ru_msgsnd; 171 | long ru_msgrcv; 172 | long ru_nsignals; 173 | long ru_nvcsw; 174 | long ru_nivcsw; 175 | }; 176 | 177 | /* The format of the struct as returned by the libc to the application, which 178 | * significantly differs from the format returned by the stat() syscall flavours. 179 | */ 180 | struct stat { 181 | dev_t st_dev; /* ID of device containing file */ 182 | ino_t st_ino; /* inode number */ 183 | mode_t st_mode; /* protection */ 184 | nlink_t st_nlink; /* number of hard links */ 185 | uid_t st_uid; /* user ID of owner */ 186 | gid_t st_gid; /* group ID of owner */ 187 | dev_t st_rdev; /* device ID (if special file) */ 188 | off_t st_size; /* total size, in bytes */ 189 | blksize_t st_blksize; /* blocksize for file system I/O */ 190 | blkcnt_t st_blocks; /* number of 512B blocks allocated */ 191 | time_t st_atime; /* time of last access */ 192 | time_t st_mtime; /* time of last modification */ 193 | time_t st_ctime; /* time of last status change */ 194 | }; 195 | 196 | /* WARNING, it only deals with the 4096 first majors and 256 first minors */ 197 | #define makedev(major, minor) ((dev_t)((((major) & 0xfff) << 8) | ((minor) & 0xff))) 198 | #define major(dev) ((unsigned int)(((dev) >> 8) & 0xfff)) 199 | #define minor(dev) ((unsigned int)(((dev) & 0xff)) 200 | 201 | #ifndef offsetof 202 | #define offsetof(TYPE, FIELD) ((size_t) &((TYPE *)0)->FIELD) 203 | #endif 204 | 205 | #ifndef container_of 206 | #define container_of(PTR, TYPE, FIELD) ({ \ 207 | __typeof__(((TYPE *)0)->FIELD) *__FIELD_PTR = (PTR); \ 208 | (TYPE *)((char *) __FIELD_PTR - offsetof(TYPE, FIELD)); \ 209 | }) 210 | #endif 211 | 212 | /* make sure to include all global symbols */ 213 | #include "nolibc.h" 214 | 215 | #endif /* _NOLIBC_TYPES_H */ 216 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-s390.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * s390 specific definitions for NOLIBC 4 | */ 5 | 6 | #ifndef _NOLIBC_ARCH_S390_H 7 | #define _NOLIBC_ARCH_S390_H 8 | #include 9 | 10 | /* The struct returned by the stat() syscall, equivalent to stat64(). The 11 | * syscall returns 116 bytes and stops in the middle of __unused. 12 | */ 13 | 14 | struct sys_stat_struct { 15 | unsigned long st_dev; 16 | unsigned long st_ino; 17 | unsigned long st_nlink; 18 | unsigned int st_mode; 19 | unsigned int st_uid; 20 | unsigned int st_gid; 21 | unsigned int __pad1; 22 | unsigned long st_rdev; 23 | unsigned long st_size; 24 | unsigned long st_atime; 25 | unsigned long st_atime_nsec; 26 | unsigned long st_mtime; 27 | unsigned long st_mtime_nsec; 28 | unsigned long st_ctime; 29 | unsigned long st_ctime_nsec; 30 | unsigned long st_blksize; 31 | long st_blocks; 32 | unsigned long __unused[3]; 33 | }; 34 | 35 | /* Syscalls for s390: 36 | * - registers are 64-bit 37 | * - syscall number is passed in r1 38 | * - arguments are in r2-r7 39 | * - the system call is performed by calling the svc instruction 40 | * - syscall return value is in r2 41 | * - r1 and r2 are clobbered, others are preserved. 42 | * 43 | * Link s390 ABI: https://github.com/IBM/s390x-abi 44 | * 45 | */ 46 | 47 | #define my_syscall0(num) \ 48 | ({ \ 49 | register long _num __asm__ ("1") = (num); \ 50 | register long _rc __asm__ ("2"); \ 51 | \ 52 | __asm__ volatile ( \ 53 | "svc 0\n" \ 54 | : "=d"(_rc) \ 55 | : "d"(_num) \ 56 | : "memory", "cc" \ 57 | ); \ 58 | _rc; \ 59 | }) 60 | 61 | #define my_syscall1(num, arg1) \ 62 | ({ \ 63 | register long _num __asm__ ("1") = (num); \ 64 | register long _arg1 __asm__ ("2") = (long)(arg1); \ 65 | \ 66 | __asm__ volatile ( \ 67 | "svc 0\n" \ 68 | : "+d"(_arg1) \ 69 | : "d"(_num) \ 70 | : "memory", "cc" \ 71 | ); \ 72 | _arg1; \ 73 | }) 74 | 75 | #define my_syscall2(num, arg1, arg2) \ 76 | ({ \ 77 | register long _num __asm__ ("1") = (num); \ 78 | register long _arg1 __asm__ ("2") = (long)(arg1); \ 79 | register long _arg2 __asm__ ("3") = (long)(arg2); \ 80 | \ 81 | __asm__ volatile ( \ 82 | "svc 0\n" \ 83 | : "+d"(_arg1) \ 84 | : "d"(_arg2), "d"(_num) \ 85 | : "memory", "cc" \ 86 | ); \ 87 | _arg1; \ 88 | }) 89 | 90 | #define my_syscall3(num, arg1, arg2, arg3) \ 91 | ({ \ 92 | register long _num __asm__ ("1") = (num); \ 93 | register long _arg1 __asm__ ("2") = (long)(arg1); \ 94 | register long _arg2 __asm__ ("3") = (long)(arg2); \ 95 | register long _arg3 __asm__ ("4") = (long)(arg3); \ 96 | \ 97 | __asm__ volatile ( \ 98 | "svc 0\n" \ 99 | : "+d"(_arg1) \ 100 | : "d"(_arg2), "d"(_arg3), "d"(_num) \ 101 | : "memory", "cc" \ 102 | ); \ 103 | _arg1; \ 104 | }) 105 | 106 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 107 | ({ \ 108 | register long _num __asm__ ("1") = (num); \ 109 | register long _arg1 __asm__ ("2") = (long)(arg1); \ 110 | register long _arg2 __asm__ ("3") = (long)(arg2); \ 111 | register long _arg3 __asm__ ("4") = (long)(arg3); \ 112 | register long _arg4 __asm__ ("5") = (long)(arg4); \ 113 | \ 114 | __asm__ volatile ( \ 115 | "svc 0\n" \ 116 | : "+d"(_arg1) \ 117 | : "d"(_arg2), "d"(_arg3), "d"(_arg4), "d"(_num) \ 118 | : "memory", "cc" \ 119 | ); \ 120 | _arg1; \ 121 | }) 122 | 123 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 124 | ({ \ 125 | register long _num __asm__ ("1") = (num); \ 126 | register long _arg1 __asm__ ("2") = (long)(arg1); \ 127 | register long _arg2 __asm__ ("3") = (long)(arg2); \ 128 | register long _arg3 __asm__ ("4") = (long)(arg3); \ 129 | register long _arg4 __asm__ ("5") = (long)(arg4); \ 130 | register long _arg5 __asm__ ("6") = (long)(arg5); \ 131 | \ 132 | __asm__ volatile ( \ 133 | "svc 0\n" \ 134 | : "+d"(_arg1) \ 135 | : "d"(_arg2), "d"(_arg3), "d"(_arg4), "d"(_arg5), \ 136 | "d"(_num) \ 137 | : "memory", "cc" \ 138 | ); \ 139 | _arg1; \ 140 | }) 141 | 142 | #define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ 143 | ({ \ 144 | register long _num __asm__ ("1") = (num); \ 145 | register long _arg1 __asm__ ("2") = (long)(arg1); \ 146 | register long _arg2 __asm__ ("3") = (long)(arg2); \ 147 | register long _arg3 __asm__ ("4") = (long)(arg3); \ 148 | register long _arg4 __asm__ ("5") = (long)(arg4); \ 149 | register long _arg5 __asm__ ("6") = (long)(arg5); \ 150 | register long _arg6 __asm__ ("7") = (long)(arg6); \ 151 | \ 152 | __asm__ volatile ( \ 153 | "svc 0\n" \ 154 | : "+d"(_arg1) \ 155 | : "d"(_arg2), "d"(_arg3), "d"(_arg4), "d"(_arg5), \ 156 | "d"(_arg6), "d"(_num) \ 157 | : "memory", "cc" \ 158 | ); \ 159 | _arg1; \ 160 | }) 161 | 162 | char **environ __attribute__((weak)); 163 | const unsigned long *_auxv __attribute__((weak)); 164 | 165 | /* startup code */ 166 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 167 | { 168 | __asm__ volatile ( 169 | "lg %r2,0(%r15)\n" /* argument count */ 170 | "la %r3,8(%r15)\n" /* argument pointers */ 171 | 172 | "xgr %r0,%r0\n" /* r0 will be our NULL value */ 173 | /* search for envp */ 174 | "lgr %r4,%r3\n" /* start at argv */ 175 | "0:\n" 176 | "clg %r0,0(%r4)\n" /* entry zero? */ 177 | "la %r4,8(%r4)\n" /* advance pointer */ 178 | "jnz 0b\n" /* no -> test next pointer */ 179 | /* yes -> r4 now contains start of envp */ 180 | // "larl %r1,environ\n" 181 | "stg %r4,0(%r1)\n" 182 | 183 | /* search for auxv */ 184 | "lgr %r5,%r4\n" /* start at envp */ 185 | "1:\n" 186 | "clg %r0,0(%r5)\n" /* entry zero? */ 187 | "la %r5,8(%r5)\n" /* advance pointer */ 188 | "jnz 1b\n" /* no -> test next pointer */ 189 | // "larl %r1,_auxv\n" /* yes -> store value in _auxv */ 190 | "stg %r5,0(%r1)\n" 191 | 192 | "aghi %r15,-160\n" /* allocate new stackframe */ 193 | "xc 0(8,%r15),0(%r15)\n" /* clear backchain */ 194 | "brasl %r14,main\n" /* ret value of main is arg to exit */ 195 | "lghi %r1,1\n" /* __NR_exit */ 196 | "svc 0\n" 197 | ); 198 | __builtin_unreachable(); 199 | } 200 | 201 | struct s390_mmap_arg_struct { 202 | unsigned long addr; 203 | unsigned long len; 204 | unsigned long prot; 205 | unsigned long flags; 206 | unsigned long fd; 207 | unsigned long offset; 208 | }; 209 | 210 | static __attribute__((unused)) 211 | void *sys_mmap(void *addr, size_t length, int prot, int flags, int fd, 212 | off_t offset) 213 | { 214 | struct s390_mmap_arg_struct args = { 215 | .addr = (unsigned long)addr, 216 | .len = (unsigned long)length, 217 | .prot = prot, 218 | .flags = flags, 219 | .fd = fd, 220 | .offset = (unsigned long)offset 221 | }; 222 | 223 | return (void *)my_syscall1(__NR_mmap, &args); 224 | } 225 | #define sys_mmap sys_mmap 226 | #endif // _NOLIBC_ARCH_S390_H 227 | -------------------------------------------------------------------------------- /vendor/nolibc/stdio.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * minimal stdio function definitions for NOLIBC 4 | * Copyright (C) 2017-2021 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_STDIO_H 8 | #define _NOLIBC_STDIO_H 9 | 10 | #include 11 | 12 | #include "std.h" 13 | #include "arch.h" 14 | #include "errno.h" 15 | #include "types.h" 16 | #include "sys.h" 17 | #include "stdlib.h" 18 | #include "string.h" 19 | 20 | #ifndef EOF 21 | #define EOF (-1) 22 | #endif 23 | 24 | /* just define FILE as a non-empty type */ 25 | typedef struct FILE { 26 | char dummy[1]; 27 | } FILE; 28 | 29 | /* We define the 3 common stdio files as constant invalid pointers that 30 | * are easily recognized. 31 | */ 32 | static __attribute__((unused)) FILE* const stdin = (FILE*)-3; 33 | static __attribute__((unused)) FILE* const stdout = (FILE*)-2; 34 | static __attribute__((unused)) FILE* const stderr = (FILE*)-1; 35 | 36 | /* getc(), fgetc(), getchar() */ 37 | 38 | #define getc(stream) fgetc(stream) 39 | 40 | static __attribute__((unused)) 41 | int fgetc(FILE* stream) 42 | { 43 | unsigned char ch; 44 | int fd; 45 | 46 | if (stream < stdin || stream > stderr) 47 | return EOF; 48 | 49 | fd = 3 + (long)stream; 50 | 51 | if (read(fd, &ch, 1) <= 0) 52 | return EOF; 53 | return ch; 54 | } 55 | 56 | static __attribute__((unused)) 57 | int getchar(void) 58 | { 59 | return fgetc(stdin); 60 | } 61 | 62 | 63 | /* putc(), fputc(), putchar() */ 64 | 65 | #define putc(c, stream) fputc(c, stream) 66 | 67 | static __attribute__((unused)) 68 | int fputc(int c, FILE* stream) 69 | { 70 | unsigned char ch = c; 71 | int fd; 72 | 73 | if (stream < stdin || stream > stderr) 74 | return EOF; 75 | 76 | fd = 3 + (long)stream; 77 | 78 | if (write(fd, &ch, 1) <= 0) 79 | return EOF; 80 | return ch; 81 | } 82 | 83 | static __attribute__((unused)) 84 | int putchar(int c) 85 | { 86 | return fputc(c, stdout); 87 | } 88 | 89 | 90 | /* fwrite(), puts(), fputs(). Note that puts() emits '\n' but not fputs(). */ 91 | 92 | /* internal fwrite()-like function which only takes a size and returns 0 on 93 | * success or EOF on error. It automatically retries on short writes. 94 | */ 95 | static __attribute__((unused)) 96 | int _fwrite(const void *buf, size_t size, FILE *stream) 97 | { 98 | ssize_t ret; 99 | int fd; 100 | 101 | if (stream < stdin || stream > stderr) 102 | return EOF; 103 | 104 | fd = 3 + (long)stream; 105 | 106 | while (size) { 107 | ret = write(fd, buf, size); 108 | if (ret <= 0) 109 | return EOF; 110 | size -= ret; 111 | buf += ret; 112 | } 113 | return 0; 114 | } 115 | 116 | static __attribute__((unused)) 117 | size_t fwrite(const void *s, size_t size, size_t nmemb, FILE *stream) 118 | { 119 | size_t written; 120 | 121 | for (written = 0; written < nmemb; written++) { 122 | if (_fwrite(s, size, stream) != 0) 123 | break; 124 | s += size; 125 | } 126 | return written; 127 | } 128 | 129 | static __attribute__((unused)) 130 | int fputs(const char *s, FILE *stream) 131 | { 132 | return _fwrite(s, strlen(s), stream); 133 | } 134 | 135 | static __attribute__((unused)) 136 | int puts(const char *s) 137 | { 138 | if (fputs(s, stdout) == EOF) 139 | return EOF; 140 | return putchar('\n'); 141 | } 142 | 143 | 144 | /* fgets() */ 145 | static __attribute__((unused)) 146 | char *fgets(char *s, int size, FILE *stream) 147 | { 148 | int ofs; 149 | int c; 150 | 151 | for (ofs = 0; ofs + 1 < size;) { 152 | c = fgetc(stream); 153 | if (c == EOF) 154 | break; 155 | s[ofs++] = c; 156 | if (c == '\n') 157 | break; 158 | } 159 | if (ofs < size) 160 | s[ofs] = 0; 161 | return ofs ? s : NULL; 162 | } 163 | 164 | 165 | /* minimal vfprintf(). It supports the following formats: 166 | * - %[l*]{d,u,c,x,p} 167 | * - %s 168 | * - unknown modifiers are ignored. 169 | */ 170 | static __attribute__((unused)) 171 | int vfprintf(FILE *stream, const char *fmt, va_list args) 172 | { 173 | char escape, lpref, c; 174 | unsigned long long v; 175 | unsigned int written; 176 | size_t len, ofs; 177 | char tmpbuf[21]; 178 | const char *outstr; 179 | 180 | written = ofs = escape = lpref = 0; 181 | while (1) { 182 | c = fmt[ofs++]; 183 | 184 | if (escape) { 185 | /* we're in an escape sequence, ofs == 1 */ 186 | escape = 0; 187 | if (c == 'c' || c == 'd' || c == 'u' || c == 'x' || c == 'p') { 188 | char *out = tmpbuf; 189 | 190 | if (c == 'p') 191 | v = va_arg(args, unsigned long); 192 | else if (lpref) { 193 | if (lpref > 1) 194 | v = va_arg(args, unsigned long long); 195 | else 196 | v = va_arg(args, unsigned long); 197 | } else 198 | v = va_arg(args, unsigned int); 199 | 200 | if (c == 'd') { 201 | /* sign-extend the value */ 202 | if (lpref == 0) 203 | v = (long long)(int)v; 204 | else if (lpref == 1) 205 | v = (long long)(long)v; 206 | } 207 | 208 | switch (c) { 209 | case 'c': 210 | out[0] = v; 211 | out[1] = 0; 212 | break; 213 | case 'd': 214 | i64toa_r(v, out); 215 | break; 216 | case 'u': 217 | u64toa_r(v, out); 218 | break; 219 | case 'p': 220 | *(out++) = '0'; 221 | *(out++) = 'x'; 222 | /* fall through */ 223 | default: /* 'x' and 'p' above */ 224 | u64toh_r(v, out); 225 | break; 226 | } 227 | outstr = tmpbuf; 228 | } 229 | else if (c == 's') { 230 | outstr = va_arg(args, char *); 231 | if (!outstr) 232 | outstr="(null)"; 233 | } 234 | else if (c == '%') { 235 | /* queue it verbatim */ 236 | continue; 237 | } 238 | else { 239 | /* modifiers or final 0 */ 240 | if (c == 'l') { 241 | /* long format prefix, maintain the escape */ 242 | lpref++; 243 | } 244 | escape = 1; 245 | goto do_escape; 246 | } 247 | len = strlen(outstr); 248 | goto flush_str; 249 | } 250 | 251 | /* not an escape sequence */ 252 | if (c == 0 || c == '%') { 253 | /* flush pending data on escape or end */ 254 | escape = 1; 255 | lpref = 0; 256 | outstr = fmt; 257 | len = ofs - 1; 258 | flush_str: 259 | if (_fwrite(outstr, len, stream) != 0) 260 | break; 261 | 262 | written += len; 263 | do_escape: 264 | if (c == 0) 265 | break; 266 | fmt += ofs; 267 | ofs = 0; 268 | continue; 269 | } 270 | 271 | /* literal char, just queue it */ 272 | } 273 | return written; 274 | } 275 | 276 | static __attribute__((unused, format(printf, 2, 3))) 277 | int fprintf(FILE *stream, const char *fmt, ...) 278 | { 279 | va_list args; 280 | int ret; 281 | 282 | va_start(args, fmt); 283 | ret = vfprintf(stream, fmt, args); 284 | va_end(args); 285 | return ret; 286 | } 287 | 288 | static __attribute__((unused, format(printf, 1, 2))) 289 | int printf(const char *fmt, ...) 290 | { 291 | va_list args; 292 | int ret; 293 | 294 | va_start(args, fmt); 295 | ret = vfprintf(stdout, fmt, args); 296 | va_end(args); 297 | return ret; 298 | } 299 | 300 | static __attribute__((unused)) 301 | void perror(const char *msg) 302 | { 303 | fprintf(stderr, "%s%serrno=%d\n", (msg && *msg) ? msg : "", (msg && *msg) ? ": " : "", errno); 304 | } 305 | 306 | /* make sure to include all global symbols */ 307 | #include "nolibc.h" 308 | 309 | #endif /* _NOLIBC_STDIO_H */ 310 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::{Path, PathBuf}; 3 | use std::process::Command; 4 | 5 | use rstest::*; 6 | use std::sync::OnceLock; 7 | use tempfile::TempDir; 8 | 9 | #[fixture] 10 | #[once] 11 | fn libtest() -> &'static str { 12 | eprintln!("Testing {EXE} on {TARGET}"); 13 | 14 | eprintln!("Building libtest"); 15 | compile_test_lib("test"); 16 | get_tmpdir().path().to_str().unwrap() 17 | } 18 | 19 | #[fixture] 20 | #[once] 21 | fn dt_needed_bin() -> PathBuf { 22 | compile_test_bin("dt-needed", &["test"]) 23 | } 24 | 25 | /// Check that we can run a simple binary. 26 | #[rstest] 27 | fn test_hello() { 28 | let bin = compile_test_bin("hello", &[]); 29 | 30 | let (stdout, _) = Command::new(&bin) 31 | .env_remove("LD_LIBRARY_PATH") 32 | .env_remove("NIX_LD_LIBRARY_PATH") 33 | .must_succeed(); 34 | assert!(stdout.contains("Hello, world!")); 35 | } 36 | 37 | /// Check that we can run a binary with DT_NEEDED dependencies. 38 | #[rstest] 39 | fn test_dt_needed(libtest: &str, dt_needed_bin: &Path) { 40 | // First make sure it doesn't run without the library 41 | { 42 | let (_, stderr) = Command::new(dt_needed_bin) 43 | .env_remove("LD_LIBRARY_PATH") 44 | .env_remove("NIX_LD_LIBRARY_PATH") 45 | .must_fail(); 46 | assert!(stderr.contains("loading shared")); 47 | } 48 | 49 | // Now it should work 50 | { 51 | let (stdout, _) = Command::new(dt_needed_bin) 52 | .env_remove("LD_LIBRARY_PATH") 53 | .env("NIX_LD_LIBRARY_PATH", libtest) 54 | .must_succeed(); 55 | assert!(stdout.contains("Hello from libtest")); 56 | } 57 | } 58 | 59 | /// Check that we can run a binary that does dlopen. 60 | #[rstest] 61 | fn test_dlopen(libtest: &str) { 62 | let bin = compile_test_bin("dlopen", &[]); 63 | eprintln!("test_dlopen: {libtest}"); 64 | 65 | // First make sure it doesn't run without the library 66 | { 67 | let (_, stderr) = Command::new(&bin) 68 | .env_remove("LD_LIBRARY_PATH") 69 | .env_remove("NIX_LD_LIBRARY_PATH") 70 | .must_fail(); 71 | assert!(stderr.contains("Failed to dlopen libtest.so")); 72 | } 73 | 74 | // Now it should work 75 | { 76 | let (stdout, _) = Command::new(&bin) 77 | .env_remove("LD_LIBRARY_PATH") 78 | .env("NIX_LD_LIBRARY_PATH", libtest) 79 | .must_succeed(); 80 | assert!(stdout.contains("Hello from libtest")); 81 | } 82 | } 83 | 84 | /// Check that LD_LIBRARY_PATH is restored. 85 | #[cfg(all( 86 | feature = "entry_trampoline", 87 | any( 88 | target_arch = "x86_64", 89 | target_arch = "aarch64", 90 | target_arch = "riscv64" 91 | ) 92 | ))] 93 | #[rstest] 94 | fn test_ld_path_restore(libtest: &str, _dt_needed_bin: &Path) { 95 | let bin = compile_test_bin("ld-path-restore", &["test"]); 96 | 97 | let nix_ld_path = format!("{libtest}:POISON"); 98 | 99 | // First try without LD_LIBRARY_PATH 100 | { 101 | let (stdout, stderr) = Command::new(&bin) 102 | .env_remove("LD_LIBRARY_PATH") 103 | .env("NIX_LD_LIBRARY_PATH", &nix_ld_path) 104 | .must_succeed(); 105 | assert!(stderr.contains("No LD_LIBRARY_PATH")); 106 | assert!(stdout.contains("Hello from libtest")); 107 | } 108 | 109 | // Now with LD_LIBRARY_PATH 110 | { 111 | let (stdout, stderr) = Command::new(&bin) 112 | .env("LD_LIBRARY_PATH", "NEEDLE") 113 | .env("NIX_LD_LIBRARY_PATH", &nix_ld_path) 114 | .must_succeed(); 115 | assert!(stderr.contains("LD_LIBRARY_PATH contains needle")); 116 | assert!(stdout.contains("Hello from libtest")); 117 | assert!(stderr.contains("Launching child process")); 118 | assert!(stderr.contains("loading shared")); // error from the child process 119 | } 120 | } 121 | 122 | // Utilities 123 | 124 | const EXE: &str = env!("CARGO_BIN_EXE_nix-ld"); 125 | const TARGET: &str = env!("NIX_LD_TEST_TARGET"); 126 | 127 | static TMPDIR: OnceLock = OnceLock::new(); 128 | 129 | fn get_tmpdir() -> &'static TempDir { 130 | TMPDIR.get_or_init(|| tempfile::tempdir().expect("Failed to create temporary directory")) 131 | } 132 | 133 | fn find_cc() -> String { 134 | let target_suffix = TARGET.replace('-', "_"); 135 | env::var(format!("CC_{target_suffix}")) 136 | .or_else(|_| env::var("CC")) 137 | .unwrap_or_else(|_| "cc".to_string()) 138 | } 139 | 140 | fn get_source_file(file: &str) -> PathBuf { 141 | // CARGO_MANIFEST_DIR doesn't necessarily point to the source, but 142 | // then there is no good way to get the source from here 143 | let base = PathBuf::from(&env::var("CARGO_MANIFEST_DIR").unwrap()); 144 | base.join(file) 145 | } 146 | 147 | fn compile_test_lib(name: &str) { 148 | let cc = find_cc(); 149 | let source_path = get_source_file(&format!("tests/lib{name}.c")); 150 | let out_path = get_tmpdir().path().join(format!("lib{name}.so")); 151 | 152 | let status = Command::new(cc) 153 | .arg("-fPIC") 154 | .arg("-shared") 155 | .arg("-o") 156 | .arg(&out_path) 157 | .arg(source_path) 158 | .status() 159 | .expect("Failed to spawn compiler"); 160 | 161 | assert!(status.success(), "Failed to build test library {name}"); 162 | } 163 | 164 | fn compile_test_bin(name: &str, libs: &[&str]) -> PathBuf { 165 | let cc = find_cc(); 166 | let source_path = get_source_file(&format!("tests/{name}.c")); 167 | let out_path = get_tmpdir().path().join(name); 168 | 169 | let out_dir_arg = format!("-DOUT_DIR=\"{}\"", get_tmpdir().path().to_str().unwrap()); 170 | let dynamic_linker_arg = format!("-Wl,--dynamic-linker,{EXE}"); 171 | 172 | let status = Command::new(cc) 173 | .arg("-o") 174 | .arg(&out_path) 175 | .arg(out_dir_arg) 176 | .arg(dynamic_linker_arg) 177 | .arg("-L") 178 | .arg(get_tmpdir().path()) 179 | .args(libs.iter().map(|l| format!("-l{l}"))) 180 | .arg(source_path) 181 | .status() 182 | .expect("Failed to spawn compiler"); 183 | 184 | assert!(status.success(), "Failed to build test binary {name}"); 185 | 186 | out_path 187 | } 188 | 189 | trait CommandExt { 190 | fn output_checked(&mut self, want_success: bool) -> (String, String); 191 | fn must_succeed(&mut self) -> (String, String); 192 | fn must_fail(&mut self) -> (String, String); 193 | } 194 | 195 | impl CommandExt for Command { 196 | fn output_checked(&mut self, want_success: bool) -> (String, String) { 197 | eprintln!("Running binary {:?}", self.get_program()); 198 | let output = self.output().expect("Failed to spawn test binary"); 199 | 200 | let stdout = String::from_utf8(output.stdout).expect("stdout contains non-UTF-8"); 201 | let stderr = String::from_utf8(output.stderr).expect("stderr contains non-UTF-8"); 202 | 203 | print!("{stdout}"); 204 | eprint!("{stderr}"); 205 | 206 | if want_success { 207 | assert!( 208 | output.status.success(), 209 | "{:?} did not run successfully", 210 | self.get_program() 211 | ); 212 | } else { 213 | assert!( 214 | !output.status.success(), 215 | "{:?} unexpectedly succeeded", 216 | self.get_program() 217 | ); 218 | } 219 | 220 | (stdout, stderr) 221 | } 222 | 223 | fn must_succeed(&mut self) -> (String, String) { 224 | self.output_checked(true) 225 | } 226 | 227 | fn must_fail(&mut self) -> (String, String) { 228 | self.output_checked(false) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/arch.rs: -------------------------------------------------------------------------------- 1 | //! Arch-specific stuff. 2 | 3 | use core::ffi::c_void; 4 | use core::mem; 5 | use core::ptr; 6 | 7 | use crate::args::EnvEdit; 8 | use crate::const_concat::concat; 9 | 10 | #[cfg(not(target_os = "linux"))] 11 | compiler_error!("Only Linux is supported"); 12 | 13 | #[cfg(target_pointer_width = "32")] 14 | pub use goblin::elf32 as elf_types; 15 | #[cfg(target_pointer_width = "64")] 16 | pub use goblin::elf64 as elf_types; 17 | 18 | // Typically 16 is required 19 | pub const STACK_ALIGNMENT: usize = 32; 20 | 21 | pub const EM_SELF: u16 = { 22 | use elf_types::header::*; 23 | #[cfg(target_arch = "x86_64")] 24 | const VALUE: u16 = EM_X86_64; 25 | #[cfg(target_arch = "x86")] 26 | const VALUE: u16 = EM_386; 27 | #[cfg(target_arch = "aarch64")] 28 | const VALUE: u16 = EM_AARCH64; 29 | #[cfg(target_arch = "riscv64")] 30 | const VALUE: u16 = EM_RISCV; 31 | VALUE 32 | }; 33 | 34 | pub const R_RELATIVE: u32 = { 35 | use elf_types::reloc::*; 36 | #[cfg(target_arch = "x86_64")] 37 | const VALUE: u32 = R_X86_64_RELATIVE; 38 | #[cfg(target_arch = "x86")] 39 | const VALUE: u32 = R_386_RELATIVE; 40 | #[cfg(target_arch = "aarch64")] 41 | const VALUE: u32 = R_AARCH64_RELATIVE; 42 | #[cfg(target_arch = "riscv64")] 43 | const VALUE: u32 = R_RISCV_RELATIVE; 44 | VALUE 45 | }; 46 | 47 | pub const NIX_SYSTEM: &str = match option_env!("NIX_SYSTEM") { 48 | Some(system) => system, 49 | None => { 50 | #[cfg(target_arch = "x86_64")] 51 | const VALUE: &str = "x86_64_linux"; 52 | #[cfg(target_arch = "x86")] 53 | const VALUE: &str = "i686_linux"; 54 | #[cfg(target_arch = "aarch64")] 55 | const VALUE: &str = "aarch64_linux"; 56 | #[cfg(target_arch = "riscv64")] 57 | const VALUE: &str = "riscv64_linux"; 58 | VALUE 59 | } 60 | }; 61 | 62 | pub const NIX_LD_SYSTEM_ENV: &str = concat!("NIX_LD_", NIX_SYSTEM); 63 | pub const NIX_LD_LIBRARY_PATH_SYSTEM_ENV: &str = concat!("NIX_LD_LIBRARY_PATH_", NIX_SYSTEM); 64 | pub const NIX_LD_SYSTEM_ENV_BYTES: &[u8] = NIX_LD_SYSTEM_ENV.as_bytes(); 65 | pub const NIX_LD_LIBRARY_PATH_SYSTEM_ENV_BYTES: &[u8] = NIX_LD_LIBRARY_PATH_SYSTEM_ENV.as_bytes(); 66 | 67 | // Note: We separate main_relocate_stack and elf_jmp to make stack alignment 68 | // easier. For elf_jmp, we expect the loader to take care of aligning the 69 | // stack pointer in _start. 70 | 71 | macro_rules! main_relocate_stack { 72 | ($sp:ident, $func:ident) => { 73 | #[cfg(target_arch = "x86_64")] 74 | core::arch::asm!("mov rsp, {}; call {}", in(reg) $sp, sym $func, options(noreturn)); 75 | #[cfg(target_arch = "x86")] 76 | core::arch::asm!("mov esp, {}; call {}", in(reg) $sp, sym $func, options(noreturn)); 77 | #[cfg(target_arch = "aarch64")] 78 | core::arch::asm!("mov sp, {}; bl {}", in(reg) $sp, sym $func, options(noreturn)); 79 | #[cfg(target_arch = "riscv64")] 80 | core::arch::asm!("mv sp, {}; call {}", in(reg) $sp, sym $func, options(noreturn)); 81 | }; 82 | } 83 | pub(crate) use main_relocate_stack; 84 | 85 | macro_rules! elf_jmp { 86 | ($sp:ident, $target:expr) => { 87 | #[cfg(target_arch = "x86_64")] 88 | core::arch::asm!("mov rsp, {}; jmp {}", in(reg) $sp, in(reg) $target, options(noreturn)); 89 | #[cfg(target_arch = "x86")] 90 | core::arch::asm!("mov esp, {}; jmp {}", in(reg) $sp, in(reg) $target, options(noreturn)); 91 | #[cfg(target_arch = "aarch64")] 92 | core::arch::asm!("mov sp, {}; br {}", in(reg) $sp, in(reg) $target, options(noreturn)); 93 | #[cfg(target_arch = "riscv64")] 94 | core::arch::asm!("mv sp, {}; jr {}", in(reg) $sp, in(reg) $target, options(noreturn)); 95 | }; 96 | } 97 | pub(crate) use elf_jmp; 98 | 99 | /// Context for the entry point trampoline. 100 | /// 101 | /// The goal is to revert our LD_LIBRARY_PATH changes once 102 | /// ld.so has done its job. 103 | #[repr(C, align(4096))] 104 | #[derive(Debug)] 105 | pub struct TrampolineContext { 106 | elf_entry: *const c_void, 107 | env_entry: *const *const u8, 108 | env_string: *const u8, 109 | } 110 | 111 | impl TrampolineContext { 112 | #[allow(unused)] 113 | const ENV_ENTRY_OFFSET: usize = mem::size_of::<*const u8>(); 114 | 115 | #[allow(unused)] 116 | const ENV_STRING_OFFSET: usize = mem::size_of::<*const u8>() * 2; 117 | 118 | pub fn set_elf_entry(&mut self, entry: *const c_void) { 119 | self.elf_entry = entry; 120 | } 121 | 122 | pub fn revert_env(&mut self, edit: &EnvEdit) { 123 | self.env_entry = edit.entry; 124 | self.env_string = edit.old_string; 125 | } 126 | 127 | pub fn revert_env_entry(&mut self, entry: *const *const u8) { 128 | self.env_entry = entry; 129 | } 130 | } 131 | 132 | pub static mut TRAMPOLINE_CONTEXT: TrampolineContext = TrampolineContext { 133 | elf_entry: ptr::null(), 134 | env_entry: ptr::null(), 135 | env_string: ptr::null(), 136 | }; 137 | 138 | #[cfg(not(feature = "entry_trampoline"))] 139 | pub const ENTRY_TRAMPOLINE: Option !> = None; 140 | 141 | #[cfg(target_arch = "x86_64")] 142 | pub const ENTRY_TRAMPOLINE: Option !> = Some(entry_trampoline); 143 | 144 | #[cfg(target_arch = "riscv64")] 145 | pub const ENTRY_TRAMPOLINE: Option !> = Some(entry_trampoline); 146 | 147 | #[cfg(target_arch = "x86_64")] 148 | #[unsafe(naked)] 149 | unsafe extern "C" fn entry_trampoline() -> ! { 150 | core::arch::naked_asm!( 151 | "lea r10, [rip + {context}]", 152 | "mov r11, [r10 + {size} * 1]", // .env_entry 153 | "test r11, r11", 154 | "jz 2f", 155 | "mov r10, [r10 + {size} * 2]", // .env_string 156 | "mov [r11], r10", 157 | "2:", 158 | "jmp [rip + {context}]", 159 | context = sym TRAMPOLINE_CONTEXT, 160 | size = const core::mem::size_of::<*const u8>(), 161 | ) 162 | } 163 | 164 | #[cfg(target_arch = "aarch64")] 165 | pub const ENTRY_TRAMPOLINE: Option !> = Some(entry_trampoline); 166 | 167 | #[cfg(target_arch = "aarch64")] 168 | #[unsafe(naked)] 169 | unsafe extern "C" fn entry_trampoline() -> ! { 170 | core::arch::naked_asm!( 171 | "adrp x8, {context}", 172 | "ldr x9, [x8, {env_entry_off}]", // .env_entry 173 | "cbz x9, 2f", 174 | "ldr x10, [x8, {env_string_off}]", // .env_string 175 | "str x10, [x9]", 176 | "2:", 177 | "ldr x8, [x8]", 178 | "br x8", 179 | context = sym TRAMPOLINE_CONTEXT, 180 | env_entry_off = const TrampolineContext::ENV_ENTRY_OFFSET, 181 | env_string_off = const TrampolineContext::ENV_STRING_OFFSET, 182 | ) 183 | } 184 | 185 | #[cfg(target_arch = "riscv64")] 186 | #[unsafe(naked)] 187 | unsafe extern "C" fn entry_trampoline() -> ! { 188 | core::arch::naked_asm!( 189 | "1:", 190 | "auipc t0, %pcrel_hi({context})", 191 | "addi t0, t0, %pcrel_lo(1b)", 192 | "ld t1, {env_entry_off}(t0)", // .env_entry 193 | "beqz t1, 2f", 194 | "ld t2, {env_string_off}(t0)", // .env_string 195 | "sd t2, 0(t1)", 196 | "2:", 197 | "ld t0, 0(t0)", 198 | "jr t0", 199 | context = sym TRAMPOLINE_CONTEXT, 200 | env_entry_off = const TrampolineContext::ENV_ENTRY_OFFSET, 201 | env_string_off = const TrampolineContext::ENV_STRING_OFFSET, 202 | ) 203 | } 204 | 205 | // !!!! 206 | // After adding a trampoline, remember to enable test_ld_path_restore for 207 | // the target_arch in tests/tests.rs as well 208 | // !!!! 209 | #[cfg(all( 210 | feature = "entry_trampoline", 211 | not(target_arch = "x86_64"), 212 | not(target_arch = "aarch64"), 213 | not(target_arch = "riscv64") 214 | ))] 215 | pub const ENTRY_TRAMPOLINE: Option !> = None; 216 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(lang_items)] 2 | #![no_std] 3 | #![no_main] 4 | #![allow(internal_features)] 5 | #![allow(static_mut_refs)] 6 | 7 | mod arch; 8 | mod args; 9 | mod auxv; 10 | mod const_concat; 11 | mod elf; 12 | mod fixup; 13 | mod support; 14 | mod sys; 15 | 16 | use core::ffi::{CStr, c_void}; 17 | use core::mem::MaybeUninit; 18 | use core::ptr; 19 | 20 | use crate::const_concat::concat_slices; 21 | 22 | use arch::{ 23 | NIX_LD_LIBRARY_PATH_SYSTEM_ENV, NIX_LD_LIBRARY_PATH_SYSTEM_ENV_BYTES, NIX_LD_SYSTEM_ENV, 24 | NIX_LD_SYSTEM_ENV_BYTES, 25 | }; 26 | use args::{Args, EnvEdit, VarHandle}; 27 | use support::StackSpace; 28 | 29 | static mut ARGS: MaybeUninit = MaybeUninit::uninit(); 30 | static mut STACK: MaybeUninit = MaybeUninit::uninit(); 31 | 32 | const DEFAULT_NIX_LD: &CStr = unsafe { 33 | CStr::from_bytes_with_nul_unchecked(concat_slices!([u8]: 34 | match option_env!("DEFAULT_NIX_LD") { 35 | Some(path) => path, 36 | None => "/run/current-system/sw/share/nix-ld/lib/ld.so", 37 | }.as_bytes(), 38 | b"\0" 39 | )) 40 | }; 41 | 42 | const DEFAULT_NIX_LD_LIBRARY_PATH: &[u8] = b"/run/current-system/sw/share/nix-ld/lib"; 43 | const EMPTY_LD_LIBRARY_PATH_ENV: &CStr = c"LD_LIBRARY_PATH="; 44 | 45 | #[derive(Default)] 46 | struct Context { 47 | nix_ld: Option, 48 | nix_ld_library_path: Option, 49 | ld_library_path: Option, 50 | } 51 | 52 | #[unsafe(no_mangle)] 53 | unsafe extern "C" fn main(argc: usize, argv: *const *const u8, envp: *const *const u8) -> ! { 54 | unsafe { 55 | fixup::fixup_relocs(envp); 56 | 57 | ARGS.write(Args::new(argc, argv, envp)); 58 | let stack = STACK.assume_init_mut().bottom(); 59 | arch::main_relocate_stack!(stack, real_main); 60 | } 61 | } 62 | 63 | #[unsafe(no_mangle)] 64 | extern "C" fn real_main() -> ! { 65 | let args = unsafe { ARGS.assume_init_mut() }; 66 | let mut ctx = Context::default(); 67 | 68 | log::set_logger(&support::LOGGER) 69 | .map(|_| log::set_max_level(log::LevelFilter::Warn)) 70 | .unwrap(); 71 | 72 | for env in args.iter_env().unwrap() { 73 | match env.name() { 74 | b"NIX_LD_LOG" => { 75 | if let Ok(log_level) = env.value_cstr().to_str() { 76 | if let Ok(level) = log_level.parse::() { 77 | log::set_max_level(level); 78 | } else { 79 | log::warn!("Unknown log level {log_level}"); 80 | } 81 | } 82 | } 83 | 84 | // The system-specific variants (e.g., NIX_LD_x86_64_linux) always 85 | // take precedence. Currently, NIX_LD_LIBRARY_PATH_{system} clobbers 86 | // the generic one, and we should revisit this decision (maybe 87 | // concatenate?). 88 | NIX_LD_SYSTEM_ENV_BYTES => { 89 | ctx.nix_ld = Some(env); 90 | } 91 | b"NIX_LD" => { 92 | ctx.nix_ld.get_or_insert(env); 93 | } 94 | NIX_LD_LIBRARY_PATH_SYSTEM_ENV_BYTES => { 95 | ctx.nix_ld_library_path = Some(env); 96 | } 97 | b"NIX_LD_LIBRARY_PATH" => { 98 | ctx.nix_ld_library_path.get_or_insert(env); 99 | } 100 | b"LD_LIBRARY_PATH" => { 101 | ctx.ld_library_path = Some(env); 102 | } 103 | _ => {} 104 | } 105 | } 106 | 107 | // Deal with NIX_LD 108 | let nix_ld = match &mut ctx.nix_ld { 109 | None => { 110 | log::info!("NIX_LD is not set - Falling back to default"); 111 | DEFAULT_NIX_LD 112 | } 113 | Some(nix_ld) if nix_ld.value().is_empty() => { 114 | log::info!("NIX_LD is empty - Falling back to default"); 115 | DEFAULT_NIX_LD 116 | } 117 | Some(nix_ld) => { 118 | let cstr = nix_ld.value_cstr(); 119 | log::info!("NIX_LD is set to {cstr:?}"); 120 | cstr 121 | } 122 | }; 123 | 124 | // Deal with {NIX_,}LD_LIBRARY_PATH 125 | let env_edit = if let Some(ld_library_path) = ctx.ld_library_path { 126 | // Concatenate: 127 | // 128 | // Basically LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$NIX_LD_LIBRARY_PATH 129 | let head = ld_library_path.value(); 130 | let tail = if let Some(nix_ld_library_path) = &ctx.nix_ld_library_path { 131 | log::info!("Appending NIX_LD_LIBRARY_PATH to LD_LIBRARY_PATH"); 132 | nix_ld_library_path.value() 133 | } else { 134 | log::info!("Appending default NIX_LD_LIBRARY_PATH to LD_LIBRARY_PATH"); 135 | DEFAULT_NIX_LD_LIBRARY_PATH 136 | }; 137 | 138 | let sep: &[u8] = if head.is_empty() || head.last() == Some(&b':') { 139 | &[] 140 | } else { 141 | b":" 142 | }; 143 | let new_len = head.len() + tail.len() + sep.len(); 144 | 145 | ld_library_path.edit(None, new_len, |head, new| { 146 | new[..head.len()].copy_from_slice(head); 147 | new[head.len()..head.len() + sep.len()].copy_from_slice(sep); 148 | new[head.len() + sep.len()..].copy_from_slice(tail); 149 | }) 150 | } else if let Some(nix_ld_library_path) = ctx.nix_ld_library_path.take() { 151 | log::info!("Renaming NIX_LD_LIBRARY_PATH to LD_LIBRARY_PATH"); 152 | 153 | // NIX_LD_LIBRARY_PATH must always exist for impure child processes to work 154 | nix_ld_library_path.rename("LD_LIBRARY_PATH") 155 | } else { 156 | log::info!("Neither LD_LIBRARY_PATH or NIX_LD_LIBRARY_PATH exist - Setting default"); 157 | 158 | args.add_env( 159 | "LD_LIBRARY_PATH", 160 | DEFAULT_NIX_LD_LIBRARY_PATH.len(), 161 | |buf| { 162 | buf.copy_from_slice(DEFAULT_NIX_LD_LIBRARY_PATH); 163 | }, 164 | ) 165 | .unwrap(); 166 | 167 | // If the entry trampoline is available on the platform, LD_LIBRARY_PATH 168 | // will be replaced with an empty LD_LIBRARY_PATH when ld.so launches 169 | // the actual program. 170 | // 171 | // We cannot replace it with NIX_LD_LIBRARY_PATH as it would take 172 | // precedence over config files. 173 | EnvEdit { 174 | entry: ptr::null(), 175 | old_string: EMPTY_LD_LIBRARY_PATH_ENV.as_ptr().cast(), 176 | } 177 | }; 178 | 179 | let pagesz = args 180 | .auxv() 181 | .at_pagesz 182 | .as_ref() 183 | .expect("AT_PAGESZ must exist") 184 | .value(); 185 | 186 | log::info!("Loading {nix_ld:?}"); 187 | let loader = elf::ElfHandle::open(nix_ld, pagesz).unwrap(); 188 | let loader_map = loader.map().unwrap(); 189 | 190 | let mut at_base = args.auxv_mut().at_base.as_mut().and_then(|base| { 191 | if base.value().is_null() { 192 | None 193 | } else { 194 | Some(base) 195 | } 196 | }); 197 | 198 | match at_base { 199 | None => { 200 | // We were executed directly - execve the actual loader 201 | if args.argc() <= 1 { 202 | log::warn!("Environment honored by nix-ld:"); 203 | log::warn!("- NIX_LD, {NIX_LD_SYSTEM_ENV}"); 204 | log::warn!("- NIX_LD_LIBRARY_PATH, {NIX_LD_LIBRARY_PATH_SYSTEM_ENV}"); 205 | log::warn!("- NIX_LD_LOG (error, warn, info, debug, trace)"); 206 | log::warn!("Default ld.so: {DEFAULT_NIX_LD:?}"); 207 | } 208 | 209 | args.handoff(|start| unsafe { 210 | log::debug!("Start context: {start:#?}"); 211 | sys::execve(nix_ld.as_ptr(), start.argv, start.envp); 212 | sys::abort(); 213 | }); 214 | } 215 | Some(ref mut at_base) => { 216 | // We are the loader - Set the AT_BASE to the actual loader 217 | at_base.set(loader_map.load_bias() as *const c_void); 218 | } 219 | } 220 | 221 | // We want our LD_LIBRARY_PATH to only affect the loaded binary 222 | // and not propagate to child processes. To achieve this, we 223 | // replace the entry point with a trampoline that reverts our 224 | // LD_LIBRARY_PATH edit and jumps to the real entry point. 225 | if let Some(trampoline) = arch::ENTRY_TRAMPOLINE { 226 | log::info!("Using entry trampoline"); 227 | if let Some(ref mut at_entry) = args.auxv_mut().at_entry { 228 | unsafe { 229 | arch::TRAMPOLINE_CONTEXT.set_elf_entry(at_entry.value()); 230 | arch::TRAMPOLINE_CONTEXT.revert_env(&env_edit); 231 | } 232 | at_entry.set(trampoline as *const _); 233 | } else { 234 | log::warn!("No AT_ENTRY found"); 235 | } 236 | } 237 | 238 | args.handoff(|start| unsafe { 239 | log::debug!("Start context: {start:#?}"); 240 | 241 | if arch::ENTRY_TRAMPOLINE.is_some() { 242 | if let Some(extra_env) = start.extra_env { 243 | arch::TRAMPOLINE_CONTEXT.revert_env_entry(extra_env); 244 | } 245 | log::debug!("Trampoline context: {:#?}", arch::TRAMPOLINE_CONTEXT); 246 | } 247 | 248 | log::info!("Transferring control to ld.so"); 249 | loader_map.jump_with_sp(start.sp); 250 | }); 251 | } 252 | -------------------------------------------------------------------------------- /vendor/nolibc/vendor.patch: -------------------------------------------------------------------------------- 1 | # Vendored from Linux 6.3.8 (mirror://kernel/linux/kernel/v6.x/linux-6.3.8.tar.xz) 2 | diff --git a/nolibc.orig/arch-aarch64.h b/nolibc/arch-aarch64.h 3 | index 383badd..249cf8f 100644 4 | --- a/nolibc.orig/arch-aarch64.h 5 | +++ b/nolibc/arch-aarch64.h 6 | @@ -181,14 +181,14 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 7 | "lsl x2, x0, 3\n" // envp (x2) = 8*argc ... 8 | "add x2, x2, 8\n" // + 8 (skip null) 9 | "add x2, x2, x1\n" // + argv 10 | - "adrp x3, environ\n" // x3 = &environ (high bits) 11 | - "str x2, [x3, #:lo12:environ]\n" // store envp into environ 12 | +// "adrp x3, environ\n" // x3 = &environ (high bits) 13 | +// "str x2, [x3, #:lo12:environ]\n" // store envp into environ 14 | "mov x4, x2\n" // search for auxv (follows NULL after last env) 15 | "0:\n" 16 | "ldr x5, [x4], 8\n" // x5 = *x4; x4 += 8 17 | "cbnz x5, 0b\n" // and stop at NULL after last env 18 | - "adrp x3, _auxv\n" // x3 = &_auxv (high bits) 19 | - "str x4, [x3, #:lo12:_auxv]\n" // store x4 into _auxv 20 | +// "adrp x3, _auxv\n" // x3 = &_auxv (high bits) 21 | +// "str x4, [x3, #:lo12:_auxv]\n" // store x4 into _auxv 22 | "and sp, x1, -16\n" // sp must be 16-byte aligned in the callee 23 | "bl main\n" // main() returns the status code, we'll exit with it. 24 | "mov x8, 93\n" // NR_exit == 93 25 | diff --git a/nolibc.orig/arch-arm.h b/nolibc/arch-arm.h 26 | index 42499f2..bc4d2d6 100644 27 | --- a/nolibc.orig/arch-arm.h 28 | +++ b/nolibc/arch-arm.h 29 | @@ -209,8 +209,8 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 30 | "add %r2, %r0, $1\n" // envp = (argc + 1) ... 31 | "lsl %r2, %r2, $2\n" // * 4 ... 32 | "add %r2, %r2, %r1\n" // + argv 33 | - "ldr %r3, 1f\n" // r3 = &environ (see below) 34 | - "str %r2, [r3]\n" // store envp into environ 35 | +// "ldr %r3, 1f\n" // r3 = &environ (see below) 36 | +// "str %r2, [r3]\n" // store envp into environ 37 | 38 | "mov r4, r2\n" // search for auxv (follows NULL after last env) 39 | "0:\n" 40 | @@ -219,8 +219,8 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 41 | "ldr r5,[r5]\n" // r5 = *r5 = *(r4-4) 42 | "cmp r5, #0\n" // and stop at NULL after last env 43 | "bne 0b\n" 44 | - "ldr %r3, 2f\n" // r3 = &_auxv (low bits) 45 | - "str r4, [r3]\n" // store r4 into _auxv 46 | +// "ldr %r3, 2f\n" // r3 = &_auxv (low bits) 47 | +// "str r4, [r3]\n" // store r4 into _auxv 48 | 49 | "mov %r3, $8\n" // AAPCS : sp must be 8-byte aligned in the 50 | "neg %r3, %r3\n" // callee, and bl doesn't push (lr=pc) 51 | @@ -232,9 +232,9 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 52 | "svc $0x00\n" 53 | ".align 2\n" // below are the pointers to a few variables 54 | "1:\n" 55 | - ".word environ\n" 56 | +// ".word environ\n" 57 | "2:\n" 58 | - ".word _auxv\n" 59 | +// ".word _auxv\n" 60 | ); 61 | __builtin_unreachable(); 62 | } 63 | diff --git a/nolibc.orig/arch-i386.h b/nolibc/arch-i386.h 64 | index e8d0cf5..9605e87 100644 65 | --- a/nolibc.orig/arch-i386.h 66 | +++ b/nolibc/arch-i386.h 67 | @@ -194,14 +194,14 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 68 | "pop %eax\n" // argc (first arg, %eax) 69 | "mov %esp, %ebx\n" // argv[] (second arg, %ebx) 70 | "lea 4(%ebx,%eax,4),%ecx\n" // then a NULL then envp (third arg, %ecx) 71 | - "mov %ecx, environ\n" // save environ 72 | +// "mov %ecx, environ\n" // save environ 73 | "xor %ebp, %ebp\n" // zero the stack frame 74 | "mov %ecx, %edx\n" // search for auxv (follows NULL after last env) 75 | "0:\n" 76 | "add $4, %edx\n" // search for auxv using edx, it follows the 77 | "cmp -4(%edx), %ebp\n" // ... NULL after last env (ebp is zero here) 78 | "jnz 0b\n" 79 | - "mov %edx, _auxv\n" // save it into _auxv 80 | +// "mov %edx, _auxv\n" // save it into _auxv 81 | "and $-16, %esp\n" // x86 ABI : esp must be 16-byte aligned before 82 | "sub $4, %esp\n" // the call instruction (args are aligned) 83 | "push %ecx\n" // push all registers on the stack so that we 84 | diff --git a/nolibc.orig/arch-mips.h b/nolibc/arch-mips.h 85 | index bf83432..01a9648 100644 86 | --- a/nolibc.orig/arch-mips.h 87 | +++ b/nolibc/arch-mips.h 88 | @@ -194,18 +194,18 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __start(void) 89 | "sll $a2, $a0, 2\n" // a2 = argc * 4 90 | "add $a2, $a2, $a1\n" // envp = argv + 4*argc ... 91 | "addiu $a2, $a2, 4\n" // ... + 4 92 | - "lui $a3, %hi(environ)\n" // load environ into a3 (hi) 93 | - "addiu $a3, %lo(environ)\n" // load environ into a3 (lo) 94 | - "sw $a2,($a3)\n" // store envp(a2) into environ 95 | +// "lui $a3, %hi(environ)\n" // load environ into a3 (hi) 96 | +// "addiu $a3, %lo(environ)\n" // load environ into a3 (lo) 97 | +// "sw $a2,($a3)\n" // store envp(a2) into environ 98 | 99 | "move $t0, $a2\n" // iterate t0 over envp, look for NULL 100 | "0:" // do { 101 | "lw $a3, ($t0)\n" // a3=*(t0); 102 | "bne $a3, $0, 0b\n" // } while (a3); 103 | "addiu $t0, $t0, 4\n" // delayed slot: t0+=4; 104 | - "lui $a3, %hi(_auxv)\n" // load _auxv into a3 (hi) 105 | - "addiu $a3, %lo(_auxv)\n" // load _auxv into a3 (lo) 106 | - "sw $t0, ($a3)\n" // store t0 into _auxv 107 | +// "lui $a3, %hi(_auxv)\n" // load _auxv into a3 (hi) 108 | +// "addiu $a3, %lo(_auxv)\n" // load _auxv into a3 (lo) 109 | +// "sw $t0, ($a3)\n" // store t0 into _auxv 110 | 111 | "li $t0, -8\n" 112 | "and $sp, $sp, $t0\n" // sp must be 8-byte aligned 113 | diff --git a/nolibc.orig/arch-riscv.h b/nolibc/arch-riscv.h 114 | index e197fcb..cf51678 100644 115 | --- a/nolibc.orig/arch-riscv.h 116 | +++ b/nolibc/arch-riscv.h 117 | @@ -192,11 +192,11 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 118 | "ld a4, 0(a3)\n" // a4 = *a3; 119 | "add a3, a3, "SZREG"\n" // a3 += sizeof(void*); 120 | "bne a4, zero, 0b\n" // } while (a4); 121 | - "lui a4, %hi(_auxv)\n" // a4 = &_auxv (high bits) 122 | - "sd a3, %lo(_auxv)(a4)\n" // store a3 into _auxv 123 | +// "lui a4, %hi(_auxv)\n" // a4 = &_auxv (high bits) 124 | +// "sd a3, %lo(_auxv)(a4)\n" // store a3 into _auxv 125 | 126 | - "lui a3, %hi(environ)\n" // a3 = &environ (high bits) 127 | - "sd a2,%lo(environ)(a3)\n" // store envp(a2) into environ 128 | +// "lui a3, %hi(environ)\n" // a3 = &environ (high bits) 129 | +// "sd a2,%lo(environ)(a3)\n" // store envp(a2) into environ 130 | "andi sp,a1,-16\n" // sp must be 16-byte aligned 131 | "call main\n" // main() returns the status code, we'll exit with it. 132 | "li a7, 93\n" // NR_exit == 93 133 | diff --git a/nolibc.orig/arch-s390.h b/nolibc/arch-s390.h 134 | index 6b0e54e..0569cfb 100644 135 | --- a/nolibc.orig/arch-s390.h 136 | +++ b/nolibc/arch-s390.h 137 | @@ -177,7 +177,7 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 138 | "la %r4,8(%r4)\n" /* advance pointer */ 139 | "jnz 0b\n" /* no -> test next pointer */ 140 | /* yes -> r4 now contains start of envp */ 141 | - "larl %r1,environ\n" 142 | +// "larl %r1,environ\n" 143 | "stg %r4,0(%r1)\n" 144 | 145 | /* search for auxv */ 146 | @@ -186,7 +186,7 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 147 | "clg %r0,0(%r5)\n" /* entry zero? */ 148 | "la %r5,8(%r5)\n" /* advance pointer */ 149 | "jnz 1b\n" /* no -> test next pointer */ 150 | - "larl %r1,_auxv\n" /* yes -> store value in _auxv */ 151 | +// "larl %r1,_auxv\n" /* yes -> store value in _auxv */ 152 | "stg %r5,0(%r1)\n" 153 | 154 | "aghi %r15,-160\n" /* allocate new stackframe */ 155 | diff --git a/nolibc.orig/arch-x86_64.h b/nolibc/arch-x86_64.h 156 | index 17f6751..62106d3 100644 157 | --- a/nolibc.orig/arch-x86_64.h 158 | +++ b/nolibc/arch-x86_64.h 159 | @@ -194,14 +194,14 @@ void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 160 | "pop %rdi\n" // argc (first arg, %rdi) 161 | "mov %rsp, %rsi\n" // argv[] (second arg, %rsi) 162 | "lea 8(%rsi,%rdi,8),%rdx\n" // then a NULL then envp (third arg, %rdx) 163 | - "mov %rdx, environ\n" // save environ 164 | +// "mov %rdx, environ\n" // save environ 165 | "xor %ebp, %ebp\n" // zero the stack frame 166 | "mov %rdx, %rax\n" // search for auxv (follows NULL after last env) 167 | "0:\n" 168 | "add $8, %rax\n" // search for auxv using rax, it follows the 169 | "cmp -8(%rax), %rbp\n" // ... NULL after last env (rbp is zero here) 170 | "jnz 0b\n" 171 | - "mov %rax, _auxv\n" // save it into _auxv 172 | +// "mov %rax, _auxv\n" // save it into _auxv 173 | "and $-16, %rsp\n" // x86 ABI : esp must be 16-byte aligned before call 174 | "call main\n" // main() returns the status code, we'll exit with it. 175 | "mov %eax, %edi\n" // retrieve exit code (32 bit) 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nix-ld 2 | 3 | Run unpatched dynamic binaries on NixOS. 4 | 5 | ## Where is this useful? 6 | 7 | While many proprietary packages in nixpkgs have already been patched with 8 | `autoPatchelfHook` patching, there are cases where patching is not possible: 9 | 10 | - Use binary executable downloaded with third-party package managers (e.g. vscode, npm or pip) without having to patch them on every update. 11 | - Run games or proprietary software that attempts to verify its integrity. 12 | - Run programs that are too large for the nix store (e.g. FPGA IDEs). 13 | 14 | While there are other solutions such as `buildFHSUserEnv` that restore a Linux file 15 | hierarchy as found on common Linux systems (`ld-linux-x86-64.so.2`), these 16 | sandboxes have their own weaknesses: 17 | 18 | - setuid binaries cannot be executed inside a fhsuserenv 19 | - inside a `buildFHSUserEnv` you can not use other sandbox tools like bwrap or 'nix build'. 20 | - `buildFHSUserEnv` requires a subshell which does not work well with direnv 21 | 22 | ## How does nix-ld work? 23 | 24 | Also read this [blog post](https://blog.thalheim.io/2022/12/31/nix-ld-a-clean-solution-for-issues-with-pre-compiled-executables-on-nixos/) 25 | to get the explaination in full detail. A summary is below: 26 | 27 | Precompiled binaries that were not created for NixOS usually have a so-called 28 | link-loader hardcoded into them. On Linux/x86_64 this is for example 29 | `/lib64/ld-linux-x86-64.so.2`. for glibc. NixOS, on the other hand, usually has 30 | its dynamic linker in the glibc package in the Nix store and therefore cannot 31 | run these binaries. Nix-ld provides a shim layer for these types of binaries. It 32 | is installed in the same location where other Linux distributions install their 33 | link loader, ie. `/lib64/ld-linux-x86-64.so.2` and then loads the actual link 34 | loader as specified in the environment variable `NIX_LD`. In addition, it also 35 | accepts a colon-separated path from library lookup paths in `NIX_LD_LIBRARY_PATH`. 36 | This environment variable is rewritten to `LD_LIBRARY_PATH` before 37 | passing execution to the actual ld. This allows you to specify additional 38 | libraries that the executable needs to run. 39 | 40 | ## Installation 41 | 42 | nix-ld is part of nixpkgs since NixOS 22.05. There one can enable it with the following 43 | nixos setting: 44 | 45 | ```nix 46 | { 47 | programs.nix-ld.enable = true; 48 | } 49 | ``` 50 | 51 | To install `nix-ld` from the repository instead, use the following method: 52 | 53 | ```sh 54 | $ sudo nix-channel --add https://github.com/Mic92/nix-ld/archive/main.tar.gz nix-ld 55 | $ sudo nix-channel --update 56 | ``` 57 | 58 | `/etc/nixos/configuration.nix` 59 | 60 | ```nix 61 | { 62 | imports = [ 63 | 64 | ]; 65 | # The module in this repository defines a new module under (programs.nix-ld.dev) instead of (programs.nix-ld) 66 | # to not collide with the nixpkgs version. 67 | programs.nix-ld.dev.enable = true; 68 | } 69 | ``` 70 | 71 | 72 | ### With nix flake 73 | 74 | Add the following lines to `/etc/nixos/flake.nix`. Replace `myhostname` with the 75 | actual hostname of your system. 76 | 77 | ```nix 78 | # flake.nix 79 | { 80 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/master"; 81 | inputs.nix-ld.url = "github:Mic92/nix-ld"; 82 | # this line assume that you also have nixpkgs as an input 83 | inputs.nix-ld.inputs.nixpkgs.follows = "nixpkgs"; 84 | 85 | outputs = { nix-ld, nixpkgs, ... }: { 86 | # replace `myhostname` with your actual hostname 87 | nixosConfigurations.myhostname = nixpkgs.lib.nixosSystem { 88 | system = "x86_64-linux"; 89 | modules = [ 90 | # ... add this line to the rest of your configuration modules 91 | nix-ld.nixosModules.nix-ld 92 | 93 | # The module in this repository defines a new module under (programs.nix-ld.dev) instead of (programs.nix-ld) 94 | # to not collide with the nixpkgs version. 95 | { programs.nix-ld.dev.enable = true; } 96 | ]; 97 | }; 98 | }; 99 | } 100 | ``` 101 | 102 | ## Usage 103 | 104 | nix-ld honors the following environment variables: 105 | 106 | - `NIX_LD` 107 | - `NIX_LD_{system}` 108 | - `NIX_LD_LIBRARY_PATH` 109 | - `NIX_LD_LIBRARY_PATH_{system}` 110 | - `NIX_LD_LOG` (error, warn, info, debug, trace) 111 | 112 | Here `{system}` is the value of the Nix `system` with dashes replaced with underscores, like `x86_64_linux`. 113 | You can also run `nix-ld` directly for a list. 114 | 115 | After setting up the nix-ld symlink as described above, one needs to set at least 116 | `NIX_LD` and `NIX_LD_LIBRARY_PATH` to run executables. For example, this can 117 | be done with a `shell.nix` in a nix-shell like this: 118 | 119 | ```nix 120 | with import {}; 121 | mkShell { 122 | NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [ 123 | stdenv.cc.cc 124 | openssl 125 | # ... 126 | ]; 127 | NIX_LD = lib.fileContents "${stdenv.cc}/nix-support/dynamic-linker"; 128 | } 129 | ``` 130 | 131 | A full example is shown in [`./examples/masterpdfeditor.nix`](examples/masterpdfeditor.nix). 132 | 133 | In [nix-autobahn](https://github.com/Lassulus/nix-autobahn) there is also a 134 | script called `nix-autobahn-ld` that automates generating shell expressions. 135 | 136 | In [nix-alien](https://github.com/thiagokokada/nix-alien) there is another 137 | script called `nix-alien-ld` that uses another strategy, wrapping the program in 138 | a `writeShellScriptBin` with the `NIX_LD`/`NIX_LD_LIBRARY_PATH` environment 139 | variables set. 140 | 141 | To figure out what libraries a program needs, you can use `ldd` on the binary or 142 | set the `LD_DEBUG=libs` environment variable. 143 | 144 | ## FAQ 145 | 146 | ### How to find libraries for my executables? 147 | 148 | You can use tools like [nix-autobahn](https://github.com/Lassulus/nix-autobahn), 149 | [nix-alien](https://github.com/thiagokokada/nix-alien) or use 150 | [nix-index](https://github.com/bennofs/nix-index) 151 | 152 | ### Why not set LD_LIBRARY_PATH directly instead of NIX_LD_LIBRARY_PATH? 153 | 154 | LD_LIBRARY_PATH affects all programs, which can inject the wrong libraries in 155 | correct build nix application that have an RPATH set in their executable. 156 | 157 | ### Does this work on non-NixOS system? 158 | 159 | No. Normal Linux distributions will have their own link-loader. Replacing those 160 | with nix-ld will break the system. 161 | 162 | ### My python/nodejs/ruby/$interpreter libraries do not find the libraries configured by nix-ld 163 | 164 | Nix-ld is only used by unpatched executables that use the link loader at `/lib` 165 | or `/lib64`. If you use for example python from nixpkgs than it will not pick 166 | up `NIX_LD_LIBRARY_PATH` and `NIX_LD` since these types of binaries are 167 | configured to use a glibc from the nix store. If you encounter these cases i.e. 168 | when you are trying to use python packages installed in a virtualenv than you 169 | need to set `LD_LIBRARY_PATH` directly. You can also create yourself a wrapper 170 | like this: 171 | 172 | ```nix 173 | (pkgs.writeShellScriptBin "python" '' 174 | export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH 175 | exec ${pkgs.python3}/bin/python "$@" 176 | '') 177 | ``` 178 | 179 | ## Development 180 | 181 | The included `devShell` provides all dependencies required to build the project. 182 | It's recommended to set up transparent emulation using binfmt-misc so you can run tests on all supported platforms: 183 | 184 | ```nix 185 | { 186 | # x86_64-linux, i686-linux, aarch64-linux 187 | boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; 188 | } 189 | ``` 190 | 191 | Run `cargo test` or `cargo nextest run` to run the integration tests, and `just test` to run them on all supported platforms (binfmt required). 192 | 193 | ## Current behavior 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 |
LaunchSeen by ld.soSeen by getenv() and children (a)
NIX_LD_LIBRARY_PATHLD_LIBRARY_PATHNIX_LD_LIBRARY_PATHLD_LIBRARY_PATHNIX_LD_LIBRARY_PATHLD_LIBRARY_PATH
1(unset)(unset)(unset)"/run/current-system/sw/share/nix-ld/lib"(unset)"" (b)
2(unset)"/some/lib"(unset)"/some/lib:/run/current-system/sw/share/nix-ld/lib"(unset)"/some/lib"
3"/some/nix/ld/lib"(unset)(unset)"/some/nix/ld/lib""/some/nix/ld/lib"(unset)
4"/some/nix/ld/lib""/some/lib""/some/nix/ld/lib""/some/lib:/some/nix/ld/lib""/some/nix/ld/lib""/some/lib"
251 | 252 | (a) On X86-64 and AArch64 only (see `src/arch.rs`). On other platforms, the "Seen by ld.so" state will persist.
253 | (b) The variable will be present but set to an empty string.
254 | 255 | ## History of the project 256 | 257 | * nix-ld was originally written by [Mic92](https://github.com/Mic92) in 2020 258 | * [@zhaofengli](https://github.com/zhaofengli) create a new project based on the idea, called nix-ld-rs in 2023 259 | * Later nix-ld-rs was merged into nix-ld in 2024 260 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-aarch64.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * AARCH64 specific definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ARCH_AARCH64_H 8 | #define _NOLIBC_ARCH_AARCH64_H 9 | 10 | /* The struct returned by the newfstatat() syscall. Differs slightly from the 11 | * x86_64's stat one by field ordering, so be careful. 12 | */ 13 | struct sys_stat_struct { 14 | unsigned long st_dev; 15 | unsigned long st_ino; 16 | unsigned int st_mode; 17 | unsigned int st_nlink; 18 | unsigned int st_uid; 19 | unsigned int st_gid; 20 | 21 | unsigned long st_rdev; 22 | unsigned long __pad1; 23 | long st_size; 24 | int st_blksize; 25 | int __pad2; 26 | 27 | long st_blocks; 28 | long st_atime; 29 | unsigned long st_atime_nsec; 30 | long st_mtime; 31 | 32 | unsigned long st_mtime_nsec; 33 | long st_ctime; 34 | unsigned long st_ctime_nsec; 35 | unsigned int __unused[2]; 36 | }; 37 | 38 | /* Syscalls for AARCH64 : 39 | * - registers are 64-bit 40 | * - stack is 16-byte aligned 41 | * - syscall number is passed in x8 42 | * - arguments are in x0, x1, x2, x3, x4, x5 43 | * - the system call is performed by calling svc 0 44 | * - syscall return comes in x0. 45 | * - the arguments are cast to long and assigned into the target registers 46 | * which are then simply passed as registers to the asm code, so that we 47 | * don't have to experience issues with register constraints. 48 | * 49 | * On aarch64, select() is not implemented so we have to use pselect6(). 50 | */ 51 | #define __ARCH_WANT_SYS_PSELECT6 52 | 53 | #define my_syscall0(num) \ 54 | ({ \ 55 | register long _num __asm__ ("x8") = (num); \ 56 | register long _arg1 __asm__ ("x0"); \ 57 | \ 58 | __asm__ volatile ( \ 59 | "svc #0\n" \ 60 | : "=r"(_arg1) \ 61 | : "r"(_num) \ 62 | : "memory", "cc" \ 63 | ); \ 64 | _arg1; \ 65 | }) 66 | 67 | #define my_syscall1(num, arg1) \ 68 | ({ \ 69 | register long _num __asm__ ("x8") = (num); \ 70 | register long _arg1 __asm__ ("x0") = (long)(arg1); \ 71 | \ 72 | __asm__ volatile ( \ 73 | "svc #0\n" \ 74 | : "=r"(_arg1) \ 75 | : "r"(_arg1), \ 76 | "r"(_num) \ 77 | : "memory", "cc" \ 78 | ); \ 79 | _arg1; \ 80 | }) 81 | 82 | #define my_syscall2(num, arg1, arg2) \ 83 | ({ \ 84 | register long _num __asm__ ("x8") = (num); \ 85 | register long _arg1 __asm__ ("x0") = (long)(arg1); \ 86 | register long _arg2 __asm__ ("x1") = (long)(arg2); \ 87 | \ 88 | __asm__ volatile ( \ 89 | "svc #0\n" \ 90 | : "=r"(_arg1) \ 91 | : "r"(_arg1), "r"(_arg2), \ 92 | "r"(_num) \ 93 | : "memory", "cc" \ 94 | ); \ 95 | _arg1; \ 96 | }) 97 | 98 | #define my_syscall3(num, arg1, arg2, arg3) \ 99 | ({ \ 100 | register long _num __asm__ ("x8") = (num); \ 101 | register long _arg1 __asm__ ("x0") = (long)(arg1); \ 102 | register long _arg2 __asm__ ("x1") = (long)(arg2); \ 103 | register long _arg3 __asm__ ("x2") = (long)(arg3); \ 104 | \ 105 | __asm__ volatile ( \ 106 | "svc #0\n" \ 107 | : "=r"(_arg1) \ 108 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), \ 109 | "r"(_num) \ 110 | : "memory", "cc" \ 111 | ); \ 112 | _arg1; \ 113 | }) 114 | 115 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 116 | ({ \ 117 | register long _num __asm__ ("x8") = (num); \ 118 | register long _arg1 __asm__ ("x0") = (long)(arg1); \ 119 | register long _arg2 __asm__ ("x1") = (long)(arg2); \ 120 | register long _arg3 __asm__ ("x2") = (long)(arg3); \ 121 | register long _arg4 __asm__ ("x3") = (long)(arg4); \ 122 | \ 123 | __asm__ volatile ( \ 124 | "svc #0\n" \ 125 | : "=r"(_arg1) \ 126 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ 127 | "r"(_num) \ 128 | : "memory", "cc" \ 129 | ); \ 130 | _arg1; \ 131 | }) 132 | 133 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 134 | ({ \ 135 | register long _num __asm__ ("x8") = (num); \ 136 | register long _arg1 __asm__ ("x0") = (long)(arg1); \ 137 | register long _arg2 __asm__ ("x1") = (long)(arg2); \ 138 | register long _arg3 __asm__ ("x2") = (long)(arg3); \ 139 | register long _arg4 __asm__ ("x3") = (long)(arg4); \ 140 | register long _arg5 __asm__ ("x4") = (long)(arg5); \ 141 | \ 142 | __asm__ volatile ( \ 143 | "svc #0\n" \ 144 | : "=r" (_arg1) \ 145 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 146 | "r"(_num) \ 147 | : "memory", "cc" \ 148 | ); \ 149 | _arg1; \ 150 | }) 151 | 152 | #define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ 153 | ({ \ 154 | register long _num __asm__ ("x8") = (num); \ 155 | register long _arg1 __asm__ ("x0") = (long)(arg1); \ 156 | register long _arg2 __asm__ ("x1") = (long)(arg2); \ 157 | register long _arg3 __asm__ ("x2") = (long)(arg3); \ 158 | register long _arg4 __asm__ ("x3") = (long)(arg4); \ 159 | register long _arg5 __asm__ ("x4") = (long)(arg5); \ 160 | register long _arg6 __asm__ ("x5") = (long)(arg6); \ 161 | \ 162 | __asm__ volatile ( \ 163 | "svc #0\n" \ 164 | : "=r" (_arg1) \ 165 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 166 | "r"(_arg6), "r"(_num) \ 167 | : "memory", "cc" \ 168 | ); \ 169 | _arg1; \ 170 | }) 171 | 172 | char **environ __attribute__((weak)); 173 | const unsigned long *_auxv __attribute__((weak)); 174 | 175 | /* startup code */ 176 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 177 | { 178 | __asm__ volatile ( 179 | "ldr x0, [sp]\n" // argc (x0) was in the stack 180 | "add x1, sp, 8\n" // argv (x1) = sp 181 | "lsl x2, x0, 3\n" // envp (x2) = 8*argc ... 182 | "add x2, x2, 8\n" // + 8 (skip null) 183 | "add x2, x2, x1\n" // + argv 184 | // "adrp x3, environ\n" // x3 = &environ (high bits) 185 | // "str x2, [x3, #:lo12:environ]\n" // store envp into environ 186 | "mov x4, x2\n" // search for auxv (follows NULL after last env) 187 | "0:\n" 188 | "ldr x5, [x4], 8\n" // x5 = *x4; x4 += 8 189 | "cbnz x5, 0b\n" // and stop at NULL after last env 190 | // "adrp x3, _auxv\n" // x3 = &_auxv (high bits) 191 | // "str x4, [x3, #:lo12:_auxv]\n" // store x4 into _auxv 192 | "and sp, x1, -16\n" // sp must be 16-byte aligned in the callee 193 | "bl main\n" // main() returns the status code, we'll exit with it. 194 | "mov x8, 93\n" // NR_exit == 93 195 | "svc #0\n" 196 | ); 197 | __builtin_unreachable(); 198 | } 199 | #endif // _NOLIBC_ARCH_AARCH64_H 200 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-riscv.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * RISCV (32 and 64) specific definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ARCH_RISCV_H 8 | #define _NOLIBC_ARCH_RISCV_H 9 | 10 | struct sys_stat_struct { 11 | unsigned long st_dev; /* Device. */ 12 | unsigned long st_ino; /* File serial number. */ 13 | unsigned int st_mode; /* File mode. */ 14 | unsigned int st_nlink; /* Link count. */ 15 | unsigned int st_uid; /* User ID of the file's owner. */ 16 | unsigned int st_gid; /* Group ID of the file's group. */ 17 | unsigned long st_rdev; /* Device number, if device. */ 18 | unsigned long __pad1; 19 | long st_size; /* Size of file, in bytes. */ 20 | int st_blksize; /* Optimal block size for I/O. */ 21 | int __pad2; 22 | long st_blocks; /* Number 512-byte blocks allocated. */ 23 | long st_atime; /* Time of last access. */ 24 | unsigned long st_atime_nsec; 25 | long st_mtime; /* Time of last modification. */ 26 | unsigned long st_mtime_nsec; 27 | long st_ctime; /* Time of last status change. */ 28 | unsigned long st_ctime_nsec; 29 | unsigned int __unused4; 30 | unsigned int __unused5; 31 | }; 32 | 33 | #if __riscv_xlen == 64 34 | #define PTRLOG "3" 35 | #define SZREG "8" 36 | #elif __riscv_xlen == 32 37 | #define PTRLOG "2" 38 | #define SZREG "4" 39 | #endif 40 | 41 | /* Syscalls for RISCV : 42 | * - stack is 16-byte aligned 43 | * - syscall number is passed in a7 44 | * - arguments are in a0, a1, a2, a3, a4, a5 45 | * - the system call is performed by calling ecall 46 | * - syscall return comes in a0 47 | * - the arguments are cast to long and assigned into the target 48 | * registers which are then simply passed as registers to the asm code, 49 | * so that we don't have to experience issues with register constraints. 50 | * 51 | * On riscv, select() is not implemented so we have to use pselect6(). 52 | */ 53 | #define __ARCH_WANT_SYS_PSELECT6 54 | 55 | #define my_syscall0(num) \ 56 | ({ \ 57 | register long _num __asm__ ("a7") = (num); \ 58 | register long _arg1 __asm__ ("a0"); \ 59 | \ 60 | __asm__ volatile ( \ 61 | "ecall\n\t" \ 62 | : "=r"(_arg1) \ 63 | : "r"(_num) \ 64 | : "memory", "cc" \ 65 | ); \ 66 | _arg1; \ 67 | }) 68 | 69 | #define my_syscall1(num, arg1) \ 70 | ({ \ 71 | register long _num __asm__ ("a7") = (num); \ 72 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 73 | \ 74 | __asm__ volatile ( \ 75 | "ecall\n" \ 76 | : "+r"(_arg1) \ 77 | : "r"(_num) \ 78 | : "memory", "cc" \ 79 | ); \ 80 | _arg1; \ 81 | }) 82 | 83 | #define my_syscall2(num, arg1, arg2) \ 84 | ({ \ 85 | register long _num __asm__ ("a7") = (num); \ 86 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 87 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 88 | \ 89 | __asm__ volatile ( \ 90 | "ecall\n" \ 91 | : "+r"(_arg1) \ 92 | : "r"(_arg2), \ 93 | "r"(_num) \ 94 | : "memory", "cc" \ 95 | ); \ 96 | _arg1; \ 97 | }) 98 | 99 | #define my_syscall3(num, arg1, arg2, arg3) \ 100 | ({ \ 101 | register long _num __asm__ ("a7") = (num); \ 102 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 103 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 104 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 105 | \ 106 | __asm__ volatile ( \ 107 | "ecall\n\t" \ 108 | : "+r"(_arg1) \ 109 | : "r"(_arg2), "r"(_arg3), \ 110 | "r"(_num) \ 111 | : "memory", "cc" \ 112 | ); \ 113 | _arg1; \ 114 | }) 115 | 116 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 117 | ({ \ 118 | register long _num __asm__ ("a7") = (num); \ 119 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 120 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 121 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 122 | register long _arg4 __asm__ ("a3") = (long)(arg4); \ 123 | \ 124 | __asm__ volatile ( \ 125 | "ecall\n" \ 126 | : "+r"(_arg1) \ 127 | : "r"(_arg2), "r"(_arg3), "r"(_arg4), \ 128 | "r"(_num) \ 129 | : "memory", "cc" \ 130 | ); \ 131 | _arg1; \ 132 | }) 133 | 134 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 135 | ({ \ 136 | register long _num __asm__ ("a7") = (num); \ 137 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 138 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 139 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 140 | register long _arg4 __asm__ ("a3") = (long)(arg4); \ 141 | register long _arg5 __asm__ ("a4") = (long)(arg5); \ 142 | \ 143 | __asm__ volatile ( \ 144 | "ecall\n" \ 145 | : "+r"(_arg1) \ 146 | : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 147 | "r"(_num) \ 148 | : "memory", "cc" \ 149 | ); \ 150 | _arg1; \ 151 | }) 152 | 153 | #define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ 154 | ({ \ 155 | register long _num __asm__ ("a7") = (num); \ 156 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 157 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 158 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 159 | register long _arg4 __asm__ ("a3") = (long)(arg4); \ 160 | register long _arg5 __asm__ ("a4") = (long)(arg5); \ 161 | register long _arg6 __asm__ ("a5") = (long)(arg6); \ 162 | \ 163 | __asm__ volatile ( \ 164 | "ecall\n" \ 165 | : "+r"(_arg1) \ 166 | : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), "r"(_arg6), \ 167 | "r"(_num) \ 168 | : "memory", "cc" \ 169 | ); \ 170 | _arg1; \ 171 | }) 172 | 173 | char **environ __attribute__((weak)); 174 | const unsigned long *_auxv __attribute__((weak)); 175 | 176 | /* startup code */ 177 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 178 | { 179 | __asm__ volatile ( 180 | ".option push\n" 181 | ".option norelax\n" 182 | "lla gp, __global_pointer$\n" 183 | ".option pop\n" 184 | "lw a0, 0(sp)\n" // argc (a0) was in the stack 185 | "add a1, sp, "SZREG"\n" // argv (a1) = sp 186 | "slli a2, a0, "PTRLOG"\n" // envp (a2) = SZREG*argc ... 187 | "add a2, a2, "SZREG"\n" // + SZREG (skip null) 188 | "add a2,a2,a1\n" // + argv 189 | 190 | "add a3, a2, zero\n" // iterate a3 over envp to find auxv (after NULL) 191 | "0:\n" // do { 192 | "ld a4, 0(a3)\n" // a4 = *a3; 193 | "add a3, a3, "SZREG"\n" // a3 += sizeof(void*); 194 | "bne a4, zero, 0b\n" // } while (a4); 195 | // "lui a4, %hi(_auxv)\n" // a4 = &_auxv (high bits) 196 | // "sd a3, %lo(_auxv)(a4)\n" // store a3 into _auxv 197 | 198 | // "lui a3, %hi(environ)\n" // a3 = &environ (high bits) 199 | // "sd a2,%lo(environ)(a3)\n" // store envp(a2) into environ 200 | "andi sp,a1,-16\n" // sp must be 16-byte aligned 201 | "call main\n" // main() returns the status code, we'll exit with it. 202 | "li a7, 93\n" // NR_exit == 93 203 | "ecall\n" 204 | ); 205 | __builtin_unreachable(); 206 | } 207 | 208 | #endif // _NOLIBC_ARCH_RISCV_H 209 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-i386.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * i386 specific definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ARCH_I386_H 8 | #define _NOLIBC_ARCH_I386_H 9 | 10 | /* The struct returned by the stat() syscall, 32-bit only, the syscall returns 11 | * exactly 56 bytes (stops before the unused array). 12 | */ 13 | struct sys_stat_struct { 14 | unsigned long st_dev; 15 | unsigned long st_ino; 16 | unsigned short st_mode; 17 | unsigned short st_nlink; 18 | unsigned short st_uid; 19 | unsigned short st_gid; 20 | 21 | unsigned long st_rdev; 22 | unsigned long st_size; 23 | unsigned long st_blksize; 24 | unsigned long st_blocks; 25 | 26 | unsigned long st_atime; 27 | unsigned long st_atime_nsec; 28 | unsigned long st_mtime; 29 | unsigned long st_mtime_nsec; 30 | 31 | unsigned long st_ctime; 32 | unsigned long st_ctime_nsec; 33 | unsigned long __unused[2]; 34 | }; 35 | 36 | /* Syscalls for i386 : 37 | * - mostly similar to x86_64 38 | * - registers are 32-bit 39 | * - syscall number is passed in eax 40 | * - arguments are in ebx, ecx, edx, esi, edi, ebp respectively 41 | * - all registers are preserved (except eax of course) 42 | * - the system call is performed by calling int $0x80 43 | * - syscall return comes in eax 44 | * - the arguments are cast to long and assigned into the target registers 45 | * which are then simply passed as registers to the asm code, so that we 46 | * don't have to experience issues with register constraints. 47 | * - the syscall number is always specified last in order to allow to force 48 | * some registers before (gcc refuses a %-register at the last position). 49 | * 50 | * Also, i386 supports the old_select syscall if newselect is not available 51 | */ 52 | #define __ARCH_WANT_SYS_OLD_SELECT 53 | 54 | #define my_syscall0(num) \ 55 | ({ \ 56 | long _ret; \ 57 | register long _num __asm__ ("eax") = (num); \ 58 | \ 59 | __asm__ volatile ( \ 60 | "int $0x80\n" \ 61 | : "=a" (_ret) \ 62 | : "0"(_num) \ 63 | : "memory", "cc" \ 64 | ); \ 65 | _ret; \ 66 | }) 67 | 68 | #define my_syscall1(num, arg1) \ 69 | ({ \ 70 | long _ret; \ 71 | register long _num __asm__ ("eax") = (num); \ 72 | register long _arg1 __asm__ ("ebx") = (long)(arg1); \ 73 | \ 74 | __asm__ volatile ( \ 75 | "int $0x80\n" \ 76 | : "=a" (_ret) \ 77 | : "r"(_arg1), \ 78 | "0"(_num) \ 79 | : "memory", "cc" \ 80 | ); \ 81 | _ret; \ 82 | }) 83 | 84 | #define my_syscall2(num, arg1, arg2) \ 85 | ({ \ 86 | long _ret; \ 87 | register long _num __asm__ ("eax") = (num); \ 88 | register long _arg1 __asm__ ("ebx") = (long)(arg1); \ 89 | register long _arg2 __asm__ ("ecx") = (long)(arg2); \ 90 | \ 91 | __asm__ volatile ( \ 92 | "int $0x80\n" \ 93 | : "=a" (_ret) \ 94 | : "r"(_arg1), "r"(_arg2), \ 95 | "0"(_num) \ 96 | : "memory", "cc" \ 97 | ); \ 98 | _ret; \ 99 | }) 100 | 101 | #define my_syscall3(num, arg1, arg2, arg3) \ 102 | ({ \ 103 | long _ret; \ 104 | register long _num __asm__ ("eax") = (num); \ 105 | register long _arg1 __asm__ ("ebx") = (long)(arg1); \ 106 | register long _arg2 __asm__ ("ecx") = (long)(arg2); \ 107 | register long _arg3 __asm__ ("edx") = (long)(arg3); \ 108 | \ 109 | __asm__ volatile ( \ 110 | "int $0x80\n" \ 111 | : "=a" (_ret) \ 112 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), \ 113 | "0"(_num) \ 114 | : "memory", "cc" \ 115 | ); \ 116 | _ret; \ 117 | }) 118 | 119 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 120 | ({ \ 121 | long _ret; \ 122 | register long _num __asm__ ("eax") = (num); \ 123 | register long _arg1 __asm__ ("ebx") = (long)(arg1); \ 124 | register long _arg2 __asm__ ("ecx") = (long)(arg2); \ 125 | register long _arg3 __asm__ ("edx") = (long)(arg3); \ 126 | register long _arg4 __asm__ ("esi") = (long)(arg4); \ 127 | \ 128 | __asm__ volatile ( \ 129 | "int $0x80\n" \ 130 | : "=a" (_ret) \ 131 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ 132 | "0"(_num) \ 133 | : "memory", "cc" \ 134 | ); \ 135 | _ret; \ 136 | }) 137 | 138 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 139 | ({ \ 140 | long _ret; \ 141 | register long _num __asm__ ("eax") = (num); \ 142 | register long _arg1 __asm__ ("ebx") = (long)(arg1); \ 143 | register long _arg2 __asm__ ("ecx") = (long)(arg2); \ 144 | register long _arg3 __asm__ ("edx") = (long)(arg3); \ 145 | register long _arg4 __asm__ ("esi") = (long)(arg4); \ 146 | register long _arg5 __asm__ ("edi") = (long)(arg5); \ 147 | \ 148 | __asm__ volatile ( \ 149 | "int $0x80\n" \ 150 | : "=a" (_ret) \ 151 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 152 | "0"(_num) \ 153 | : "memory", "cc" \ 154 | ); \ 155 | _ret; \ 156 | }) 157 | 158 | #define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ 159 | ({ \ 160 | long _eax = (long)(num); \ 161 | long _arg6 = (long)(arg6); /* Always in memory */ \ 162 | __asm__ volatile ( \ 163 | "pushl %[_arg6]\n\t" \ 164 | "pushl %%ebp\n\t" \ 165 | "movl 4(%%esp),%%ebp\n\t" \ 166 | "int $0x80\n\t" \ 167 | "popl %%ebp\n\t" \ 168 | "addl $4,%%esp\n\t" \ 169 | : "+a"(_eax) /* %eax */ \ 170 | : "b"(arg1), /* %ebx */ \ 171 | "c"(arg2), /* %ecx */ \ 172 | "d"(arg3), /* %edx */ \ 173 | "S"(arg4), /* %esi */ \ 174 | "D"(arg5), /* %edi */ \ 175 | [_arg6]"m"(_arg6) /* memory */ \ 176 | : "memory", "cc" \ 177 | ); \ 178 | _eax; \ 179 | }) 180 | 181 | char **environ __attribute__((weak)); 182 | const unsigned long *_auxv __attribute__((weak)); 183 | 184 | /* startup code */ 185 | /* 186 | * i386 System V ABI mandates: 187 | * 1) last pushed argument must be 16-byte aligned. 188 | * 2) The deepest stack frame should be set to zero 189 | * 190 | */ 191 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 192 | { 193 | __asm__ volatile ( 194 | "pop %eax\n" // argc (first arg, %eax) 195 | "mov %esp, %ebx\n" // argv[] (second arg, %ebx) 196 | "lea 4(%ebx,%eax,4),%ecx\n" // then a NULL then envp (third arg, %ecx) 197 | // "mov %ecx, environ\n" // save environ 198 | "xor %ebp, %ebp\n" // zero the stack frame 199 | "mov %ecx, %edx\n" // search for auxv (follows NULL after last env) 200 | "0:\n" 201 | "add $4, %edx\n" // search for auxv using edx, it follows the 202 | "cmp -4(%edx), %ebp\n" // ... NULL after last env (ebp is zero here) 203 | "jnz 0b\n" 204 | // "mov %edx, _auxv\n" // save it into _auxv 205 | "and $-16, %esp\n" // x86 ABI : esp must be 16-byte aligned before 206 | "sub $4, %esp\n" // the call instruction (args are aligned) 207 | "push %ecx\n" // push all registers on the stack so that we 208 | "push %ebx\n" // support both regparm and plain stack modes 209 | "push %eax\n" 210 | "call main\n" // main() returns the status code in %eax 211 | "mov %eax, %ebx\n" // retrieve exit code (32-bit int) 212 | "movl $1, %eax\n" // NR_exit == 1 213 | "int $0x80\n" // exit now 214 | "hlt\n" // ensure it does not 215 | ); 216 | __builtin_unreachable(); 217 | } 218 | 219 | #endif // _NOLIBC_ARCH_I386_H 220 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-x86_64.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * x86_64 specific definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ARCH_X86_64_H 8 | #define _NOLIBC_ARCH_X86_64_H 9 | 10 | /* The struct returned by the stat() syscall, equivalent to stat64(). The 11 | * syscall returns 116 bytes and stops in the middle of __unused. 12 | */ 13 | struct sys_stat_struct { 14 | unsigned long st_dev; 15 | unsigned long st_ino; 16 | unsigned long st_nlink; 17 | unsigned int st_mode; 18 | unsigned int st_uid; 19 | 20 | unsigned int st_gid; 21 | unsigned int __pad0; 22 | unsigned long st_rdev; 23 | long st_size; 24 | long st_blksize; 25 | 26 | long st_blocks; 27 | unsigned long st_atime; 28 | unsigned long st_atime_nsec; 29 | unsigned long st_mtime; 30 | 31 | unsigned long st_mtime_nsec; 32 | unsigned long st_ctime; 33 | unsigned long st_ctime_nsec; 34 | long __unused[3]; 35 | }; 36 | 37 | /* Syscalls for x86_64 : 38 | * - registers are 64-bit 39 | * - syscall number is passed in rax 40 | * - arguments are in rdi, rsi, rdx, r10, r8, r9 respectively 41 | * - the system call is performed by calling the syscall instruction 42 | * - syscall return comes in rax 43 | * - rcx and r11 are clobbered, others are preserved. 44 | * - the arguments are cast to long and assigned into the target registers 45 | * which are then simply passed as registers to the asm code, so that we 46 | * don't have to experience issues with register constraints. 47 | * - the syscall number is always specified last in order to allow to force 48 | * some registers before (gcc refuses a %-register at the last position). 49 | * - see also x86-64 ABI section A.2 AMD64 Linux Kernel Conventions, A.2.1 50 | * Calling Conventions. 51 | * 52 | * Link x86-64 ABI: https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/home 53 | * 54 | */ 55 | 56 | #define my_syscall0(num) \ 57 | ({ \ 58 | long _ret; \ 59 | register long _num __asm__ ("rax") = (num); \ 60 | \ 61 | __asm__ volatile ( \ 62 | "syscall\n" \ 63 | : "=a"(_ret) \ 64 | : "0"(_num) \ 65 | : "rcx", "r11", "memory", "cc" \ 66 | ); \ 67 | _ret; \ 68 | }) 69 | 70 | #define my_syscall1(num, arg1) \ 71 | ({ \ 72 | long _ret; \ 73 | register long _num __asm__ ("rax") = (num); \ 74 | register long _arg1 __asm__ ("rdi") = (long)(arg1); \ 75 | \ 76 | __asm__ volatile ( \ 77 | "syscall\n" \ 78 | : "=a"(_ret) \ 79 | : "r"(_arg1), \ 80 | "0"(_num) \ 81 | : "rcx", "r11", "memory", "cc" \ 82 | ); \ 83 | _ret; \ 84 | }) 85 | 86 | #define my_syscall2(num, arg1, arg2) \ 87 | ({ \ 88 | long _ret; \ 89 | register long _num __asm__ ("rax") = (num); \ 90 | register long _arg1 __asm__ ("rdi") = (long)(arg1); \ 91 | register long _arg2 __asm__ ("rsi") = (long)(arg2); \ 92 | \ 93 | __asm__ volatile ( \ 94 | "syscall\n" \ 95 | : "=a"(_ret) \ 96 | : "r"(_arg1), "r"(_arg2), \ 97 | "0"(_num) \ 98 | : "rcx", "r11", "memory", "cc" \ 99 | ); \ 100 | _ret; \ 101 | }) 102 | 103 | #define my_syscall3(num, arg1, arg2, arg3) \ 104 | ({ \ 105 | long _ret; \ 106 | register long _num __asm__ ("rax") = (num); \ 107 | register long _arg1 __asm__ ("rdi") = (long)(arg1); \ 108 | register long _arg2 __asm__ ("rsi") = (long)(arg2); \ 109 | register long _arg3 __asm__ ("rdx") = (long)(arg3); \ 110 | \ 111 | __asm__ volatile ( \ 112 | "syscall\n" \ 113 | : "=a"(_ret) \ 114 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), \ 115 | "0"(_num) \ 116 | : "rcx", "r11", "memory", "cc" \ 117 | ); \ 118 | _ret; \ 119 | }) 120 | 121 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 122 | ({ \ 123 | long _ret; \ 124 | register long _num __asm__ ("rax") = (num); \ 125 | register long _arg1 __asm__ ("rdi") = (long)(arg1); \ 126 | register long _arg2 __asm__ ("rsi") = (long)(arg2); \ 127 | register long _arg3 __asm__ ("rdx") = (long)(arg3); \ 128 | register long _arg4 __asm__ ("r10") = (long)(arg4); \ 129 | \ 130 | __asm__ volatile ( \ 131 | "syscall\n" \ 132 | : "=a"(_ret) \ 133 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ 134 | "0"(_num) \ 135 | : "rcx", "r11", "memory", "cc" \ 136 | ); \ 137 | _ret; \ 138 | }) 139 | 140 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 141 | ({ \ 142 | long _ret; \ 143 | register long _num __asm__ ("rax") = (num); \ 144 | register long _arg1 __asm__ ("rdi") = (long)(arg1); \ 145 | register long _arg2 __asm__ ("rsi") = (long)(arg2); \ 146 | register long _arg3 __asm__ ("rdx") = (long)(arg3); \ 147 | register long _arg4 __asm__ ("r10") = (long)(arg4); \ 148 | register long _arg5 __asm__ ("r8") = (long)(arg5); \ 149 | \ 150 | __asm__ volatile ( \ 151 | "syscall\n" \ 152 | : "=a"(_ret) \ 153 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 154 | "0"(_num) \ 155 | : "rcx", "r11", "memory", "cc" \ 156 | ); \ 157 | _ret; \ 158 | }) 159 | 160 | #define my_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ 161 | ({ \ 162 | long _ret; \ 163 | register long _num __asm__ ("rax") = (num); \ 164 | register long _arg1 __asm__ ("rdi") = (long)(arg1); \ 165 | register long _arg2 __asm__ ("rsi") = (long)(arg2); \ 166 | register long _arg3 __asm__ ("rdx") = (long)(arg3); \ 167 | register long _arg4 __asm__ ("r10") = (long)(arg4); \ 168 | register long _arg5 __asm__ ("r8") = (long)(arg5); \ 169 | register long _arg6 __asm__ ("r9") = (long)(arg6); \ 170 | \ 171 | __asm__ volatile ( \ 172 | "syscall\n" \ 173 | : "=a"(_ret) \ 174 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 175 | "r"(_arg6), "0"(_num) \ 176 | : "rcx", "r11", "memory", "cc" \ 177 | ); \ 178 | _ret; \ 179 | }) 180 | 181 | char **environ __attribute__((weak)); 182 | const unsigned long *_auxv __attribute__((weak)); 183 | 184 | /* startup code */ 185 | /* 186 | * x86-64 System V ABI mandates: 187 | * 1) %rsp must be 16-byte aligned right before the function call. 188 | * 2) The deepest stack frame should be zero (the %rbp). 189 | * 190 | */ 191 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 192 | { 193 | __asm__ volatile ( 194 | "pop %rdi\n" // argc (first arg, %rdi) 195 | "mov %rsp, %rsi\n" // argv[] (second arg, %rsi) 196 | "lea 8(%rsi,%rdi,8),%rdx\n" // then a NULL then envp (third arg, %rdx) 197 | // "mov %rdx, environ\n" // save environ 198 | "xor %ebp, %ebp\n" // zero the stack frame 199 | "mov %rdx, %rax\n" // search for auxv (follows NULL after last env) 200 | "0:\n" 201 | "add $8, %rax\n" // search for auxv using rax, it follows the 202 | "cmp -8(%rax), %rbp\n" // ... NULL after last env (rbp is zero here) 203 | "jnz 0b\n" 204 | // "mov %rax, _auxv\n" // save it into _auxv 205 | "and $-16, %rsp\n" // x86 ABI : esp must be 16-byte aligned before call 206 | "call main\n" // main() returns the status code, we'll exit with it. 207 | "mov %eax, %edi\n" // retrieve exit code (32 bit) 208 | "mov $60, %eax\n" // NR_exit == 60 209 | "syscall\n" // really exit 210 | "hlt\n" // ensure it does not return 211 | ); 212 | __builtin_unreachable(); 213 | } 214 | 215 | #endif // _NOLIBC_ARCH_X86_64_H 216 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-mips.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * MIPS specific definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ARCH_MIPS_H 8 | #define _NOLIBC_ARCH_MIPS_H 9 | 10 | /* The struct returned by the stat() syscall. 88 bytes are returned by the 11 | * syscall. 12 | */ 13 | struct sys_stat_struct { 14 | unsigned int st_dev; 15 | long st_pad1[3]; 16 | unsigned long st_ino; 17 | unsigned int st_mode; 18 | unsigned int st_nlink; 19 | unsigned int st_uid; 20 | unsigned int st_gid; 21 | unsigned int st_rdev; 22 | long st_pad2[2]; 23 | long st_size; 24 | long st_pad3; 25 | 26 | long st_atime; 27 | long st_atime_nsec; 28 | long st_mtime; 29 | long st_mtime_nsec; 30 | 31 | long st_ctime; 32 | long st_ctime_nsec; 33 | long st_blksize; 34 | long st_blocks; 35 | long st_pad4[14]; 36 | }; 37 | 38 | /* Syscalls for MIPS ABI O32 : 39 | * - WARNING! there's always a delayed slot! 40 | * - WARNING again, the syntax is different, registers take a '$' and numbers 41 | * do not. 42 | * - registers are 32-bit 43 | * - stack is 8-byte aligned 44 | * - syscall number is passed in v0 (starts at 0xfa0). 45 | * - arguments are in a0, a1, a2, a3, then the stack. The caller needs to 46 | * leave some room in the stack for the callee to save a0..a3 if needed. 47 | * - Many registers are clobbered, in fact only a0..a2 and s0..s8 are 48 | * preserved. See: https://www.linux-mips.org/wiki/Syscall as well as 49 | * scall32-o32.S in the kernel sources. 50 | * - the system call is performed by calling "syscall" 51 | * - syscall return comes in v0, and register a3 needs to be checked to know 52 | * if an error occurred, in which case errno is in v0. 53 | * - the arguments are cast to long and assigned into the target registers 54 | * which are then simply passed as registers to the asm code, so that we 55 | * don't have to experience issues with register constraints. 56 | */ 57 | 58 | #define my_syscall0(num) \ 59 | ({ \ 60 | register long _num __asm__ ("v0") = (num); \ 61 | register long _arg4 __asm__ ("a3"); \ 62 | \ 63 | __asm__ volatile ( \ 64 | "addiu $sp, $sp, -32\n" \ 65 | "syscall\n" \ 66 | "addiu $sp, $sp, 32\n" \ 67 | : "=r"(_num), "=r"(_arg4) \ 68 | : "r"(_num) \ 69 | : "memory", "cc", "at", "v1", "hi", "lo", \ 70 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9" \ 71 | ); \ 72 | _arg4 ? -_num : _num; \ 73 | }) 74 | 75 | #define my_syscall1(num, arg1) \ 76 | ({ \ 77 | register long _num __asm__ ("v0") = (num); \ 78 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 79 | register long _arg4 __asm__ ("a3"); \ 80 | \ 81 | __asm__ volatile ( \ 82 | "addiu $sp, $sp, -32\n" \ 83 | "syscall\n" \ 84 | "addiu $sp, $sp, 32\n" \ 85 | : "=r"(_num), "=r"(_arg4) \ 86 | : "0"(_num), \ 87 | "r"(_arg1) \ 88 | : "memory", "cc", "at", "v1", "hi", "lo", \ 89 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9" \ 90 | ); \ 91 | _arg4 ? -_num : _num; \ 92 | }) 93 | 94 | #define my_syscall2(num, arg1, arg2) \ 95 | ({ \ 96 | register long _num __asm__ ("v0") = (num); \ 97 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 98 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 99 | register long _arg4 __asm__ ("a3"); \ 100 | \ 101 | __asm__ volatile ( \ 102 | "addiu $sp, $sp, -32\n" \ 103 | "syscall\n" \ 104 | "addiu $sp, $sp, 32\n" \ 105 | : "=r"(_num), "=r"(_arg4) \ 106 | : "0"(_num), \ 107 | "r"(_arg1), "r"(_arg2) \ 108 | : "memory", "cc", "at", "v1", "hi", "lo", \ 109 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9" \ 110 | ); \ 111 | _arg4 ? -_num : _num; \ 112 | }) 113 | 114 | #define my_syscall3(num, arg1, arg2, arg3) \ 115 | ({ \ 116 | register long _num __asm__ ("v0") = (num); \ 117 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 118 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 119 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 120 | register long _arg4 __asm__ ("a3"); \ 121 | \ 122 | __asm__ volatile ( \ 123 | "addiu $sp, $sp, -32\n" \ 124 | "syscall\n" \ 125 | "addiu $sp, $sp, 32\n" \ 126 | : "=r"(_num), "=r"(_arg4) \ 127 | : "0"(_num), \ 128 | "r"(_arg1), "r"(_arg2), "r"(_arg3) \ 129 | : "memory", "cc", "at", "v1", "hi", "lo", \ 130 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9" \ 131 | ); \ 132 | _arg4 ? -_num : _num; \ 133 | }) 134 | 135 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 136 | ({ \ 137 | register long _num __asm__ ("v0") = (num); \ 138 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 139 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 140 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 141 | register long _arg4 __asm__ ("a3") = (long)(arg4); \ 142 | \ 143 | __asm__ volatile ( \ 144 | "addiu $sp, $sp, -32\n" \ 145 | "syscall\n" \ 146 | "addiu $sp, $sp, 32\n" \ 147 | : "=r" (_num), "=r"(_arg4) \ 148 | : "0"(_num), \ 149 | "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4) \ 150 | : "memory", "cc", "at", "v1", "hi", "lo", \ 151 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9" \ 152 | ); \ 153 | _arg4 ? -_num : _num; \ 154 | }) 155 | 156 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 157 | ({ \ 158 | register long _num __asm__ ("v0") = (num); \ 159 | register long _arg1 __asm__ ("a0") = (long)(arg1); \ 160 | register long _arg2 __asm__ ("a1") = (long)(arg2); \ 161 | register long _arg3 __asm__ ("a2") = (long)(arg3); \ 162 | register long _arg4 __asm__ ("a3") = (long)(arg4); \ 163 | register long _arg5 = (long)(arg5); \ 164 | \ 165 | __asm__ volatile ( \ 166 | "addiu $sp, $sp, -32\n" \ 167 | "sw %7, 16($sp)\n" \ 168 | "syscall\n " \ 169 | "addiu $sp, $sp, 32\n" \ 170 | : "=r" (_num), "=r"(_arg4) \ 171 | : "0"(_num), \ 172 | "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5) \ 173 | : "memory", "cc", "at", "v1", "hi", "lo", \ 174 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9" \ 175 | ); \ 176 | _arg4 ? -_num : _num; \ 177 | }) 178 | 179 | char **environ __attribute__((weak)); 180 | const unsigned long *_auxv __attribute__((weak)); 181 | 182 | /* startup code, note that it's called __start on MIPS */ 183 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) __start(void) 184 | { 185 | __asm__ volatile ( 186 | //".set nomips16\n" 187 | ".set push\n" 188 | ".set noreorder\n" 189 | ".option pic0\n" 190 | //".ent __start\n" 191 | //"__start:\n" 192 | "lw $a0,($sp)\n" // argc was in the stack 193 | "addiu $a1, $sp, 4\n" // argv = sp + 4 194 | "sll $a2, $a0, 2\n" // a2 = argc * 4 195 | "add $a2, $a2, $a1\n" // envp = argv + 4*argc ... 196 | "addiu $a2, $a2, 4\n" // ... + 4 197 | // "lui $a3, %hi(environ)\n" // load environ into a3 (hi) 198 | // "addiu $a3, %lo(environ)\n" // load environ into a3 (lo) 199 | // "sw $a2,($a3)\n" // store envp(a2) into environ 200 | 201 | "move $t0, $a2\n" // iterate t0 over envp, look for NULL 202 | "0:" // do { 203 | "lw $a3, ($t0)\n" // a3=*(t0); 204 | "bne $a3, $0, 0b\n" // } while (a3); 205 | "addiu $t0, $t0, 4\n" // delayed slot: t0+=4; 206 | // "lui $a3, %hi(_auxv)\n" // load _auxv into a3 (hi) 207 | // "addiu $a3, %lo(_auxv)\n" // load _auxv into a3 (lo) 208 | // "sw $t0, ($a3)\n" // store t0 into _auxv 209 | 210 | "li $t0, -8\n" 211 | "and $sp, $sp, $t0\n" // sp must be 8-byte aligned 212 | "addiu $sp,$sp,-16\n" // the callee expects to save a0..a3 there! 213 | "jal main\n" // main() returns the status code, we'll exit with it. 214 | "nop\n" // delayed slot 215 | "move $a0, $v0\n" // retrieve 32-bit exit code from v0 216 | "li $v0, 4001\n" // NR_exit == 4001 217 | "syscall\n" 218 | //".end __start\n" 219 | ".set pop\n" 220 | ); 221 | __builtin_unreachable(); 222 | } 223 | 224 | #endif // _NOLIBC_ARCH_MIPS_H 225 | -------------------------------------------------------------------------------- /vendor/nolibc/arch-arm.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * ARM specific definitions for NOLIBC 4 | * Copyright (C) 2017-2022 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_ARCH_ARM_H 8 | #define _NOLIBC_ARCH_ARM_H 9 | 10 | /* The struct returned by the stat() syscall, 32-bit only, the syscall returns 11 | * exactly 56 bytes (stops before the unused array). In big endian, the format 12 | * differs as devices are returned as short only. 13 | */ 14 | struct sys_stat_struct { 15 | #if defined(__ARMEB__) 16 | unsigned short st_dev; 17 | unsigned short __pad1; 18 | #else 19 | unsigned long st_dev; 20 | #endif 21 | unsigned long st_ino; 22 | unsigned short st_mode; 23 | unsigned short st_nlink; 24 | unsigned short st_uid; 25 | unsigned short st_gid; 26 | 27 | #if defined(__ARMEB__) 28 | unsigned short st_rdev; 29 | unsigned short __pad2; 30 | #else 31 | unsigned long st_rdev; 32 | #endif 33 | unsigned long st_size; 34 | unsigned long st_blksize; 35 | unsigned long st_blocks; 36 | 37 | unsigned long st_atime; 38 | unsigned long st_atime_nsec; 39 | unsigned long st_mtime; 40 | unsigned long st_mtime_nsec; 41 | 42 | unsigned long st_ctime; 43 | unsigned long st_ctime_nsec; 44 | unsigned long __unused[2]; 45 | }; 46 | 47 | /* Syscalls for ARM in ARM or Thumb modes : 48 | * - registers are 32-bit 49 | * - stack is 8-byte aligned 50 | * ( http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka4127.html) 51 | * - syscall number is passed in r7 52 | * - arguments are in r0, r1, r2, r3, r4, r5 53 | * - the system call is performed by calling svc #0 54 | * - syscall return comes in r0. 55 | * - only lr is clobbered. 56 | * - the arguments are cast to long and assigned into the target registers 57 | * which are then simply passed as registers to the asm code, so that we 58 | * don't have to experience issues with register constraints. 59 | * - the syscall number is always specified last in order to allow to force 60 | * some registers before (gcc refuses a %-register at the last position). 61 | * - in thumb mode without -fomit-frame-pointer, r7 is also used to store the 62 | * frame pointer, and we cannot directly assign it as a register variable, 63 | * nor can we clobber it. Instead we assign the r6 register and swap it 64 | * with r7 before calling svc, and r6 is marked as clobbered. 65 | * We're just using any regular register which we assign to r7 after saving 66 | * it. 67 | * 68 | * Also, ARM supports the old_select syscall if newselect is not available 69 | */ 70 | #define __ARCH_WANT_SYS_OLD_SELECT 71 | 72 | #if (defined(__THUMBEB__) || defined(__THUMBEL__)) && \ 73 | !defined(NOLIBC_OMIT_FRAME_POINTER) 74 | /* swap r6,r7 needed in Thumb mode since we can't use nor clobber r7 */ 75 | #define _NOLIBC_SYSCALL_REG "r6" 76 | #define _NOLIBC_THUMB_SET_R7 "eor r7, r6\neor r6, r7\neor r7, r6\n" 77 | #define _NOLIBC_THUMB_RESTORE_R7 "mov r7, r6\n" 78 | 79 | #else /* we're in ARM mode */ 80 | /* in Arm mode we can directly use r7 */ 81 | #define _NOLIBC_SYSCALL_REG "r7" 82 | #define _NOLIBC_THUMB_SET_R7 "" 83 | #define _NOLIBC_THUMB_RESTORE_R7 "" 84 | 85 | #endif /* end THUMB */ 86 | 87 | #define my_syscall0(num) \ 88 | ({ \ 89 | register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ 90 | register long _arg1 __asm__ ("r0"); \ 91 | \ 92 | __asm__ volatile ( \ 93 | _NOLIBC_THUMB_SET_R7 \ 94 | "svc #0\n" \ 95 | _NOLIBC_THUMB_RESTORE_R7 \ 96 | : "=r"(_arg1), "=r"(_num) \ 97 | : "r"(_arg1), \ 98 | "r"(_num) \ 99 | : "memory", "cc", "lr" \ 100 | ); \ 101 | _arg1; \ 102 | }) 103 | 104 | #define my_syscall1(num, arg1) \ 105 | ({ \ 106 | register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ 107 | register long _arg1 __asm__ ("r0") = (long)(arg1); \ 108 | \ 109 | __asm__ volatile ( \ 110 | _NOLIBC_THUMB_SET_R7 \ 111 | "svc #0\n" \ 112 | _NOLIBC_THUMB_RESTORE_R7 \ 113 | : "=r"(_arg1), "=r" (_num) \ 114 | : "r"(_arg1), \ 115 | "r"(_num) \ 116 | : "memory", "cc", "lr" \ 117 | ); \ 118 | _arg1; \ 119 | }) 120 | 121 | #define my_syscall2(num, arg1, arg2) \ 122 | ({ \ 123 | register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ 124 | register long _arg1 __asm__ ("r0") = (long)(arg1); \ 125 | register long _arg2 __asm__ ("r1") = (long)(arg2); \ 126 | \ 127 | __asm__ volatile ( \ 128 | _NOLIBC_THUMB_SET_R7 \ 129 | "svc #0\n" \ 130 | _NOLIBC_THUMB_RESTORE_R7 \ 131 | : "=r"(_arg1), "=r" (_num) \ 132 | : "r"(_arg1), "r"(_arg2), \ 133 | "r"(_num) \ 134 | : "memory", "cc", "lr" \ 135 | ); \ 136 | _arg1; \ 137 | }) 138 | 139 | #define my_syscall3(num, arg1, arg2, arg3) \ 140 | ({ \ 141 | register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ 142 | register long _arg1 __asm__ ("r0") = (long)(arg1); \ 143 | register long _arg2 __asm__ ("r1") = (long)(arg2); \ 144 | register long _arg3 __asm__ ("r2") = (long)(arg3); \ 145 | \ 146 | __asm__ volatile ( \ 147 | _NOLIBC_THUMB_SET_R7 \ 148 | "svc #0\n" \ 149 | _NOLIBC_THUMB_RESTORE_R7 \ 150 | : "=r"(_arg1), "=r" (_num) \ 151 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), \ 152 | "r"(_num) \ 153 | : "memory", "cc", "lr" \ 154 | ); \ 155 | _arg1; \ 156 | }) 157 | 158 | #define my_syscall4(num, arg1, arg2, arg3, arg4) \ 159 | ({ \ 160 | register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ 161 | register long _arg1 __asm__ ("r0") = (long)(arg1); \ 162 | register long _arg2 __asm__ ("r1") = (long)(arg2); \ 163 | register long _arg3 __asm__ ("r2") = (long)(arg3); \ 164 | register long _arg4 __asm__ ("r3") = (long)(arg4); \ 165 | \ 166 | __asm__ volatile ( \ 167 | _NOLIBC_THUMB_SET_R7 \ 168 | "svc #0\n" \ 169 | _NOLIBC_THUMB_RESTORE_R7 \ 170 | : "=r"(_arg1), "=r" (_num) \ 171 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ 172 | "r"(_num) \ 173 | : "memory", "cc", "lr" \ 174 | ); \ 175 | _arg1; \ 176 | }) 177 | 178 | #define my_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ 179 | ({ \ 180 | register long _num __asm__(_NOLIBC_SYSCALL_REG) = (num); \ 181 | register long _arg1 __asm__ ("r0") = (long)(arg1); \ 182 | register long _arg2 __asm__ ("r1") = (long)(arg2); \ 183 | register long _arg3 __asm__ ("r2") = (long)(arg3); \ 184 | register long _arg4 __asm__ ("r3") = (long)(arg4); \ 185 | register long _arg5 __asm__ ("r4") = (long)(arg5); \ 186 | \ 187 | __asm__ volatile ( \ 188 | _NOLIBC_THUMB_SET_R7 \ 189 | "svc #0\n" \ 190 | _NOLIBC_THUMB_RESTORE_R7 \ 191 | : "=r"(_arg1), "=r" (_num) \ 192 | : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ 193 | "r"(_num) \ 194 | : "memory", "cc", "lr" \ 195 | ); \ 196 | _arg1; \ 197 | }) 198 | 199 | char **environ __attribute__((weak)); 200 | const unsigned long *_auxv __attribute__((weak)); 201 | 202 | /* startup code */ 203 | void __attribute__((weak,noreturn,optimize("omit-frame-pointer"))) _start(void) 204 | { 205 | __asm__ volatile ( 206 | "pop {%r0}\n" // argc was in the stack 207 | "mov %r1, %sp\n" // argv = sp 208 | 209 | "add %r2, %r0, $1\n" // envp = (argc + 1) ... 210 | "lsl %r2, %r2, $2\n" // * 4 ... 211 | "add %r2, %r2, %r1\n" // + argv 212 | // "ldr %r3, 1f\n" // r3 = &environ (see below) 213 | // "str %r2, [r3]\n" // store envp into environ 214 | 215 | "mov r4, r2\n" // search for auxv (follows NULL after last env) 216 | "0:\n" 217 | "mov r5, r4\n" // r5 = r4 218 | "add r4, r4, #4\n" // r4 += 4 219 | "ldr r5,[r5]\n" // r5 = *r5 = *(r4-4) 220 | "cmp r5, #0\n" // and stop at NULL after last env 221 | "bne 0b\n" 222 | // "ldr %r3, 2f\n" // r3 = &_auxv (low bits) 223 | // "str r4, [r3]\n" // store r4 into _auxv 224 | 225 | "mov %r3, $8\n" // AAPCS : sp must be 8-byte aligned in the 226 | "neg %r3, %r3\n" // callee, and bl doesn't push (lr=pc) 227 | "and %r3, %r3, %r1\n" // so we do sp = r1(=sp) & r3(=-8); 228 | "mov %sp, %r3\n" // 229 | 230 | "bl main\n" // main() returns the status code, we'll exit with it. 231 | "movs r7, $1\n" // NR_exit == 1 232 | "svc $0x00\n" 233 | ".align 2\n" // below are the pointers to a few variables 234 | "1:\n" 235 | // ".word environ\n" 236 | "2:\n" 237 | // ".word _auxv\n" 238 | ); 239 | __builtin_unreachable(); 240 | } 241 | 242 | #endif // _NOLIBC_ARCH_ARM_H 243 | -------------------------------------------------------------------------------- /vendor/nolibc/stdlib.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ 2 | /* 3 | * stdlib function definitions for NOLIBC 4 | * Copyright (C) 2017-2021 Willy Tarreau 5 | */ 6 | 7 | #ifndef _NOLIBC_STDLIB_H 8 | #define _NOLIBC_STDLIB_H 9 | 10 | #include "std.h" 11 | #include "arch.h" 12 | #include "types.h" 13 | #include "sys.h" 14 | #include "string.h" 15 | #include 16 | 17 | struct nolibc_heap { 18 | size_t len; 19 | char user_p[] __attribute__((__aligned__)); 20 | }; 21 | 22 | /* Buffer used to store int-to-ASCII conversions. Will only be implemented if 23 | * any of the related functions is implemented. The area is large enough to 24 | * store "18446744073709551615" or "-9223372036854775808" and the final zero. 25 | */ 26 | static __attribute__((unused)) char itoa_buffer[21]; 27 | 28 | /* 29 | * As much as possible, please keep functions alphabetically sorted. 30 | */ 31 | 32 | /* must be exported, as it's used by libgcc for various divide functions */ 33 | __attribute__((weak,unused,noreturn,section(".text.nolibc_abort"))) 34 | void abort(void) 35 | { 36 | sys_kill(sys_getpid(), SIGABRT); 37 | for (;;); 38 | } 39 | 40 | static __attribute__((unused)) 41 | long atol(const char *s) 42 | { 43 | unsigned long ret = 0; 44 | unsigned long d; 45 | int neg = 0; 46 | 47 | if (*s == '-') { 48 | neg = 1; 49 | s++; 50 | } 51 | 52 | while (1) { 53 | d = (*s++) - '0'; 54 | if (d > 9) 55 | break; 56 | ret *= 10; 57 | ret += d; 58 | } 59 | 60 | return neg ? -ret : ret; 61 | } 62 | 63 | static __attribute__((unused)) 64 | int atoi(const char *s) 65 | { 66 | return atol(s); 67 | } 68 | 69 | static __attribute__((unused)) 70 | void free(void *ptr) 71 | { 72 | struct nolibc_heap *heap; 73 | 74 | if (!ptr) 75 | return; 76 | 77 | heap = container_of(ptr, struct nolibc_heap, user_p); 78 | munmap(heap, heap->len); 79 | } 80 | 81 | /* getenv() tries to find the environment variable named in the 82 | * environment array pointed to by global variable "environ" which must be 83 | * declared as a char **, and must be terminated by a NULL (it is recommended 84 | * to set this variable to the "envp" argument of main()). If the requested 85 | * environment variable exists its value is returned otherwise NULL is 86 | * returned. getenv() is forcefully inlined so that the reference to "environ" 87 | * will be dropped if unused, even at -O0. 88 | */ 89 | static __attribute__((unused)) 90 | char *_getenv(const char *name, char **environ) 91 | { 92 | int idx, i; 93 | 94 | if (environ) { 95 | for (idx = 0; environ[idx]; idx++) { 96 | for (i = 0; name[i] && name[i] == environ[idx][i];) 97 | i++; 98 | if (!name[i] && environ[idx][i] == '=') 99 | return &environ[idx][i+1]; 100 | } 101 | } 102 | return NULL; 103 | } 104 | 105 | static inline __attribute__((unused,always_inline)) 106 | char *getenv(const char *name) 107 | { 108 | extern char **environ; 109 | return _getenv(name, environ); 110 | } 111 | 112 | static __attribute__((unused)) 113 | unsigned long getauxval(unsigned long type) 114 | { 115 | const unsigned long *auxv = _auxv; 116 | unsigned long ret; 117 | 118 | if (!auxv) 119 | return 0; 120 | 121 | while (1) { 122 | if (!auxv[0] && !auxv[1]) { 123 | ret = 0; 124 | break; 125 | } 126 | 127 | if (auxv[0] == type) { 128 | ret = auxv[1]; 129 | break; 130 | } 131 | 132 | auxv += 2; 133 | } 134 | 135 | return ret; 136 | } 137 | 138 | static __attribute__((unused)) 139 | void *malloc(size_t len) 140 | { 141 | struct nolibc_heap *heap; 142 | 143 | /* Always allocate memory with size multiple of 4096. */ 144 | len = sizeof(*heap) + len; 145 | len = (len + 4095UL) & -4096UL; 146 | heap = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 147 | -1, 0); 148 | if (__builtin_expect(heap == MAP_FAILED, 0)) 149 | return NULL; 150 | 151 | heap->len = len; 152 | return heap->user_p; 153 | } 154 | 155 | static __attribute__((unused)) 156 | void *calloc(size_t size, size_t nmemb) 157 | { 158 | size_t x = size * nmemb; 159 | 160 | if (__builtin_expect(size && ((x / size) != nmemb), 0)) { 161 | SET_ERRNO(ENOMEM); 162 | return NULL; 163 | } 164 | 165 | /* 166 | * No need to zero the heap, the MAP_ANONYMOUS in malloc() 167 | * already does it. 168 | */ 169 | return malloc(x); 170 | } 171 | 172 | static __attribute__((unused)) 173 | void *realloc(void *old_ptr, size_t new_size) 174 | { 175 | struct nolibc_heap *heap; 176 | size_t user_p_len; 177 | void *ret; 178 | 179 | if (!old_ptr) 180 | return malloc(new_size); 181 | 182 | heap = container_of(old_ptr, struct nolibc_heap, user_p); 183 | user_p_len = heap->len - sizeof(*heap); 184 | /* 185 | * Don't realloc() if @user_p_len >= @new_size, this block of 186 | * memory is still enough to handle the @new_size. Just return 187 | * the same pointer. 188 | */ 189 | if (user_p_len >= new_size) 190 | return old_ptr; 191 | 192 | ret = malloc(new_size); 193 | if (__builtin_expect(!ret, 0)) 194 | return NULL; 195 | 196 | memcpy(ret, heap->user_p, heap->len); 197 | munmap(heap, heap->len); 198 | return ret; 199 | } 200 | 201 | /* Converts the unsigned long integer to its hex representation into 202 | * buffer , which must be long enough to store the number and the 203 | * trailing zero (17 bytes for "ffffffffffffffff" or 9 for "ffffffff"). The 204 | * buffer is filled from the first byte, and the number of characters emitted 205 | * (not counting the trailing zero) is returned. The function is constructed 206 | * in a way to optimize the code size and avoid any divide that could add a 207 | * dependency on large external functions. 208 | */ 209 | static __attribute__((unused)) 210 | int utoh_r(unsigned long in, char *buffer) 211 | { 212 | signed char pos = (~0UL > 0xfffffffful) ? 60 : 28; 213 | int digits = 0; 214 | int dig; 215 | 216 | do { 217 | dig = in >> pos; 218 | in -= (uint64_t)dig << pos; 219 | pos -= 4; 220 | if (dig || digits || pos < 0) { 221 | if (dig > 9) 222 | dig += 'a' - '0' - 10; 223 | buffer[digits++] = '0' + dig; 224 | } 225 | } while (pos >= 0); 226 | 227 | buffer[digits] = 0; 228 | return digits; 229 | } 230 | 231 | /* converts unsigned long to an hex string using the static itoa_buffer 232 | * and returns the pointer to that string. 233 | */ 234 | static inline __attribute__((unused)) 235 | char *utoh(unsigned long in) 236 | { 237 | utoh_r(in, itoa_buffer); 238 | return itoa_buffer; 239 | } 240 | 241 | /* Converts the unsigned long integer to its string representation into 242 | * buffer , which must be long enough to store the number and the 243 | * trailing zero (21 bytes for 18446744073709551615 in 64-bit, 11 for 244 | * 4294967295 in 32-bit). The buffer is filled from the first byte, and the 245 | * number of characters emitted (not counting the trailing zero) is returned. 246 | * The function is constructed in a way to optimize the code size and avoid 247 | * any divide that could add a dependency on large external functions. 248 | */ 249 | static __attribute__((unused)) 250 | int utoa_r(unsigned long in, char *buffer) 251 | { 252 | unsigned long lim; 253 | int digits = 0; 254 | int pos = (~0UL > 0xfffffffful) ? 19 : 9; 255 | int dig; 256 | 257 | do { 258 | for (dig = 0, lim = 1; dig < pos; dig++) 259 | lim *= 10; 260 | 261 | if (digits || in >= lim || !pos) { 262 | for (dig = 0; in >= lim; dig++) 263 | in -= lim; 264 | buffer[digits++] = '0' + dig; 265 | } 266 | } while (pos--); 267 | 268 | buffer[digits] = 0; 269 | return digits; 270 | } 271 | 272 | /* Converts the signed long integer to its string representation into 273 | * buffer , which must be long enough to store the number and the 274 | * trailing zero (21 bytes for -9223372036854775808 in 64-bit, 12 for 275 | * -2147483648 in 32-bit). The buffer is filled from the first byte, and the 276 | * number of characters emitted (not counting the trailing zero) is returned. 277 | */ 278 | static __attribute__((unused)) 279 | int itoa_r(long in, char *buffer) 280 | { 281 | char *ptr = buffer; 282 | int len = 0; 283 | 284 | if (in < 0) { 285 | in = -in; 286 | *(ptr++) = '-'; 287 | len++; 288 | } 289 | len += utoa_r(in, ptr); 290 | return len; 291 | } 292 | 293 | /* for historical compatibility, same as above but returns the pointer to the 294 | * buffer. 295 | */ 296 | static inline __attribute__((unused)) 297 | char *ltoa_r(long in, char *buffer) 298 | { 299 | itoa_r(in, buffer); 300 | return buffer; 301 | } 302 | 303 | /* converts long integer to a string using the static itoa_buffer and 304 | * returns the pointer to that string. 305 | */ 306 | static inline __attribute__((unused)) 307 | char *itoa(long in) 308 | { 309 | itoa_r(in, itoa_buffer); 310 | return itoa_buffer; 311 | } 312 | 313 | /* converts long integer to a string using the static itoa_buffer and 314 | * returns the pointer to that string. Same as above, for compatibility. 315 | */ 316 | static inline __attribute__((unused)) 317 | char *ltoa(long in) 318 | { 319 | itoa_r(in, itoa_buffer); 320 | return itoa_buffer; 321 | } 322 | 323 | /* converts unsigned long integer to a string using the static itoa_buffer 324 | * and returns the pointer to that string. 325 | */ 326 | static inline __attribute__((unused)) 327 | char *utoa(unsigned long in) 328 | { 329 | utoa_r(in, itoa_buffer); 330 | return itoa_buffer; 331 | } 332 | 333 | /* Converts the unsigned 64-bit integer to its hex representation into 334 | * buffer , which must be long enough to store the number and the 335 | * trailing zero (17 bytes for "ffffffffffffffff"). The buffer is filled from 336 | * the first byte, and the number of characters emitted (not counting the 337 | * trailing zero) is returned. The function is constructed in a way to optimize 338 | * the code size and avoid any divide that could add a dependency on large 339 | * external functions. 340 | */ 341 | static __attribute__((unused)) 342 | int u64toh_r(uint64_t in, char *buffer) 343 | { 344 | signed char pos = 60; 345 | int digits = 0; 346 | int dig; 347 | 348 | do { 349 | if (sizeof(long) >= 8) { 350 | dig = (in >> pos) & 0xF; 351 | } else { 352 | /* 32-bit platforms: avoid a 64-bit shift */ 353 | uint32_t d = (pos >= 32) ? (in >> 32) : in; 354 | dig = (d >> (pos & 31)) & 0xF; 355 | } 356 | if (dig > 9) 357 | dig += 'a' - '0' - 10; 358 | pos -= 4; 359 | if (dig || digits || pos < 0) 360 | buffer[digits++] = '0' + dig; 361 | } while (pos >= 0); 362 | 363 | buffer[digits] = 0; 364 | return digits; 365 | } 366 | 367 | /* converts uint64_t to an hex string using the static itoa_buffer and 368 | * returns the pointer to that string. 369 | */ 370 | static inline __attribute__((unused)) 371 | char *u64toh(uint64_t in) 372 | { 373 | u64toh_r(in, itoa_buffer); 374 | return itoa_buffer; 375 | } 376 | 377 | /* Converts the unsigned 64-bit integer to its string representation into 378 | * buffer , which must be long enough to store the number and the 379 | * trailing zero (21 bytes for 18446744073709551615). The buffer is filled from 380 | * the first byte, and the number of characters emitted (not counting the 381 | * trailing zero) is returned. The function is constructed in a way to optimize 382 | * the code size and avoid any divide that could add a dependency on large 383 | * external functions. 384 | */ 385 | static __attribute__((unused)) 386 | int u64toa_r(uint64_t in, char *buffer) 387 | { 388 | unsigned long long lim; 389 | int digits = 0; 390 | int pos = 19; /* start with the highest possible digit */ 391 | int dig; 392 | 393 | do { 394 | for (dig = 0, lim = 1; dig < pos; dig++) 395 | lim *= 10; 396 | 397 | if (digits || in >= lim || !pos) { 398 | for (dig = 0; in >= lim; dig++) 399 | in -= lim; 400 | buffer[digits++] = '0' + dig; 401 | } 402 | } while (pos--); 403 | 404 | buffer[digits] = 0; 405 | return digits; 406 | } 407 | 408 | /* Converts the signed 64-bit integer to its string representation into 409 | * buffer , which must be long enough to store the number and the 410 | * trailing zero (21 bytes for -9223372036854775808). The buffer is filled from 411 | * the first byte, and the number of characters emitted (not counting the 412 | * trailing zero) is returned. 413 | */ 414 | static __attribute__((unused)) 415 | int i64toa_r(int64_t in, char *buffer) 416 | { 417 | char *ptr = buffer; 418 | int len = 0; 419 | 420 | if (in < 0) { 421 | in = -in; 422 | *(ptr++) = '-'; 423 | len++; 424 | } 425 | len += u64toa_r(in, ptr); 426 | return len; 427 | } 428 | 429 | /* converts int64_t to a string using the static itoa_buffer and returns 430 | * the pointer to that string. 431 | */ 432 | static inline __attribute__((unused)) 433 | char *i64toa(int64_t in) 434 | { 435 | i64toa_r(in, itoa_buffer); 436 | return itoa_buffer; 437 | } 438 | 439 | /* converts uint64_t to a string using the static itoa_buffer and returns 440 | * the pointer to that string. 441 | */ 442 | static inline __attribute__((unused)) 443 | char *u64toa(uint64_t in) 444 | { 445 | u64toa_r(in, itoa_buffer); 446 | return itoa_buffer; 447 | } 448 | 449 | /* make sure to include all global symbols */ 450 | #include "nolibc.h" 451 | 452 | #endif /* _NOLIBC_STDLIB_H */ 453 | --------------------------------------------------------------------------------