├── .dockerignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── cron.yml │ └── release.yml ├── .gitignore ├── .typos.toml ├── .vscode └── settings.json ├── BENCHMARKING.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── DESIGN.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASING.md ├── cackle.toml ├── dist-workspace.toml ├── docker ├── alpine.Dockerfile ├── arch-basic.Dockerfile ├── arch.Dockerfile ├── debian.Dockerfile ├── opensuse.Dockerfile └── ubuntu.Dockerfile ├── fakes-debug ├── README ├── ld └── ld.lld ├── fakes ├── README ├── ld └── ld.lld ├── ld ├── libwild ├── Cargo.toml ├── README.md └── src │ ├── aarch64.rs │ ├── alignment.rs │ ├── arch.rs │ ├── archive.rs │ ├── archive_splitter.rs │ ├── args.rs │ ├── debug_trace.rs │ ├── diagnostics.rs │ ├── diff.rs │ ├── dwarf_address_info.rs │ ├── elf.rs │ ├── elf_writer.rs │ ├── error.rs │ ├── file_kind.rs │ ├── fs.rs │ ├── gc_stats.rs │ ├── grouping.rs │ ├── hash.rs │ ├── identity.rs │ ├── input_data.rs │ ├── layout.rs │ ├── layout_rules.rs │ ├── lib.rs │ ├── linker_script.rs │ ├── output_section_id.rs │ ├── output_section_map.rs │ ├── output_section_part_map.rs │ ├── output_trace.rs │ ├── parsing.rs │ ├── part_id.rs │ ├── program_segments.rs │ ├── resolution.rs │ ├── riscv64.rs │ ├── save-dir-prelude.sh │ ├── save_dir.rs │ ├── sharding.rs │ ├── slice.rs │ ├── string_merging.rs │ ├── subprocess.rs │ ├── subprocess_unsupported.rs │ ├── symbol.rs │ ├── symbol_db.rs │ ├── test_data │ └── a.a │ ├── timing.rs │ ├── validation.rs │ ├── verification.rs │ ├── version_script.rs │ └── x86_64.rs ├── linker-diff ├── Cargo.toml ├── README.md └── src │ ├── aarch64.rs │ ├── arch.rs │ ├── asm_diff.rs │ ├── bin │ └── linker-diff.rs │ ├── debug_info_diff.rs │ ├── diagnostics.rs │ ├── eh_frame_diff.rs │ ├── gnu_hash.rs │ ├── header_diff.rs │ ├── init_order.rs │ ├── lib.rs │ ├── section_map.rs │ ├── segment.rs │ ├── symbol_diff.rs │ ├── symtab.rs │ ├── trace.rs │ ├── version_diff.rs │ └── x86_64.rs ├── linker-layout ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── linker-trace ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── linker-utils ├── Cargo.toml ├── README.md └── src │ ├── aarch64.rs │ ├── elf.rs │ ├── lib.rs │ ├── relaxation.rs │ ├── riscv64.rs │ ├── utils.rs │ └── x86_64.rs ├── rustfmt.toml ├── test-config-ci.toml ├── test-config.toml.sample └── wild ├── Cargo.toml ├── README.md ├── src └── main.rs └── tests ├── .gitignore ├── integration_tests.rs └── sources ├── archive_activation.c ├── archive_activation0.c ├── archive_activation1.c ├── basic-comdat-1.s ├── basic-comdat.s ├── comments.c ├── comments0.c ├── comments1.c ├── common_section.c ├── common_section0.c ├── common_section1.c ├── copy-relocations-2.c ├── copy-relocations-3.c ├── copy-relocations.c ├── cpp-integration-2.cc ├── cpp-integration.cc ├── custom_section.c ├── custom_section0.c ├── data-pointers-2.c ├── data-pointers.c ├── data.c ├── duplicate_strong_symbols.c ├── duplicate_strong_symbols2.c ├── dynamic-bss-only.c ├── eh_frame.c ├── eh_frame_end.c ├── empty.a ├── entry_arg.c ├── exception.cc ├── exclude-libs-archive.c ├── exclude-libs.c ├── exit.c ├── force-undefined.c ├── global_definitions.c ├── global_definitions.h ├── global_references.c ├── gnu-unique-1.cc ├── gnu-unique-2.cc ├── gnu-unique.c ├── gnu-unique.h ├── got_ref_to_local-1.s ├── got_ref_to_local.c ├── ifunc.c ├── ifunc1.c ├── ifunc2.c ├── ifunc_init.c ├── ifunc_init.h ├── init.c ├── init.h ├── init_test.c ├── init_tls.c ├── init_tls.h ├── input_does_not_exist.c ├── internal-syms.c ├── libc-ifunc.c ├── libc-integration-0.c ├── libc-integration-0b.c ├── libc-integration-1.c ├── libc-integration.c ├── link_args.c ├── linker-script-executable.c ├── linker-script-executable.ld ├── linker-script.c ├── linker-script.ld ├── local_symbol_refs.s ├── mixed-verdef-verneed-2.c ├── mixed-verdef-verneed.c ├── mixed-verdef-verneed.map ├── no_start.c ├── non-alloc.s ├── old_init.c ├── old_init0.s ├── old_init1.s ├── preinit-array.c ├── preinit-array.s ├── rdyn1.rs ├── runtime.c ├── runtime.h ├── rust-integration-dynamic.rs ├── rust-integration.rs ├── rust-tls.rs ├── shared-a1.c ├── shared-a2.c ├── shared-priority-1.c ├── shared-priority-2.c ├── shared-priority.c ├── shared-s1.c ├── shared.c ├── stack_alignment.s ├── string_merging.c ├── string_merging1.s ├── string_merging2.s ├── symbol-versions-2.c ├── symbol-versions-script.map ├── symbol-versions.c ├── tls-local-exec.c ├── tls-variant-1.c ├── tls-variant-2.c ├── tls-variant-3.c ├── tls-variant.c ├── tls.c ├── tls1.c ├── tlsdesc-obj.c ├── tlsdesc.c ├── trivial-dynamic-2.c ├── trivial-dynamic.c ├── trivial-main.c ├── trivial.c ├── trivial_asm.s ├── undefined_symbols.c ├── visibility-merging-1.c ├── visibility-merging.c ├── weak-fns-archive.c ├── weak-fns.c ├── weak-fns1.c ├── weak-vars-archive.c ├── weak-vars.c ├── weak-vars1.c ├── whole_archive.c └── whole_archive0.c /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | perf.data 3 | wild/tests/build 4 | 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: davidlattimore 2 | 3 | -------------------------------------------------------------------------------- /.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 | versioning-strategy: lockfile-only 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | groups: 13 | weekly-updates: 14 | patterns: 15 | - "*" 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ 'main' ] 6 | pull_request: 7 | branches: [ '**' ] 8 | workflow_dispatch: 9 | 10 | permissions: {} 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | RUSTFLAGS: '-D warnings' 15 | 16 | jobs: 17 | test-nightly: 18 | name: Test (${{ contains(matrix.runs-on, 'arm') && 'AArch64' || 'x86_64'}}, ${{ matrix.container }}${{ matrix.test-qemu && ', QEMU' || ''}}) 19 | 20 | strategy: 21 | matrix: 22 | runs-on: 23 | - ubuntu-24.04 24 | - ubuntu-24.04-arm 25 | container: 26 | - 'ubuntu:24.04' 27 | - 'opensuse/tumbleweed:latest' 28 | - 'rust:1.87-alpine' 29 | test-qemu: 30 | - false 31 | include: 32 | - runs-on: ubuntu-24.04 33 | container: 'ubuntu:25.04' 34 | test-qemu: true 35 | - runs-on: ubuntu-24.04-arm 36 | container: 'ubuntu:25.04' 37 | test-qemu: false 38 | - runs-on: ubuntu-24.04 39 | container: 'ubuntu:25.10' 40 | test-qemu: true 41 | - runs-on: ubuntu-24.04-arm 42 | container: 'ubuntu:25.10' 43 | test-qemu: false 44 | # Ubuntu 22.04 contains an old lld linker which cannot do a relaxation on AArch64 (required by linker-diff) 45 | - runs-on: ubuntu-24.04 46 | container: 'ubuntu:22.04' 47 | exclude: # TODO: https://github.com/actions/runner/issues/1637 48 | - runs-on: ubuntu-24.04-arm 49 | container: 'rust:1.87-alpine' 50 | fail-fast: false 51 | 52 | runs-on: ${{ matrix.runs-on }} 53 | 54 | container: 55 | image: ${{ matrix.container }} 56 | 57 | steps: 58 | - run: echo "WILD_TEST_CONFIG=test-config-ci.toml" >> $GITHUB_ENV 59 | if: ${{ env.CI == 'true' }} 60 | - run: echo "WILD_TEST_CROSS=aarch64,riscv64" >> $GITHUB_ENV 61 | if: ${{ matrix.test-qemu }} 62 | - run: apt-get update && apt-get -y install gcc g++ clang lld curl bubblewrap binutils-aarch64-linux-gnu binutils-riscv64-linux-gnu 63 | if: ${{ contains(matrix.container, 'ubuntu') }} 64 | - run: apt-get update && apt-get -y install qemu-user gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-riscv64-linux-gnu g++-riscv64-linux-gnu 65 | if: ${{ matrix.test-qemu }} 66 | - run: zypper in -y gcc gcc-c++ glibc-devel-static clang lld curl rustup bubblewrap 67 | if: ${{ contains(matrix.container, 'opensuse') }} 68 | - run: apk add build-base lld clang bash curl 69 | if: ${{ contains(matrix.container, 'alpine') }} 70 | - uses: actions/checkout@v4 71 | with: 72 | persist-credentials: false 73 | - uses: dtolnay/rust-toolchain@nightly 74 | id: rust-toolchain 75 | with: 76 | targets: x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl,aarch64-unknown-linux-gnu,aarch64-unknown-linux-musl,riscv64gc-unknown-linux-gnu,riscv64gc-unknown-linux-musl 77 | components: rustc-codegen-cranelift-preview 78 | - uses: actions/cache@v4 79 | with: 80 | path: | 81 | ~/.cargo/bin/ 82 | ~/.cargo/registry/index/ 83 | ~/.cargo/registry/cache/ 84 | ~/.cargo/git/db/ 85 | target/ 86 | key: ${{ runner.os }}-${{ matrix.container }}-${{ matrix.runs-on }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} 87 | if: ${{ !contains(matrix.container, 'alpine') }} 88 | - run: cargo build --profile ci --workspace --no-default-features 89 | - run: WILD_TEST_CROSS=$WILD_TEST_CROSS cargo test --profile ci --workspace 90 | 91 | clippy: 92 | name: Clippy 93 | runs-on: ubuntu-24.04 94 | steps: 95 | - uses: actions/checkout@v4 96 | with: 97 | persist-credentials: false 98 | # This is where we check that we're not using features from a more recent rust version. When 99 | # updating this, please also update workspace.package.rust-version in `Cargo.toml`. 100 | - uses: dtolnay/rust-toolchain@1.87.0 101 | id: rust-toolchain 102 | with: 103 | components: clippy 104 | - uses: actions/cache@v4 105 | with: 106 | path: | 107 | ~/.cargo/bin/ 108 | ~/.cargo/registry/index/ 109 | ~/.cargo/registry/cache/ 110 | ~/.cargo/git/db/ 111 | target/ 112 | key: ${{ runner.os }}-clippy-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} 113 | - run: cargo clippy --workspace --target x86_64-unknown-linux-gnu 114 | 115 | rustfmt: 116 | name: Check formatting 117 | runs-on: ubuntu-24.04 118 | steps: 119 | - uses: actions/checkout@v4 120 | with: 121 | persist-credentials: false 122 | - uses: dtolnay/rust-toolchain@nightly 123 | with: 124 | components: rustfmt 125 | - run: cargo fmt --all -- --check 126 | 127 | spelling: 128 | name: Spell Check with Typos 129 | runs-on: ubuntu-24.04 130 | steps: 131 | - uses: actions/checkout@v4 132 | with: 133 | persist-credentials: false 134 | - name: Spell Check Repo 135 | uses: crate-ci/typos@v1.32.0 136 | -------------------------------------------------------------------------------- /.github/workflows/cron.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '35 12 9,22 * *' 4 | workflow_dispatch: 5 | 6 | name: Cron continuous integration 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | markdown-link-check: 12 | if: github.repository_owner == 'davidlattimore' 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | persist-credentials: false 18 | - uses: umbrelladocs/action-linkspector@v1 19 | with: 20 | reporter: github-check 21 | filter_mode: "nofilter" 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /target2 3 | sample-link* 4 | *.strace 5 | flamegraph.svg 6 | profile.json 7 | cachegrind.out* 8 | perf.data* 9 | dhat.out.* 10 | heaptrack*.zst 11 | .vscode/launch.json 12 | .idea 13 | .cargo/config.toml 14 | dhat-heap.json 15 | test-config.toml 16 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | flate = "flate" 3 | rela = "rela" 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "Cargo.toml", 4 | ] 5 | } -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | This document provides a high level overview of Wild's design. The intent is to not go into too much 4 | detail, otherwise we increase the risk that it'll get out-of-sync with the code. For full details, 5 | see comments in the code and the code itself. 6 | 7 | ## Phases 8 | 9 | The linker runs several phases. Each phase borrows data immutably from the previous phases. The high 10 | level phases are: 11 | 12 | * `args.rs`: Parse command-line arguments. 13 | * `input_data.rs`: Open input files with mmap. 14 | * `archive_splitter.rs`: Split archives into their separate objects. 15 | * `string_merging.rs`: Strings in string-merge sections are deduplicated. 16 | * `symbol_db.rs`: Build a hashmap from symbol names to symbol IDs. 17 | * `resolution.rs`: Resolve all undefined symbols and in the process decide which archived objects 18 | will be processed. 19 | * `layout.rs`: 20 | * Traverse graph of relocations, in the process, determining which input sections are needed and 21 | how much space is needed in the various linker-generated sections such as the GOT (global offset 22 | table), symbol tables, dynamic relocation tables etc. 23 | * Allocate addresses for sections, symbols, program segments etc. 24 | * `elf_writer.rs`: Copy input sections to the output file, applying relocations as we go. Write 25 | linker-generated sections. 26 | 27 | For a more detailed look at the phases of the linker, run with the `--time` flag. 28 | 29 | ## Threading 30 | 31 | The linker makes extensive use of multiple threads. The thread pool is owned by the rayon library. 32 | Where possible, we use functions like rayon's `par_iter` to process collections in parallel. Failing 33 | that, we use `par_bridge` which allows the main thread to create work to send out to the thread 34 | pool. In a couple of cases however, we have graph algorithms that don't fit neatly into rayon's 35 | model. In those cases, we spawn one rayon scoped task per thread and then do job control ourselves. 36 | 37 | There are various phases within the linker that are single threaded. This is fine, so long as those 38 | phases run quickly enough. 39 | 40 | ## Testing 41 | 42 | Most testing is done by `integration_tests.rs`. This compiles various programs that are written in 43 | C, C++, Rust and assembly. It then links them with our reference linkers - GNU ld and in some cases 44 | also LLD. It links them with Wild and compares the resulting binaries using our own custom diff 45 | tool, `linker-diff`. Provided that succeeds, it then executes all the linked programs and checks 46 | that they give the correct answer. 47 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing wild 2 | 3 | Releases are automated using `cargo-dist`. 4 | 5 | ## First time or on changes in release structure 6 | 7 | It can be installed with either `cargo install cargo-dist` or `cargo binstall cargo-dist` (if you have cargo binstall 8 | installed). 9 | 10 | To init, or refresh the `cargo-dist` generated files (`dist-workspace.toml` and `.github/workflows/release.yml`) use: 11 | 12 | ```shell 13 | dist init 14 | ``` 15 | 16 | This will ask you a series of questions about the targets you want to build, and installers you want to create and 17 | will then generate or update the mentioned files. 18 | 19 | Then commit and push those files, merging a PR if necessary, so those files are in the `main` branch. 20 | 21 | ## Crate version and Release numbering 22 | 23 | You probably want the version number of the crate to match the version number of the release! 24 | 25 | In that case you will want to modify the version number in [`wild/Cargo.toml`](wild/Cargo.toml). 26 | 27 | ## Generating a release 28 | 29 | With that setup, generating a release is as simple as pushing a tag on the `main` branch, for the release. 30 | 31 | Example: 32 | 33 | ```shell 34 | git tag 0.2.0 # Where "0.2.0" is the number in wild/Cargo.toml 35 | git push --tags 36 | ``` 37 | 38 | That should trigger the `release.yml` workflow in GitHub. You can follow its progress in the 39 | [Actions tab](https://github.com/davidlattimore/wild/actions) in GitHub. 40 | 41 | When complete, it should create the release in [Releases](https://github.com/davidlattimore/wild/releases). 42 | 43 | Maintainers can then edit the release notes associated with the release. 44 | 45 | ## Crates.io 46 | 47 | You can optionally decide to also distribute wild in source form via [crates.io](https://crates.io/) using 48 | 49 | ```shell 50 | cargo publish 51 | ``` 52 | 53 | NOTE: This will require a name change or publishing under a different name as there is a crate already published 54 | with the name `wild` 55 | -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:wild"] 3 | 4 | # Config for 'dist' 5 | [dist] 6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 7 | cargo-dist-version = "0.28.5" 8 | # CI backends to support 9 | ci = "github" 10 | # The installers to generate for each app 11 | installers = ["shell"] 12 | # Target platforms to build apps for (Rust target-triple syntax) 13 | targets = ["aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"] 14 | # Path that installers should place binaries in 15 | install-path = "CARGO_HOME" 16 | # Whether to install an updater program 17 | install-updater = false 18 | -------------------------------------------------------------------------------- /docker/alpine.Dockerfile: -------------------------------------------------------------------------------- 1 | # Run on a recent version of alpine Linux 2 | # 3 | # docker build --progress=plain -t wild-dev-alpine . -f docker/alpine.Dockerfile 4 | # docker run -it wild-dev-alpine 5 | 6 | FROM rust:1.87-alpine AS chef 7 | RUN wget -qO- https://github.com/LukeMathWalker/cargo-chef/releases/download/v0.1.70/cargo-chef-x86_64-unknown-linux-musl.tar.gz | tar -xzf- && \ 8 | mv cargo-chef /usr/local/bin 9 | RUN rustup toolchain install nightly && \ 10 | rustup component add rustc-codegen-cranelift-preview --toolchain nightly 11 | 12 | RUN apk add build-base lld clang bash 13 | 14 | WORKDIR /wild 15 | 16 | FROM chef AS planner 17 | COPY . . 18 | RUN cargo chef prepare --recipe-path recipe.json 19 | 20 | FROM chef AS builder 21 | COPY --from=planner /wild/recipe.json recipe.json 22 | RUN cargo chef cook --all-targets --recipe-path recipe.json 23 | COPY . . 24 | -------------------------------------------------------------------------------- /docker/arch-basic.Dockerfile: -------------------------------------------------------------------------------- 1 | # Run on Arch Linux with no rustup. 2 | # 3 | # docker build --progress=plain -t wild-dev-arch-basic . -f docker/arch-basic.Dockerfile 4 | # 5 | # docker run -it wild-dev-arch-basic 6 | 7 | FROM archlinux:base-20250406.0.331908 AS chef 8 | 9 | RUN pacman --noconfirm -Syu \ 10 | wget \ 11 | less \ 12 | gcc \ 13 | clang \ 14 | lld \ 15 | rust 16 | 17 | RUN wget -qO- https://github.com/LukeMathWalker/cargo-chef/releases/download/v0.1.71/cargo-chef-x86_64-unknown-linux-musl.tar.gz | tar -xzf- && \ 18 | mv cargo-chef /usr/local/bin 19 | 20 | WORKDIR /wild 21 | 22 | FROM chef AS planner 23 | COPY . . 24 | RUN cargo chef prepare --recipe-path recipe.json 25 | 26 | FROM chef AS builder 27 | COPY --from=planner /wild/recipe.json recipe.json 28 | RUN cargo chef cook --all-targets --recipe-path recipe.json 29 | COPY . . 30 | -------------------------------------------------------------------------------- /docker/arch.Dockerfile: -------------------------------------------------------------------------------- 1 | # Run on Arch Linux. Includes some useful tools for debugging linker problems. In particular, 2 | # includes rr - the replay debugger. 3 | # 4 | # docker build --progress=plain -t wild-dev-arch . -f docker/arch.Dockerfile 5 | # 6 | # docker run -it wild-dev-arch 7 | # 8 | # To actually use rr, you'll need to run with 9 | # `--cap-add=SYS_PTRACE --security-opt seccomp=unconfined` 10 | # See https://github.com/rr-debugger/rr/wiki/Docker 11 | 12 | FROM archlinux:base-20250406.0.331908 AS chef 13 | 14 | RUN pacman --noconfirm -Syu \ 15 | wget \ 16 | less \ 17 | gcc \ 18 | clang \ 19 | lld \ 20 | aarch64-linux-gnu-gcc \ 21 | qemu-user \ 22 | git \ 23 | base-devel \ 24 | perf \ 25 | capnproto \ 26 | cmake \ 27 | gdb \ 28 | ninja 29 | 30 | # Install rr 31 | RUN useradd --no-create-home --shell=/bin/false build && usermod -L build && \ 32 | echo -e "build ALL=(ALL) NOPASSWD: ALL\nroot ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers && \ 33 | mkdir /build && \ 34 | chown build /build 35 | USER build 36 | RUN cd /build && \ 37 | git clone https://aur.archlinux.org/rr.git && \ 38 | cd /build/rr && \ 39 | makepkg 40 | USER root 41 | RUN pacman --noconfirm -U /build/rr/rr-*.tar.zst 42 | 43 | RUN wget -qO- https://github.com/LukeMathWalker/cargo-chef/releases/download/v0.1.71/cargo-chef-x86_64-unknown-linux-musl.tar.gz | tar -xzf- && \ 44 | mv cargo-chef /usr/local/bin 45 | 46 | RUN wget https://sh.rustup.rs -O rustup-installer && \ 47 | chmod +x rustup-installer && \ 48 | ./rustup-installer -y --default-toolchain 1.87.0 49 | 50 | ENV PATH="/root/.cargo/bin:$PATH" 51 | 52 | RUN rustup toolchain install nightly && \ 53 | rustup target add --toolchain nightly \ 54 | x86_64-unknown-linux-musl \ 55 | aarch64-unknown-linux-gnu \ 56 | aarch64-unknown-linux-musl \ 57 | && \ 58 | rustup component add rustc-codegen-cranelift-preview --toolchain nightly 59 | 60 | WORKDIR /wild 61 | 62 | FROM chef AS planner 63 | COPY . . 64 | RUN cargo chef prepare --recipe-path recipe.json 65 | 66 | FROM chef AS builder 67 | COPY --from=planner /wild/recipe.json recipe.json 68 | RUN cargo chef cook --all-targets --recipe-path recipe.json 69 | COPY . . 70 | -------------------------------------------------------------------------------- /docker/debian.Dockerfile: -------------------------------------------------------------------------------- 1 | # Run on a recent version of Debian 2 | # 3 | # docker build --progress=plain -t wild-dev-debian . -f docker/debian.Dockerfile 4 | # docker run -it wild-dev-debian 5 | 6 | FROM rust:1.87 AS chef 7 | RUN apt-get update && \ 8 | apt-get install -y \ 9 | clang \ 10 | lld-16 \ 11 | less \ 12 | qemu-user \ 13 | gcc-aarch64-linux-gnu \ 14 | g++-aarch64-linux-gnu \ 15 | binutils-aarch64-linux-gnu \ 16 | build-essential \ 17 | && \ 18 | rm -rf /var/lib/apt/lists/* 19 | RUN ln -s `which ld.lld-16` /usr/local/bin/ld.lld 20 | RUN cargo install --locked cargo-chef 21 | RUN rustup toolchain install nightly && \ 22 | rustup target add --toolchain nightly \ 23 | x86_64-unknown-linux-musl \ 24 | aarch64-unknown-linux-gnu \ 25 | aarch64-unknown-linux-musl \ 26 | && \ 27 | rustup component add rustc-codegen-cranelift-preview --toolchain nightly 28 | WORKDIR /wild 29 | 30 | FROM chef AS planner 31 | COPY . . 32 | RUN cargo chef prepare --recipe-path recipe.json 33 | 34 | FROM chef AS builder 35 | COPY --from=planner /wild/recipe.json recipe.json 36 | RUN cargo chef cook --all-targets --recipe-path recipe.json 37 | COPY . . 38 | -------------------------------------------------------------------------------- /docker/opensuse.Dockerfile: -------------------------------------------------------------------------------- 1 | # Runs on openSUSE 2 | # 3 | # docker build --progress=plain -t wild-dev-opensuse . -f docker/opensuse.Dockerfile 4 | # docker run -it wild-dev-opensuse 5 | 6 | FROM opensuse/tumbleweed@sha256:56df080fe1129ac6f15a59dd7c9a3ae27bd1f4719e2cfc0e8890afd090c677c9 AS chef 7 | RUN zypper install -y -t pattern devel_C_C++ && \ 8 | zypper install -y \ 9 | rustup \ 10 | clang \ 11 | glibc-devel-static \ 12 | cross-aarch64-gcc14 \ 13 | cross-aarch64-binutils \ 14 | cross-riscv64-gcc14 \ 15 | cross-riscv64-binutils \ 16 | qemu-linux-user \ 17 | lld \ 18 | vim \ 19 | less 20 | RUN rustup toolchain install nightly 21 | RUN cargo install --locked cargo-chef 22 | RUN rustup toolchain install nightly && \ 23 | rustup target add --toolchain nightly \ 24 | x86_64-unknown-linux-musl \ 25 | aarch64-unknown-linux-gnu \ 26 | aarch64-unknown-linux-musl \ 27 | riscv64gc-unknown-linux-gnu \ 28 | riscv64gc-unknown-linux-musl \ 29 | && \ 30 | rustup component add rustc-codegen-cranelift-preview --toolchain nightly 31 | WORKDIR /wild 32 | 33 | FROM chef AS planner 34 | COPY . . 35 | RUN cargo chef prepare --recipe-path recipe.json 36 | 37 | FROM chef AS builder 38 | COPY --from=planner /wild/recipe.json recipe.json 39 | RUN cargo chef cook --all-targets --recipe-path recipe.json 40 | COPY . . 41 | -------------------------------------------------------------------------------- /docker/ubuntu.Dockerfile: -------------------------------------------------------------------------------- 1 | # Run on a recent version of Ubuntu 2 | # 3 | # docker build --progress=plain -t wild-dev-ubuntu . -f docker/ubuntu.Dockerfile 4 | # docker run -it wild-dev-ubuntu 5 | 6 | FROM ubuntu:25.04 AS chef 7 | RUN apt-get update && \ 8 | apt-get install -y \ 9 | clang \ 10 | lld \ 11 | less \ 12 | qemu-user \ 13 | gcc-aarch64-linux-gnu \ 14 | g++-aarch64-linux-gnu \ 15 | binutils-aarch64-linux-gnu \ 16 | gcc-riscv64-linux-gnu \ 17 | g++-riscv64-linux-gnu \ 18 | binutils-riscv64-linux-gnu \ 19 | build-essential \ 20 | rustup \ 21 | && \ 22 | rm -rf /var/lib/apt/lists/* 23 | RUN rustup toolchain install nightly && \ 24 | rustup target add --toolchain nightly \ 25 | x86_64-unknown-linux-musl \ 26 | aarch64-unknown-linux-gnu \ 27 | aarch64-unknown-linux-musl \ 28 | riscv64gc-unknown-linux-gnu \ 29 | riscv64gc-unknown-linux-musl \ 30 | && \ 31 | rustup component add rustc-codegen-cranelift-preview --toolchain nightly 32 | RUN cargo install --locked cargo-chef 33 | WORKDIR /wild 34 | -------------------------------------------------------------------------------- /fakes-debug/README: -------------------------------------------------------------------------------- 1 | The symlinks in this directory exist make it easier to pretend to be a 2 | different linker. For example, see the section about building rustc with wild 3 | in BENCHMARKING.md. 4 | 5 | -------------------------------------------------------------------------------- /fakes-debug/ld: -------------------------------------------------------------------------------- 1 | ../target/debug/wild -------------------------------------------------------------------------------- /fakes-debug/ld.lld: -------------------------------------------------------------------------------- 1 | ../target/debug/wild -------------------------------------------------------------------------------- /fakes/README: -------------------------------------------------------------------------------- 1 | The symlinks in this directory exist make it easier to pretend to be a 2 | different linker. For example, see the section about building rustc with wild 3 | in BENCHMARKING.md. 4 | 5 | -------------------------------------------------------------------------------- /fakes/ld: -------------------------------------------------------------------------------- 1 | ../ld -------------------------------------------------------------------------------- /fakes/ld.lld: -------------------------------------------------------------------------------- 1 | ld -------------------------------------------------------------------------------- /ld: -------------------------------------------------------------------------------- 1 | target/release/wild -------------------------------------------------------------------------------- /libwild/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libwild" 3 | description = "A library that provides a fast Linux linker" 4 | version.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | edition.workspace = true 9 | 10 | [dependencies] 11 | anyhow = { workspace = true } 12 | atomic-take = { workspace = true } 13 | bitflags = { workspace = true } 14 | blake3 = { workspace = true } 15 | bumpalo-herd = { workspace = true } 16 | bytemuck = { workspace = true } 17 | bytesize = { workspace = true } 18 | crossbeam-queue = { workspace = true } 19 | crossbeam-utils = { workspace = true } 20 | flate2 = { workspace = true } 21 | foldhash = { workspace = true } 22 | gimli = { workspace = true } 23 | hashbrown = { workspace = true } 24 | hex = { workspace = true } 25 | itertools = { workspace = true } 26 | libc = { workspace = true } 27 | linker-layout = { path = "../linker-layout", version = "0.5.0" } 28 | linker-trace = { path = "../linker-trace", version = "0.5.0" } 29 | linker-utils = { path = "../linker-utils", version = "0.5.0" } 30 | memchr = { workspace = true } 31 | memmap2 = { workspace = true } 32 | object = { workspace = true } 33 | rayon = { workspace = true } 34 | sharded-offset-map = { workspace = true } 35 | sharded-vec-writer = { workspace = true } 36 | smallvec = { workspace = true } 37 | symbolic-demangle = { workspace = true } 38 | tracing = { workspace = true } 39 | tracing-subscriber = { workspace = true } 40 | typed-arena = { workspace = true } 41 | uuid = { workspace = true } 42 | winnow = { workspace = true } 43 | zstd = { workspace = true } 44 | 45 | [dev-dependencies] 46 | ar = "0.9.0" 47 | 48 | [features] 49 | # Support for running the linker as a subprocess. 50 | fork = [] 51 | 52 | # Enable work-in-progress features 53 | wip = [] 54 | 55 | [lints] 56 | workspace = true 57 | -------------------------------------------------------------------------------- /libwild/README.md: -------------------------------------------------------------------------------- 1 | # libwild 2 | 3 | This crate allows the wild linker to be used as a library. The API is currently fairly narrow and is 4 | basically the same as the command-line interface. The reason for this is that when a linker is 5 | embedded in something like a compiler, you often need to be able to pass arbitrary linker arguments 6 | to the linker. For this to be possible, the command-line parsing itself needs to be exposed as part 7 | of the API. 8 | 9 | Alternative APIs may be added in future based on actual use-cases. Until such use-cases arise 10 | though, it's hard to determine what those APIs should look like. 11 | 12 | For more details about the wild linker, see the [wild 13 | repository](https://github.com/davidlattimore/wild). 14 | -------------------------------------------------------------------------------- /libwild/src/alignment.rs: -------------------------------------------------------------------------------- 1 | use crate::bail; 2 | use crate::error::Result; 3 | use std::fmt::Debug; 4 | use std::fmt::Display; 5 | 6 | /// An alignment. Always a power of two. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)] 8 | pub(crate) struct Alignment { 9 | pub(crate) exponent: u8, 10 | } 11 | 12 | pub(crate) const NUM_ALIGNMENTS: usize = 16; 13 | 14 | /// The minimum alignment that we support. 15 | pub(crate) const MIN: Alignment = Alignment { exponent: 0 }; 16 | 17 | /// The maximum alignment that we support. 18 | pub(crate) const MAX: Alignment = Alignment { exponent: 15 }; 19 | 20 | /// Alignment for entries in the symbol table. 21 | pub(crate) const SYMTAB_ENTRY: Alignment = Alignment { exponent: 3 }; 22 | 23 | /// Alignment for entries in the global offset table. 24 | pub(crate) const GOT_ENTRY: Alignment = Alignment { exponent: 3 }; 25 | 26 | /// The minimum alignment of a rela entry. 27 | pub(crate) const RELA_ENTRY: Alignment = Alignment { exponent: 3 }; 28 | 29 | /// Alignment of the .gnu.hash section. 30 | pub(crate) const GNU_HASH: Alignment = Alignment { exponent: 3 }; 31 | 32 | /// The minimum alignment of a phdr entry. 33 | pub(crate) const PROGRAM_HEADER_ENTRY: Alignment = Alignment { exponent: 3 }; 34 | 35 | /// The minimum alignment of a PLT entry. 36 | pub(crate) const PLT: Alignment = Alignment { exponent: 4 }; 37 | 38 | pub(crate) const VERSION_D: Alignment = Alignment { exponent: 3 }; 39 | pub(crate) const VERSION_R: Alignment = Alignment { exponent: 3 }; 40 | pub(crate) const VERSYM: Alignment = Alignment { exponent: 1 }; 41 | 42 | pub(crate) const USIZE: Alignment = Alignment { exponent: 3 }; 43 | 44 | pub(crate) const EH_FRAME_HDR: Alignment = Alignment { exponent: 2 }; 45 | pub(crate) const NOTE_GNU_PROPERTY: Alignment = Alignment { exponent: 3 }; 46 | pub(crate) const NOTE_GNU_BUILD_ID: Alignment = Alignment { exponent: 2 }; 47 | 48 | // GNU_STACK.alignment 49 | pub(crate) const STACK_ALIGNMENT: Alignment = Alignment { exponent: 4 }; 50 | 51 | impl Alignment { 52 | pub(crate) fn new(raw: u64) -> Result { 53 | if !raw.is_power_of_two() { 54 | bail!("Invalid alignment 0x{raw:x}"); 55 | } 56 | let exponent = raw.trailing_zeros(); 57 | if exponent > u32::from(MAX.exponent) { 58 | bail!("Unsupported alignment 0x{raw:x}"); 59 | } 60 | Ok(Alignment { 61 | exponent: exponent as u8, 62 | }) 63 | } 64 | 65 | pub(crate) fn value(self) -> u64 { 66 | 1 << self.exponent 67 | } 68 | 69 | pub(crate) fn mask(self) -> u64 { 70 | self.value() - 1 71 | } 72 | 73 | pub(crate) fn align_up(self, value: u64) -> u64 { 74 | value.next_multiple_of(self.value()) 75 | } 76 | 77 | pub(crate) fn align_up_usize(self, value: usize) -> usize { 78 | value.next_multiple_of(self.value() as usize) 79 | } 80 | 81 | pub(crate) fn align_down(self, value: u64) -> u64 { 82 | value & !self.mask() 83 | } 84 | 85 | /// Returns `offset`, possibly adjusted up so that it is >= `align_up(offset)` and has the same 86 | /// modulo as `ref_offset` 87 | pub(crate) fn align_modulo(self, ref_offset: u64, mut offset: u64) -> u64 { 88 | let mask = self.mask(); 89 | offset = self.align_up(offset); 90 | if offset & mask == ref_offset & mask { 91 | return offset; 92 | } 93 | let mut adjustment = (ref_offset & mask) + self.value() - (offset & mask); 94 | if adjustment > self.value() { 95 | adjustment -= self.value(); 96 | } 97 | offset + adjustment 98 | } 99 | } 100 | 101 | impl Display for Alignment { 102 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 103 | Display::fmt(&self.value(), f) 104 | } 105 | } 106 | 107 | #[test] 108 | fn test_align_up() { 109 | assert_eq!(Alignment::new(16).unwrap().align_up(16), 16); 110 | assert_eq!(Alignment::new(16).unwrap().align_up(15), 16); 111 | assert_eq!(Alignment::new(16).unwrap().align_up(1), 16); 112 | assert_eq!(Alignment::new(16).unwrap().align_up(0), 0); 113 | assert_eq!(Alignment::new(16).unwrap().align_up(31), 32); 114 | } 115 | 116 | #[test] 117 | fn test_align_modulo() { 118 | const PAGE: Alignment = Alignment { exponent: 12 }; 119 | assert_eq!(PAGE.align_modulo(0x123456, 0x987456), 0x988456); 120 | assert_eq!(PAGE.align_modulo(0x123456, 0x987555), 0x988456); 121 | assert_eq!(PAGE.align_modulo(0x123456, 0x987222), 0x988456); 122 | assert_eq!(PAGE.align_modulo(0x123456, 0x987001), 0x988456); 123 | assert_eq!(PAGE.align_modulo(0x123456, 0x987000), 0x987456); 124 | assert_eq!(PAGE.align_modulo(0x2afce, 0x42af7e), 0x42bfce); 125 | } 126 | 127 | #[test] 128 | fn test_align_down() { 129 | assert_eq!(Alignment::new(16).unwrap().align_down(16), 16); 130 | assert_eq!(Alignment::new(16).unwrap().align_down(17), 16); 131 | assert_eq!(Alignment::new(16).unwrap().align_down(32), 32); 132 | assert_eq!(Alignment::new(16).unwrap().align_down(0), 0); 133 | assert_eq!(Alignment::new(16).unwrap().align_down(1), 0); 134 | } 135 | -------------------------------------------------------------------------------- /libwild/src/arch.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over different CPU architectures. 2 | 3 | use crate::args::OutputKind; 4 | use crate::bail; 5 | use crate::error::Result; 6 | use crate::resolution::ValueFlags; 7 | use linker_utils::elf::DynamicRelocationKind; 8 | use linker_utils::elf::RelocationKindInfo; 9 | use linker_utils::elf::SectionFlags; 10 | use linker_utils::relaxation::RelocationModifier; 11 | use object::elf::EM_AARCH64; 12 | use object::elf::EM_RISCV; 13 | use object::elf::EM_X86_64; 14 | use std::borrow::Cow; 15 | use std::str::FromStr; 16 | 17 | pub(crate) trait Arch { 18 | type Relaxation: Relaxation; 19 | 20 | // Get ELF header magic for the architecture. 21 | fn elf_header_arch_magic() -> u16; 22 | 23 | // Get dynamic relocation value specific for the architecture. 24 | fn get_dynamic_relocation_type(relocation: DynamicRelocationKind) -> u32; 25 | 26 | // Write PLT entry for the architecture. 27 | fn write_plt_entry(plt_entry: &mut [u8], got_address: u64, plt_address: u64) -> Result; 28 | 29 | // Make architecture-specific parsing of the relocation types. 30 | fn relocation_from_raw(r_type: u32) -> Result; 31 | 32 | // Get string representation of a relocation specific for the architecture. 33 | fn rel_type_to_string(r_type: u32) -> Cow<'static, str>; 34 | 35 | // Get DTV OFFSET. 36 | fn get_dtv_offset() -> u64 { 37 | 0 38 | } 39 | 40 | // Some architectures use debug info relocation that depend on local symbols. 41 | fn local_symbols_in_debug_info() -> bool; 42 | } 43 | 44 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 45 | pub(crate) enum Architecture { 46 | X86_64, 47 | AArch64, 48 | RISCV64, 49 | } 50 | 51 | impl FromStr for Architecture { 52 | type Err = crate::error::Error; 53 | 54 | fn from_str(s: &str) -> Result { 55 | match s { 56 | "elf_x86_64" => Ok(Architecture::X86_64), 57 | "aarch64elf" | "aarch64linux" => Ok(Architecture::AArch64), 58 | "elf64lriscv" => Ok(Architecture::RISCV64), 59 | _ => bail!("-m {s} is not yet supported"), 60 | } 61 | } 62 | } 63 | 64 | impl TryFrom for Architecture { 65 | type Error = crate::error::Error; 66 | 67 | fn try_from(arch: u16) -> Result { 68 | match arch { 69 | EM_X86_64 => Ok(Self::X86_64), 70 | EM_AARCH64 => Ok(Self::AArch64), 71 | EM_RISCV => Ok(Self::RISCV64), 72 | _ => bail!("Unsupported architecture: 0x{:x}", arch), 73 | } 74 | } 75 | } 76 | 77 | pub(crate) trait Relaxation { 78 | /// Tries to create a relaxation for the relocation of the specified kind, to be applied at the 79 | /// specified offset in the supplied section. 80 | fn new( 81 | relocation_kind: u32, 82 | section_bytes: &[u8], 83 | offset_in_section: u64, 84 | value_flags: ValueFlags, 85 | output_kind: OutputKind, 86 | section_flags: SectionFlags, 87 | non_zero_address: bool, 88 | ) -> Option 89 | where 90 | Self: std::marker::Sized; 91 | 92 | fn apply(&self, section_bytes: &mut [u8], offset_in_section: &mut u64, addend: &mut i64); 93 | 94 | fn rel_info(&self) -> RelocationKindInfo; 95 | 96 | fn debug_kind(&self) -> impl std::fmt::Debug; 97 | 98 | fn next_modifier(&self) -> RelocationModifier; 99 | } 100 | -------------------------------------------------------------------------------- /libwild/src/archive_splitter.rs: -------------------------------------------------------------------------------- 1 | use crate::archive::ArchiveEntry; 2 | use crate::archive::ArchiveIterator; 3 | use crate::archive::EntryMeta; 4 | use crate::args::Modifiers; 5 | use crate::bail; 6 | use crate::error::Context as _; 7 | use crate::error::Result; 8 | use crate::file_kind::FileKind; 9 | use crate::input_data::InputData; 10 | use crate::input_data::InputRef; 11 | use rayon::iter::IntoParallelRefIterator; 12 | use rayon::iter::ParallelIterator; 13 | use std::fmt::Display; 14 | 15 | pub(crate) struct InputBytes<'data> { 16 | pub(crate) input: InputRef<'data>, 17 | pub(crate) kind: FileKind, 18 | pub(crate) data: &'data [u8], 19 | pub(crate) modifiers: Modifiers, 20 | } 21 | 22 | #[tracing::instrument(skip_all, name = "Split archives")] 23 | pub fn split_archives<'data>(input_data: &InputData<'data>) -> Result>> { 24 | let split_output = input_data 25 | .files 26 | .par_iter() 27 | .map(|f| match f.kind { 28 | FileKind::Archive => { 29 | let mut extended_filenames = None; 30 | let mut outputs = Vec::new(); 31 | for entry in ArchiveIterator::from_archive_bytes(f.data())? { 32 | let entry = entry?; 33 | match entry { 34 | ArchiveEntry::Ignored => {} 35 | ArchiveEntry::Filenames(t) => extended_filenames = Some(t), 36 | ArchiveEntry::Regular(archive_entry) => { 37 | let archive_and_member_name = || { 38 | format!( 39 | "{} @ {}", 40 | f.filename.to_string_lossy(), 41 | archive_entry 42 | .identifier(extended_filenames) 43 | .as_path() 44 | .to_string_lossy() 45 | ) 46 | }; 47 | let kind = FileKind::identify_bytes(archive_entry.entry_data) 48 | .with_context(|| { 49 | format!( 50 | "Failed to parse archive `{}`", 51 | archive_and_member_name() 52 | ) 53 | })?; 54 | if kind != FileKind::ElfObject { 55 | bail!( 56 | "Archive member is not an object `{}`", 57 | archive_and_member_name() 58 | ) 59 | } 60 | outputs.push(InputBytes { 61 | kind, 62 | input: InputRef { 63 | file: f, 64 | entry: Some(EntryMeta { 65 | identifier: archive_entry.identifier(extended_filenames), 66 | from: archive_entry.data_range(), 67 | }), 68 | }, 69 | data: archive_entry.entry_data, 70 | modifiers: f.modifiers, 71 | }); 72 | } 73 | ArchiveEntry::Thin(_) => unreachable!(), 74 | } 75 | } 76 | Ok(outputs) 77 | } 78 | _ => Ok(vec![InputBytes { 79 | input: InputRef { 80 | file: f, 81 | entry: None, 82 | }, 83 | kind: f.kind, 84 | data: f.data(), 85 | modifiers: f.modifiers, 86 | }]), 87 | }) 88 | .collect::>>>()?; 89 | Ok(split_output.into_iter().flatten().collect()) 90 | } 91 | 92 | impl Display for InputBytes<'_> { 93 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 94 | Display::fmt(&self.input, f) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /libwild/src/debug_trace.rs: -------------------------------------------------------------------------------- 1 | //! Sets up a tracing layer for debugging the linker. 2 | 3 | use crate::error::AlreadyInitialised; 4 | 5 | /// All trace messages within a span with this name will be emitted. 6 | pub(crate) const TRACE_SPAN_NAME: &str = "trace_file"; 7 | 8 | pub(crate) fn init() -> Result<(), AlreadyInitialised> { 9 | use tracing_subscriber::prelude::*; 10 | 11 | let filter = tracing_subscriber::filter::DynFilterFn::new(|metadata, cx| { 12 | if metadata.is_span() && metadata.name() == TRACE_SPAN_NAME { 13 | return true; 14 | } 15 | let mut current = cx.lookup_current(); 16 | while let Some(span) = current { 17 | if span.name() == TRACE_SPAN_NAME { 18 | return true; 19 | } 20 | current = span.parent(); 21 | } 22 | false 23 | }); 24 | 25 | tracing_subscriber::registry() 26 | .with(tracing_subscriber::fmt::layer().with_filter(filter)) 27 | .try_init() 28 | .map_err(|_| AlreadyInitialised) 29 | } 30 | -------------------------------------------------------------------------------- /libwild/src/diff.rs: -------------------------------------------------------------------------------- 1 | //! If the environment variable `WILD_REFERENCE_LINKER` is set, then once we've finished linking, 2 | //! we'll link again using the linker specified in the environment variable. The output from the 3 | //! reference linker will be the same, but with '.ref-linker' appended. We'll then diff the two 4 | //! outputs and report any unexpected differences found. Setting the environment variable will also 5 | //! enable writing of trace and layout files by the Wild linker, which allow additional information 6 | //! to be added to the diff outputs. 7 | //! 8 | //! For this to work, the linker-diff binary needs to be installed in the same directory as wild. 9 | 10 | use crate::bail; 11 | use crate::error::Context as _; 12 | use crate::error::Result; 13 | use std::path::PathBuf; 14 | use std::process::Command; 15 | 16 | pub(crate) fn maybe_diff() -> Result { 17 | if let Ok(reference_linker) = std::env::var(crate::args::REFERENCE_LINKER_ENV) { 18 | if let Some(paths) = run_with_linker(&reference_linker)? { 19 | run_diff(&paths)?; 20 | } 21 | } 22 | Ok(()) 23 | } 24 | 25 | struct BinPaths { 26 | our_output: PathBuf, 27 | reference_output: PathBuf, 28 | } 29 | 30 | fn run_with_linker(reference_linker: &str) -> Result> { 31 | let mut command = Command::new(reference_linker); 32 | let mut next_is_output = false; 33 | let mut paths = None; 34 | for mut arg in std::env::args().skip(1) { 35 | if next_is_output { 36 | let our_output = PathBuf::from(&arg); 37 | arg.push_str(".ref-linker"); 38 | paths = Some(BinPaths { 39 | our_output, 40 | reference_output: PathBuf::from(&arg), 41 | }); 42 | } 43 | next_is_output = arg == "-o"; 44 | command.arg(arg); 45 | } 46 | // If the linker was run without -o, then there's nothing to diff 47 | let Some(paths) = paths else { 48 | return Ok(None); 49 | }; 50 | let status = command 51 | .status() 52 | .with_context(|| format!("Failed to run `{reference_linker}`"))?; 53 | if !status.success() { 54 | bail!("Reference linker exited with non-zero status"); 55 | } 56 | Ok(Some(paths)) 57 | } 58 | 59 | fn run_diff(paths: &BinPaths) -> Result { 60 | let linker_diff_path = std::env::current_exe()?.with_file_name("linker-diff"); 61 | 62 | if !linker_diff_path.exists() { 63 | bail!("linker-diff binary needs to be in the same directory as wild") 64 | } 65 | 66 | let mut command = Command::new(linker_diff_path); 67 | command 68 | .arg("--wild-defaults") 69 | .arg("--display-names") 70 | .arg("wild,ref") 71 | .arg("--ref") 72 | .arg(&paths.reference_output) 73 | .arg(&paths.our_output); 74 | 75 | let status = command.status()?; 76 | 77 | if !status.success() { 78 | let args: Vec = command 79 | .get_args() 80 | .map(|arg| arg.to_string_lossy().into_owned()) 81 | .collect(); 82 | 83 | bail!( 84 | "linker-diff reported errors. To rerun, execute:\n{} {}", 85 | command.get_program().to_string_lossy(), 86 | args.join(" ") 87 | ); 88 | } 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /libwild/src/file_kind.rs: -------------------------------------------------------------------------------- 1 | //! Code for identifying what sort of file we're dealing with based on the bytes of the file. 2 | 3 | use crate::bail; 4 | use crate::elf; 5 | use crate::error::Result; 6 | use object::LittleEndian; 7 | use object::read::elf::FileHeader; 8 | 9 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 10 | pub(crate) enum FileKind { 11 | ElfObject, 12 | ElfDynamic, 13 | Archive, 14 | ThinArchive, 15 | Text, 16 | } 17 | 18 | impl FileKind { 19 | pub(crate) fn identify_bytes(bytes: &[u8]) -> Result { 20 | if bytes.starts_with(&object::archive::MAGIC) { 21 | Ok(FileKind::Archive) 22 | } else if bytes.starts_with(&object::archive::THIN_MAGIC) { 23 | Ok(FileKind::ThinArchive) 24 | } else if bytes.starts_with(&object::elf::ELFMAG) { 25 | const HEADER_LEN: usize = size_of::(); 26 | if bytes.len() < HEADER_LEN { 27 | bail!("Invalid ELF file"); 28 | } 29 | let header: &elf::FileHeader = object::from_bytes(&bytes[..HEADER_LEN]).unwrap().0; 30 | if header.e_ident.class != object::elf::ELFCLASS64 { 31 | bail!("Only 64 bit ELF is currently supported"); 32 | } 33 | if header.e_ident.data != object::elf::ELFDATA2LSB { 34 | bail!("Only little endian is currently supported"); 35 | } 36 | 37 | let sections = header.sections(LittleEndian, bytes)?; 38 | if sections.iter().any(|sec| { 39 | sections 40 | .section_name(LittleEndian, sec) 41 | .map(|section_name| section_name.starts_with(b".gnu.lto_.symtab")) 42 | .unwrap_or(false) 43 | }) { 44 | bail!("GCC IR (LTO mode) is not supported yet"); 45 | } 46 | 47 | match header.e_type.get(LittleEndian) { 48 | object::elf::ET_REL => Ok(FileKind::ElfObject), 49 | object::elf::ET_DYN => Ok(FileKind::ElfDynamic), 50 | t => bail!("Unsupported ELF kind {t}"), 51 | } 52 | } else if bytes.is_ascii() { 53 | Ok(FileKind::Text) 54 | } else if bytes.starts_with(b"BC") { 55 | bail!("LLVM IR (LTO mode) is not supported yet"); 56 | } else { 57 | bail!("Couldn't identify file type"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /libwild/src/fs.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use std::fs::File; 3 | 4 | pub(crate) fn make_executable(file: &File) -> Result { 5 | use std::os::unix::prelude::PermissionsExt; 6 | 7 | let mut permissions = file.metadata()?.permissions(); 8 | let mut mode = PermissionsExt::mode(&permissions); 9 | // Set execute permission wherever we currently have read permission. 10 | mode = mode | ((mode & 0o444) >> 2); 11 | PermissionsExt::set_mode(&mut permissions, mode); 12 | file.set_permissions(permissions)?; 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /libwild/src/hash.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::BuildHasher; 3 | use std::hash::Hasher; 4 | use std::ops::Deref; 5 | 6 | pub(crate) type PassThroughHashMap = HashMap, V, PassThroughHasher>; 7 | 8 | #[derive(Default)] 9 | pub(crate) struct PassThroughHasher { 10 | hash: u64, 11 | } 12 | 13 | impl Hasher for PassThroughHasher { 14 | fn finish(&self) -> u64 { 15 | self.hash 16 | } 17 | 18 | fn write_u64(&mut self, i: u64) { 19 | self.hash = i; 20 | } 21 | 22 | fn write(&mut self, _bytes: &[u8]) { 23 | panic!("PassThroughHasher used with inappropriate hash implementation"); 24 | } 25 | } 26 | 27 | impl BuildHasher for PassThroughHasher { 28 | type Hasher = PassThroughHasher; 29 | 30 | fn build_hasher(&self) -> Self::Hasher { 31 | PassThroughHasher::default() 32 | } 33 | } 34 | 35 | pub(crate) fn hash_bytes(bytes: &[u8]) -> u64 { 36 | let mut hasher = foldhash::fast::FixedState::default().build_hasher(); 37 | hasher.write(bytes); 38 | hasher.finish() 39 | } 40 | 41 | #[derive(Eq, Clone, Copy)] 42 | pub(crate) struct PreHashed { 43 | value: T, 44 | hash: u64, 45 | } 46 | 47 | impl std::fmt::Debug for PreHashed { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | std::fmt::Debug::fmt(&self.value, f) 50 | } 51 | } 52 | 53 | impl PartialEq for PreHashed { 54 | fn eq(&self, other: &Self) -> bool { 55 | self.value == other.value 56 | } 57 | } 58 | 59 | impl PreHashed { 60 | pub(crate) fn new(value: T, hash: u64) -> Self { 61 | Self { value, hash } 62 | } 63 | 64 | pub(crate) fn hash(&self) -> u64 { 65 | self.hash 66 | } 67 | } 68 | 69 | impl std::hash::Hash for PreHashed { 70 | fn hash(&self, state: &mut H) { 71 | self.hash.hash(state); 72 | } 73 | } 74 | 75 | impl Deref for PreHashed { 76 | type Target = T; 77 | 78 | fn deref(&self) -> &Self::Target { 79 | &self.value 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /libwild/src/identity.rs: -------------------------------------------------------------------------------- 1 | /// Returns a null-terminated string that identifies this linker. This is written into the .comment 2 | /// section which usually also contains the versions of compilers that were used. 3 | pub(crate) fn linker_identity() -> String { 4 | format!("Linker: Wild version {}\0", env!("CARGO_PKG_VERSION")) 5 | } 6 | -------------------------------------------------------------------------------- /libwild/src/output_section_map.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::output_section_id::OutputSectionId; 3 | 4 | /// A map from output section IDs to something. 5 | pub(crate) struct OutputSectionMap { 6 | // TODO: Consider only storing frequently used segment IDs in an array and storing less 7 | // frequently used IDs in an on-demand sorted Vec or smallvec. 8 | values: Vec, 9 | } 10 | 11 | impl OutputSectionMap { 12 | pub(crate) fn with_size(len: usize) -> Self { 13 | let mut values = Vec::new(); 14 | values.resize_with(len, T::default); 15 | Self { values } 16 | } 17 | 18 | pub(crate) fn values_iter(&self) -> impl Iterator { 19 | self.values.iter() 20 | } 21 | } 22 | 23 | impl OutputSectionMap { 24 | pub(crate) fn add_new(&mut self, value: T) -> OutputSectionId { 25 | let id = OutputSectionId::from_usize(self.values.len()); 26 | self.values.push(value); 27 | id 28 | } 29 | 30 | pub(crate) fn iter(&self) -> impl Iterator { 31 | self.values 32 | .iter() 33 | .enumerate() 34 | .map(|(raw, info)| (OutputSectionId::from_usize(raw), info)) 35 | } 36 | 37 | pub(crate) fn from_values(values: Vec) -> Self { 38 | Self { values } 39 | } 40 | 41 | pub(crate) fn get_mut(&mut self, id: OutputSectionId) -> &mut T { 42 | &mut self.values[id.as_usize()] 43 | } 44 | 45 | pub(crate) fn get(&self, id: OutputSectionId) -> &T { 46 | &self.values[id.as_usize()] 47 | } 48 | 49 | pub(crate) fn for_each(&self, mut cb: impl FnMut(OutputSectionId, &T)) { 50 | self.values 51 | .iter() 52 | .enumerate() 53 | .for_each(|(k, v)| cb(OutputSectionId::from_usize(k), v)); 54 | } 55 | 56 | pub(crate) fn try_for_each(&self, mut cb: impl FnMut(OutputSectionId, &T) -> Result) -> Result { 57 | self.values 58 | .iter() 59 | .enumerate() 60 | .try_for_each(|(k, v)| cb(OutputSectionId::from_usize(k), v)) 61 | } 62 | 63 | pub(crate) fn len(&self) -> usize { 64 | self.values.len() 65 | } 66 | 67 | pub(crate) fn into_map(self, cb: impl FnMut(T) -> U) -> OutputSectionMap { 68 | OutputSectionMap { 69 | values: self.values.into_iter().map(cb).collect(), 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /libwild/src/output_trace.rs: -------------------------------------------------------------------------------- 1 | //! Sets up a tracing layer for recording diagnostics associated with particular addresses in the 2 | //! output file. 3 | 4 | use crate::error::Result; 5 | use linker_trace::AddressTrace; 6 | use std::mem::take; 7 | use std::ops::DerefMut; 8 | use std::path::Path; 9 | use std::path::PathBuf; 10 | use std::sync::Mutex; 11 | 12 | pub(crate) struct TraceOutput { 13 | state: Option, 14 | } 15 | 16 | struct State { 17 | trace_path: PathBuf, 18 | data: Mutex, 19 | } 20 | 21 | impl TraceOutput { 22 | pub(crate) fn new(should_write_trace: bool, base_output: &Path) -> Self { 23 | if !should_write_trace { 24 | return TraceOutput { state: None }; 25 | } 26 | 27 | let trace_path = linker_trace::trace_path(base_output); 28 | 29 | TraceOutput { 30 | state: Some(State { 31 | trace_path, 32 | data: Default::default(), 33 | }), 34 | } 35 | } 36 | 37 | #[inline(always)] 38 | pub(crate) fn emit(&self, address: u64, message_cb: impl Fn() -> String) { 39 | if let Some(state) = self.state.as_ref() { 40 | let message = message_cb(); 41 | state.data.lock().unwrap().traces.push(AddressTrace { 42 | address, 43 | messages: message.split('\n').map(|s| s.to_owned()).collect(), 44 | }); 45 | } 46 | } 47 | 48 | pub(crate) fn close(&self) -> Result { 49 | if let Some(state) = self.state.as_ref() { 50 | let mut file = std::io::BufWriter::new(std::fs::File::create(&state.trace_path)?); 51 | let data = take(state.data.lock().unwrap().deref_mut()); 52 | data.write(&mut file)?; 53 | } 54 | 55 | Ok(()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libwild/src/riscv64.rs: -------------------------------------------------------------------------------- 1 | use crate::elf::PLT_ENTRY_SIZE; 2 | use crate::error; 3 | use crate::error::Result; 4 | use linker_utils::elf::DynamicRelocationKind; 5 | use linker_utils::elf::RISCVInstruction; 6 | use linker_utils::elf::RelocationKindInfo; 7 | use linker_utils::elf::riscv64_rel_type_to_string; 8 | use linker_utils::relaxation::RelocationModifier; 9 | 10 | pub(crate) struct RISCV64; 11 | 12 | const PLT_ENTRY_TEMPLATE: &[u8] = &[ 13 | 0x17, 0x0e, 0x0, 0x0, // auipc t3,offset_high(&(.got.plt[n]) 14 | 0x03, 0x3e, 0x0e, 0x0, // ld t3,offset_low(&(.got.plt[n])(t3) 15 | 0x67, 0x03, 0x0e, 0x0, // jalr t1,t3 16 | 0x73, 0x0, 0x10, 0x0, // ebreak 17 | ]; 18 | 19 | const _ASSERTS: () = { 20 | assert!(PLT_ENTRY_TEMPLATE.len() as u64 == PLT_ENTRY_SIZE); 21 | }; 22 | 23 | impl crate::arch::Arch for RISCV64 { 24 | type Relaxation = Relaxation; 25 | 26 | fn elf_header_arch_magic() -> u16 { 27 | object::elf::EM_RISCV 28 | } 29 | 30 | #[inline(always)] 31 | fn relocation_from_raw(r_type: u32) -> Result { 32 | linker_utils::riscv64::relocation_type_from_raw(r_type).ok_or_else(|| { 33 | error!( 34 | "Unsupported relocation type {}", 35 | Self::rel_type_to_string(r_type) 36 | ) 37 | }) 38 | } 39 | 40 | fn get_dynamic_relocation_type(relocation: DynamicRelocationKind) -> u32 { 41 | relocation.riscv64_r_type() 42 | } 43 | 44 | fn rel_type_to_string(r_type: u32) -> std::borrow::Cow<'static, str> { 45 | riscv64_rel_type_to_string(r_type) 46 | } 47 | 48 | fn write_plt_entry( 49 | plt_entry: &mut [u8], 50 | got_address: u64, 51 | plt_address: u64, 52 | ) -> crate::error::Result { 53 | // TODO: For simplicity, we assume now the PLT entry precedes the GOT entry, so we can 54 | // make the offset calculation in the unsigned type. 55 | debug_assert!(plt_address < got_address); 56 | 57 | plt_entry.copy_from_slice(PLT_ENTRY_TEMPLATE); 58 | RISCVInstruction::UIType.write_to_value( 59 | got_address.wrapping_sub(plt_address), 60 | false, 61 | &mut plt_entry[0..8], 62 | ); 63 | Ok(()) 64 | } 65 | 66 | fn get_dtv_offset() -> u64 { 67 | 0x800 68 | } 69 | 70 | fn local_symbols_in_debug_info() -> bool { 71 | true 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | pub(crate) struct Relaxation {} 77 | 78 | impl crate::arch::Relaxation for Relaxation { 79 | #[allow(unused_variables)] 80 | #[inline(always)] 81 | fn new( 82 | relocation_kind: u32, 83 | section_bytes: &[u8], 84 | offset_in_section: u64, 85 | value_flags: crate::resolution::ValueFlags, 86 | output_kind: crate::args::OutputKind, 87 | section_flags: linker_utils::elf::SectionFlags, 88 | non_zero_address: bool, 89 | ) -> Option 90 | where 91 | Self: std::marker::Sized, 92 | { 93 | None 94 | } 95 | 96 | fn apply(&self, _section_bytes: &mut [u8], _offset_in_section: &mut u64, _addend: &mut i64) {} 97 | 98 | fn rel_info(&self) -> RelocationKindInfo { 99 | todo!("") 100 | } 101 | 102 | fn debug_kind(&self) -> impl std::fmt::Debug { 103 | todo!("") 104 | } 105 | 106 | fn next_modifier(&self) -> RelocationModifier { 107 | todo!("") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /libwild/src/save-dir-prelude.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | D=$(dirname $BASH_SOURCE) 3 | if [ -z "$OUT" ]; then 4 | OUT=$D/bin${S} 5 | fi 6 | exec "$@" \ 7 | -------------------------------------------------------------------------------- /libwild/src/sharding.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait ShardKey: Copy { 2 | fn zero() -> Self; 3 | 4 | fn add_usize(self, offset: usize) -> Self; 5 | 6 | fn as_usize(self) -> usize; 7 | } 8 | -------------------------------------------------------------------------------- /libwild/src/slice.rs: -------------------------------------------------------------------------------- 1 | /// Removes `prefix` elements from `data` and returns them. 2 | #[track_caller] 3 | pub(crate) fn slice_take_prefix_mut<'t, T>(data: &mut &'t mut [T], prefix: usize) -> &'t mut [T] { 4 | data.split_off_mut(..prefix).unwrap_or_else(|| { 5 | panic!( 6 | "Attempted to slice {prefix} elements when only {len} available", 7 | len = data.len() 8 | ) 9 | }) 10 | } 11 | 12 | pub(crate) fn try_slice_take_prefix_mut<'t, T>( 13 | data: &mut &'t mut [T], 14 | prefix: usize, 15 | ) -> Option<&'t mut [T]> { 16 | data.split_off_mut(..prefix) 17 | } 18 | 19 | pub(crate) fn take_first_mut<'t, T>(data: &mut &'t mut [T]) -> Option<&'t mut T> { 20 | data.split_off_first_mut() 21 | } 22 | -------------------------------------------------------------------------------- /libwild/src/subprocess.rs: -------------------------------------------------------------------------------- 1 | use crate::Args; 2 | use crate::bail; 3 | use crate::error::Context as _; 4 | use crate::error::Result; 5 | use libc::c_char; 6 | use libc::fork; 7 | use libc::pid_t; 8 | use std::ffi::c_int; 9 | use std::ffi::c_void; 10 | 11 | /// Runs the linker, in a subprocess if possible, prints any errors, then exits. 12 | /// 13 | /// This is done by forking a sub-process which runs the linker and waits for communication back 14 | /// from the sub-process (via a pipe) when the main link task is done (the output file has been 15 | /// written, but some shutdown tasks remain. 16 | /// 17 | /// Don't call `setup_tracing` or `setup_thread_pool` if using this function, these will be called 18 | /// for you in the subprocess. 19 | /// 20 | /// # Safety 21 | /// Must not be called once threads have been spawned. Calling this function from main is generally 22 | /// the best way to ensure this. 23 | pub unsafe fn run_in_subprocess(args: &Args) -> ! { 24 | let exit_code = match subprocess_result(args) { 25 | Ok(code) => code, 26 | Err(error) => { 27 | eprintln!("Error: {error:?}"); 28 | -1 29 | } 30 | }; 31 | std::process::exit(exit_code); 32 | } 33 | 34 | fn subprocess_result(args: &Args) -> Result { 35 | let mut fds: [c_int; 2] = [0; 2]; 36 | // create the pipe used to communicate between the parent and child processes - exit on failure 37 | make_pipe(&mut fds).context("make_pipe")?; 38 | 39 | // Safety: The function we're in is private to this module and is only called from 40 | // run_in_subprocess, which imposed the requirement that threads have not yet been started on 41 | // its caller. 42 | match unsafe { fork() } { 43 | 0 => { 44 | // Fork success in child - Run linker in this process. 45 | crate::setup_tracing(args)?; 46 | crate::setup_thread_pool(args)?; 47 | let linker = crate::Linker::new(); 48 | let _outputs = linker.run(args)?; 49 | inform_parent_done(&fds); 50 | Ok(0) 51 | } 52 | -1 => { 53 | // Fork failure in the parent - Fallback to running linker in this process 54 | crate::run(args)?; 55 | Ok(0) 56 | } 57 | pid => { 58 | // Fork success in the parent - wait for the child to "signal" us it's done 59 | let exit_status = wait_for_child_done(&fds, pid); 60 | Ok(exit_status) 61 | } 62 | } 63 | } 64 | 65 | /// Inform the parent process that work of linker is done and that it succeeded. 66 | fn inform_parent_done(fds: &[c_int]) { 67 | unsafe { 68 | libc::close(fds[0]); 69 | let stream = libc::fdopen(fds[1], "w".as_ptr() as *const c_char); 70 | let bytes: [u8; 1] = [b'X']; 71 | libc::fwrite(bytes.as_ptr() as *const c_void, 1, 1, stream); 72 | libc::fclose(stream); 73 | libc::close(libc::STDOUT_FILENO); 74 | libc::close(libc::STDERR_FILENO); 75 | } 76 | } 77 | 78 | /// Wait for the child process to signal it is done, by sending a byte on the pipe. In the case the 79 | /// child crashes, or exits via some path that doesn't send a byte, then the pipe will be closed and 80 | /// we'll then wait for the subprocess to exit, returning its exit code. 81 | fn wait_for_child_done(fds: &[c_int], child_pid: pid_t) -> i32 { 82 | unsafe { 83 | // close our sending end of the pipe 84 | libc::close(fds[1]); 85 | // open the other end of the pipe for reading 86 | let stream = libc::fdopen(fds[0], "r".as_ptr() as *const c_char); 87 | 88 | // Wait for child to send a byte via the pipe or for the pipe to be closed. 89 | let mut response: [u8; 1] = [0u8; 1]; 90 | match libc::fread(response.as_mut_ptr() as *mut c_void, 1, 1, stream) { 91 | 1 => { 92 | // Child sent a byte, which indicates that it succeeded and is now shutting down in 93 | // the background. 94 | 0 95 | } 96 | _ => { 97 | // Child closed pipe without sending a byte - get the process exit_status 98 | let mut status: libc::c_int = -1i32; 99 | libc::waitpid(child_pid, &mut status, 0); 100 | libc::WEXITSTATUS(status) 101 | } 102 | } 103 | } 104 | } 105 | 106 | /// Create a pipe for communication between parent and child processes. 107 | /// If successful it will return Ok and `fds` will have file descriptors for reading and writing 108 | /// If errors it will return an error message with the errno set, if it can be read or -1 if not 109 | fn make_pipe(fds: &mut [c_int; 2]) -> Result { 110 | match unsafe { libc::pipe(fds.as_mut_ptr()) } { 111 | 0 => Ok(()), 112 | _ => bail!( 113 | "Error creating pipe. Errno = {:?}", 114 | std::io::Error::last_os_error().raw_os_error().unwrap_or(-1) 115 | ), 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /libwild/src/subprocess_unsupported.rs: -------------------------------------------------------------------------------- 1 | /// # Safety 2 | /// See function of the same name in `subprocess.rs` 3 | pub unsafe fn run_in_subprocess(args: &crate::Args) -> ! { 4 | let exit_code = match crate::run(args) { 5 | Ok(()) => 0, 6 | Err(error) => { 7 | eprintln!("{}", error.to_string()); 8 | -1 9 | } 10 | }; 11 | std::process::exit(exit_code); 12 | } 13 | -------------------------------------------------------------------------------- /libwild/src/symbol.rs: -------------------------------------------------------------------------------- 1 | use crate::hash::PreHashed; 2 | use object::LittleEndian; 3 | use object::read::elf::Sym as _; 4 | use std::fmt::Display; 5 | use std::ops::BitXor as _; 6 | 7 | /// A prehashed symbol that may or may not be versioned. Note, we have the enum as the outer layer 8 | /// and prehash inside the enum. It might be tempting to think that we should do this the other way 9 | /// around. i.e. define a type SymbolName, that's either an enum or has an optional version, then 10 | /// prehash that. However, doing that would mean that the type stored in our names map would be 11 | /// larger which would hurt performance. Benchmarks showed about a 2.4% slowdown just from adding an 12 | /// optional version to the type stored in our names map. So instead, we handle versioned and 13 | /// unversioned symbols separately. 14 | #[derive(Clone, Copy, PartialEq, Eq)] 15 | pub(crate) enum PreHashedSymbolName<'data> { 16 | Unversioned(PreHashed>), 17 | Versioned(PreHashed>), 18 | } 19 | 20 | #[derive(Clone, Copy, PartialEq, Eq)] 21 | pub(crate) struct UnversionedSymbolName<'data> { 22 | bytes: &'data [u8], 23 | } 24 | 25 | #[derive(Clone, Copy, PartialEq, Eq)] 26 | pub(crate) struct VersionedSymbolName<'data> { 27 | name: UnversionedSymbolName<'data>, 28 | version: &'data [u8], 29 | } 30 | 31 | impl<'data> UnversionedSymbolName<'data> { 32 | pub(crate) fn new(bytes: &'data [u8]) -> UnversionedSymbolName<'data> { 33 | Self { bytes } 34 | } 35 | 36 | pub(crate) fn prehashed(bytes: &'data [u8]) -> PreHashed> { 37 | PreHashed::new(Self::new(bytes), crate::hash::hash_bytes(bytes)) 38 | } 39 | 40 | pub(crate) fn bytes(&self) -> &'data [u8] { 41 | self.bytes 42 | } 43 | 44 | pub(crate) fn len(&self) -> usize { 45 | self.bytes.len() 46 | } 47 | } 48 | 49 | impl<'data> VersionedSymbolName<'data> { 50 | pub(crate) fn prehashed( 51 | name: PreHashed>, 52 | version: &'data [u8], 53 | ) -> PreHashed> { 54 | PreHashed::new( 55 | VersionedSymbolName { 56 | name: *name, 57 | version, 58 | }, 59 | name.hash().bitxor(crate::hash::hash_bytes(version)), 60 | ) 61 | } 62 | } 63 | 64 | impl Display for UnversionedSymbolName<'_> { 65 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 66 | if let Ok(s) = std::str::from_utf8(self.bytes) { 67 | Display::fmt(s, f) 68 | } else { 69 | write!(f, "INVALID UTF-8({:?})", self.bytes) 70 | } 71 | } 72 | } 73 | 74 | impl std::fmt::Debug for UnversionedSymbolName<'_> { 75 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 76 | std::fmt::Debug::fmt(&String::from_utf8_lossy(self.bytes), f) 77 | } 78 | } 79 | 80 | pub(crate) struct SymDebug<'data>(pub(crate) &'data crate::elf::SymtabEntry); 81 | 82 | impl<'data> PreHashedSymbolName<'data> { 83 | pub(crate) fn from_raw( 84 | name_info: &crate::symbol_db::RawSymbolName<'data>, 85 | ) -> PreHashedSymbolName<'data> { 86 | let name = UnversionedSymbolName::prehashed(name_info.name_bytes); 87 | if let Some(version) = name_info.version_name { 88 | PreHashedSymbolName::Versioned(VersionedSymbolName::prehashed(name, version)) 89 | } else { 90 | PreHashedSymbolName::Unversioned(name) 91 | } 92 | } 93 | } 94 | 95 | impl Display for SymDebug<'_> { 96 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 97 | let e = LittleEndian; 98 | let sym = self.0; 99 | 100 | let vis = if sym.is_local() { 101 | "Local" 102 | } else if sym.is_weak() { 103 | "Weak" 104 | } else { 105 | "Global" 106 | }; 107 | 108 | let kind = if sym.is_undefined(e) { 109 | "Undefined" 110 | } else { 111 | match sym.st_type() { 112 | object::elf::STT_FUNC => "Func", 113 | object::elf::STT_GNU_IFUNC => "IFunc", 114 | object::elf::STT_OBJECT => "Data", 115 | object::elf::STT_COMMON => "Common", 116 | object::elf::STT_SECTION => "Section", 117 | object::elf::STT_FILE => "File", 118 | object::elf::STT_NOTYPE => "NoType", 119 | object::elf::STT_TLS => "Tls", 120 | _ => "Unknown", 121 | } 122 | }; 123 | 124 | write!(f, "{vis} {kind}") 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /libwild/src/test_data/a.a: -------------------------------------------------------------------------------- 1 | ! 2 | a.txt/ 0 0 0 644 5 ` 3 | Hello 4 | b.txt/ 0 0 0 644 5 ` 5 | World 6 | -------------------------------------------------------------------------------- /libwild/src/timing.rs: -------------------------------------------------------------------------------- 1 | //! Code for reporting how long each phase of linking takes when the --time argument is supplied. 2 | 3 | use crate::error::AlreadyInitialised; 4 | use std::fmt::Display; 5 | use std::time::Instant; 6 | use tracing::field::Visit; 7 | 8 | #[derive(Default)] 9 | struct TimingLayer {} 10 | 11 | struct Data { 12 | start: Instant, 13 | child_count: u32, 14 | attributes_string: String, 15 | } 16 | 17 | #[derive(Default)] 18 | pub struct ValuesFormatter { 19 | out: String, 20 | } 21 | 22 | impl ValuesFormatter { 23 | fn finish(mut self) -> String { 24 | if !self.out.is_empty() { 25 | self.out.push(']'); 26 | } 27 | self.out 28 | } 29 | } 30 | 31 | impl Visit for ValuesFormatter { 32 | fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { 33 | use std::fmt::Write; 34 | 35 | if self.out.is_empty() { 36 | write!(&mut self.out, " [").unwrap(); 37 | } else { 38 | write!(&mut self.out, ", ").unwrap(); 39 | } 40 | match field.name() { 41 | "message" => { 42 | write!(&mut self.out, "{value:?}").unwrap(); 43 | } 44 | name => { 45 | write!(&mut self.out, "{name}={value:?}").unwrap(); 46 | } 47 | } 48 | } 49 | } 50 | 51 | impl tracing_subscriber::Layer for TimingLayer 52 | where 53 | S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, 54 | { 55 | fn max_level_hint(&self) -> Option { 56 | Some(tracing::level_filters::LevelFilter::INFO) 57 | } 58 | 59 | fn on_new_span( 60 | &self, 61 | attributes: &tracing::span::Attributes, 62 | id: &tracing::span::Id, 63 | ctx: tracing_subscriber::layer::Context, 64 | ) { 65 | if *attributes.metadata().level() > tracing::Level::INFO { 66 | return; 67 | } 68 | let span = ctx.span(id).expect("valid span ID"); 69 | 70 | let mut formatted = ValuesFormatter::default(); 71 | attributes.values().record(&mut formatted); 72 | 73 | span.extensions_mut().insert(Data { 74 | start: Instant::now(), 75 | child_count: 0, 76 | attributes_string: formatted.finish(), 77 | }); 78 | } 79 | 80 | fn on_enter(&self, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context) { 81 | let span = ctx.span(id).expect("valid span ID"); 82 | if let Some(data) = span.extensions_mut().get_mut::() { 83 | data.start = Instant::now(); 84 | } 85 | } 86 | 87 | fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context) { 88 | let span = ctx.span(&id).expect("valid span ID"); 89 | let metadata = span.metadata(); 90 | if *metadata.level() > tracing::Level::INFO { 91 | return; 92 | } 93 | 94 | let parent_child_count = span 95 | .parent() 96 | .and_then(|parent| { 97 | parent 98 | .extensions_mut() 99 | .get_mut::() 100 | .map(|parent_data| { 101 | parent_data.child_count += 1; 102 | parent_data.child_count 103 | }) 104 | }) 105 | .unwrap_or(0); 106 | 107 | if let Some(data) = span.extensions().get::() { 108 | let scope_depth = span.scope().count() - 1; 109 | let name = metadata.name(); 110 | let ms = data.start.elapsed().as_secs_f64() * 1000.0; 111 | let indent = Indent { 112 | scope_depth, 113 | child_count: data.child_count, 114 | parent_child_count, 115 | }; 116 | println!("{indent}{ms:>8.2} {name}{}", data.attributes_string); 117 | }; 118 | } 119 | } 120 | 121 | pub(crate) fn init_tracing() -> Result<(), AlreadyInitialised> { 122 | use tracing_subscriber::prelude::*; 123 | let layer = TimingLayer::default(); 124 | let subscriber = tracing_subscriber::Registry::default().with(layer); 125 | tracing::subscriber::set_global_default(subscriber).map_err(|_| AlreadyInitialised) 126 | } 127 | 128 | struct Indent { 129 | scope_depth: usize, 130 | parent_child_count: u32, 131 | child_count: u32, 132 | } 133 | 134 | impl Display for Indent { 135 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 136 | if self.scope_depth == 0 { 137 | write!(f, "└─")?; 138 | return Ok(()); 139 | } 140 | for _ in 0..self.scope_depth - 1 { 141 | write!(f, "│ ")?; 142 | } 143 | if self.parent_child_count >= 2 { 144 | write!(f, "├─")?; 145 | } else { 146 | write!(f, "┌─")?; 147 | } 148 | if self.child_count > 0 { 149 | write!(f, "┴─")?; 150 | } else { 151 | write!(f, "──")?; 152 | } 153 | Ok(()) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /libwild/src/validation.rs: -------------------------------------------------------------------------------- 1 | //! Code to double-check that we did certain things correctly. Generally only used in debug builds. 2 | 3 | use crate::bail; 4 | use crate::error::Context as _; 5 | use crate::error::Result; 6 | use crate::layout::Layout; 7 | use linker_utils::elf::secnames::GOT_SECTION_NAME_STR; 8 | use object::LittleEndian; 9 | use object::read::elf::SectionHeader as _; 10 | 11 | pub(crate) fn validate_bytes(layout: &Layout, file_bytes: &[u8]) -> Result { 12 | let object = 13 | crate::elf::File::parse(file_bytes, true).context("Failed to parse our output file")?; 14 | validate_object(&object, layout).context("Output validation failed") 15 | } 16 | 17 | /// Checks that what we actually wrote to our output file matches what we intended to write in 18 | /// `layout`. 19 | fn validate_object(object: &crate::elf::File, layout: &Layout) -> Result { 20 | if layout.args().is_relocatable() { 21 | // For now, we don't do any validation of relocatable outputs. The only thing we're 22 | // currently validating is GOT entries and they'll all have dynamic relocations. 23 | return Ok(()); 24 | } 25 | let Some((_, got)) = object.section_by_name(GOT_SECTION_NAME_STR) else { 26 | return Ok(()); 27 | }; 28 | 29 | let got_data = got.data(LittleEndian, object.data)?; 30 | 31 | for (symbol_name, symbol_id) in layout.symbol_db.all_unversioned_symbols() { 32 | match layout.local_symbol_resolution(*symbol_id) { 33 | None => {} 34 | Some(resolution) => { 35 | validate_resolution(symbol_name.bytes(), resolution, got, got_data)?; 36 | } 37 | } 38 | } 39 | for group in &layout.group_layouts { 40 | for file in &group.files { 41 | match file { 42 | crate::layout::FileLayout::Prelude(_) => {} 43 | crate::layout::FileLayout::Object(obj) => { 44 | for (sec_index, sec) in obj.object.sections.enumerate() { 45 | if let Some(resolution) = 46 | obj.section_resolutions[sec_index.0].full_resolution() 47 | { 48 | validate_resolution( 49 | obj.object.section_name(sec)?, 50 | &resolution, 51 | got, 52 | got_data, 53 | )?; 54 | } 55 | } 56 | } 57 | crate::layout::FileLayout::LinkerScript(_) => {} 58 | crate::layout::FileLayout::Dynamic(_) => {} 59 | crate::layout::FileLayout::Epilogue(_) => {} 60 | crate::layout::FileLayout::NotLoaded => {} 61 | } 62 | } 63 | } 64 | Ok(()) 65 | } 66 | 67 | fn validate_resolution( 68 | name: &[u8], 69 | resolution: &crate::layout::Resolution, 70 | got: &crate::elf::SectionHeader, 71 | got_data: &[u8], 72 | ) -> Result { 73 | let res_flags = resolution.resolution_flags; 74 | let value_flags = resolution.value_flags; 75 | if value_flags.is_ifunc() 76 | || res_flags.needs_got_tls_module() 77 | || res_flags.needs_got_tls_offset() 78 | || res_flags.needs_got_tls_descriptor() 79 | { 80 | return Ok(()); 81 | }; 82 | if let Some(got_address) = resolution.got_address { 83 | let start_offset = (got_address.get() - got.sh_addr(LittleEndian)) as usize; 84 | let end_offset = start_offset + size_of::(); 85 | if end_offset > got_data.len() { 86 | bail!("GOT offset beyond end of GOT 0x{end_offset}"); 87 | } 88 | if resolution.value_flags.is_dynamic() || resolution.value_flags.is_ifunc() { 89 | return Ok(()); 90 | } 91 | let expected = resolution.raw_value; 92 | let address = bytemuck::pod_read_unaligned(&got_data[start_offset..end_offset]); 93 | if expected != address { 94 | let name = String::from_utf8_lossy(name); 95 | bail!( 96 | "res={res_flags:?} `{name}` has address 0x{expected:x}, but GOT \ 97 | (at 0x{got_address:x}) points to 0x{address:x}" 98 | ); 99 | } 100 | } 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /libwild/src/verification.rs: -------------------------------------------------------------------------------- 1 | //! Used after `finalise_layout` to verify that all section output offsets were bumped by an amount 2 | //! equal to the size requested for that section. 3 | 4 | use crate::bail; 5 | use crate::error::Result; 6 | use crate::layout::FileLayout; 7 | use crate::output_section_id::OutputOrder; 8 | use crate::output_section_id::OutputSections; 9 | use crate::output_section_part_map::OutputSectionPartMap; 10 | use crate::part_id; 11 | use crate::part_id::PartId; 12 | use itertools::Itertools; 13 | 14 | pub(crate) struct OffsetVerifier { 15 | expected: OutputSectionPartMap, 16 | sizes: OutputSectionPartMap, 17 | } 18 | 19 | impl OffsetVerifier { 20 | pub(crate) fn new( 21 | starting_offsets: &OutputSectionPartMap, 22 | sizes: &OutputSectionPartMap, 23 | ) -> Self { 24 | let mut expected = starting_offsets.clone(); 25 | expected.merge(sizes); 26 | clear_ignored(&mut expected); 27 | Self { 28 | expected, 29 | sizes: sizes.clone(), 30 | } 31 | } 32 | 33 | pub(crate) fn verify( 34 | &self, 35 | memory_offsets: &OutputSectionPartMap, 36 | output_sections: &OutputSections, 37 | output_order: &OutputOrder, 38 | files: &[FileLayout], 39 | ) -> Result { 40 | if memory_offsets == &self.expected { 41 | return Ok(()); 42 | } 43 | let expected = offsets_by_key(&self.expected, output_order); 44 | let actual = offsets_by_key(memory_offsets, output_order); 45 | let sizes = offsets_by_key(&self.sizes, output_order); 46 | let mut problems = Vec::new(); 47 | 48 | for (((part_id, exp), (_, act)), (_, size)) in expected.iter().zip(actual.iter()).zip(sizes) 49 | { 50 | let alignment = part_id.alignment(); 51 | if exp != act { 52 | let actual_bump = *act as i64 - (*exp as i64 - size as i64); 53 | problems.push(format!( 54 | "Part #{part_id} (section {} alignment: {alignment}) expected: 0x{exp:x} \ 55 | actual: 0x{act:x} bumped by: 0x{actual_bump:x} requested size: 0x{size:x}\n", 56 | output_sections.display_name(part_id.output_section_id()) 57 | )); 58 | } 59 | } 60 | 61 | let files = files.iter().map(|f| f.to_string()).collect_vec(); 62 | 63 | bail!( 64 | "Unexpected memory offsets:\n{}\nfor files:\n{}", 65 | problems.join(""), 66 | files.join("\n") 67 | ); 68 | } 69 | } 70 | 71 | /// Clear offsets for sections where we never take the address of a section offset during 72 | /// `finalise_layout`. 73 | pub(crate) fn clear_ignored(expected: &mut OutputSectionPartMap) { 74 | /// A distinctive value that should definitely make things fail if we actually do make use of 75 | /// one of these offsets during `finalise_layout`. 76 | const IGNORED_OFFSET: u64 = 0x98760000; 77 | 78 | const IGNORED: &[PartId] = &[ 79 | part_id::RELA_PLT, 80 | part_id::EH_FRAME_HDR, 81 | part_id::RELA_DYN_GENERAL, 82 | part_id::RELA_DYN_RELATIVE, 83 | part_id::GNU_VERSION, 84 | part_id::GNU_HASH, 85 | part_id::DYNAMIC, 86 | part_id::INTERP, 87 | part_id::FILE_HEADER, 88 | part_id::PROGRAM_HEADERS, 89 | part_id::SECTION_HEADERS, 90 | part_id::SHSTRTAB, 91 | ]; 92 | 93 | for part_id in IGNORED { 94 | *expected.get_mut(*part_id) = IGNORED_OFFSET; 95 | } 96 | } 97 | 98 | fn offsets_by_key( 99 | memory_offsets: &OutputSectionPartMap, 100 | output_order: &OutputOrder, 101 | ) -> Vec<(PartId, u64)> { 102 | let mut offsets_by_key = Vec::new(); 103 | memory_offsets.output_order_map(output_order, |part_id, _alignment, offset| { 104 | offsets_by_key.push((part_id, *offset)); 105 | }); 106 | offsets_by_key 107 | } 108 | -------------------------------------------------------------------------------- /linker-diff/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linker-diff" 3 | description = "Diffs and validates ELF binaries" 4 | version.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | edition.workspace = true 9 | 10 | [dependencies] 11 | linker-layout = { path = "../linker-layout", version = "0.5.0" } 12 | linker-trace = { path = "../linker-trace", version = "0.5.0" } 13 | linker-utils = { path = "../linker-utils", version = "0.5.0" } 14 | object = { workspace = true } 15 | iced-x86 = { workspace = true } 16 | symbolic-demangle = { workspace = true } 17 | anyhow = { workspace = true } 18 | clap = { workspace = true } 19 | rayon = { workspace = true } 20 | bytemuck = { workspace = true } 21 | itertools = { workspace = true } 22 | gimli = { workspace = true } 23 | fallible-iterator = { workspace = true } 24 | colored = { workspace = true } 25 | tracing = { workspace = true } 26 | tracing-subscriber = { workspace = true } 27 | memmap2 = { workspace = true } 28 | memchr = { workspace = true } 29 | tempfile = { workspace = true } 30 | which = { workspace = true } 31 | ascii_table = { workspace = true } 32 | 33 | [lints] 34 | workspace = true 35 | -------------------------------------------------------------------------------- /linker-diff/README.md: -------------------------------------------------------------------------------- 1 | # linker-diff 2 | 3 | Linker-diff is a command-line utility that diffs two ELF binaries (shared objects or executables). 4 | At least one of the binaries being diffed needs layout information as can optionally be produced by 5 | the Wild linker. 6 | 7 | ## Usage 8 | 9 | The easiest way to use linker-diff is to first make sure it's installed into the same directory as 10 | the wild linker, then build with the environment variable `WILD_REFERENCE_LINKER` set to the name of 11 | another linker. e.g. 12 | 13 | ```sh 14 | WILD_REFERENCE_LINKER=ld cargo test 15 | ``` 16 | 17 | When this variable is set, each time the wild linker is invoked, it'll call the specified linker 18 | then run linker-diff on the result. 19 | -------------------------------------------------------------------------------- /linker-diff/src/bin/linker-diff.rs: -------------------------------------------------------------------------------- 1 | fn main() -> anyhow::Result<()> { 2 | linker_diff::enable_diagnostics(); 3 | 4 | let config = linker_diff::Config::from_env(); 5 | let report = linker_diff::Report::from_config(config)?; 6 | 7 | if report.has_problems() { 8 | println!("{report}"); 9 | std::process::exit(1); 10 | } else { 11 | println!("No differences or validation failures detected"); 12 | } 13 | 14 | if let Some(coverage) = report.coverage.as_ref() { 15 | println!("{coverage}"); 16 | } 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /linker-diff/src/diagnostics.rs: -------------------------------------------------------------------------------- 1 | //! Provides mechanisms for helping diagnose problems in linker-diff. Specifically, a way to set up 2 | //! tracing so that we can collect tracing logs within some scope and attach them to a diff report. 3 | //! 4 | //! To get tracing messages, make sure the code you want to trace from is called from somewhere 5 | //! inside a call to `trace_scope`. Assuming it is, you can then call `tracing::trace!()` and see 6 | //! the output in the diff report for whatever was being diffed. 7 | 8 | use std::cell::RefCell; 9 | use std::fmt::Write as _; 10 | use tracing_subscriber::layer::SubscriberExt as _; 11 | 12 | /// Enable diagnostics. Configures the global tracing subscriber, so cannot be used in conjunction 13 | /// with other things that do that. For that reason, this should be called from the main binary. 14 | pub fn enable_diagnostics() { 15 | let layer = TraceLayer; 16 | let subscriber = tracing_subscriber::Registry::default().with(layer); 17 | tracing::subscriber::set_global_default(subscriber) 18 | .expect("Only one global tracing subscriber can be setup"); 19 | } 20 | 21 | #[derive(Default, Debug, Clone)] 22 | pub(crate) struct TraceOutput { 23 | pub(crate) messages: Vec, 24 | } 25 | 26 | impl TraceOutput { 27 | pub(crate) fn append(&mut self, mut other: TraceOutput) { 28 | self.messages.append(&mut other.messages); 29 | } 30 | } 31 | 32 | thread_local! { 33 | pub static TRACE_STACK: RefCell> = const { RefCell::new(Vec::new()) }; 34 | } 35 | 36 | /// Runs `f` then returns all trace output emitted while it was running. 37 | pub(crate) fn trace_scope(trace_output: &mut TraceOutput, f: impl FnOnce() -> T) -> T { 38 | TRACE_STACK.with_borrow_mut(|stack| stack.push(TraceOutput::default())); 39 | 40 | let result = f(); 41 | 42 | *trace_output = TRACE_STACK.with_borrow_mut(|stack| stack.pop()).unwrap(); 43 | 44 | result 45 | } 46 | 47 | struct TraceLayer; 48 | 49 | impl tracing_subscriber::Layer for TraceLayer 50 | where 51 | S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>, 52 | { 53 | fn on_event( 54 | &self, 55 | event: &tracing::Event<'_>, 56 | _ctx: tracing_subscriber::layer::Context<'_, S>, 57 | ) { 58 | if TRACE_STACK.with_borrow(|stack| stack.is_empty()) { 59 | return; 60 | } 61 | 62 | let mut formatter = MessageFormatter::default(); 63 | event.record(&mut formatter); 64 | 65 | TRACE_STACK.with_borrow_mut(|stack| { 66 | if let Some(out) = stack.last_mut() { 67 | out.messages.push(formatter.out); 68 | } 69 | }); 70 | } 71 | } 72 | 73 | #[derive(Default)] 74 | struct MessageFormatter { 75 | out: String, 76 | } 77 | 78 | impl tracing::field::Visit for MessageFormatter { 79 | fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { 80 | if !self.out.is_empty() { 81 | self.out.push(' '); 82 | } 83 | let _ = write!(&mut self.out, "{field}={value:?}"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /linker-diff/src/init_order.rs: -------------------------------------------------------------------------------- 1 | //! Checks order in sections that contain pointers. e.g. `.init_array`, `.fini_array`. 2 | 3 | use crate::Arch; 4 | use crate::Binary; 5 | use crate::Result; 6 | use crate::arch::RType as _; 7 | use crate::get_r_type; 8 | use crate::header_diff::ResolvedValue; 9 | use anyhow::Context; 10 | use linker_utils::elf::DynamicRelocationKind; 11 | use object::Object; 12 | use object::ObjectSection; 13 | use object::ObjectSymbol; 14 | use object::ObjectSymbolTable; 15 | use object::RelocationTarget; 16 | use object::SymbolKind; 17 | use std::borrow::Cow; 18 | 19 | pub(crate) fn report_diffs(report: &mut crate::Report, objects: &[crate::Binary]) { 20 | report.add_diffs(crate::header_diff::diff_array( 21 | objects, 22 | |bin| get_pointer_list::(bin, ".init_array"), 23 | "init_array", 24 | )); 25 | 26 | report.add_diffs(crate::header_diff::diff_array( 27 | objects, 28 | |bin| get_pointer_list::(bin, ".fini_array"), 29 | "fini_array", 30 | )); 31 | } 32 | 33 | fn get_pointer_list(bin: &Binary, section_name: &str) -> Result> { 34 | let Some(sec) = bin.section_by_name(section_name) else { 35 | return Ok(Vec::new()); 36 | }; 37 | 38 | let section_address = sec.address(); 39 | 40 | let data = sec.data()?; 41 | 42 | const ADDRESS_SIZE: usize = size_of::(); 43 | 44 | let mut names = Vec::with_capacity(data.len() / ADDRESS_SIZE); 45 | 46 | for (entry_num, address_bytes) in data.chunks_exact(ADDRESS_SIZE).enumerate() { 47 | let mut address = u64::from_le_bytes(*address_bytes.first_chunk::().unwrap()); 48 | let entry_address = section_address + (entry_num * ADDRESS_SIZE) as u64; 49 | let mut symbol_names = Vec::new(); 50 | 51 | let relocation = bin.address_index.relocation_at_address(entry_address); 52 | 53 | if let Some(relocation) = relocation { 54 | if let RelocationTarget::Symbol(symbol_index) = relocation.target() { 55 | let dynsym = bin 56 | .elf_file 57 | .dynamic_symbol_table() 58 | .context("Missing .dynsym")?; 59 | 60 | symbol_names.push(String::from_utf8_lossy( 61 | dynsym.symbol_by_index(symbol_index)?.name_bytes()?, 62 | )); 63 | } 64 | 65 | let r_type = get_r_type::(relocation); 66 | 67 | match r_type.dynamic_relocation_kind() { 68 | Some(DynamicRelocationKind::Relative) => { 69 | address = relocation.addend() as u64; 70 | } 71 | Some(other) => { 72 | symbol_names.push(Cow::Owned(format!("Rel({other:?})"))); 73 | } 74 | None => {} 75 | } 76 | } 77 | 78 | if symbol_names.is_empty() && address != 0 { 79 | for symbol_index in bin.address_index.symbols_at_address(address) { 80 | let symbol = bin.elf_file.symbol_by_index(*symbol_index)?; 81 | 82 | let name_bytes = symbol.name_bytes()?; 83 | 84 | if name_bytes.is_empty() || symbol.kind() != SymbolKind::Text { 85 | continue; 86 | } 87 | 88 | symbol_names.push(String::from_utf8_lossy(name_bytes)); 89 | } 90 | } 91 | 92 | if symbol_names.is_empty() { 93 | if let Some(relocation) = relocation { 94 | symbol_names.push(Cow::Owned(format!("{:?}", relocation.kind()))); 95 | } else if address == 0 { 96 | symbol_names.push(Cow::Borrowed("0x0")); 97 | } 98 | } 99 | 100 | symbol_names.sort(); 101 | 102 | if symbol_names.is_empty() { 103 | names.push(ResolvedValue { 104 | for_comparison: "??".to_owned(), 105 | formatted: format!("0x{address:x}"), 106 | }); 107 | } else { 108 | let joined = symbol_names.join(" / "); 109 | 110 | names.push(ResolvedValue { 111 | for_comparison: joined.clone(), 112 | formatted: joined, 113 | }); 114 | } 115 | } 116 | 117 | Ok(names) 118 | } 119 | -------------------------------------------------------------------------------- /linker-diff/src/segment.rs: -------------------------------------------------------------------------------- 1 | use crate::header_diff::Converter; 2 | use crate::header_diff::DiffMode; 3 | use crate::header_diff::FieldValues; 4 | use anyhow::Ok; 5 | use anyhow::Result; 6 | use linker_utils::elf::segment_type_to_string; 7 | use object::LittleEndian; 8 | use object::elf::PT_LOAD; 9 | use object::read::elf::ProgramHeader as _; 10 | 11 | pub(crate) fn report_diffs(report: &mut crate::Report, objects: &[crate::Binary]) { 12 | report.add_diffs(crate::header_diff::diff_fields( 13 | objects, 14 | read_program_segment_fields, 15 | "segment", 16 | DiffMode::Normal, 17 | )); 18 | } 19 | 20 | #[allow(clippy::unnecessary_wraps)] 21 | fn read_program_segment_fields(object: &crate::Binary) -> Result { 22 | let e = LittleEndian; 23 | let mut values = FieldValues::default(); 24 | 25 | for segment in object.elf_file.elf_program_headers() { 26 | let p_type = segment.p_type(e); 27 | let p_flags = segment.p_flags(e); 28 | let p_align = segment.p_align(e); 29 | 30 | if p_type == PT_LOAD { 31 | let mut flag_str = String::new(); 32 | if p_flags & 4 != 0 { 33 | flag_str.push('R'); 34 | } 35 | if p_flags & 2 != 0 { 36 | flag_str.push('W'); 37 | } 38 | if p_flags & 1 != 0 { 39 | flag_str.push('X'); 40 | } 41 | 42 | values.insert( 43 | format!("LOAD.{flag_str}.alignment"), 44 | p_align, 45 | Converter::None, 46 | object, 47 | ); 48 | } else { 49 | let type_str = segment_type_to_string(p_type); 50 | 51 | values.insert( 52 | format!("{type_str}.alignment"), 53 | p_align, 54 | Converter::None, 55 | object, 56 | ); 57 | values.insert( 58 | format!("{type_str}.flags"), 59 | p_flags, 60 | Converter::None, 61 | object, 62 | ); 63 | } 64 | } 65 | 66 | Ok(values) 67 | } 68 | -------------------------------------------------------------------------------- /linker-diff/src/symbol_diff.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | use crate::header_diff::DiffMode; 3 | use crate::header_diff::FieldValues; 4 | use object::Object as _; 5 | use object::ObjectSection as _; 6 | use object::ObjectSymbol as _; 7 | 8 | pub(crate) fn report_diffs(report: &mut crate::Report, bins: &[crate::Binary]) { 9 | report.add_diffs(crate::header_diff::diff_fields( 10 | bins, 11 | read_dynsym, 12 | "dynsym", 13 | DiffMode::Normal, 14 | )); 15 | } 16 | 17 | fn read_dynsym(bin: &crate::Binary) -> Result { 18 | let mut values = FieldValues::default(); 19 | 20 | for sym in bin.elf_file.dynamic_symbols() { 21 | let Ok(name) = sym.name_bytes() else { 22 | continue; 23 | }; 24 | 25 | if sym.is_local() { 26 | continue; 27 | } 28 | 29 | // On aarch64, GNU ld emits a dynamic symbol called "_stack", which it puts in some section 30 | // or other that doesn't make sense. e.g. ".got.plt". It probably puts it in that section 31 | // because it's closest to the value that it assigns to the symbol. It's not clear where 32 | // this symbol comes from. It's neither in any input files, nor in GNU ld's built-in linker 33 | // script. 34 | if name == b"_stack" { 35 | continue; 36 | } 37 | 38 | // TODO: Diff type, binding and visibility. Also, diff undefined symbols. 39 | if let Some(section_index) = sym.section_index() { 40 | let section = bin.elf_file.section_by_index(section_index)?; 41 | let section_name = String::from_utf8_lossy(section.name_bytes()?).into_owned(); 42 | 43 | values.insert_string_owned( 44 | format!("{}.section", String::from_utf8_lossy(name)), 45 | section_name, 46 | ); 47 | }; 48 | } 49 | 50 | Ok(values) 51 | } 52 | -------------------------------------------------------------------------------- /linker-diff/src/symtab.rs: -------------------------------------------------------------------------------- 1 | use crate::Binary; 2 | use crate::Result; 3 | use anyhow::bail; 4 | use linker_utils::elf::SectionType; 5 | use linker_utils::elf::sht; 6 | use object::LittleEndian; 7 | use object::Object as _; 8 | use object::ObjectSymbol; 9 | use object::read::elf::SectionHeader as _; 10 | use std::ops::Not; 11 | 12 | pub(crate) fn validate_debug(object: &Binary) -> Result { 13 | validate(object, false) 14 | } 15 | 16 | pub(crate) fn validate_dynamic(object: &Binary) -> Result { 17 | validate(object, true) 18 | } 19 | 20 | fn validate(object: &Binary, dynamic: bool) -> Result { 21 | let mut symtab_info = 0; 22 | let (symtab_section_type, mut symbols) = if dynamic { 23 | (sht::DYNSYM, object.elf_file.dynamic_symbols()) 24 | } else { 25 | (sht::SYMTAB, object.elf_file.symbols()) 26 | }; 27 | for section in object.elf_file.elf_section_table().iter() { 28 | if SectionType::from_header(section) == symtab_section_type { 29 | symtab_info = section.sh_info(LittleEndian); 30 | } 31 | } 32 | let first_non_local = symbols.find_map(|sym| sym.is_local().not().then(|| sym.index())); 33 | if let Some(first_non_local) = first_non_local { 34 | if first_non_local.0 != symtab_info as usize { 35 | bail!("info={symtab_info}, but first non-local is {first_non_local}") 36 | } 37 | } 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /linker-diff/src/trace.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use anyhow::Result; 3 | use linker_trace::TraceData; 4 | use std::ops::Range; 5 | use std::path::Path; 6 | 7 | pub(crate) struct Trace { 8 | data: linker_trace::TraceData, 9 | } 10 | 11 | impl Trace { 12 | pub(crate) fn for_path(base_path: &Path) -> Result { 13 | let trace_path = linker_trace::trace_path(base_path); 14 | if !trace_path.exists() { 15 | return Ok(Trace { 16 | data: Default::default(), 17 | }); 18 | } 19 | let bytes = std::fs::read(&trace_path) 20 | .with_context(|| format!("Failed to read `{}`", trace_path.display()))?; 21 | let mut trace = Trace { 22 | data: TraceData::from_bytes(&bytes)?, 23 | }; 24 | trace.data.traces.sort_by_key(|t| t.address); 25 | Ok(trace) 26 | } 27 | 28 | pub(crate) fn messages_in(&self, range: Range) -> Vec<&str> { 29 | let mut messages = Vec::new(); 30 | let mut i = self 31 | .data 32 | .traces 33 | .binary_search_by_key(&range.start, |t| t.address) 34 | .unwrap_or_else(|v| v); 35 | while let Some(t) = self.data.traces.get(i) { 36 | if !range.contains(&t.address) { 37 | break; 38 | } 39 | messages.extend(t.messages.iter().map(String::as_str)); 40 | i += 1; 41 | } 42 | messages 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /linker-diff/src/version_diff.rs: -------------------------------------------------------------------------------- 1 | use crate::header_diff::DiffMode; 2 | use crate::header_diff::FieldValues; 3 | use anyhow::Result; 4 | use linker_utils::elf::secnames::GNU_VERSION_D_SECTION_NAME_STR; 5 | use object::LittleEndian; 6 | use object::Object; 7 | use object::read::elf::SectionHeader; 8 | 9 | pub(crate) fn report_diffs(report: &mut crate::Report, objects: &[crate::Binary]) { 10 | report.add_diffs(crate::header_diff::diff_fields( 11 | objects, 12 | read_version_d_fields, 13 | "version_d", 14 | DiffMode::Normal, 15 | )); 16 | } 17 | 18 | fn read_version_d_fields(object: &crate::Binary) -> Result { 19 | let e = LittleEndian; 20 | let mut values = FieldValues::default(); 21 | 22 | // Copied and adapted from asm_diff.rs 23 | let maybe_verdef = object 24 | .elf_file 25 | .sections() 26 | .find_map(|section| { 27 | section 28 | .elf_section_header() 29 | .gnu_verdef(e, object.elf_file.data()) 30 | .transpose() 31 | }) 32 | .transpose()?; 33 | 34 | let Some((mut verdef_iterator, strings_index)) = maybe_verdef else { 35 | values.insert_string_owned( 36 | GNU_VERSION_D_SECTION_NAME_STR.to_owned(), 37 | "Missing".to_owned(), 38 | ); 39 | return Ok(values); 40 | }; 41 | 42 | let strings = 43 | object 44 | .elf_file 45 | .elf_section_table() 46 | .strings(e, object.elf_file.data(), strings_index)?; 47 | 48 | while let Some((verdef, mut aux_iterator)) = verdef_iterator.next()? { 49 | let verdef_index = verdef.vd_ndx.get(e); 50 | let mut verdef_versions = String::new(); 51 | 52 | if let Some(aux) = aux_iterator.next()? { 53 | let name = std::str::from_utf8(aux.name(e, strings)?)?; 54 | verdef_versions = format!("Version name: {name}"); 55 | } 56 | 57 | let mut version_parents = Vec::new(); 58 | while let Some(aux) = aux_iterator.next()? { 59 | version_parents.push(std::str::from_utf8(aux.name(e, strings)?)?); 60 | } 61 | if !version_parents.is_empty() { 62 | verdef_versions += &format!(" Version parents: {}", version_parents.join(",")); 63 | } 64 | 65 | values.insert_string_owned(format!("verdef_{verdef_index}"), verdef_versions); 66 | } 67 | 68 | Ok(values) 69 | } 70 | -------------------------------------------------------------------------------- /linker-layout/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linker-layout" 3 | description = "Data structures for storing linker layout information" 4 | version.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | edition.workspace = true 9 | 10 | [dependencies] 11 | anyhow = { workspace = true } 12 | postcard = { workspace = true } 13 | serde = { workspace = true } 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /linker-layout/README.md: -------------------------------------------------------------------------------- 1 | # linker-layout 2 | 3 | This crate provides data structures for representing and persisting to disk, the layout used by a 4 | linker such as [Wild](https://github.com/davidlattimore/wild). This layout information can then be 5 | used by tools such as linker-diff. 6 | -------------------------------------------------------------------------------- /linker-layout/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate defines a format for providing information about where a linker put stuff. 2 | 3 | use anyhow::Context; 4 | use anyhow::Result; 5 | use serde::Deserialize; 6 | use serde::Serialize; 7 | use std::io::Write; 8 | use std::ops::Range; 9 | use std::path::Path; 10 | use std::path::PathBuf; 11 | 12 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 13 | pub struct Layout { 14 | /// The input files to the linker. 15 | pub files: Vec, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 19 | pub struct InputFile { 20 | /// Path to the input file on disk. In case of archives, multiple inputs may have the same path. 21 | pub path: PathBuf, 22 | 23 | /// If the input is an archive, then contains information about where in the archive the file 24 | /// came from. 25 | pub archive_entry: Option, 26 | 27 | /// Sections that were written to the output. Indexes correspond to the sections in the input 28 | /// file. Contains None for sections that were discarded or weren't fully copied. 29 | pub sections: Vec>, 30 | } 31 | 32 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] 33 | pub struct ArchiveEntryInfo { 34 | /// The range within the file that contains the archive entry (not including the entry header). 35 | pub range: Range, 36 | 37 | pub identifier: Vec, 38 | } 39 | 40 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 41 | pub struct Section { 42 | pub mem_range: Range, 43 | } 44 | 45 | impl Layout { 46 | pub fn write(&self, writer: &mut impl Write) -> Result<()> { 47 | postcard::to_io(self, writer)?; 48 | Ok(()) 49 | } 50 | 51 | pub fn to_bytes(&self) -> Result> { 52 | Ok(postcard::to_stdvec(self)?) 53 | } 54 | 55 | pub fn from_bytes(bytes: &[u8]) -> Result { 56 | postcard::from_bytes(bytes).context("Invalid linker layout") 57 | } 58 | } 59 | 60 | #[must_use] 61 | pub fn layout_path(base_path: &Path) -> PathBuf { 62 | // We always want to append, not use with_extension, since we don't want to remove any existing 63 | // extension, otherwise we'd likely get collisions. 64 | let mut s = base_path.as_os_str().to_owned(); 65 | s.push(".layout"); 66 | PathBuf::from(s) 67 | } 68 | 69 | impl std::fmt::Display for InputFile { 70 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | self.path.display().fmt(f)?; 72 | if let Some(e) = self.archive_entry.as_ref() { 73 | write!(f, " @ {}", String::from_utf8_lossy(&e.identifier))?; 74 | } 75 | Ok(()) 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use super::*; 82 | 83 | #[test] 84 | fn test_round_trip() { 85 | let layout = Layout { 86 | files: vec![InputFile { 87 | path: PathBuf::new(), 88 | archive_entry: None, 89 | sections: vec![Some(Section { mem_range: 42..48 })], 90 | }], 91 | }; 92 | let bytes = layout.to_bytes().unwrap(); 93 | let layout2 = Layout::from_bytes(&bytes).unwrap(); 94 | assert_eq!(layout, layout2); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /linker-trace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linker-trace" 3 | description = "Data structures for storing trace outputs from the Wild linker" 4 | version.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | edition.workspace = true 9 | 10 | [dependencies] 11 | anyhow = { workspace = true } 12 | postcard = { workspace = true } 13 | serde = { workspace = true } 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /linker-trace/README.md: -------------------------------------------------------------------------------- 1 | # linker-trace 2 | 3 | This crate provides data structures for storing trace information from the [Wild 4 | linker](https://github.com/davidlattimore/wild). This trace information can then be consumed by 5 | linker-diff. 6 | -------------------------------------------------------------------------------- /linker-trace/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate defines a format for storing debug traces associated with particular addresses in the 2 | //! linker output. 3 | 4 | use anyhow::Context; 5 | use anyhow::Result; 6 | use serde::Deserialize; 7 | use serde::Serialize; 8 | use std::io::Write; 9 | use std::ops::Range; 10 | use std::path::Path; 11 | use std::path::PathBuf; 12 | 13 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default)] 14 | pub struct TraceData { 15 | pub traces: Vec, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 19 | pub struct AddressTrace { 20 | pub address: u64, 21 | pub messages: Vec, 22 | } 23 | 24 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 25 | pub struct Section { 26 | pub mem_range: Range, 27 | } 28 | 29 | impl TraceData { 30 | pub fn write(&self, writer: &mut impl Write) -> Result<()> { 31 | //postcard::to_io(self, writer)?; 32 | let r = postcard::to_io(self, writer); 33 | let r: Result<()> = r.map(|_| ()).map_err(|e| e.into()); 34 | r?; 35 | Ok(()) 36 | } 37 | 38 | pub fn to_bytes(&self) -> Result> { 39 | Ok(postcard::to_stdvec(self)?) 40 | } 41 | 42 | pub fn from_bytes(bytes: &[u8]) -> Result { 43 | postcard::from_bytes(bytes).context("Invalid linker trace") 44 | } 45 | } 46 | 47 | #[must_use] 48 | pub fn trace_path(base_path: &Path) -> PathBuf { 49 | let mut new_extension = base_path.extension().unwrap_or_default().to_owned(); 50 | new_extension.push(".trace"); 51 | base_path.with_extension(new_extension) 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn test_round_trip() { 60 | let layout = TraceData { 61 | traces: vec![AddressTrace { 62 | address: 100, 63 | messages: vec!["Test".to_owned()], 64 | }], 65 | }; 66 | let bytes = layout.to_bytes().unwrap(); 67 | let layout2 = TraceData::from_bytes(&bytes).unwrap(); 68 | assert_eq!(layout, layout2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /linker-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linker-utils" 3 | description = "Code shared by libwild and linker-diff" 4 | version.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | edition.workspace = true 9 | 10 | [dependencies] 11 | anyhow = { workspace = true } 12 | object = { workspace = true } 13 | leb128 = { workspace = true } 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /linker-utils/README.md: -------------------------------------------------------------------------------- 1 | # linker-utils 2 | 3 | This crate provides common code shared between libwild and linker-diff. It's unlikely to be useful 4 | for much else. 5 | -------------------------------------------------------------------------------- /linker-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod aarch64; 2 | pub mod elf; 3 | pub mod relaxation; 4 | pub mod riscv64; 5 | pub mod utils; 6 | pub mod x86_64; 7 | -------------------------------------------------------------------------------- /linker-utils/src/relaxation.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 2 | pub enum RelocationModifier { 3 | Normal, 4 | SkipNextRelocation, 5 | } 6 | -------------------------------------------------------------------------------- /linker-utils/src/utils.rs: -------------------------------------------------------------------------------- 1 | // Return u32 from a byte slice 2 | #[must_use] 3 | pub fn u32_from_slice(data: &[u8]) -> u32 { 4 | u32::from_le_bytes(*data.first_chunk::<4>().unwrap()) 5 | } 6 | 7 | // Copy `mask` slice into the `dest` slice using OR operation. 8 | pub fn or_from_slice(dest: &mut [u8], mask_bytes: &[u8]) { 9 | for (i, v) in mask_bytes.iter().enumerate() { 10 | dest[i] |= *v; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | use_field_init_shorthand = true 3 | imports_granularity = "Item" 4 | group_imports = "One" 5 | -------------------------------------------------------------------------------- /test-config-ci.toml: -------------------------------------------------------------------------------- 1 | rustc_channel = "nightly" 2 | allow_rust_musl_target = true 3 | -------------------------------------------------------------------------------- /test-config.toml.sample: -------------------------------------------------------------------------------- 1 | # Setting to nightly enables a few more tests, e.g. cranelift backend 2 | rustc_channel = "default" # `"stable"`, `"beta"`, `"nightly"` or "default" 3 | qemu_arch = [] # `["X86_64"]`, `["AArch64"]`, `["RISCV64"]` 4 | allow_rust_musl_target = false # `true` or `false` 5 | diff_ignore = [] # ["rule1, "rule2"] 6 | -------------------------------------------------------------------------------- /wild/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wild-linker" 3 | description = "A very fast linker for Linux" 4 | version.workspace = true 5 | license.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | edition.workspace = true 9 | 10 | [[bin]] 11 | name = "wild" 12 | path = "src/main.rs" 13 | 14 | [dependencies] 15 | libwild = { path = "../libwild", version = "0.5.0" } 16 | 17 | # This is off by default, since it doesn't appear to help. However, if you're linking against musl 18 | # libc, which has a comparatively slow allocator, then enabling this does help. To enable this, 19 | # build with `--features mimalloc`. 20 | mimalloc = { workspace = true, optional = true } 21 | 22 | dhat = { workspace = true, optional = true } 23 | 24 | [dev-dependencies] 25 | anyhow = { workspace = true } 26 | fd-lock = { workspace = true } 27 | itertools = { workspace = true } 28 | libc = { workspace = true } 29 | linker-diff = { path = "../linker-diff" } 30 | object = { workspace = true } 31 | os_info = { workspace = true } 32 | rstest = { workspace = true } 33 | serde = { workspace = true } 34 | strum = { workspace = true } 35 | strum_macros = { workspace = true } 36 | toml = { workspace = true } 37 | wait-timeout = { workspace = true } 38 | which = { workspace = true } 39 | 40 | [features] 41 | default = ["fork"] 42 | 43 | fork = ["libwild/fork"] 44 | -------------------------------------------------------------------------------- /wild/README.md: -------------------------------------------------------------------------------- 1 | # Wild linker 2 | 3 | This crate provides the binary for the wild linker. The binary is named "wild". For more details, 4 | see the [Wild linker repository](https://github.com/davidlattimore/wild). 5 | -------------------------------------------------------------------------------- /wild/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "mimalloc")] 2 | #[global_allocator] 3 | static MIMALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; 4 | 5 | #[cfg(feature = "dhat")] 6 | #[global_allocator] 7 | static ALLOC: dhat::Alloc = dhat::Alloc; 8 | 9 | fn main() -> libwild::error::Result { 10 | #[cfg(feature = "dhat")] 11 | let _profiler = dhat::Profiler::new_heap(); 12 | 13 | let args = libwild::Args::parse(|| std::env::args().skip(1))?; 14 | 15 | if args.should_fork() { 16 | // Safety: We haven't spawned any threads yet. 17 | unsafe { libwild::run_in_subprocess(&args) }; 18 | } else { 19 | // Run the linker in this process without forking. 20 | libwild::run(&args) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /wild/tests/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | target/ 3 | 4 | -------------------------------------------------------------------------------- /wild/tests/sources/archive_activation.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#CompArgs:-ffunction-sections 3 | //#DiffIgnore:section.relro_padding 4 | //#EnableLinker:lld 5 | 6 | //#Config:regular:default 7 | //#Archive:archive_activation0.c 8 | //#Archive:archive_activation1.c 9 | //#Archive:runtime.c 10 | //#Archive:empty.a 11 | 12 | //#Config:thin:default 13 | //#ThinArchive:archive_activation0.c 14 | //#ThinArchive:archive_activation1.c 15 | //#ThinArchive:runtime.c 16 | //#ThinArchive:empty.a 17 | 18 | //#Config:lib:default 19 | // GNU ld doesn't yet support --start-lib 20 | //#SkipLinker:ld 21 | //#Cross:false 22 | //#LinkArgs:--start-lib 23 | //#Object:archive_activation0.c 24 | //#Object:archive_activation1.c 25 | //#Object:runtime.c 26 | //#Object:empty.a 27 | //#DiffIgnore:segment.GNU_STACK.alignment 28 | 29 | #include "runtime.h" 30 | 31 | int bar(void); 32 | int does_not_exist(void); 33 | 34 | __attribute__ ((weak)) int is_archive0_loaded() { 35 | return 0; 36 | } 37 | 38 | __attribute__ ((weak)) int is_archive1_loaded() { 39 | return 0; 40 | } 41 | 42 | void _start(void) { 43 | runtime_init(); 44 | 45 | if (!is_archive0_loaded()) { 46 | exit_syscall(101); 47 | } 48 | if (is_archive1_loaded()) { 49 | exit_syscall(102); 50 | } 51 | exit_syscall(42); 52 | } 53 | 54 | // The following function is dead code. It's not referenced from anywhere and will be GCed when we 55 | // link. However its presence, or rather the reference that it contains to the function `bar` causes 56 | // the archive member containing `bar` to be activated, which causes an alternate version of 57 | // `is_archive_loaded` to be used, one which returns 1 rather than 0. 58 | void load_bar(void) { 59 | bar(); 60 | 61 | // While we're here, make sure that we can reference a function that isn't defined anywhere and 62 | // not fail to link, since this code gets GCed. 63 | does_not_exist(); 64 | } 65 | -------------------------------------------------------------------------------- /wild/tests/sources/archive_activation0.c: -------------------------------------------------------------------------------- 1 | __attribute__ ((weak)) int bar(void) { 2 | return 6; 3 | } 4 | 5 | int is_archive0_loaded(void) { 6 | return 1; 7 | } 8 | -------------------------------------------------------------------------------- /wild/tests/sources/archive_activation1.c: -------------------------------------------------------------------------------- 1 | __attribute__ ((weak)) int bar(void) { 2 | return 6; 3 | } 4 | 5 | int is_archive1_loaded(void) { 6 | return 1; 7 | } 8 | -------------------------------------------------------------------------------- /wild/tests/sources/basic-comdat-1.s: -------------------------------------------------------------------------------- 1 | .section .data.foo1,"awG",@progbits,foobar,comdat 2 | .globl foo1 3 | .type foo1, @object 4 | .size foo1, 4 5 | foo1: 6 | .long 42 7 | 8 | .section .data.aaa1,"awG",@progbits,abc,comdat 9 | .globl aaa1 10 | .type aaa1, @object 11 | .size aaa1, 4 12 | aaa1: 13 | .long 42 14 | -------------------------------------------------------------------------------- /wild/tests/sources/basic-comdat.s: -------------------------------------------------------------------------------- 1 | /* For some reason, GAS on riscv64 does not support '//' comments. 2 | //#Object:basic-comdat-1.s 3 | //#RunEnabled:false 4 | //#Static:false 5 | //#LinkArgs:-shared -z now 6 | //#DiffIgnore:section.got 7 | //#DiffIgnore:segment.GNU_STACK.alignment 8 | //#DiffIgnore:segment.GNU_STACK.flags 9 | */ 10 | 11 | .section .data.foo1,"awG",@progbits,foobar,comdat 12 | .globl foo1 13 | .type foo1, @object 14 | .size foo1, 4 15 | foo1: 16 | .long 42 17 | 18 | .section .data.foo2,"awG",@progbits,foobar,comdat 19 | .globl foo2 20 | .type foo2, @object 21 | .size foo2, 4 22 | foo2: 23 | .long 42 24 | 25 | .section .data.aaa1,"awG",@progbits,abc,comdat 26 | .globl aaa1 27 | .type aaa1, @object 28 | .size aaa1, 4 29 | aaa1: 30 | .long 42 31 | -------------------------------------------------------------------------------- /wild/tests/sources/comments.c: -------------------------------------------------------------------------------- 1 | //#Object:comments0.c 2 | //#Object:comments1.c 3 | //#Object:runtime.c 4 | 5 | #include "runtime.h" 6 | 7 | int v0(void); 8 | int v1(void); 9 | 10 | void _start(void) { 11 | runtime_init(); 12 | 13 | // References functions are here just to make sure we're using a symbol for each of our files, 14 | // otherwise the .comment section doesn't get used. 15 | if (v0() != 4) { 16 | exit_syscall(100); 17 | } 18 | if (v1() != 5) { 19 | exit_syscall(101); 20 | } 21 | exit_syscall(42); 22 | } 23 | 24 | //#ExpectComment:GCC* 25 | //#ExpectComment:Foo 26 | //#ExpectComment:Bar 27 | -------------------------------------------------------------------------------- /wild/tests/sources/comments0.c: -------------------------------------------------------------------------------- 1 | #ident "Foo" 2 | 3 | int v0(void) { 4 | return 4; 5 | } 6 | -------------------------------------------------------------------------------- /wild/tests/sources/comments1.c: -------------------------------------------------------------------------------- 1 | #ident "Foo" 2 | #ident "Bar" 3 | 4 | int v1(void) { 5 | return 5; 6 | } 7 | -------------------------------------------------------------------------------- /wild/tests/sources/common_section.c: -------------------------------------------------------------------------------- 1 | //#CompArgs:-fcommon 2 | //#Object:common_section0.c 3 | //#Object:common_section1.c 4 | //#Object:runtime.c 5 | //#EnableLinker:lld 6 | 7 | #include "runtime.h" 8 | 9 | int a; 10 | extern int data[]; 11 | extern int q[]; 12 | extern int z[]; 13 | 14 | void _start(void) { 15 | runtime_init(); 16 | 17 | a = 30; 18 | q[0] = 20; 19 | z[0] = 40; 20 | // We have two declarations of `data`. One has size 10, the other 1000. The linker should choose 21 | // the one with the larger size. 22 | for (int i = 0; i < 1000; i++) { 23 | data[i] = 6; 24 | } 25 | // Try to detect if we've overflowed the space allocated to data. It's luck whether the linker 26 | // decides to put any of our canary variables after `data`, but if we have enough of them, then 27 | // there's a reasonable chance. 28 | if (a != 30 || q[0] != 20 || z[0] != 40) { 29 | exit_syscall(101); 30 | } 31 | data[100] = 10; 32 | exit_syscall(42); 33 | } 34 | 35 | //#ExpectSym: a .bss 36 | //#ExpectSym: data .bss 37 | //#ExpectSym: q .bss 38 | //#ExpectSym: z .bss 39 | -------------------------------------------------------------------------------- /wild/tests/sources/common_section0.c: -------------------------------------------------------------------------------- 1 | extern int data[]; 2 | int data[10]; 3 | 4 | // Not referenced. 5 | extern int data2[]; 6 | int data2[10]; 7 | -------------------------------------------------------------------------------- /wild/tests/sources/common_section1.c: -------------------------------------------------------------------------------- 1 | int q[10]; 2 | extern int data[]; 3 | int data[1000]; 4 | int z[10]; 5 | -------------------------------------------------------------------------------- /wild/tests/sources/copy-relocations-2.c: -------------------------------------------------------------------------------- 1 | int s1 = 1; 2 | 3 | // Because this alias is a weak symbol, any copy relocations produced by references to w1 should 4 | // instead locate the strong symbol `s1` that is at the same address and emit a copy relocation for 5 | // that instead. 6 | __attribute__ ((weak, alias("s1"))) extern int w1; 7 | 8 | int get_w1(void) { 9 | return w1; 10 | } 11 | 12 | int get_s1(void) { 13 | return s1; 14 | } 15 | 16 | // Repeat the same scenario twice more. These are effectively identical in this file. The 17 | // differences are in how they are referenced in the main file. 18 | 19 | int s2 = 2; 20 | 21 | __attribute__ ((weak, alias("s2"))) extern int w2; 22 | 23 | int get_s2(void) { 24 | return s2; 25 | } 26 | int get_w2(void) { 27 | return w2; 28 | } 29 | 30 | int s3 = 3; 31 | 32 | __attribute__ ((weak, alias("s3"))) extern int w3; 33 | 34 | int get_s3(void) { 35 | return s3; 36 | } 37 | int get_w3(void) { 38 | return w3; 39 | } 40 | -------------------------------------------------------------------------------- /wild/tests/sources/copy-relocations-3.c: -------------------------------------------------------------------------------- 1 | extern int s1; 2 | 3 | int get_s1_pic(void) { 4 | return s1; 5 | } 6 | -------------------------------------------------------------------------------- /wild/tests/sources/copy-relocations.c: -------------------------------------------------------------------------------- 1 | //#Object:runtime.c 2 | //#EnableLinker:lld 3 | //#Static:false 4 | //#CompSoArgs:-fPIC 5 | //#LinkArgs:-z now 6 | //#Shared:copy-relocations-2.c 7 | //#Object:copy-relocations-3.c:-fPIC 8 | // We're linking different .so files, so this is expected. 9 | //#DiffIgnore:.dynamic.DT_NEEDED 10 | //#DiffIgnore:dynsym.w2.section 11 | 12 | #include "runtime.h" 13 | 14 | // These two symbols are at the same address in the shared object, so references to both should 15 | // point to the same copy relocation and that location should be what `get_w` returns. 16 | extern int w1; 17 | extern int s1; 18 | int get_w1(void); 19 | int get_s1(void); 20 | 21 | // This time we only reference the non-weak symbol. 22 | extern int s2; 23 | int get_s2(void); 24 | int get_w2(void); 25 | 26 | // Lastly, we reference the weak symbol and not the strong one. 27 | extern int w3; 28 | int get_s3(void); 29 | int get_w3(void); 30 | 31 | // This is defined in a separate object file that is compiled with -fPIC. 32 | int get_s1_pic(void); 33 | 34 | void _start(void) { 35 | runtime_init(); 36 | 37 | // Reference both the weak and the strong versions of the symbol. 38 | w1 = 10; 39 | if (get_w1() != 10) { 40 | exit_syscall(20); 41 | } 42 | if (get_s1() != 10) { 43 | exit_syscall(21); 44 | } 45 | if (get_s1_pic() != 10) { 46 | exit_syscall(22); 47 | } 48 | s1 = 11; 49 | if (get_w1() != 11) { 50 | exit_syscall(30); 51 | } 52 | if (get_s1() != 11) { 53 | exit_syscall(31); 54 | } 55 | if (get_s1_pic() != 11) { 56 | exit_syscall(32); 57 | } 58 | 59 | // Strong only. Note, we don't check get_w2 since linker behaviour differs in this case. GNU ld 60 | // doesn't export the weak alias, lld and Wild do. 61 | s2 = 12; 62 | if (get_s2() != 12) { 63 | exit_syscall(40); 64 | } 65 | 66 | // Weak only. 67 | w3 = 13; 68 | if (get_w3() != 13) { 69 | exit_syscall(50); 70 | } 71 | if (get_s3() != 13) { 72 | exit_syscall(51); 73 | } 74 | 75 | exit_syscall(42); 76 | } 77 | -------------------------------------------------------------------------------- /wild/tests/sources/cpp-integration-2.cc: -------------------------------------------------------------------------------- 1 | const char* colon() { 2 | return ":"; 3 | } 4 | 5 | const char* char_a() { 6 | return "a"; 7 | } 8 | 9 | const char* char_b() { 10 | return "b"; 11 | } 12 | 13 | const char* char_c() { 14 | return "c"; 15 | } 16 | 17 | const char* char_d() { 18 | return "d"; 19 | } 20 | -------------------------------------------------------------------------------- /wild/tests/sources/cpp-integration.cc: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#DiffIgnore:section.rodata 3 | //#DiffIgnore:section.data 4 | //#DiffIgnore:section.sdata 5 | //#DiffIgnore:section.rodata.alignment 6 | //#DiffIgnore:section.bss.alignment 7 | // On aarch64, GNU ld puts the copy relocation for this symbol in .data.rel.ro rather than .bss. 8 | //#DiffIgnore:dynsym.__stack_chk_guard.section 9 | //#Object:cpp-integration-2.cc 10 | 11 | //#Config:pie:default 12 | //#CompArgs:-fpie -fmerge-constants 13 | //#LinkerDriver:g++ 14 | //#LinkArgs:-pie -Wl,-z,now 15 | //#EnableLinker:lld 16 | 17 | //#Config:no-pie:default 18 | //#CompArgs:-fno-pie -fmerge-constants 19 | //#LinkerDriver:g++ 20 | //#LinkArgs:-no-pie -Wl,-z,now 21 | //#EnableLinker:lld 22 | 23 | //#Config:clang-pie:default 24 | //#CompArgs:-fpie 25 | //#Compiler:clang 26 | //#LinkerDriver:clang++ 27 | //#LinkArgs:-pie -Wl,-z,now 28 | //#EnableLinker:lld 29 | 30 | //#Config:model-large:default 31 | //#CompArgs:-mcmodel=large 32 | //#LinkerDriver:g++ 33 | //#LinkArgs:-Wl,-z,now 34 | //#EnableLinker:lld 35 | // TODO: Ubuntu: cc1plus: sorry, unimplemented: code model 'large' with '-fPIC' 36 | //#Arch: x86_64 37 | 38 | //#Config:clang-model-large:default 39 | //#Compiler:clang 40 | //#CompArgs:-mcmodel=large 41 | //#LinkerDriver:clang++ 42 | //#LinkArgs:-Wl,-z,now 43 | //#EnableLinker:lld 44 | //#Arch: x86_64 45 | 46 | #include 47 | #include 48 | 49 | const char* colon(); 50 | const char* char_c(); 51 | const char* char_d(); 52 | 53 | int main() { 54 | std::string foo; 55 | foo += "aaa"; 56 | foo += colon(); 57 | foo += "b"; 58 | foo += ":"; 59 | foo += char_c(); 60 | foo += ":"; 61 | foo += "d"; 62 | if (foo != "aaa:b:c:d") { 63 | std::cout << foo << std::endl; 64 | return 10; 65 | } 66 | return 42; 67 | } 68 | -------------------------------------------------------------------------------- /wild/tests/sources/custom_section.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:runtime.c 3 | 4 | //#Config:archive:default 5 | //#Archive:custom_section0.c 6 | 7 | //#Config:object:default 8 | //#Object:custom_section0.c 9 | 10 | #include "runtime.h" 11 | 12 | static int foo1 __attribute__ ((used, section ("foo"))) = 2; 13 | static int foo2 __attribute__ ((used, section ("foo"))) = 5; 14 | 15 | static int w1a __attribute__ ((used, section ("w1"))) = 88; 16 | static int w3a __attribute__ ((used, section ("w3"))) = 88; 17 | 18 | extern int __start_foo[]; 19 | extern int __stop_foo[]; 20 | 21 | // The `bar` section is only defined in our other file. 22 | extern int __start_bar[]; 23 | extern int __stop_bar[]; 24 | 25 | extern int __start_w1[] __attribute__ ((weak)); 26 | extern int __stop_w1[] __attribute__ ((weak)); 27 | extern int __start_w2[] __attribute__ ((weak)); 28 | extern int __stop_w2[] __attribute__ ((weak)); 29 | 30 | static int dot1 __attribute__ ((used, section (".dot"))) = 7; 31 | static int dot2 __attribute__ ((used, section (".dot.2"))) = 8; 32 | 33 | // Make sure we don't discard this custom, alloc section just because of its name. 34 | static int debug_script __attribute__ ((section (".debug_script"))) = 15; 35 | 36 | // Override a symbol that would normally be created by the custom section. 37 | int __stop_w3 = 88; 38 | 39 | // Not really custom-section related, but also override a symbol that's normally defined by a 40 | // built-in section. 41 | int __init_array_start = 89; 42 | 43 | int fn1(void); 44 | void set_foo1(int value); 45 | int get_foo1(void); 46 | int h1(); 47 | int h2(int x); 48 | 49 | void _start(void) { 50 | runtime_init(); 51 | 52 | int value = fn1(); 53 | for (int *foo = __start_foo; foo < __stop_foo; foo++) { 54 | value += *foo; 55 | } 56 | for (int *bar = __start_bar; bar < __stop_bar; bar++) { 57 | value += *bar; 58 | } 59 | if (__start_w2 || __stop_w2) { 60 | exit_syscall(100); 61 | } 62 | if (__start_w1 == __stop_w1) { 63 | exit_syscall(101); 64 | } 65 | if (__start_w1[0] != 88) { 66 | exit_syscall(102); 67 | } 68 | if (h1() != 6) { 69 | exit_syscall(103); 70 | } 71 | if (h2(2) != 8) { 72 | exit_syscall(104); 73 | } 74 | if (__stop_w3 != 88) { 75 | exit_syscall(105); 76 | } 77 | if (__init_array_start != 89) { 78 | exit_syscall(106); 79 | } 80 | if (dot1 != 7) { 81 | exit_syscall(107); 82 | } 83 | if (dot2 != 8) { 84 | exit_syscall(108); 85 | } 86 | // Verify that we can write to a custom section. 87 | set_foo1(10); 88 | if (get_foo1() != 10) { 89 | exit_syscall(109); 90 | } 91 | 92 | if (debug_script != 15) { 93 | exit_syscall(110); 94 | } 95 | 96 | exit_syscall(value); 97 | } 98 | 99 | //#ExpectSym: dot1 .dot 100 | //#ExpectSym: dot2 .dot.2 101 | -------------------------------------------------------------------------------- /wild/tests/sources/custom_section0.c: -------------------------------------------------------------------------------- 1 | static int foo1 __attribute__ ((used, section ("foo"))) = 1; 2 | static int foo2 __attribute__ ((used, section ("foo"))) = 20; 3 | static int foo3 __attribute__ ((used, section ("foo"))) = 5; 4 | 5 | static int bar1 __attribute__ ((used, section ("bar"))) = 7; 6 | 7 | int fn1(void) { 8 | return 2; 9 | } 10 | 11 | int __attribute__ ((section ("hot"))) h1() { 12 | return 6; 13 | } 14 | 15 | int __attribute__ ((section ("hot"))) h2(int x) { 16 | return 6 + x; 17 | } 18 | 19 | void set_foo1(int value) { 20 | foo1 = value; 21 | } 22 | 23 | int get_foo1(void) { 24 | return foo1; 25 | } 26 | -------------------------------------------------------------------------------- /wild/tests/sources/data-pointers-2.c: -------------------------------------------------------------------------------- 1 | int foo[8] = {0, 1, 2, 3, 4, 5, 6, 7}; 2 | int bar[8] = {0x0, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70}; 3 | 4 | int check_pointers(int **p) { 5 | if (*p[0] != 2) { 6 | return *p[0]; 7 | } 8 | 9 | if (*p[1] != 0x60) { 10 | return *p[1]; 11 | } 12 | 13 | return 42; 14 | } 15 | -------------------------------------------------------------------------------- /wild/tests/sources/data-pointers.c: -------------------------------------------------------------------------------- 1 | //#Shared:runtime.c 2 | //#EnableLinker:lld 3 | //#Static:false 4 | //#LinkArgs:-z now 5 | //#Shared:data-pointers-2.c 6 | //#EnableLinker:lld 7 | // We're linking different .so files, so this is expected. 8 | //#DiffIgnore:.dynamic.DT_NEEDED 9 | // GNU ld emits a .got section for the shared object, despite it not being necessary. 10 | //#DiffIgnore:section.got 11 | 12 | #include "runtime.h" 13 | 14 | extern int foo[8]; 15 | extern int bar[8]; 16 | 17 | // Since `foo` and `bar` come from a shared object, this should result in a couple of runtime 18 | // relocations in our data section. We have non-zero offsets relative to these symbols in order to 19 | // make sure addends work as expected. 20 | int *pointers[2] = {&foo[2], &bar[6]}; 21 | 22 | int check_pointers(int **p); 23 | 24 | void _start(void) { 25 | runtime_init(); 26 | 27 | exit_syscall(check_pointers(pointers)); 28 | } 29 | -------------------------------------------------------------------------------- /wild/tests/sources/data.c: -------------------------------------------------------------------------------- 1 | //#Object:runtime.c 2 | //#EnableLinker:lld 3 | 4 | #include "runtime.h" 5 | #include 6 | 7 | static char data1[] = "QQQ"; 8 | 9 | // Specify an alignment that is larger than the size of the data we're putting in the section. 10 | __attribute__ ((aligned (64))) 11 | static char data2[] = "abcdefghijklmnopqrstuvwxyz"; 12 | 13 | void _start(void) { 14 | runtime_init(); 15 | 16 | if (data1[0] != 'Q') { 17 | exit_syscall(1); 18 | } 19 | if (((size_t)data2 & 63) != 0) { 20 | exit_syscall(2); 21 | } 22 | if (data2[0] != 'a' || data2[sizeof(data2) - 2] != 'z') { 23 | exit_syscall(3); 24 | } 25 | exit_syscall(42); 26 | } 27 | -------------------------------------------------------------------------------- /wild/tests/sources/duplicate_strong_symbols.c: -------------------------------------------------------------------------------- 1 | //#Config:dup 2 | //#SkipLinker:ld 3 | //#Object:duplicate_strong_symbols2.c 4 | //#ExpectError:Duplicate symbols 5 | 6 | int test_func(void) { 7 | return 0; 8 | } 9 | 10 | void _start(void) { 11 | test_func(); 12 | } 13 | -------------------------------------------------------------------------------- /wild/tests/sources/duplicate_strong_symbols2.c: -------------------------------------------------------------------------------- 1 | int test_func(void) { 2 | return 1; 3 | } 4 | -------------------------------------------------------------------------------- /wild/tests/sources/dynamic-bss-only.c: -------------------------------------------------------------------------------- 1 | // This test sets up the scenario where we have TBSS, but not TDATA. We then have a TLSGD relocation 2 | // for a local TLS variable in TBSS. We hope to verify that the TLSGD entry gets the correct offset. 3 | 4 | //#CompArgs:-fPIC -ftls-model=global-dynamic 5 | //#LinkArgs:-shared -z now 6 | //#RunEnabled:false 7 | //#DiffIgnore:.dynamic.DT_RELAENT 8 | //#DiffIgnore:.dynamic.DT_RELA 9 | 10 | // We use a large alignment here so that it's almost certain that padding will need to be added 11 | // before our TLS segment, which could cause us to compute incorrect offsets if we used the address 12 | // of the non-existent TDATA section as the start of TLS. 13 | __attribute__ ((aligned(256))) 14 | static __thread long int tvar1; 15 | 16 | long int get_tvar1(void) { 17 | return tvar1; 18 | } 19 | 20 | void set_tvar1(long int value) { 21 | tvar1 = value; 22 | } 23 | -------------------------------------------------------------------------------- /wild/tests/sources/eh_frame.c: -------------------------------------------------------------------------------- 1 | //#Object:eh_frame_end.c 2 | //#Object:runtime.c 3 | //#DiffIgnore: segment.LOAD.RW.alignment 4 | 5 | #include 6 | 7 | #include "runtime.h" 8 | 9 | typedef uint32_t u32; 10 | 11 | struct EhFrameEntry { 12 | u32 length; 13 | u32 cie_ptr; 14 | }; 15 | 16 | static char EH_FRAME_START[] __attribute__((section(".eh_frame"), aligned(__alignof__ (void *)))) = {}; 17 | 18 | extern char EH_FRAME_END[]; 19 | 20 | void _start(void) { 21 | runtime_init(); 22 | 23 | const struct EhFrameEntry* frame1 = (struct EhFrameEntry*) EH_FRAME_START; 24 | 25 | // The first entry should be a CIE. Its length should be non-zero. 26 | if (frame1->length == 0) { 27 | exit_syscall(101); 28 | } 29 | // Since it's a CIE, its cie_ptr should be zero. 30 | if (frame1->cie_ptr != 0) { 31 | exit_syscall(102); 32 | } 33 | 34 | const struct EhFrameEntry* frame2 = (struct EhFrameEntry*) (EH_FRAME_START + frame1->length + 4); 35 | // The second entry should be an FDE. Its length should be non-zero. 36 | if (frame2->length == 0) { 37 | exit_syscall(103); 38 | } 39 | // Its CIE pointer should point back to the start of the first entry. This pointer is relative 40 | // to the position of the pointer, so we need to add the offset of the pointer within the entry 41 | // (4). The length field doesn't include the size of the length (4 bytes), so we need to add 42 | // that too. 43 | if (frame2->cie_ptr != frame1->length + 8) { 44 | exit_syscall(104); 45 | } 46 | 47 | int eh_frame_len = EH_FRAME_END - EH_FRAME_START; 48 | 49 | if (eh_frame_len == 0) { 50 | exit_syscall(105); 51 | } 52 | 53 | // Make sure that all memory between start and end is valid to read. 54 | int total = 0; 55 | for (const char* c = EH_FRAME_START; c < EH_FRAME_END; c++) { 56 | total += *c; 57 | } 58 | if (total == 0) { 59 | exit_syscall(106); 60 | } 61 | 62 | exit_syscall(42); 63 | } 64 | -------------------------------------------------------------------------------- /wild/tests/sources/eh_frame_end.c: -------------------------------------------------------------------------------- 1 | char EH_FRAME_END[] __attribute__((section(".eh_frame"), aligned(__alignof__ (void *)))) = {}; 2 | -------------------------------------------------------------------------------- /wild/tests/sources/empty.a: -------------------------------------------------------------------------------- 1 | ! 2 | -------------------------------------------------------------------------------- /wild/tests/sources/entry_arg.c: -------------------------------------------------------------------------------- 1 | //#Object:runtime.c 2 | //#LinkArgs:--entry=custom_entry 3 | 4 | #include "runtime.h" 5 | 6 | void custom_entry(void) { 7 | runtime_init(); 8 | exit_syscall(42); 9 | } 10 | -------------------------------------------------------------------------------- /wild/tests/sources/exception.cc: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#LinkArgs:-Wl,-z,now 3 | //#DiffIgnore:section.rodata 4 | //#DiffIgnore:section.data 5 | //#DiffIgnore:dynsym._ZTIi.section 6 | // TODO: Fix this. Note, it only shows up on openSUSE aarch64 7 | //#DiffIgnore:rel.missing-copy-relocation.R_AARCH64_ABS64 8 | 9 | //#Config:gcc:default 10 | //#LinkerDriver:g++ 11 | 12 | //#Config:clang:default 13 | //#Compiler:clang 14 | //#LinkerDriver:clang++ 15 | 16 | #include 17 | 18 | void bar() 19 | { 20 | throw 42; 21 | } 22 | 23 | void foo() 24 | { 25 | bar(); 26 | } 27 | 28 | int main() 29 | { 30 | try 31 | { 32 | foo(); 33 | } 34 | catch (int myNum) 35 | { 36 | std::cout << myNum << std::endl; 37 | return myNum; 38 | } 39 | 40 | return 1; 41 | } 42 | -------------------------------------------------------------------------------- /wild/tests/sources/exclude-libs-archive.c: -------------------------------------------------------------------------------- 1 | int foo(void) { 2 | return 10; 3 | } 4 | -------------------------------------------------------------------------------- /wild/tests/sources/exclude-libs.c: -------------------------------------------------------------------------------- 1 | //#LinkArgs:-z now -Bshareable --exclude-libs ALL 2 | //#Static:false 3 | //#Archive:exclude-libs-archive.c 4 | //#RunEnabled:false 5 | // We optimise away the GOT, but GNU ld doesn't. 6 | //#DiffIgnore:section.got 7 | 8 | // This symbol shouldn't end up in .dynsym. linker-diff checks this. 9 | int foo(void); 10 | 11 | int call_foo(void) { 12 | // This reference to foo should be optimised by the linker, since the symbol is made hidden, so 13 | // we know it cannot be overridden. 14 | return foo() + 2; 15 | } 16 | -------------------------------------------------------------------------------- /wild/tests/sources/exit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if defined(__x86_64__) 5 | void exit_syscall(int exit_code) { 6 | register int64_t rax __asm__ ("rax") = 60; 7 | register int rdi __asm__ ("rdi") = exit_code; 8 | __asm__ __volatile__ ( 9 | "syscall" 10 | : "+r" (rax) 11 | : "r" (rdi) 12 | : "rcx", "r11", "memory" 13 | ); 14 | } 15 | #elif defined(__aarch64__) 16 | void exit_syscall(int exit_code) { 17 | register long w8 __asm__("w8") = 93; 18 | register long x0 __asm__("x0") = exit_code; 19 | __asm__ __volatile__( 20 | "svc 0" 21 | : "=r"(x0) 22 | : "r"(w8) 23 | : "cc", "memory"); 24 | } 25 | #elif defined(__riscv) 26 | void exit_syscall(int exit_code) { 27 | register long a7 __asm__("a7") = 93; 28 | register long a0 __asm__("a0") = exit_code; 29 | __asm__ __volatile__( 30 | "ecall" 31 | : /* no output */ 32 | : "r"(a7), "r"(a0) 33 | : "memory"); 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /wild/tests/sources/force-undefined.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:runtime.c 3 | 4 | //#Config:undefined:default 5 | //#LinkArgs:--undefined=foo -u bar -ubaz 6 | //#ExpectSym:foo 7 | //#ExpectSym:bar 8 | //#ExpectSym:baz 9 | 10 | // Verify that we can activate an archive entry by listing a symbol it defines as undefined. 11 | //#Config:archive-activation:default 12 | //#Archive:archive_activation0.c 13 | //#CompArgs:-DEXPECT_ARCH0 14 | //#LinkArgs:--undefined=bar 15 | 16 | #include "runtime.h" 17 | 18 | __attribute__ ((weak)) int is_archive0_loaded() { 19 | return 0; 20 | } 21 | 22 | void _start(void) { 23 | runtime_init(); 24 | 25 | #ifdef EXPECT_ARCH0 26 | if (!is_archive0_loaded()){ 27 | exit_syscall(10); 28 | } 29 | #endif 30 | 31 | exit_syscall(42); 32 | } 33 | -------------------------------------------------------------------------------- /wild/tests/sources/global_definitions.c: -------------------------------------------------------------------------------- 1 | //#Object:global_references.c 2 | //#Object:runtime.c 3 | //#EnableLinker:lld 4 | 5 | int global_value = 38; 6 | int global_values[4] = {1, 2, 3, 4}; 7 | 8 | asm( 9 | ".globl abs1\n\ 10 | .set abs1, 25\n"); 11 | -------------------------------------------------------------------------------- /wild/tests/sources/global_definitions.h: -------------------------------------------------------------------------------- 1 | extern int global_value; 2 | extern int global_values[4]; 3 | extern int abs1; 4 | -------------------------------------------------------------------------------- /wild/tests/sources/global_references.c: -------------------------------------------------------------------------------- 1 | #include "runtime.h" 2 | #include "global_definitions.h" 3 | 4 | #include 5 | 6 | // Returns the passed value, but don't let the compiler make any assumptions about the returned 7 | // value. 8 | #if defined(__x86_64__) 9 | int black_box(int input) { 10 | register int rdi __asm__ ("rdi") = input; 11 | __asm__ __volatile__ ( 12 | "nop" 13 | : "+r" (rdi) 14 | ); 15 | return rdi; 16 | } 17 | #elif defined(__aarch64__) 18 | int black_box(int input) { 19 | register int w0 __asm__ ("w0") = input; 20 | __asm__ __volatile__ ( 21 | "nop" 22 | : "+r" (w0) 23 | ); 24 | return w0; 25 | } 26 | #elif defined(__riscv) 27 | int black_box(int input) { 28 | register int a0 __asm__ ("a0") = input; 29 | __asm__ __volatile__ ( 30 | "nop" 31 | : "+r" (a0) 32 | ); 33 | return a0; 34 | } 35 | #endif 36 | 37 | void _start() { 38 | runtime_init(); 39 | 40 | if (global_value != 38) { 41 | exit_syscall(100); 42 | } 43 | if (global_values[3] != 4) { 44 | exit_syscall(101); 45 | } 46 | // Without passing our value through a black box, the compiler gets rid of the if-statement 47 | // below, treating it as always true, since it figures that an integer obtained from a pointer 48 | // can never be equal to 25. 49 | int abs1_value = black_box((size_t)&abs1); 50 | if (abs1_value != 25) { 51 | exit_syscall(abs1_value); 52 | } 53 | exit_syscall(42); 54 | } 55 | -------------------------------------------------------------------------------- /wild/tests/sources/gnu-unique-1.cc: -------------------------------------------------------------------------------- 1 | #include "gnu-unique.h" 2 | 3 | typedef int(*get_int_fn_t)(int); 4 | 5 | extern "C" { 6 | 7 | get_int_fn_t get_fn1(void) { 8 | return get_value; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /wild/tests/sources/gnu-unique-2.cc: -------------------------------------------------------------------------------- 1 | #include "gnu-unique.h" 2 | 3 | typedef int(*get_int_fn_t)(int); 4 | 5 | extern "C" { 6 | 7 | get_int_fn_t get_fn2(void) { 8 | return get_value; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /wild/tests/sources/gnu-unique.c: -------------------------------------------------------------------------------- 1 | //#Object:runtime.c 2 | //#Object:gnu-unique-1.cc 3 | //#Object:gnu-unique-2.cc 4 | 5 | #include "runtime.h" 6 | 7 | typedef int(*get_int_fn_t)(int); 8 | 9 | // Each of these functions instantiates the same template with the same type. The template contains 10 | // a static variable that is incremented each time it's called. GCC will emit this static as 11 | // STB_GNU_UNIQUE in order to ensure that there's only a single instance of it. 12 | get_int_fn_t get_fn1(void); 13 | get_int_fn_t get_fn2(void); 14 | 15 | void _start(void) { 16 | runtime_init(); 17 | 18 | // 10 + 1 == 1 19 | if (get_fn1()(10) != 11) { 20 | exit_syscall(11); 21 | } 22 | 23 | // 10 + 2 = 17 24 | if (get_fn2()(15) != 17) { 25 | exit_syscall(12); 26 | } 27 | 28 | exit_syscall(42); 29 | } 30 | -------------------------------------------------------------------------------- /wild/tests/sources/gnu-unique.h: -------------------------------------------------------------------------------- 1 | template 2 | T get_value(T v) { 3 | // This static variable inside a template is what causes GCC to emit a symbol as UNIQUE. 4 | static T def = 0; 5 | def++; 6 | return v + def; 7 | } 8 | -------------------------------------------------------------------------------- /wild/tests/sources/got_ref_to_local-1.s: -------------------------------------------------------------------------------- 1 | .section .text,"ax",@progbits 2 | 3 | .type foo1, @function 4 | foo1: 5 | endbr64 6 | mov $2, %rax 7 | ret 8 | 9 | .type foo2, @function 10 | foo2: 11 | endbr64 12 | mov $22, %rax 13 | ret 14 | 15 | // We do a 32 bit relocation here, since at the time of writing, we don't optimise away 32 bit GOT 16 | // references. 17 | .globl get_foo1 18 | .type get_foo1, @function 19 | get_foo1: 20 | endbr64 21 | mov foo1@GOTPCREL(%eip), %eax 22 | ret 23 | 24 | .globl get_foo2 25 | .type get_foo2, @function 26 | get_foo2: 27 | endbr64 28 | mov foo2@GOTPCREL(%eip), %eax 29 | ret 30 | -------------------------------------------------------------------------------- /wild/tests/sources/got_ref_to_local.c: -------------------------------------------------------------------------------- 1 | // Checks that we work correctly when there's a GOT reference to a local. I'm not entirely sure why 2 | // you'd have a GOT reference to a local, but it is something I've observed. 3 | 4 | //#Object:got_ref_to_local-1.s 5 | //#LinkArgs:-z noexecstack 6 | //#Object:runtime.c 7 | //#Arch: x86_64 8 | 9 | #include "runtime.h" 10 | 11 | typedef int (*fnptr)(void); 12 | 13 | fnptr get_foo1(void); 14 | fnptr get_foo2(void); 15 | 16 | void _start(void) { 17 | runtime_init(); 18 | 19 | if (get_foo1()() != 2) { 20 | exit_syscall(100); 21 | } 22 | if (get_foo2()() != 22) { 23 | exit_syscall(101); 24 | } 25 | exit_syscall(42); 26 | } 27 | -------------------------------------------------------------------------------- /wild/tests/sources/ifunc.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:ifunc1.c 3 | //#Object:ifunc_init.c 4 | //#Object:runtime.c 5 | //#DiffIgnore:section.rela.plt.link 6 | //#RequiresGlibc:true 7 | //#Arch: x86_64 8 | 9 | //#Config:pie:default 10 | //#CompArgs:-fpie -ffunction-sections 11 | // This can be in any test that's x86_64 only. 12 | //#ExpectSym:_GLOBAL_OFFSET_TABLE_ 13 | 14 | //#Config:no-pie:default 15 | //#CompArgs:-fno-pie 16 | 17 | #include "runtime.h" 18 | #include "init.h" 19 | #include "ifunc_init.h" 20 | 21 | extern int compute_value10(void); 22 | extern int compute_value32(void); 23 | 24 | extern int resolve_count; 25 | 26 | typedef int (*vptr)(void); 27 | 28 | const vptr v10_ptr = compute_value10; 29 | 30 | void _start(void) { 31 | runtime_init(); 32 | 33 | int rv = init_ifuncs(); 34 | if (rv != 0) { 35 | exit_syscall(rv); 36 | } 37 | if (compute_value10() != 10) { 38 | exit_syscall(1); 39 | } 40 | if (compute_value32() != 32) { 41 | exit_syscall(2); 42 | } 43 | if (v10_ptr() != 10) { 44 | exit_syscall(3); 45 | } 46 | if (resolve_count != 2) { 47 | exit_syscall(4); 48 | } 49 | if (v10_ptr == compute_value32) { 50 | exit_syscall(5); 51 | } 52 | if (v10_ptr != compute_value10) { 53 | exit_syscall(5); 54 | } 55 | exit_syscall(42); 56 | } 57 | -------------------------------------------------------------------------------- /wild/tests/sources/ifunc1.c: -------------------------------------------------------------------------------- 1 | extern int compute_value(void); 2 | 3 | int resolve_count = 0; 4 | 5 | static int return10(void) { 6 | return 10; 7 | } 8 | 9 | int compute_value10(void) __attribute__((ifunc ("resolve_compute_value10"))); 10 | 11 | static void *resolve_compute_value10(void) { 12 | resolve_count++; 13 | return return10; 14 | } 15 | 16 | static int return32(void) { 17 | return 32; 18 | } 19 | 20 | int compute_value32(void) __attribute__((ifunc ("resolve_compute_value32"))); 21 | 22 | static void *resolve_compute_value32(void) { 23 | resolve_count++; 24 | return return32; 25 | } 26 | 27 | static int unused(void) { 28 | return 20; 29 | } 30 | 31 | int compute_unused(void) __attribute__((ifunc ("resolve_compute_unused"))); 32 | 33 | static void *resolve_compute_unused(void) { 34 | return unused; 35 | } 36 | -------------------------------------------------------------------------------- /wild/tests/sources/ifunc2.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#DiffIgnore:section.data 3 | //#DiffIgnore:section.data.alignment 4 | //#DiffIgnore:section.rodata 5 | //#DiffIgnore:section.rodata.alignment 6 | //#RequiresGlibc:true 7 | 8 | //#Config:pie:default 9 | //#CompArgs:-fpie 10 | //#LinkerDriver:gcc 11 | //#LinkArgs:-Wl,-z,now 12 | 13 | int exit_code = 2; 14 | 15 | static void impl(void) { 16 | exit_code += 40; 17 | } 18 | static void *resolver(void) { 19 | return impl; 20 | } 21 | void *ifunc(void) __attribute__((ifunc("resolver"))); 22 | 23 | int main() { 24 | ifunc(); 25 | return exit_code; 26 | } 27 | -------------------------------------------------------------------------------- /wild/tests/sources/ifunc_init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ifunc_init.h" 5 | 6 | struct Rela { 7 | size_t offset; 8 | size_t info; 9 | size_t addend; 10 | }; 11 | 12 | typedef size_t (*ifunc_resolve_fn_t)(void); 13 | 14 | const uint64_t R_X86_64_IRELATIVE = 37; 15 | 16 | // Initialises ifuncs in a similar way to how glibc would do it if we were linking against it. 17 | int init_ifuncs(void) { 18 | extern const struct Rela __rela_iplt_start[]; 19 | extern const struct Rela __rela_iplt_end[]; 20 | for (const struct Rela *i = __rela_iplt_start; i < __rela_iplt_end; i++) { 21 | if ((i->info & 0xffffffff) != R_X86_64_IRELATIVE) { 22 | return 7; 23 | } 24 | size_t *offset = (void *) i->offset; 25 | ifunc_resolve_fn_t resolve_fn = (ifunc_resolve_fn_t)(i->addend); 26 | *offset = resolve_fn(); 27 | } 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /wild/tests/sources/ifunc_init.h: -------------------------------------------------------------------------------- 1 | int init_ifuncs(void); 2 | -------------------------------------------------------------------------------- /wild/tests/sources/init.c: -------------------------------------------------------------------------------- 1 | typedef void (*init_fn_t)(void); 2 | 3 | extern init_fn_t __init_array_start[]; 4 | extern init_fn_t __init_array_end[]; 5 | extern init_fn_t __preinit_array_start[]; 6 | extern init_fn_t __preinit_array_end[]; 7 | 8 | void call_init_functions(void) { 9 | int count = __preinit_array_end - __preinit_array_start; 10 | for (int i = 0; i < count; i++) { 11 | __preinit_array_start[i](); 12 | } 13 | count = __init_array_end - __init_array_start; 14 | for (int i = 0; i < count; i++) { 15 | __init_array_start[i](); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /wild/tests/sources/init.h: -------------------------------------------------------------------------------- 1 | void call_init_functions(void); 2 | -------------------------------------------------------------------------------- /wild/tests/sources/init_test.c: -------------------------------------------------------------------------------- 1 | //#Object:init.c 2 | //#Object:runtime.c 3 | //#CompArgs:default: 4 | //#CompArgs:-static -pie 5 | 6 | #include "runtime.h" 7 | #include "init.h" 8 | 9 | static int value = 0; 10 | 11 | void __attribute__ ((constructor)) premain() { 12 | value = 42; 13 | } 14 | 15 | void _start(void) { 16 | runtime_init(); 17 | call_init_functions(); 18 | exit_syscall(value); 19 | } 20 | -------------------------------------------------------------------------------- /wild/tests/sources/init_tls.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef uint8_t u8; 4 | typedef uint16_t u16; 5 | typedef uint32_t u32; 6 | typedef uint64_t u64; 7 | 8 | 9 | struct FileHeader { 10 | u8 magic[4]; 11 | u8 class; 12 | u8 data; 13 | u8 ei_version; 14 | u8 os_abi; 15 | u8 abi_version; 16 | u8 padding[7]; 17 | u16 ty; 18 | u16 machine; 19 | u32 e_version; 20 | u64 entry_point; 21 | u64 program_header_offset; 22 | u64 section_header_offset; 23 | u32 flags; 24 | u16 ehsize; 25 | u16 program_header_entry_size; 26 | u16 program_header_num; 27 | u16 section_header_entry_size; 28 | u16 section_header_num; 29 | u16 section_names_index; 30 | }; 31 | 32 | struct ProgramHeader { 33 | u32 segment_type; 34 | u32 flags; 35 | u64 offset; 36 | u64 virtual_addr; 37 | u64 physical_addr; 38 | u64 file_size; 39 | u64 mem_size; 40 | u64 alignment; 41 | }; 42 | 43 | extern const struct FileHeader __ehdr_start; 44 | 45 | static void set_fs_register(void *address) { 46 | register int64_t rax __asm__ ("rax") = 158; // arch_prctl 47 | register int64_t rdi __asm__ ("rdi") = 0x1002; // ARCH_SET_FS 48 | register int64_t rsi __asm__ ("rsi") = (int64_t)address; 49 | __asm__ __volatile__ ( 50 | "syscall" 51 | : "+r" (rax) 52 | : "r" (rdi), "r" (rsi) 53 | : "rcx", "r11", "memory" 54 | ); 55 | } 56 | 57 | static u8*** tcb; 58 | 59 | u8*** get_tcb(void) { 60 | return tcb; 61 | } 62 | 63 | int init_tls(uint64_t base_address) { 64 | // A buffer to hold our TLS storage. 65 | static u8 tls_area[1024] __attribute__ ((aligned (8))); 66 | 67 | const u32 SHT_TLS = 7; 68 | 69 | u8 *t_out = tls_area; 70 | int num_headers = __ehdr_start.program_header_num; 71 | struct ProgramHeader *headers = (struct ProgramHeader *)((void *)(&__ehdr_start) + __ehdr_start.program_header_offset); 72 | for (int i; i < num_headers; i++) { 73 | struct ProgramHeader *h = &headers[i]; 74 | if (h->segment_type == SHT_TLS) { 75 | u8 *t_in = (u8*)h->virtual_addr + base_address; 76 | for (int j = 0; j < h->mem_size; j++) { 77 | if (j < h->file_size) { 78 | *t_out = *t_in; 79 | t_in++; 80 | } else { 81 | // We're past file_size, initialise with zeros. 82 | *t_out = 0; 83 | } 84 | t_out++; 85 | } 86 | // Keep going until we're 8-byte aligned, otherwise the TCB might not have the correct 87 | // alignment. 88 | while ((((u64)t_out) & 0x7) != 0) { 89 | t_out++; 90 | } 91 | 92 | // Put a pointer to the TCB at the start of the TCB. 93 | u64 *tcb_u64 = (u64*)t_out; 94 | tcb_u64[0] = (u64)tcb_u64; 95 | 96 | // Next entry in the TCB 97 | u64 *modules = &tcb_u64[2]; 98 | modules[1] = (u64)tcb_u64; 99 | 100 | tcb_u64[1] = (u64)modules; 101 | 102 | // Point the GS register to the TCB. 103 | set_fs_register(t_out); 104 | tcb = (u8***)t_out; 105 | return 0; 106 | } 107 | } 108 | return 1; 109 | } 110 | -------------------------------------------------------------------------------- /wild/tests/sources/init_tls.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // A very simplistic implementation of TLS initialisation for use when we aren't linking against 4 | // libc. Definitely won't work with multiple threads, but should be enough for accessing TLS 5 | // variables from the main thread. Returns 0 on success, 1 if no TLS segment was found. 6 | int init_tls(uint64_t base_address); 7 | -------------------------------------------------------------------------------- /wild/tests/sources/input_does_not_exist.c: -------------------------------------------------------------------------------- 1 | //#Object:/does/not/exist.o 2 | //#ExpectError:/does/not/exist.o 3 | -------------------------------------------------------------------------------- /wild/tests/sources/internal-syms.c: -------------------------------------------------------------------------------- 1 | // This test does stuff with some of the linker-defined symbols. These symbols are generally used by 2 | // libc. 3 | 4 | //#Object:runtime.c 5 | 6 | #include "runtime.h" 7 | 8 | struct Rela { 9 | long long a, b, c; 10 | }; 11 | 12 | extern const struct Rela __rela_iplt_start __attribute__ ((weak)); 13 | extern const struct Rela __rela_iplt_end __attribute__ ((weak)); 14 | 15 | void _start(void) { 16 | runtime_init(); 17 | 18 | int value = 42; 19 | // We shouldn't have any .rela.plt entries, so this loop should terminate without dereferencing 20 | // any RELA entries. 21 | for (const struct Rela *e = &__rela_iplt_start; e < &__rela_iplt_end; ++e) { 22 | value += 1 + e->a; 23 | } 24 | exit_syscall(value); 25 | } 26 | -------------------------------------------------------------------------------- /wild/tests/sources/libc-ifunc.c: -------------------------------------------------------------------------------- 1 | //#CompArgs:-fPIC -g 2 | //#LinkerDriver:gcc 3 | //#LinkArgs:-pie -Wl,-z,now 4 | //#DiffIgnore:section.rodata 5 | // GNU ld emits an extra IRELATIVE relocation, while LLD and Wild instead point to the PLT entry. So 6 | // we need to diff against lld, which isn't currently enabled for cross compilation. 7 | //#Cross:false 8 | //#EnableLinker:lld 9 | //#RequiresGlibc:true 10 | 11 | int foo() { 12 | return 42; 13 | } 14 | 15 | int bar() __attribute__((ifunc("resolve_bar"))); 16 | 17 | void *resolve_bar() { return foo; } 18 | 19 | typedef int (*int_f_ptr_t)(void); 20 | 21 | int_f_ptr_t bar2 = bar; 22 | 23 | int main() { 24 | if (bar() != 42) { 25 | return bar(); 26 | } 27 | 28 | return bar2(); 29 | } 30 | -------------------------------------------------------------------------------- /wild/tests/sources/libc-integration-0.c: -------------------------------------------------------------------------------- 1 | __thread int tvar3 = 80; 2 | 3 | // These get overridden in the main file. 4 | __attribute__ ((weak)) int weak_var = 20; 5 | __attribute__ ((weak)) __thread int weak_tvar = 21; 6 | 7 | // These don't get overridden. 8 | __attribute__ ((weak)) int weak_var2 = 80; 9 | __attribute__ ((weak)) __thread int weak_tvar2 = 81; 10 | 11 | extern __thread int tvar2; 12 | 13 | static __thread int tvar_local = 8; 14 | static __thread int tvar_local2 = 70; 15 | 16 | int value42 = 42; 17 | 18 | void set_tvar2(int v) { 19 | tvar2 = v; 20 | } 21 | 22 | void set_tvar3(int v) { 23 | tvar3 = v; 24 | } 25 | 26 | void set_tvar_local(int v) { 27 | tvar_local = v; 28 | } 29 | 30 | int get_tvar_local(void) { 31 | return tvar_local; 32 | } 33 | 34 | void set_tvar_local2(int v) { 35 | tvar_local2 = v; 36 | } 37 | 38 | int get_tvar_local2(void) { 39 | return tvar_local2; 40 | } 41 | 42 | int get_weak_var(void) { 43 | return weak_var; 44 | } 45 | 46 | int get_weak_tvar(void) { 47 | return weak_tvar; 48 | } 49 | 50 | int get_weak_var2(void) { 51 | return weak_var2; 52 | } 53 | 54 | int get_weak_tvar2(void) { 55 | return weak_tvar2; 56 | } 57 | 58 | static int return10() { 59 | return 10; 60 | } 61 | 62 | int compute_value10(void) __attribute__((ifunc ("resolve_compute_value10"))); 63 | 64 | static void *resolve_compute_value10(void) { 65 | return return10; 66 | } 67 | 68 | int sometimes_weak_fn(void) { 69 | return 42; 70 | } 71 | 72 | typedef int(*get_int_fn_t)(void); 73 | 74 | get_int_fn_t sometimes_weak_fn_ptr = sometimes_weak_fn; 75 | 76 | get_int_fn_t get_sometimes_weak_fn_ptr(void) { 77 | return sometimes_weak_fn_ptr; 78 | } 79 | 80 | 81 | int black_box(int v) { 82 | return v; 83 | } 84 | 85 | // This function is also defined in libc-integration-0b.c. The definition here should be used. 86 | int __attribute__ ((weak)) weak_fn3(void) { 87 | return 15; 88 | } 89 | 90 | __attribute__ ((weak, visibility(("hidden")))) int atoi(const char *bytes) { 91 | return 77; 92 | } 93 | -------------------------------------------------------------------------------- /wild/tests/sources/libc-integration-0b.c: -------------------------------------------------------------------------------- 1 | // This function is also defined in libc-integration-0.c. The other definition should be used. 2 | int __attribute__ ((weak)) weak_fn3(void) { 3 | return 16; 4 | } 5 | -------------------------------------------------------------------------------- /wild/tests/sources/libc-integration-1.c: -------------------------------------------------------------------------------- 1 | // This file deliberately has no outgoing relocations. This means that it shouldn't get any symbol 2 | // versions, which then in turn checks that we can handle linking our executable against a mix of 3 | // shared objects, some with symbol versions and some without. 4 | 5 | int get_42(void) { 6 | return 42; 7 | } 8 | -------------------------------------------------------------------------------- /wild/tests/sources/link_args.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | // TODO: #795 3 | //#Arch: x86_64,aarch64 4 | 5 | //#Config:strip-all:default 6 | //#Object:runtime.c 7 | //#LinkArgs:--strip-all 8 | //#EnableLinker:lld 9 | //#DiffIgnore:file-header.entry 10 | 11 | //#Config:single-threaded:default 12 | //#Object:runtime.c 13 | //#WildExtraLinkArgs:--threads=1 14 | 15 | //#Config:dev_null:default 16 | //#Object:runtime.c 17 | //#LinkArgs:-o /dev/null 18 | //#DiffEnabled:false 19 | //#RunEnabled:false 20 | 21 | //#Config:gc-sections:default 22 | //#CompArgs:-g -ffunction-sections 23 | //#LinkArgs:--gc-sections 24 | //#Object:runtime.c 25 | //#NoSym:this_function_is_not_used 26 | 27 | #include "runtime.h" 28 | 29 | void _start(void) { 30 | runtime_init(); 31 | exit_syscall(42); 32 | } 33 | 34 | void this_function_is_not_used(void) {} 35 | -------------------------------------------------------------------------------- /wild/tests/sources/linker-script-executable.c: -------------------------------------------------------------------------------- 1 | //#LinkerScript:linker-script-executable.ld 2 | //#Object:runtime.c 3 | //#DiffIgnore: segment.LOAD.RW.alignment 4 | // RISC-V: BFD complains about missing __global_pointer$ (defined in the default linker script) 5 | //#Arch:x86_64,aarch64 6 | 7 | #include 8 | 9 | #include "runtime.h" 10 | 11 | int value = 42; 12 | extern const char start_of_text; 13 | extern const char start_of_data; 14 | extern const char start_of_512; 15 | 16 | void begin_here(void) { 17 | if ((size_t)&start_of_text != 0x600000) { 18 | exit_syscall(10); 19 | } 20 | 21 | if ((size_t)&start_of_data != 0x800000) { 22 | exit_syscall(11); 23 | } 24 | 25 | if ((size_t)&start_of_512 & 511 != 0) { 26 | exit_syscall(12); 27 | } 28 | 29 | exit_syscall(value); 30 | } 31 | -------------------------------------------------------------------------------- /wild/tests/sources/linker-script-executable.ld: -------------------------------------------------------------------------------- 1 | ENTRY(begin_here) 2 | 3 | SECTIONS 4 | { 5 | . = 0x600000; 6 | .text : { 7 | start_of_text = .; 8 | *(.text) 9 | } 10 | . = 0x800000; 11 | . = ALIGN(4); 12 | .data : { 13 | start_of_data = .; 14 | *(.data) 15 | . = ALIGN(512); 16 | start_of_512 = .; 17 | *(.data.2) 18 | } 19 | .bss : { *(.bss) } 20 | } 21 | -------------------------------------------------------------------------------- /wild/tests/sources/linker-script.c: -------------------------------------------------------------------------------- 1 | //#LinkerScript:linker-script.ld 2 | //#Static:false 3 | //#LinkArgs:-shared -z now 4 | //#RunEnabled:false 5 | //#DiffIgnore:section.got 6 | //#ExpectDynSym:start_bar bar 0 7 | //#ExpectDynSym:start_aaa bar 8 8 | //#ExpectDynSym:end_bar bar 12 9 | //#ExpectSym:start_bar bar 0 10 | //#ExpectSym:start_aaa bar 8 11 | //#ExpectSym:end_bar bar 12 12 | 13 | static int foo1 __attribute__ ((used, section (".data.foo"))) = 0x01; 14 | 15 | static int baz1 __attribute__ ((used, section (".data.baz1"))) = 0x02; 16 | 17 | static int aaa1 __attribute__ ((used, section (".data.aaa"))) = 0x03; 18 | -------------------------------------------------------------------------------- /wild/tests/sources/linker-script.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | bar : ALIGN(8) { 3 | start_bar = .; 4 | KEEP(*(.data.foo .data.baz*)); 5 | start_aaa = .; 6 | KEEP(*(.data.aaa)); 7 | end_bar = .; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /wild/tests/sources/local_symbol_refs.s: -------------------------------------------------------------------------------- 1 | // The C compiler seems to always reference local symbols by offsets from the section containing the 2 | // symbol. We want to make sure that actual symbol references work properly too. 3 | 4 | //#Object:runtime.c 5 | //#LinkArgs:-z noexecstack 6 | //#EnableLinker:lld 7 | //#Arch: x86_64 8 | 9 | .section .rodata.x,"aM",@progbits,16 10 | .p2align 4, 0x0 11 | 12 | vvv1: 13 | .quad 8 14 | 15 | vvv2: 16 | .quad 9 17 | 18 | .section .text._start,"ax",@progbits 19 | 20 | .globl _start 21 | .type _start, @function 22 | _start: 23 | endbr64 24 | 25 | movq vvv1(%rip), %rax 26 | cmpq $8, %rax 27 | jne fail 28 | 29 | movq vvv2(%rip), %rax 30 | cmpq $9, %rax 31 | jne fail 32 | 33 | mov $42,%rdi 34 | call exit_syscall 35 | 36 | fail: 37 | movq $99, %rdi 38 | call exit_syscall 39 | -------------------------------------------------------------------------------- /wild/tests/sources/mixed-verdef-verneed-2.c: -------------------------------------------------------------------------------- 1 | int from_so(void) { 2 | return 30; 3 | } 4 | -------------------------------------------------------------------------------- /wild/tests/sources/mixed-verdef-verneed.c: -------------------------------------------------------------------------------- 1 | // Makes sure that having both verdef and verneed doesn't cause problems. 2 | 3 | //#Static:false 4 | //#Object:runtime.c 5 | //#VersionScript:mixed-verdef-verneed.map 6 | //#Shared:mixed-verdef-verneed-2.c 7 | //#DiffIgnore:version_d.verdef_1 8 | //#DiffIgnore:.dynamic.DT_NEEDED 9 | //#DiffIgnore:.dynamic.DT_RELA* 10 | //#DiffIgnore:section.got 11 | 12 | #include "runtime.h" 13 | 14 | int from_so(void); 15 | 16 | int bar_global(void) { 17 | return 10; 18 | } 19 | 20 | void _start(void) { 21 | runtime_init(); 22 | 23 | if (bar_global() != 10) { 24 | exit_syscall(bar_global()); 25 | } 26 | 27 | if (from_so() != 30) { 28 | exit_syscall(100); 29 | } 30 | 31 | exit_syscall(42); 32 | } 33 | -------------------------------------------------------------------------------- /wild/tests/sources/mixed-verdef-verneed.map: -------------------------------------------------------------------------------- 1 | VER_1.0 { 2 | global: 3 | bar_global; 4 | }; 5 | 6 | VER_2.0 { 7 | from_so; 8 | } VER_1.0; 9 | -------------------------------------------------------------------------------- /wild/tests/sources/no_start.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:runtime.c 3 | 4 | //#Config:no-gc:default 5 | //#LinkArgs:-z now --no-gc-sections 6 | //#DiffIgnore: segment.LOAD.RW.alignment 7 | 8 | // With --gc-sections enabled, all code gets eliminated and there's nothing to run. If we try to 9 | // execute this binary, it will segfault, so we don't. 10 | //#Config:gc:default 11 | //#LinkArgs:-z now --gc-sections 12 | //#RunEnabled:false 13 | 14 | #include "runtime.h" 15 | 16 | // Provided this is the first function, it'll get used as the entry point - at least by GNU ld. LLD 17 | // doesn't set an entry point in this case. 18 | void this_is_the_entry_point(void) { 19 | exit_syscall(42); 20 | } 21 | -------------------------------------------------------------------------------- /wild/tests/sources/non-alloc.s: -------------------------------------------------------------------------------- 1 | // This test makes sure that we're able to handle a retained, non-alloc section. 2 | 3 | //#LinkArgs:-z noexecstack 4 | //#Object:runtime.c 5 | //#Arch: x86_64 6 | 7 | .section .nonloadable, "R", @progbits 8 | .asciz "Hello, World!" 9 | 10 | .section .text, "ax", @progbits 11 | .align 8 12 | 13 | .globl _start 14 | .type _start, @function 15 | _start: 16 | mov $42, %rdi 17 | call exit_syscall 18 | .size _start, .-_start 19 | -------------------------------------------------------------------------------- /wild/tests/sources/old_init.c: -------------------------------------------------------------------------------- 1 | // This test checks that we correctly handle the .init and .fini sections. These sections are a bit 2 | // different to other sections in that glibc defines the start of the _init function in one file 3 | // (crti.o) and then any other objects that define code for the .init section need to appear after 4 | // this with no padding in between. The end of the function is then from crtn.o which has the return 5 | // instruction. Of these .init sections, the only one with alignment >1 is the start of the 6 | // function. This is tricky for us since normally we pad all sections to a multiple of their 7 | // alignment. We can't do that here because then we'd end up with zero bytes in the middle of our 8 | // _init function. 9 | 10 | //#Object:old_init0.s 11 | //#Object:old_init1.s 12 | //#Object:runtime.c 13 | //#LinkArgs:-z noexecstack 14 | //#EnableLinker:lld 15 | //#Arch: x86_64 16 | 17 | #include "runtime.h" 18 | 19 | int _init(); 20 | int _fini(); 21 | 22 | void _start(void) { 23 | runtime_init(); 24 | 25 | if (_init() != 7) { 26 | exit_syscall(1); 27 | } 28 | if (_fini() != 9) { 29 | exit_syscall(2); 30 | } 31 | exit_syscall(42); 32 | } 33 | -------------------------------------------------------------------------------- /wild/tests/sources/old_init0.s: -------------------------------------------------------------------------------- 1 | .section .init,"ax",@progbits 2 | .p2align 2 3 | .globl _init 4 | _init: 5 | endbr64 6 | sub $0x8,%rsp 7 | nop 8 | 9 | .section .fini,"ax",@progbits 10 | .p2align 2 11 | .globl _fini 12 | _fini: 13 | endbr64 14 | sub $0x8,%rsp 15 | nop 16 | -------------------------------------------------------------------------------- /wild/tests/sources/old_init1.s: -------------------------------------------------------------------------------- 1 | .section .init,"ax",@progbits 2 | add $0x8, %rsp 3 | mov $7, %rax 4 | ret 5 | 6 | .section .fini,"ax",@progbits 7 | add $0x8, %rsp 8 | mov $9, %rax 9 | ret 10 | -------------------------------------------------------------------------------- /wild/tests/sources/preinit-array.c: -------------------------------------------------------------------------------- 1 | //#Object:preinit-array.s 2 | //#Shared:runtime.c 3 | // We're linking different .so files, so this is expected. 4 | //#DiffIgnore:.dynamic.DT_NEEDED 5 | //#DiffIgnore:segment.LOAD.RW.alignment 6 | //#DiffIgnore:.dynamic.DT_PREINIT_ARRAY 7 | //#DiffIgnore:.dynamic.DT_RELA 8 | //#DiffIgnore:.dynamic.DT_RELAENT 9 | //#Arch: x86_64 10 | //#RequiresGlibc:true 11 | //#Static:false 12 | 13 | #include "runtime.h" 14 | 15 | int exit_code; 16 | 17 | void preinit() { 18 | exit_code = 42; 19 | } 20 | 21 | void _start(void) { 22 | runtime_init(); 23 | exit_syscall(exit_code); 24 | } 25 | -------------------------------------------------------------------------------- /wild/tests/sources/preinit-array.s: -------------------------------------------------------------------------------- 1 | .globl preinit 2 | 3 | .section .preinit_array,"aw",@preinit_array 4 | .p2align 3 5 | .quad preinit 6 | 7 | .section .note.GNU-stack,"",@progbits 8 | -------------------------------------------------------------------------------- /wild/tests/sources/rdyn1.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | 3 | #[no_mangle] 4 | pub extern "C" fn foo() -> i32 { 5 | 10 6 | } 7 | 8 | #[no_mangle] 9 | pub extern "C" fn bar() -> i32 { 10 | 18 11 | } 12 | 13 | thread_local! { 14 | pub static TLS1: Cell = const { Cell::new(1) }; 15 | } 16 | 17 | thread_local! { 18 | pub static TLS2: Cell = const { Cell::new(2) }; 19 | } 20 | 21 | #[no_mangle] 22 | pub extern "C" fn get_tls1() -> i32 { 23 | TLS1.get() 24 | } 25 | 26 | #[no_mangle] 27 | pub extern "C" fn set_tls1(value: i32) { 28 | TLS1.set(value); 29 | } 30 | 31 | #[no_mangle] 32 | pub extern "C" fn get_tls2() -> i32 { 33 | TLS2.get() 34 | } 35 | 36 | #[no_mangle] 37 | pub extern "C" fn set_tls2(value: i32) { 38 | TLS2.set(value); 39 | } 40 | -------------------------------------------------------------------------------- /wild/tests/sources/runtime.c: -------------------------------------------------------------------------------- 1 | #include "runtime.h" 2 | 3 | #include 4 | #include 5 | 6 | // On RISC-V, the GP register needs to point to the symbol `__global_pointer$`. See 7 | // https://www.sifive.com/blog/all-aboard-part-3-linker-relaxation-in-riscv-toolchain 8 | #if defined(__riscv) 9 | void runtime_init(void) { 10 | __asm__ __volatile__( 11 | ".option push\n\ 12 | .option norelax\n\ 13 | la gp, __global_pointer$\n\ 14 | .option pop" 15 | ); 16 | } 17 | #else 18 | void runtime_init(void) {} 19 | #endif 20 | 21 | // TODO: Move contents of exit.c here. Avoiding doing that for now to avoid merge conflicts. 22 | #include "exit.c" -------------------------------------------------------------------------------- /wild/tests/sources/runtime.h: -------------------------------------------------------------------------------- 1 | // Runtime for when we're not using libc. 2 | 3 | // This should be called at the start of _start. 4 | void runtime_init(void); 5 | 6 | void exit_syscall(int exit_code); 7 | -------------------------------------------------------------------------------- /wild/tests/sources/rust-integration-dynamic.rs: -------------------------------------------------------------------------------- 1 | //#DiffIgnore:.dynamic.* 2 | // It looks like GNU ld sets .tdata's alignment to match .tbss's alignment 3 | //#DiffIgnore:section.tdata.alignment 4 | // TODO: RISC-V BFD linker keeps multiple .dynsym symbols 5 | //#DiffIgnore:dynsym.* 6 | //#CompArgs:-C debuginfo=2 7 | //#Shared:rdyn1.rs 8 | //#Cross: false 9 | 10 | extern "C" { 11 | fn foo() -> i32; 12 | fn bar() -> i32; 13 | fn get_tls1() -> i32; 14 | fn set_tls1(value: i32); 15 | fn get_tls2() -> i32; 16 | fn set_tls2(value: i32); 17 | } 18 | 19 | fn main() { 20 | if unsafe { foo() } != 10 { 21 | std::process::exit(100); 22 | } 23 | 24 | if unsafe { bar() } != 18 { 25 | std::process::exit(101); 26 | } 27 | 28 | if unsafe { get_tls1() } != 1 { 29 | std::process::exit(102); 30 | } 31 | if unsafe { get_tls2() } != 2 { 32 | std::process::exit(103); 33 | } 34 | 35 | unsafe { 36 | set_tls1(88); 37 | } 38 | unsafe { 39 | set_tls2(55); 40 | } 41 | 42 | if unsafe { get_tls1() } != 88 { 43 | std::process::exit(104); 44 | } 45 | if unsafe { get_tls2() } != 55 { 46 | std::process::exit(105); 47 | } 48 | 49 | std::process::exit(42); 50 | } 51 | -------------------------------------------------------------------------------- /wild/tests/sources/rust-integration.rs: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#DiffIgnore:section.tdata.alignment 3 | // We include some more archive members than what other linkers do (#162). 4 | //#DiffIgnore:debug_info.missing_unit 5 | // TODO: RISC-V BFD linker keeps multiple .dynsym symbols 6 | //#DiffIgnore:dynsym.* 7 | 8 | //#Config:llvm-static:default 9 | //#CompArgs:--target x86_64-unknown-linux-musl -C relocation-model=static -C target-feature=+crt-static -C debuginfo=2 10 | //#RequiresRustMusl: true 11 | //#Arch: x86_64 12 | 13 | //#Config:llvm-static-aarch64:default 14 | //#CompArgs:--target aarch64-unknown-linux-musl -C relocation-model=static -C target-feature=+crt-static -C debuginfo=2 15 | //#RequiresRustMusl: true 16 | //#Arch: aarch64 17 | 18 | //#Config:cranelift-static:default 19 | //#CompArgs:-Zcodegen-backend=cranelift --target x86_64-unknown-linux-musl -C relocation-model=static -C target-feature=+crt-static -C debuginfo=2 --cfg cranelift 20 | //#RequiresNightlyRustc: true 21 | //#RequiresRustMusl: true 22 | //#Arch: x86_64 23 | // GNU ld clears these flags and sets entsize to 0. It's not clear why. 24 | //#DiffIgnore:section.debug_str.flags 25 | //#DiffIgnore:section.debug_str.entsize 26 | 27 | //#Config:cranelift-static-aarch64:default 28 | //#CompArgs:-Zcodegen-backend=cranelift --target aarch64-unknown-linux-musl -C relocation-model=static -C target-feature=+crt-static -C debuginfo=2 --cfg cranelift 29 | //#RequiresRustMusl: true 30 | //#RequiresNightlyRustc: true 31 | //#Arch: aarch64 32 | //#DiffIgnore:section.debug_str.flags 33 | //#DiffIgnore:section.debug_str.entsize 34 | 35 | //#Config:llvm-dynamic:default 36 | //#CompArgs:-C debuginfo=2 37 | //#DiffIgnore:.dynamic.DT_JMPREL 38 | //#DiffIgnore:.dynamic.DT_PLTGOT 39 | //#DiffIgnore:.dynamic.DT_PLTREL 40 | 41 | fn foo() { 42 | panic!("Make sure unwinding works"); 43 | } 44 | 45 | fn main() { 46 | // Make sure panics and catching them work. This relies on .eh_frame being correct. Cranelift 47 | // doesn't currently support this, so we disable it there. 48 | if !cfg!(cranelift) && std::panic::catch_unwind(foo).is_ok() { 49 | std::process::exit(101); 50 | } 51 | 52 | // Make sure we can canonicalise a path. This was failing at one point due to the incorrect 53 | // version of a libc function being called. Implementing symbol versioning fixed it. 54 | let current_dir = match std::fs::canonicalize(".") { 55 | Ok(p) => p, 56 | Err(e) => { 57 | println!("{e}"); 58 | std::process::exit(102); 59 | } 60 | }; 61 | if current_dir.components().count() <= 1 { 62 | std::process::exit(103); 63 | } 64 | 65 | std::process::exit(42); 66 | } 67 | -------------------------------------------------------------------------------- /wild/tests/sources/rust-tls.rs: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#RequiresNightlyRustc: true 3 | 4 | //#Config:global-dynamic:default 5 | //#CompArgs:-Ztls-model=global-dynamic 6 | 7 | //#Config:local-dynamic:default 8 | //#CompArgs:-Ztls-model=local-dynamic 9 | 10 | //#Config:initial-exec:default 11 | //#CompArgs:-Ztls-model=initial-exec 12 | 13 | //#Config:local-exec:default 14 | //#CompArgs:-Ztls-model=local-exec 15 | 16 | use std::cell::Cell; 17 | use std::thread; 18 | 19 | thread_local!(static FOO: Cell = Cell::new(1)); 20 | 21 | fn main() { 22 | assert_eq!(FOO.get(), 1); 23 | FOO.set(2); 24 | 25 | // each thread starts out with the initial value of 1 26 | let t = thread::spawn(move || { 27 | assert_eq!(FOO.get(), 1); 28 | FOO.set(3); 29 | }); 30 | 31 | // wait for the thread to complete and bail out on panic 32 | t.join().unwrap(); 33 | 34 | // we retain our original value of 2 despite the child thread 35 | assert_eq!(FOO.get(), 2); 36 | 37 | std::process::exit(42); 38 | } 39 | -------------------------------------------------------------------------------- /wild/tests/sources/shared-a1.c: -------------------------------------------------------------------------------- 1 | int bar1(void) { 2 | return 40; 3 | } 4 | -------------------------------------------------------------------------------- /wild/tests/sources/shared-a2.c: -------------------------------------------------------------------------------- 1 | int baz(void) { 2 | return 10; 3 | } 4 | -------------------------------------------------------------------------------- /wild/tests/sources/shared-priority-1.c: -------------------------------------------------------------------------------- 1 | __attribute__ ((weak, visibility(("protected")))) 2 | int foo(void) { 3 | return 10; 4 | } 5 | 6 | int var1 = 65; 7 | -------------------------------------------------------------------------------- /wild/tests/sources/shared-priority-2.c: -------------------------------------------------------------------------------- 1 | __attribute__ ((weak, visibility(("protected")))) 2 | int foo(void) { 3 | return 20; 4 | } 5 | -------------------------------------------------------------------------------- /wild/tests/sources/shared-priority.c: -------------------------------------------------------------------------------- 1 | // Tests related to how we handle symbols that are defined in shared objects and also in other 2 | // places like archives. 3 | 4 | //#AbstractConfig:default 5 | //#CompArgs:-fPIC 6 | //#Object:runtime.c 7 | //#Static:false 8 | //#DiffIgnore:.dynamic.DT_NEEDED 9 | //#DiffIgnore:.dynamic.DT_RELA 10 | //#DiffIgnore:.dynamic.DT_RELAENT 11 | //#DiffIgnore:section.got 12 | // TODO: RISC-V BFD linker keeps the symbol in .dynsym section 13 | //#DiffIgnore:section.rela.dyn 14 | //#DiffIgnore:dynsym.var1.section 15 | 16 | //#Config:shared-first-archive-not-loaded:default 17 | //#Shared:shared-priority-1.c 18 | //#Archive:shared-priority-2.c 19 | 20 | //#Config:archive-first:default 21 | //#Archive:shared-priority-1.c 22 | //#Shared:shared-priority-2.c 23 | 24 | #include "runtime.h" 25 | 26 | int foo(void); 27 | 28 | extern int var1; 29 | 30 | void _start(void) { 31 | runtime_init(); 32 | 33 | if (var1 != 65) { 34 | exit_syscall(101); 35 | } 36 | if (foo() != 10) { 37 | exit_syscall(100); 38 | } 39 | exit_syscall(42); 40 | } 41 | -------------------------------------------------------------------------------- /wild/tests/sources/shared-s1.c: -------------------------------------------------------------------------------- 1 | int baz(void); 2 | 3 | int call_baz(void) { 4 | return baz(); 5 | } 6 | 7 | int bar2(void) { 8 | return 2; 9 | } 10 | 11 | __attribute__((weak)) int foo1 = 3; 12 | __attribute__((weak)) int get_foo() { return 4; } 13 | -------------------------------------------------------------------------------- /wild/tests/sources/shared.c: -------------------------------------------------------------------------------- 1 | // We don't currently run this, we just make sure that we can produce a shared object and that it 2 | // passes the diff test. 3 | // 4 | // One notable scenario that this test tests is having a non-weak undefined symbol (baz) in a shared 5 | // object and having that symbol be defined by an archive entry that we don't load. 6 | 7 | //#Config:default 8 | //#RunEnabled:false 9 | //#LinkArgs:-shared -z now 10 | //#Static:false 11 | // TODO: https://rust-lang.zulipchat.com/#narrow/channel/421156-gsoc/topic/Project.3A.20Improve.20Wild.20linker.20test.20suites/near/521482968 12 | //#Cross:false 13 | //#Archive:shared-a1.c,shared-a2.c 14 | //#Shared:shared-s1.c 15 | //#DiffIgnore:.dynamic.DT_RELA 16 | //#DiffIgnore:.dynamic.DT_RELAENT 17 | //#DiffIgnore:.dynamic.DT_NEEDED 18 | 19 | //#Config:symbolic:default 20 | //#LinkArgs:-shared -z now -Bsymbolic 21 | //#DiffIgnore:.dynamic.DT_FLAGS.SYMBOLIC 22 | //#DiffIgnore:.dynamic.DT_SYMBOLIC 23 | //#DiffIgnore:section.got 24 | //#DiffIgnore:rel.R_X86_64_PC32.R_X86_64_PLT32 25 | //#DiffIgnore:rel.extra-opt.R_AARCH64_CALL26.ReplaceWithNop.invalid-shared-object 26 | 27 | //#Config:symbolic-functions:default 28 | //#LinkArgs:-shared -z now -Bsymbolic-functions 29 | 30 | //#Config:nosymbolic:default 31 | //#LinkArgs:-shared -z now -Bno-symbolic 32 | 33 | //TODO: Add a test for `-Bsymbolic-non-weak`. Currently, adding such tests causes linker-diff to panic. 34 | 35 | //#Config:symbolic-non-weak-functions:default 36 | //#LinkArgs:-shared -z now -Bsymbolic-non-weak-functions 37 | //#SkipLinker:ld 38 | //#EnableLinker:lld 39 | //#DiffIgnore:section.relro_padding 40 | //#DiffIgnore:section.got.plt.entsize 41 | //#DiffIgnore:dynsym.baz.section 42 | 43 | int bar1(void); 44 | int bar2(void); 45 | 46 | int foo(void) { 47 | return bar1() + bar2(); 48 | } 49 | 50 | int call_bar1(void) { 51 | return bar1(); 52 | } 53 | -------------------------------------------------------------------------------- /wild/tests/sources/stack_alignment.s: -------------------------------------------------------------------------------- 1 | // TODO: Consider if we want to keep this test. It makes sure that we can run a movaps instruction 2 | // on a stack frame. This will segfault if the stack isn't correctly aligned to 16 bytes. 3 | 4 | //#Object:runtime.c 5 | //#LinkArgs:-z noexecstack 6 | //#EnableLinker:lld 7 | //#Arch: x86_64 8 | 9 | .globl _start 10 | _start: 11 | endbr64 12 | movaps 0x10(%rsp),%xmm1 13 | mov $42,%rdi 14 | call exit_syscall 15 | -------------------------------------------------------------------------------- /wild/tests/sources/string_merging.c: -------------------------------------------------------------------------------- 1 | // Defines identical string literals in two different C files and checks that they end up pointing 2 | // to the same memory. 3 | 4 | //#LinkArgs:-z noexecstack 5 | //#Object:string_merging1.s 6 | //#Object:string_merging2.s 7 | //#Object:runtime.c 8 | //#Arch: x86_64 9 | 10 | #include "runtime.h" 11 | 12 | extern const char s1h[]; 13 | extern const char s2h[]; 14 | extern const char s3h[]; 15 | extern const char s4h[]; 16 | extern const char s1w[]; 17 | extern const char s2w[]; 18 | extern const char a1[]; 19 | 20 | const char* get_loc1(void); 21 | const char* get_s1w(void); 22 | const char* get_s2w(void); 23 | const char* get_s2w_via_offset(void); 24 | 25 | void _start(void) { 26 | runtime_init(); 27 | 28 | if (s1h != s2h) { 29 | exit_syscall(101); 30 | } 31 | if (s1h[0] != 'H') { 32 | exit_syscall(103); 33 | } 34 | if (s1w != s2w) { 35 | exit_syscall(102); 36 | } 37 | if (s1w[0] != 'W') { 38 | exit_syscall(103); 39 | } 40 | if (get_loc1()[0] != 'L') { 41 | exit_syscall(104); 42 | } 43 | if (a1[0] != 'A') { 44 | exit_syscall(105); 45 | } 46 | if (get_s1w() != get_s2w()) { 47 | exit_syscall(106); 48 | } 49 | if (get_s1w() != s1w) { 50 | exit_syscall(107); 51 | } 52 | if (s3h != s4h) { 53 | // Identical strings in the same custom section didn't get merged. 54 | exit_syscall(108); 55 | } 56 | if (s3h == s1h) { 57 | // Identical strings in different sections got merged when they shouldn't have been. 58 | exit_syscall(109); 59 | } 60 | if (s3h[0] != 'H') { 61 | exit_syscall(110); 62 | } 63 | if (get_s2w_via_offset() != get_s2w()) { 64 | exit_syscall(111); 65 | } 66 | exit_syscall(42); 67 | } 68 | 69 | //#Contains:No reference to this string 70 | -------------------------------------------------------------------------------- /wild/tests/sources/string_merging1.s: -------------------------------------------------------------------------------- 1 | .section .rodata.strings, "aSM", @progbits, 1 2 | .align 1 3 | 4 | .globl s1h 5 | s1h: .ascii "Hello\0" 6 | 7 | .globl s1w 8 | s1w: .ascii "World\0" 9 | 10 | // Put some regular data in .rodata with alignment >1 to make sure that doesn't mess up our merged 11 | // string offsets. 12 | 13 | .section .rodata, "a", @progbits 14 | .align 8 15 | 16 | .globl a1 17 | a1: 18 | .ascii "Aligned\0" 19 | 20 | 21 | // Put another string, identical to one above, but in a custom section, not the .data section. It 22 | // should get merged with other identical strings in the same custom section, but not with those in 23 | // different sections. 24 | 25 | .section .custom1, "aSM", @progbits, 1 26 | .align 1 27 | 28 | .globl s3h 29 | s3h: .ascii "Hello\0" 30 | 31 | .globl noref 32 | noref: .ascii "No reference to this string\0" 33 | -------------------------------------------------------------------------------- /wild/tests/sources/string_merging2.s: -------------------------------------------------------------------------------- 1 | .section .rodata.strings, "aSM", @progbits, 1 2 | .align 1 3 | 4 | .globl s2w 5 | s2w: .ascii "World\0" 6 | 7 | .globl s2h 8 | s2h: .ascii "Hello\0" 9 | 10 | // Define a string-merge section containing a local then make sure we can reference it. 11 | 12 | .section .rodata.loc1, "aSM", @progbits, 1 13 | .align 1 14 | .loc1: .ascii "Local1\0" 15 | 16 | .section .text, "ax", @progbits 17 | 18 | .globl get_loc1 19 | get_loc1: 20 | endbr64 21 | lea .loc1(%rip), %rax 22 | ret 23 | 24 | // Define a getter that uses a GOT relocation to access a symbol defined in a different object file. 25 | 26 | .globl get_s1w 27 | get_s1w: 28 | endbr64 29 | movq s1w@GOTPCREL(%rip),%rax 30 | ret 31 | 32 | // Define a getter that uses a GOT relocation to access a symbol defined in this object file. 33 | 34 | .globl get_s2w 35 | get_s2w: 36 | endbr64 37 | movq s2w@GOTPCREL(%rip),%rax 38 | ret 39 | 40 | // String in custom section 41 | 42 | .section .custom1, "aSM", @progbits, 1 43 | .align 1 44 | 45 | .globl s4h 46 | s4h: .ascii "Hello\0" 47 | 48 | .section .text, "ax", @progbits 49 | .align 8 50 | 51 | // Returns a pointer to s2w, but does so using a relocation that has an addend that would put us 52 | // outside of s2w. Relocations that reference named symbols in string-merge sections shouldn't take 53 | // the addend into account when determining which string we're referencing. 54 | .globl get_s2w_via_offset 55 | .type get_s2w_via_offset, @function 56 | get_s2w_via_offset: 57 | endbr64 58 | lea s1w-100(%rip), %rax 59 | add $100, %rax 60 | ret 61 | .size get_s2w_via_offset, .-get_s2w_via_offset 62 | -------------------------------------------------------------------------------- /wild/tests/sources/symbol-versions-2.c: -------------------------------------------------------------------------------- 1 | #define SYMVER(a,b) __asm__(".symver " a "," b) 2 | 3 | SYMVER("foo_v1", "foo@1.0"); 4 | SYMVER("foo_v2", "foo@@2.0"); 5 | 6 | int foo_v1(void) { 7 | return 1; 8 | } 9 | 10 | int foo_v2(void) { 11 | return 2; 12 | } 13 | -------------------------------------------------------------------------------- /wild/tests/sources/symbol-versions-script.map: -------------------------------------------------------------------------------- 1 | VER_1.0 { 2 | global: 3 | bar_global; 4 | local: 5 | bar_local; 6 | }; 7 | 8 | VER_2.0 { 9 | bar_v2*; 10 | } VER_1.0; 11 | -------------------------------------------------------------------------------- /wild/tests/sources/symbol-versions.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:verdef 2 | //#RunEnabled:false 3 | //#DiffIgnore:.dynamic.DT_FLAGS* 4 | //#DiffIgnore:.dynamic.DT_RELA 5 | //#DiffIgnore:.dynamic.DT_RELAENT 6 | //#DiffIgnore:file-header.entry 7 | //#Object:runtime.c 8 | //#VersionScript:symbol-versions-script.map 9 | 10 | //#Config:verneed 11 | //#Object:runtime.c 12 | //#Object:symbol-versions-2.c 13 | //#ExpectSym: _start .text 14 | //#ExpectSym: exit_syscall .text 15 | //#EnableLinker:lld 16 | 17 | //#Config:verdef-0:verdef 18 | //#DiffIgnore:version_d.verdef_1 19 | //#LinkArgs:--shared 20 | 21 | //#Config:verdef-1:verdef 22 | //#LinkArgs:--shared --soname=symbol-versions.so 23 | 24 | #include "runtime.h" 25 | 26 | int foo(void); 27 | int bar_global(void); 28 | int bar_local(void); 29 | int bar_v2(void); 30 | int bar_v2_1(void); 31 | 32 | void _start(void) { 33 | runtime_init(); 34 | 35 | if (foo() != 2) { 36 | exit_syscall(foo()); 37 | } 38 | if (bar_global() != 10) { 39 | exit_syscall(bar_global()); 40 | } 41 | if (bar_local() != 11) { 42 | exit_syscall(bar_local()); 43 | } 44 | if (bar_v2() != 12) { 45 | exit_syscall(bar_v2()); 46 | } 47 | if (bar_v2_1() != 13) { 48 | exit_syscall(bar_v2_1()); 49 | } 50 | 51 | exit_syscall(42); 52 | } 53 | 54 | int bar_global(void) { 55 | return 10; 56 | } 57 | 58 | // TODO: doesn't work, the symbol is global 59 | int bar_local(void) { 60 | return 11; 61 | } 62 | 63 | int bar_v2(void) { 64 | return 12; 65 | } 66 | 67 | int bar_v2_1(void) { 68 | return 13; 69 | } 70 | -------------------------------------------------------------------------------- /wild/tests/sources/tls-local-exec.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#DiffIgnore:section.data 3 | //#DiffIgnore:section.data.alignment 4 | //#DiffIgnore:section.rodata 5 | //#DiffIgnore:section.rodata.alignment 6 | 7 | //#Config:pie:default 8 | //#CompArgs:-fpie 9 | //#LinkerDriver:gcc 10 | //#LinkArgs:-Wl,-z,now 11 | 12 | __thread long tvar = 1; 13 | __thread int tvar2 = 2; 14 | __thread char tvar3 = 3; 15 | 16 | int main() { 17 | // __builtin_printf ("%ld, %d, %d\n", tvar, tvar2, tvar3); 18 | return tvar + tvar2 + tvar3 + 36; 19 | } 20 | -------------------------------------------------------------------------------- /wild/tests/sources/tls-variant-1.c: -------------------------------------------------------------------------------- 1 | _Thread_local int global_tls1 = 1; 2 | 3 | int foo() { 4 | return global_tls1; 5 | } 6 | -------------------------------------------------------------------------------- /wild/tests/sources/tls-variant-2.c: -------------------------------------------------------------------------------- 1 | extern _Thread_local int global_tls1; 2 | 3 | int bar() { 4 | return global_tls1; 5 | } 6 | -------------------------------------------------------------------------------- /wild/tests/sources/tls-variant-3.c: -------------------------------------------------------------------------------- 1 | extern _Thread_local int global_tls1; 2 | 3 | int baz() { 4 | return global_tls1; 5 | } 6 | -------------------------------------------------------------------------------- /wild/tests/sources/tls-variant.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#LinkerDriver:gcc 3 | //#DiffIgnore:.dynamic.DT_NEEDED 4 | //#DiffIgnore:section.data 5 | //#DiffIgnore:section.rodata 6 | //#DiffIgnore:section.rodata.alignment 7 | //#DiffIgnore:rel.match_failed.R_AARCH64_TLSGD_ADR_PAGE21 8 | 9 | //#Config:gcc:default 10 | //#CompArgs:-fpic 11 | //#Object:tls-variant-1.c:-mtls-dialect=gnu2 12 | //#Object:tls-variant-2.c:-ftls-model=global-dynamic 13 | //#Object:tls-variant-3.c:-ftls-model=initial-exec 14 | //#Arch: x86_64 15 | 16 | //#Config:gcc-shared:default 17 | //#CompArgs:-fpic 18 | //#Shared:tls-variant-1.c:-mtls-dialect=gnu2,tls-variant-2.c:-ftls-model=global-dynamic,tls-variant-3.c:-ftls-model=initial-exec 19 | //#Arch: x86_64 20 | 21 | //#Config:gcc-aarch64:default 22 | //#CompArgs:-fpic 23 | //#Object:tls-variant-1.c 24 | //#Object:tls-variant-2.c:-ftls-model=global-dynamic -mtls-dialect=trad 25 | //#Object:tls-variant-3.c:-ftls-model=initial-exec -mtls-dialect=trad 26 | //#Arch: aarch64 27 | 28 | //#Config:gcc-shared-aarch64:default 29 | //#CompArgs:-fpic 30 | //#Shared:tls-variant-1.c,tls-variant-2.c:-ftls-model=global-dynamic -mtls-dialect=trad,tls-variant-3.c:-ftls-model=initial-exec -mtls-dialect=trad 31 | //#Arch: aarch64 32 | // Similarly to Mold, Wild also sets STATIC_TLS flag. 33 | //#DiffIgnore:.dynamic.DT_FLAGS.STATIC_TLS 34 | 35 | int foo(); 36 | int bar(); 37 | int baz(); 38 | 39 | int main() 40 | { 41 | if (foo() != 1) { 42 | return 1; 43 | } 44 | if (bar() != 1) { 45 | return 1; 46 | } 47 | if (baz() != 1) { 48 | return 1; 49 | } 50 | 51 | return 42; 52 | } 53 | -------------------------------------------------------------------------------- /wild/tests/sources/tls.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:tls1.c 3 | //#Object:init_tls.c 4 | //#Object:runtime.c 5 | //#Arch: x86_64 6 | 7 | //#Config:global-dynamic-0:default 8 | //#CompArgs:-ftls-model=global-dynamic 9 | //#Variant: 0 10 | 11 | //#Config:global-dynamic-1:global-dynamic-0 12 | //#Variant: 1 13 | 14 | //#Config:local-dynamic-0:default 15 | //#CompArgs:-ftls-model=local-dynamic 16 | //#Variant: 0 17 | 18 | //#Config:local-dynamic-1:local-dynamic-0 19 | //#Variant: 1 20 | 21 | //#Config:initial-exec:default 22 | //#CompArgs:-ftls-model=initial-exec 23 | 24 | //#Config:local-exec:default 25 | //#CompArgs:-ftls-model=local-exec 26 | 27 | #include "runtime.h" 28 | #include "init_tls.h" 29 | 30 | #include 31 | #include 32 | 33 | typedef uint8_t u8; 34 | typedef uint64_t u64; 35 | 36 | extern __thread int tvar1; 37 | __thread long long int tvar2 = 20; 38 | __thread char tvar3 = 12; 39 | 40 | // Make sure we have a couple of zero-initialised variables, since they go into TBSS rather than 41 | // TDATA. 42 | __thread int tvar4 = 0; 43 | static __thread int tvar5 = 0; 44 | __thread char tvar6 = 0; 45 | 46 | void _start(void) { 47 | runtime_init(); 48 | 49 | int ret = init_tls(0); 50 | if (ret != 0) { 51 | exit_syscall(ret); 52 | } 53 | exit_syscall(tvar1 + tvar2 + tvar3 + tvar4 + tvar5 + tvar6); 54 | } 55 | 56 | u8*** get_tcb(void); 57 | 58 | // When statically linking, glibc doesn't provide __tls_get_addr, however musl does. So we need to 59 | // make sure we work in either case. 60 | 61 | #if VARIANT == 1 62 | void* __tls_get_addr(size_t* mod_and_offset) { 63 | size_t mod = mod_and_offset[0]; 64 | size_t offset = mod_and_offset[1]; 65 | u8*** tcb = get_tcb(); 66 | u8** modules = tcb[1]; 67 | u8* module_data = modules[mod]; 68 | return &module_data[offset]; 69 | } 70 | #endif 71 | -------------------------------------------------------------------------------- /wild/tests/sources/tls1.c: -------------------------------------------------------------------------------- 1 | __thread int tvar1 = 10; 2 | -------------------------------------------------------------------------------- /wild/tests/sources/tlsdesc-obj.c: -------------------------------------------------------------------------------- 1 | _Thread_local long g1 = 1; 2 | static _Thread_local long l2 = 2; 3 | static _Thread_local long l3 = 3; 4 | static _Thread_local long l4 = 4; 5 | _Thread_local long g5 = 5; 6 | _Thread_local long g6 = 6; 7 | _Thread_local long g7 = 7; 8 | static _Thread_local long bss1 = 0; 9 | _Thread_local long bss2 = 0; 10 | 11 | int get_value() { 12 | bss1 = 6; 13 | bss2 = 8; 14 | 15 | return g1 + l2 + l3 + l4 + g5 + g6 + g7 + bss1 + bss2; 16 | } 17 | -------------------------------------------------------------------------------- /wild/tests/sources/tlsdesc.c: -------------------------------------------------------------------------------- 1 | // TODO: remove DiffIgnore for asm.* once relaxations are supported 2 | 3 | //#AbstractConfig:default 4 | //#DiffIgnore:.dynamic.DT_NEEDED 5 | //#DiffIgnore:section.data 6 | //#DiffIgnore:section.rodata 7 | 8 | //#Config:gcc-tls-desc:default 9 | //#CompArgs:-mtls-dialect=gnu2 10 | //#LinkerDriver:gcc 11 | //#LinkArgs:-Wl,-z,now 12 | //#Object:tlsdesc-obj.c 13 | //#Arch: x86_64 14 | 15 | //#Config:gcc-tls-desc-aarch64:gcc-tls-desc 16 | //#CompArgs:-mtls-dialect=desc 17 | //#Arch: aarch64 18 | 19 | //#Config:gcc-tls-desc-pie:gcc-tls-desc 20 | //#CompArgs:-mtls-dialect=gnu2 -fPIE 21 | //#Arch: x86_64 22 | 23 | //#Config:gcc-tls-desc-pie-aarch64:gcc-tls-desc-pie 24 | //#CompArgs:-mtls-dialect=desc 25 | //#Arch: aarch64 26 | 27 | //#Config:gcc-tls-desc-static:gcc-tls-desc 28 | //#CompArgs:-mtls-dialect=gnu2 -fPIC -static 29 | //#Shared:tlsdesc-obj.c 30 | //#DiffIgnore:asm.get_value 31 | //#Arch: x86_64 32 | 33 | //#Config:gcc-tls-desc-shared:gcc-tls-desc 34 | //#CompArgs:-mtls-dialect=gnu2 -fPIC 35 | //#Shared:tlsdesc-obj.c 36 | //#Arch: x86_64 37 | 38 | //#Config:gcc-tls-desc-shared-aarch64:gcc-tls-desc-shared 39 | //#CompArgs:-mtls-dialect=desc 40 | //#Arch: aarch64 41 | 42 | //#Config:clang-tls-desc:gcc-tls-desc 43 | //#CompArgs:-mtls-dialect=gnu2 44 | //#Compiler:clang 45 | //#RequiresClangWithTlsDesc:true 46 | //#Arch: x86_64 47 | 48 | //#Config:clang-tls-desc-aarch64:clang-tls-desc 49 | //#CompArgs:-mtls-dialect=desc 50 | //#Arch: aarch64 51 | 52 | //#Config:clang-tls-desc-shared:clang-tls-desc 53 | //#CompArgs:-mtls-dialect=gnu2 -fPIC 54 | //#Shared:tlsdesc-obj.c 55 | //#Arch: x86_64 56 | 57 | //#Config:clang-tls-desc-shared-aarch64:clang-tls-desc-shared 58 | //#CompArgs:-mtls-dialect=desc 59 | //#Arch: aarch64 60 | 61 | int get_value(); 62 | 63 | int main() 64 | { 65 | return get_value(); 66 | } 67 | -------------------------------------------------------------------------------- /wild/tests/sources/trivial-dynamic-2.c: -------------------------------------------------------------------------------- 1 | int foo(void) { 2 | return 10; 3 | } 4 | -------------------------------------------------------------------------------- /wild/tests/sources/trivial-dynamic.c: -------------------------------------------------------------------------------- 1 | //#Config:default 2 | //#Object:runtime.c 3 | //#EnableLinker:lld 4 | //#Static:false 5 | //#LinkArgs:-z now 6 | //#Shared:trivial-dynamic-2.c 7 | //#EnableLinker:lld 8 | // We're linking different .so files, so this is expected. 9 | //#DiffIgnore:.dynamic.DT_NEEDED 10 | // We put a GLOB_DAT in .rela.dyn, other linkers use a JUMP_SLOT in .rela.plt. 11 | //#DiffIgnore:.dynamic.DT_RELA 12 | //#DiffIgnore:.dynamic.DT_RELAENT 13 | // On aarch64, GNU ld seems to emit a GOT in the shared object even though it isn't needed. 14 | //#DiffIgnore:section.got 15 | 16 | //#Config:origin:default 17 | //#LinkArgs:-z now -z origin 18 | 19 | //#Config:nodelete:default 20 | //#LinkArgs:-z now -z nodelete 21 | 22 | //#Config:symbolic:default 23 | //#LinkArgs:-z now -Bsymbolic 24 | // TODO: Set these 25 | //#DiffIgnore:.dynamic.DT_FLAGS.SYMBOLIC 26 | //#DiffIgnore:.dynamic.DT_SYMBOLIC 27 | 28 | //#Config:export-dynamic:default 29 | //#LinkArgs:-z now --export-dynamic 30 | //#ExpectDynSym: runtime_init 31 | // Ld is acting weird and comparing to LLD is not possible right when when cross compiling: #804 32 | //#Cross:false 33 | 34 | #include "runtime.h" 35 | 36 | int foo(void); 37 | 38 | typedef int(*get_int_fn_t)(void); 39 | 40 | get_int_fn_t foo_ptr = foo; 41 | 42 | void _start(void) { 43 | runtime_init(); 44 | 45 | if (foo() != 10) { 46 | exit_syscall(20); 47 | } 48 | 49 | if (foo_ptr() != 10) { 50 | exit_syscall(21); 51 | } 52 | 53 | exit_syscall(42); 54 | } 55 | -------------------------------------------------------------------------------- /wild/tests/sources/trivial-main.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#LinkerDriver:gcc 3 | //#LinkArgs:-Wl,-z,now 4 | //#DiffIgnore:section.rodata 5 | //#DiffIgnore:section.data 6 | 7 | //#Config:gcc:default 8 | 9 | //#Config:clang:default 10 | //#Compiler: clang 11 | 12 | int main() 13 | { 14 | return 42; 15 | } 16 | -------------------------------------------------------------------------------- /wild/tests/sources/trivial.c: -------------------------------------------------------------------------------- 1 | //#Object:runtime.c 2 | //#ExpectSym: _start .text 3 | //#ExpectSym: exit_syscall .text 4 | //#EnableLinker:lld 5 | //#LinkArgs: -v 6 | 7 | #include "runtime.h" 8 | 9 | void _start(void) { 10 | runtime_init(); 11 | exit_syscall(42); 12 | } 13 | -------------------------------------------------------------------------------- /wild/tests/sources/trivial_asm.s: -------------------------------------------------------------------------------- 1 | //#LinkArgs:-z noexecstack 2 | //#Object:runtime.c 3 | //#Arch: x86_64 4 | 5 | .section .data.foo 6 | .p2align 4, 0x0 7 | foo: 8 | .quad 3 9 | 10 | 11 | 12 | .section .data.rel.ro,"aM",@progbits,16 13 | .p2align 4, 0x0 14 | 15 | .type .Ldata0, @object 16 | .Ldata0: 17 | .quad foo 18 | .size .Ldata0, .-.Ldata0 19 | 20 | .type .Ldata1, @object 21 | .Ldata1: 22 | .quad 7 23 | .size .Ldata1, .-.Ldata1 24 | 25 | 26 | .section .text, "ax", @progbits 27 | .align 8 28 | 29 | .globl _start 30 | .type _start, @function 31 | _start: 32 | mov $101, %rdi 33 | mov .Ldata0@GOTPCREL(%rip), %eax 34 | mov (%rax), %rax 35 | mov (%rax), %rax 36 | cmp $3, %rax 37 | jne exit_syscall 38 | 39 | mov $42, %rdi 40 | call exit_syscall 41 | .size _start, .-_start 42 | -------------------------------------------------------------------------------- /wild/tests/sources/undefined_symbols.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | 3 | //#Config:shared-lib:default 4 | //#LinkArgs:--shared -z now 5 | //#RunEnabled:false 6 | //#DiffIgnore:.dynamic.DT_RELA* 7 | //#DiffIgnore:file-header.entry 8 | 9 | //#Config:no-undefined:default 10 | //#LinkArgs:--shared --no-undefined 11 | //#ExpectError: undefined_strong 12 | 13 | //#Config:executable:default 14 | //#CompArgs:-g 15 | //#ExpectError: undefined_strong 16 | //#ExpectError:undefined_symbols.c 17 | 18 | int undefined_strong(); 19 | __attribute__((weak)) int undefined_weak(); 20 | 21 | void _start(void) { 22 | undefined_weak(); 23 | undefined_strong(); 24 | } 25 | -------------------------------------------------------------------------------- /wild/tests/sources/visibility-merging-1.c: -------------------------------------------------------------------------------- 1 | // Even though the definition of `data1` from the main file is given priority over this definition, 2 | // the fact that this definition is hidden causes the symbol to be hidden. 3 | int data1 __attribute__ ((weak, visibility(("hidden")))) = 0x100; 4 | 5 | // Similarly, this symbol makes the symbol of the same name from file protected. 6 | int data3 __attribute__ ((weak, visibility(("protected")))) = 0x55; 7 | 8 | int data4 __attribute__ ((weak, visibility(("hidden")))) = 0x99; 9 | -------------------------------------------------------------------------------- /wild/tests/sources/visibility-merging.c: -------------------------------------------------------------------------------- 1 | //#Static:false 2 | //#LinkArgs:-shared -z now 3 | //#Object:visibility-merging-1.c 4 | //#RunEnabled:false 5 | //#DiffIgnore:section.got 6 | // TODO: Prevent dynsym export of symbols like these. 7 | //#DiffIgnore:dynsym.data1.* 8 | //#DiffIgnore:dynsym.data4.* 9 | 10 | // This symbol is included, but isn't exported as a dynamic symbol because of a second definition in 11 | // our other file that's marked as hidden. 12 | int data1 __attribute__ ((weak)) = 0x42; 13 | 14 | // This symbol is exported. 15 | int data2 __attribute__ ((weak)) = 0x88888888; 16 | 17 | // This symbol is exported, but is protected, since the definition in the second file is. 18 | int data3 __attribute__ ((weak)) = 0x55; 19 | 20 | // Protected here and hidden in our second object. Hidden should take priority. 21 | int data4 __attribute__ ((weak, visibility(("protected")))) = 0x99; 22 | 23 | // Make sure that direct references to `data1` work on account of it being hidden. 24 | int get_data1(void) { 25 | return data1; 26 | } 27 | 28 | // Note, we don't check direct references to `data2` and `data3`. In the case of `data2`, direct 29 | // references wouldn't be permitted, since the symbol is interposable. In the case of `data3`, the 30 | // symbol will be protected, so direct references should be permitted, however GNU ld < 2.40 would 31 | // error in the case of direct references to protected symbols, so in order to allow our tests to 32 | // pass with such versions, we don't. 33 | -------------------------------------------------------------------------------- /wild/tests/sources/weak-fns-archive.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Archive:weak-fns1.c 3 | //#Object:runtime.c 4 | //#CompArgs:-fno-stack-protector 5 | 6 | #include "runtime.h" 7 | 8 | #if (VARIANT & 1) != 0 9 | int __attribute__ ((weak)) weak_fn1(void) { 10 | return 2; // 64 11 | } 12 | int __attribute__ ((weak)) weak_fn2(void) { 13 | return 8; 14 | } 15 | int __attribute__ ((weak)) weak_fn3(void) { 16 | return 0; // 2 17 | } 18 | int __attribute__ ((weak)) weak_fn4(void) { 19 | return 1; // 4 20 | } 21 | int __attribute__ ((weak)) weak_fn5(void) { 22 | return 16; 23 | } 24 | #else 25 | int __attribute__ ((weak)) weak_fn1(void); 26 | int __attribute__ ((weak)) weak_fn2(void); 27 | int __attribute__ ((weak)) weak_fn3(void); 28 | int __attribute__ ((weak)) weak_fn4(void); 29 | int __attribute__ ((weak)) weak_fn5(void); 30 | #endif 31 | 32 | int strong_fn1(); 33 | int strong_fn2(); 34 | 35 | void _start() { 36 | runtime_init(); 37 | int value = 0; 38 | if (&weak_fn1) { 39 | value += weak_fn1(); 40 | } 41 | if (&weak_fn2) { 42 | value += weak_fn2(); 43 | } 44 | if (&weak_fn3) { 45 | // This is different in that it doesn't reference the weak variable in the block, which 46 | // means it optimises differently. 47 | value += 32; 48 | } 49 | if (weak_fn4) { 50 | value += weak_fn4(); 51 | } 52 | if (weak_fn5) { 53 | value += weak_fn5(); 54 | } 55 | 56 | #if (VARIANT & 2) != 0 57 | value += strong_fn1(); 58 | #endif 59 | #if (VARIANT & 4) != 0 60 | value += strong_fn2(); 61 | #endif 62 | 63 | // Variant bits: 64 | // 1: Weak Functions in this file also have definitions 65 | // 2: Reference to strong fn in other file 66 | // 4: Reference to strong fn in other file 67 | // 8: Functions weakly defined in other file 68 | // 16: Functions undefined in second file 69 | 70 | int expected[24] = { 71 | //#Config:0:default 72 | //#Variant: 0 73 | // Functions defined strongly in second file, no strong refs 74 | 0, 75 | //#Config:1:default 76 | //#Variant: 1 77 | // Functions defined weakly here then strongly in second, no strong refs 78 | 2 + 8 + 32 + 1 + 16, 79 | //#Config:2:default 80 | //#Variant: 2 81 | // Functions defined weakly here then strongly in second, strong ref 82 | 64 + 32 + 4 + 128, 83 | // 3 84 | 0, 85 | // 4 86 | 0, 87 | // 5 88 | 0, 89 | // 6 90 | 0, 91 | // 7 92 | 0, 93 | //#Config:8:default 94 | //#Variant: 8 95 | // Functions defined weakly in second file, no strong refs 96 | 0, 97 | //#Config:9:default 98 | //#Variant: 9 99 | // Functions defined weakly here and in second file, no strong refs 100 | 2 + 8 + 32 + 1 + 16, 101 | //#Config:10:default 102 | //#Variant: 10 103 | // Functions defined weakly in second file, strong ref 104 | 64 + 32 + 4 + 128, 105 | // 11 106 | 0, 107 | //#Config:12:default 108 | //#Variant: 12 109 | // Functions defined weakly here and in second file, strong ref 110 | 64 + 32 + 4, 111 | // 13 112 | 0, 113 | // 14 114 | 0, 115 | // 15 116 | 0, 117 | //#Config:16:default 118 | //#Variant: 16 119 | // No weak functions defined in either file 120 | 0, 121 | //#Config:17:default 122 | //#Variant: 17 123 | // Functions weakly defined in this file only. 124 | 2 + 8 + 32 + 1 + 16, 125 | // 18 126 | 0, 127 | // 19 128 | 0, 129 | // 20 130 | 0, 131 | // 21 132 | 0, 133 | // 22 134 | 0, 135 | // 23 136 | 0 137 | }; 138 | 139 | if (value == 42) { 140 | value = 127; 141 | } else if (value == expected[VARIANT]) { 142 | value = 42; 143 | } 144 | 145 | exit_syscall(value); 146 | } 147 | -------------------------------------------------------------------------------- /wild/tests/sources/weak-fns.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:weak-fns1.c 3 | //#Object:runtime.c 4 | //#CompArgs:-fno-stack-protector 5 | 6 | #include "runtime.h" 7 | 8 | #if (VARIANT & 1) != 0 9 | int __attribute__ ((weak)) weak_fn1(void) { 10 | return 2; // 64 11 | } 12 | int __attribute__ ((weak)) weak_fn2(void) { 13 | return 8; 14 | } 15 | int __attribute__ ((weak)) weak_fn3(void) { 16 | return 0; // 2 17 | } 18 | int __attribute__ ((weak)) weak_fn4(void) { 19 | return 1; // 4 20 | } 21 | int __attribute__ ((weak)) weak_fn5(void) { 22 | return 16; 23 | } 24 | #else 25 | int __attribute__ ((weak)) weak_fn1(void); 26 | int __attribute__ ((weak)) weak_fn2(void); 27 | int __attribute__ ((weak)) weak_fn3(void); 28 | int __attribute__ ((weak)) weak_fn4(void); 29 | int __attribute__ ((weak)) weak_fn5(void); 30 | #endif 31 | 32 | int strong_fn1(); 33 | int strong_fn2(); 34 | 35 | void _start() { 36 | runtime_init(); 37 | 38 | int value = 0; 39 | if (&weak_fn1) { 40 | value += weak_fn1(); 41 | } 42 | if (&weak_fn2) { 43 | value += weak_fn2(); 44 | } 45 | if (&weak_fn3) { 46 | // This is different in that it doesn't reference the weak variable in the block, which 47 | // means it optimises differently. 48 | value += 32; 49 | } 50 | if (weak_fn4) { 51 | value += weak_fn4(); 52 | } 53 | if (weak_fn5) { 54 | value += weak_fn5(); 55 | } 56 | 57 | #if (VARIANT & 2) != 0 58 | value += strong_fn1(); 59 | #endif 60 | #if (VARIANT & 4) != 0 61 | value += strong_fn2(); 62 | #endif 63 | 64 | // Variant bits: 65 | // 1: Weak Functions in this file also have definitions 66 | // 2: Reference to strong fn in other file 67 | // 4: Reference to strong fn in other file 68 | // 8: Functions weakly defined in other file 69 | // 16: Functions undefined in second file 70 | 71 | int expected[24] = { 72 | //#Config:0:default 73 | //#Variant: 0 74 | // Functions defined strongly in second file, no strong refs 75 | 64 + 32 + 4, 76 | //#Config:1:default 77 | //#Variant: 1 78 | // Functions defined weakly here then strongly in second, no strong refs 79 | 64 + 8 + 32 + 4 + 16, 80 | //#Config:2:default 81 | //#Variant: 2 82 | // Functions defined weakly here then strongly in second, strong ref 83 | 64 + 32 + 4 + 128, 84 | // 3 85 | 0, 86 | // 4 87 | 0, 88 | // 5 89 | 0, 90 | // 6 91 | 0, 92 | // 7 93 | 0, 94 | //#Config:8:default 95 | //#Variant: 8 96 | // Functions defined weakly in second file, no strong refs 97 | 64 + 32 + 4, 98 | //#Config:9:default 99 | //#Variant: 9 100 | // Functions defined weakly here and in second file, no strong refs 101 | 2 + 8 + 32 + 1 + 16, 102 | //#Config:10:default 103 | //#Variant: 10 104 | // Functions defined weakly in second file, strong ref 105 | 64 + 32 + 4 + 128, 106 | // 11 107 | 0, 108 | //#Config:12:default 109 | //#Variant: 12 110 | // Functions defined weakly here and in second file, strong ref 111 | 64 + 32 + 4, 112 | // 13 113 | 0, 114 | // 14 115 | 0, 116 | // 15 117 | 0, 118 | //#Config:16:default 119 | //#Variant: 16 120 | // No weak functions defined in either file 121 | 0, 122 | //#Config:17:default 123 | //#Variant: 17 124 | // Functions weakly defined in this file only. 125 | 2 + 8 + 32 + 1 + 16, 126 | // 18 127 | 0, 128 | // 19 129 | 0, 130 | // 20 131 | 0, 132 | // 21 133 | 0, 134 | // 22 135 | 0, 136 | // 23 137 | 0 138 | }; 139 | 140 | if (value == 42) { 141 | value = 127; 142 | } else if (value == expected[VARIANT]) { 143 | value = 42; 144 | } 145 | 146 | exit_syscall(value); 147 | } 148 | -------------------------------------------------------------------------------- /wild/tests/sources/weak-fns1.c: -------------------------------------------------------------------------------- 1 | int return_8(void) { 2 | return 8; 3 | } 4 | 5 | #if (VARIANT & 16) == 0 6 | #if (VARIANT & 8) != 0 7 | int __attribute__ ((weak)) weak_fn1(void) { 8 | return 64; 9 | } 10 | int __attribute__ ((weak)) weak_fn4(void) { 11 | return 4; 12 | } 13 | int __attribute__ ((weak)) weak_fn3(void) { 14 | return 2; 15 | } 16 | #else 17 | int weak_fn1(void) { 18 | return 64; 19 | }; 20 | int weak_fn4(void) { 21 | return 4; 22 | } 23 | int weak_fn3(void) { 24 | return return_8(); 25 | } 26 | #endif 27 | #endif 28 | 29 | int strong_fn1(void) { 30 | return 128; 31 | } 32 | int strong_fn2(void) { 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /wild/tests/sources/weak-vars-archive.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Archive:weak-vars1.c 3 | //#Object:runtime.c 4 | //#CompArgs:-fno-stack-protector 5 | 6 | #include "runtime.h" 7 | 8 | #if (VARIANT & 1) != 0 9 | int weak_var1 __attribute__ ((weak)) = 2; // 64 10 | int weak_var2 __attribute__ ((weak)) = 8; 11 | int weak_var3 __attribute__ ((weak)) = 0; // 2 12 | int weak_arr1[4] __attribute__ ((weak)) = {1, 1, 1, 1}; // 4 13 | int weak_arr2[4] __attribute__ ((weak)) = {16, 16, 16, 16}; 14 | #else 15 | extern int weak_var1 __attribute__ ((weak)); 16 | extern int weak_var2 __attribute__ ((weak)); 17 | extern int weak_var3 __attribute__ ((weak)); 18 | extern int weak_arr1[4] __attribute__ ((weak)); 19 | extern int weak_arr2[4] __attribute__ ((weak)); 20 | #endif 21 | 22 | extern int strong_var1; // 128 23 | extern int strong_var2; 24 | 25 | void _start() { 26 | runtime_init(); 27 | 28 | int value = 0; 29 | if (&weak_var1) { 30 | value += weak_var1; 31 | } 32 | if (&weak_var2) { 33 | value += weak_var2; 34 | } 35 | if (&weak_var3) { 36 | // This is different in that it doesn't reference the weak variable in the block, which 37 | // means it optimises differently. 38 | value += 32; 39 | } 40 | if (weak_arr1) { 41 | value += weak_arr1[2]; 42 | } 43 | if (weak_arr2) { 44 | value += weak_arr2[2]; 45 | } 46 | 47 | // Referencing a strong variable that's defined in the same object as our weak symbols can 48 | // affect what definition gets used. We have two strong variables, one in DATA, the same as our 49 | // weak variables, the other in BSS. We only ever want one or the other, but we also want to try 50 | // neither, so we use a bit for each. 51 | #if (VARIANT & 2) != 0 52 | value += strong_var1; 53 | #endif 54 | #if (VARIANT & 4) != 0 55 | value += strong_var2; 56 | #endif 57 | 58 | // Variant bits: 59 | // 1: Weak variables in this file also have definitions 60 | // 2: Reference to strong var in DATA in other file 61 | // 4: Reference to strong var in BSS in other file 62 | // 8: Variables weakly defined in other file 63 | // 16: Variables undefined in second file 64 | 65 | int expected[24] = { 66 | //#Config:0:default 67 | //#Variant: 0 68 | // Variables defined strongly in second file, no strong refs 69 | 0, 70 | //#Config:1:default 71 | //#Variant: 1 72 | // Variables defined weakly here then strongly in second, no strong refs 73 | 2 + 8 + 32 + 1 + 16, 74 | //#Config:2:default 75 | //#Variant: 2 76 | // Variables defined strongly in second file, strong ref to DATA 77 | 64 + 32 + 4 + 128, 78 | //#Config:3:default 79 | //#Variant: 3 80 | // Variables defined weakly here then strongly in second, strong ref to DATA 81 | 64 + 8 + 32 + 4 + 16 + 128, 82 | // 4 83 | 0, 84 | // 5 85 | 0, 86 | // 6 87 | 0, 88 | // 7 89 | 0, 90 | //#Config:8:default 91 | //#Variant: 8 92 | // Variables defined weakly in second file, no strong refs 93 | 0, 94 | //#Config:9:default 95 | //#Variant: 9 96 | // Variables defined weakly here and in second file, no strong refs 97 | 2 + 8 + 32 + 1 + 16, 98 | //#Config:10:default 99 | //#Variant: 10 100 | // Variables defined weakly in second file, strong ref to DATA 101 | 64 + 32 + 4 + 128, 102 | // 11 103 | 0, 104 | //#Config:12:default 105 | //#Variant: 12 106 | // Variables defined weakly here and in second file, strong ref to BSS 107 | 64 + 32 + 4, 108 | // 13 109 | 0, 110 | // 14 111 | 0, 112 | // 15 113 | 0, 114 | //#Config:16:default 115 | //#Variant: 16 116 | // No weak variables defined in either file 117 | 0, 118 | //#Config:17:default 119 | //#Variant: 17 120 | // Variables weakly defined in this file only. 121 | 2 + 8 + 32 + 1 + 16, 122 | // 18 123 | 0, 124 | // 19 125 | 0, 126 | // 20 127 | 0, 128 | // 21 129 | 0, 130 | // 22 131 | 0, 132 | // 23 133 | 0, 134 | // 24 135 | }; 136 | 137 | if (value == 42) { 138 | value = 127; 139 | } else if (value == expected[VARIANT]) { 140 | value = 42; 141 | } 142 | 143 | exit_syscall(value); 144 | } 145 | -------------------------------------------------------------------------------- /wild/tests/sources/weak-vars.c: -------------------------------------------------------------------------------- 1 | //#AbstractConfig:default 2 | //#Object:weak-vars1.c 3 | //#Object:runtime.c 4 | //#CompArgs:-ffreestanding -fno-builtin -fno-stack-protector 5 | 6 | #include "runtime.h" 7 | 8 | #if (VARIANT & 1) != 0 9 | int weak_var1 __attribute__ ((weak)) = 2; // 64 10 | int weak_var2 __attribute__ ((weak)) = 8; 11 | int weak_var3 __attribute__ ((weak)) = 0; // 2 12 | int weak_arr1[4] __attribute__ ((weak)) = {1, 1, 1, 1}; // 4 13 | int weak_arr2[4] __attribute__ ((weak)) = {16, 16, 16, 16}; 14 | #else 15 | extern int weak_var1 __attribute__ ((weak)); 16 | extern int weak_var2 __attribute__ ((weak)); 17 | extern int weak_var3 __attribute__ ((weak)); 18 | extern int weak_arr1[4] __attribute__ ((weak)); 19 | extern int weak_arr2[4] __attribute__ ((weak)); 20 | #endif 21 | 22 | extern int strong_var1; 23 | extern int strong_var2; 24 | 25 | void _start() { 26 | runtime_init(); 27 | 28 | int value = 0; 29 | if (&weak_var1) { 30 | value += weak_var1; 31 | } 32 | if (&weak_var2) { 33 | value += weak_var2; 34 | } 35 | if (&weak_var3) { 36 | // This is different in that it doesn't reference the weak variable in the block, which 37 | // means it optimises differently. 38 | value += 32; 39 | } 40 | if (weak_arr1) { 41 | value += weak_arr1[2]; 42 | } 43 | if (weak_arr2) { 44 | value += weak_arr2[2]; 45 | } 46 | 47 | // Referencing a strong variable that's defined in the same object as our weak symbols can 48 | // affect what definition gets used. We have two strong variables, one in DATA, the same as our 49 | // weak variables, the other in BSS. We only ever want one or the other, but we also want to try 50 | // neither, so we use a bit for each. 51 | #if (VARIANT & 2) != 0 52 | value += strong_var1; 53 | #endif 54 | #if (VARIANT & 4) != 0 55 | value += strong_var2; 56 | #endif 57 | 58 | // Variant bits: 59 | // 1: Weak variables in this file also have definitions 60 | // 2: Reference to strong var in DATA in other file 61 | // 4: Reference to strong var in BSS in other file 62 | // 8: Variables weakly defined in other file 63 | // 16: Variables undefined in second file 64 | 65 | int expected[24] = { 66 | //#Config:0:default 67 | //#Variant: 0 68 | // Variables defined strongly in second file, no strong refs 69 | 64 + 32 + 4, 70 | //#Config:1:default 71 | //#Variant: 1 72 | // Variables defined weakly here then strongly in second, no strong refs 73 | 64 + 8 + 32 + 4 + 16, 74 | //#Config:2:default 75 | //#Variant: 2 76 | // Variables defined weakly here then strongly in second, strong ref to DATA 77 | 64 + 32 + 4 + 128, 78 | // 3 79 | 0, 80 | // 4 81 | 0, 82 | // 5 83 | 0, 84 | // 6 85 | 0, 86 | // 7 87 | 0, 88 | //#Config:8:default 89 | //#Variant: 8 90 | // Variables defined weakly in second file, no strong refs 91 | 64 + 32 + 4, 92 | //#Config:9:default 93 | //#Variant: 9 94 | // Variables defined weakly here and in second file, no strong refs 95 | 2 + 8 + 32 + 1 + 16, 96 | //#Config:10:default 97 | //#Variant: 10 98 | // Variables defined weakly in second file, strong ref to DATA 99 | 64 + 32 + 4 + 128, 100 | // 11 101 | 0, 102 | //#Config:12:default 103 | //#Variant: 12 104 | // Variables defined weakly here and in second file, strong ref to BSS 105 | 64 + 32 + 4, 106 | // 13 107 | 0, 108 | // 14 109 | 0, 110 | // 15 111 | 0, 112 | //#Config:16:default 113 | //#Variant: 16 114 | // No weak variables defined in either file 115 | 0, 116 | //#Config:17:default 117 | //#Variant: 17 118 | // Variables weakly defined in this file only. 119 | 2 + 8 + 32 + 1 + 16, 120 | // 18 121 | 0, 122 | // 19 123 | 0, 124 | // 20 125 | 0, 126 | // 21 127 | 0, 128 | // 22 129 | 0, 130 | // 23 131 | 0, 132 | // 24 133 | }; 134 | 135 | if (value == 42) { 136 | value = 127; 137 | } else if (value == expected[VARIANT]) { 138 | value = 42; 139 | } 140 | 141 | exit_syscall(value); 142 | } 143 | -------------------------------------------------------------------------------- /wild/tests/sources/weak-vars1.c: -------------------------------------------------------------------------------- 1 | #if (VARIANT & 16) == 0 2 | #if (VARIANT & 8) != 0 3 | int weak_var1 __attribute__ ((weak)) = 64; 4 | int weak_arr1[] __attribute__ ((weak)) = {4, 4, 4, 4}; 5 | int weak_var3 __attribute__ ((weak)) = 2; 6 | #else 7 | int weak_var1 = 64; 8 | int weak_arr1[4] = {4, 4, 4, 4}; 9 | int weak_var3 = 8; 10 | #endif 11 | #endif 12 | 13 | int strong_var1 = 128; 14 | int strong_var2 = 0; 15 | -------------------------------------------------------------------------------- /wild/tests/sources/whole_archive.c: -------------------------------------------------------------------------------- 1 | //#Object:runtime.c 2 | //#LinkArgs:--whole-archive 3 | //#Archive:whole_archive0.c 4 | //#Cross:false 5 | 6 | #include "runtime.h" 7 | 8 | extern int __start_foo[]; 9 | extern int __stop_foo[]; 10 | 11 | void _start(void) { 12 | runtime_init(); 13 | 14 | int value = 0; 15 | 16 | for (int *foo = __start_foo; foo < __stop_foo; ++foo) { 17 | value += *foo; 18 | } 19 | 20 | exit_syscall(value); 21 | } 22 | -------------------------------------------------------------------------------- /wild/tests/sources/whole_archive0.c: -------------------------------------------------------------------------------- 1 | static int foo1 __attribute__ ((used, retain, section ("foo"))) = 42; 2 | --------------------------------------------------------------------------------