Compare commits

..
609 Commits
Author SHA1 Message Date
Rui Ueyama 57f5c77174 ARC 2025-04-15 16:49:41 +09:00
Rui Ueyama ac6f1ec18e Do not hide foo@@VER if both foo@@VER and foo are defined
Fixes https://github.com/rui314/mold/issues/1442
2025-04-15 12:28:31 +09:00
Rui Ueyama d19f822ec0 Simplify 2025-04-15 11:51:11 +09:00
Rui Ueyama a97a628ee8 Resolve versioned symbols early
Fixes https://github.com/rui314/mold/issues/1440
2025-04-11 14:46:27 +09:00
Rui Ueyama 2888aade4c Refactor 2025-04-10 12:59:13 +09:00
Rui Ueyama 0172f22ab5 Refactor 2025-04-07 17:25:28 +09:00
Rui Ueyama 418d4ff62a Fix CI 2025-04-07 11:26:18 +09:00
Rui Ueyama 5249edc57f Simplify 2025-04-07 10:37:08 +09:00
Rui Ueyama 1870845f88 Refactor 2025-04-07 09:07:12 +09:00
Rui Ueyama d15c41a8dd Process --repro early
If mold crashes before --repro is processed, no .repro.tar file is
created. So we want to process the option as early as possible.
2025-04-07 08:50:26 +09:00
Rui Ueyama b7c8a843b2 Ignore --mmap-output-file
For the sake of completeness, --mmap-output-file is ignored just like
--no-mmap-output-file is.
2025-04-03 16:57:37 +09:00
Rui Ueyama 9b77a7ea86 Fix CI 2025-04-03 16:02:20 +09:00
Rui Ueyama af396ad5d9 Add --audit and --depaudit
These are for the sake of compatibility with GNU ld.
2025-04-03 15:36:05 +09:00
Rui Ueyama a2ee7371d5 Handle -mllvm as a synonym for -plugin-opt
Related to https://github.com/rui314/mold/pull/1437
2025-04-03 09:26:17 +09:00
Rui Ueyama c9812cf09f Merge pull request #1436 from christycylee/ignore_no_mmap_output_file
Ignore no-mmap-output-file for compatibility.
2025-04-03 08:59:31 +09:00
Christy Lee e9c070a3c2 Ignore no-mmap-output-file for compatibility. 2025-04-02 11:39:53 -07:00
Rui Ueyama 02e75be9fa Simplify 2025-04-02 21:55:17 +09:00
Rui Ueyama d5609a9bb8 Fix CI 2025-04-02 18:28:37 +09:00
Rui Ueyama 5d24db5a64 Handle COMDAT symbols in LTO IR object files
Before 9306f69a61, we completely ignored
duplciate symbol errors in IR object files. After that commit, we've
been accidentally reporting duplicate comdat symbols as errors.

Fixes https://github.com/rui314/mold/issues/1434
2025-04-02 17:54:41 +09:00
Rui Ueyama aeec8baee5 Revert "Take r_delta into account when computing distance"
This reverts commit b74a771886
because it broke buildbots.
2025-04-02 10:48:57 +09:00
Rui Ueyama b74a771886 Take r_delta into account when computing distance 2025-04-01 16:56:21 +09:00
Rui Ueyama 0124058ef7 Refactor 2025-03-26 20:56:04 +09:00
Rui Ueyama e302a2509c Fix CI 2025-03-26 20:56:02 +09:00
Rui Ueyama f70db66373 Use std::ranges 2025-03-26 20:26:51 +09:00
Rui Ueyama 9c0cbc8e6c Fix R_AARCH64_PREL16 range 2025-03-25 18:01:38 +09:00
Rui Ueyama a9acbd6b6f Refactor 2025-03-24 16:03:52 +09:00
Rui Ueyama b9d65a5787 Update RV32 toolchain 2025-03-24 15:40:06 +09:00
Rui Ueyama 50766f1ad4 Remove endbr32 support
It looks like Linux is going to support CET only on x86-64 (i.e. endbr64),
and it is very unlikely that x86-32 will get CET support. So I'll remove
the instruction from the code we generate for x86-32.
2025-03-23 18:07:21 +09:00
Rui Ueyama 3496ad1291 Update comments 2025-03-22 13:39:54 +09:00
Rui Ueyama 8a3d374251 Simplify 2025-03-22 11:20:07 +09:00
Rui Ueyama 96b2710849 Fix CI 2025-03-20 16:32:29 +09:00
Rui Ueyama a43f3953f0 Do not create redundant range extension thunks
Previously, we created thunks for all PLT function calls.
2025-03-20 13:25:05 +09:00
Rui Ueyama 2bc6ddc41c Refactor 2025-03-19 11:18:15 +09:00
Rui Ueyama f6575d53c1 Report an error if CREL contains addends for REL-type targets
Fixes https://github.com/rui314/mold/issues/1430
2025-03-18 10:52:55 +09:00
Rui Ueyama bf1ec5ac8f Allow very large offsets in SHT_CREL 2025-03-14 15:32:38 +09:00
Rui Ueyama b291100cfe Refactor 2025-03-14 15:05:16 +09:00
Rui Ueyama c43a859a90 Support CREL as input section
Fixes https://github.com/rui314/mold/issues/1427
2025-03-13 15:17:11 +09:00
Rui Ueyama 6e3d0e3a3c Fix build failure on Windows 2025-03-12 14:24:11 +09:00
Rui Ueyama 790ce3f64d Downgrade mimalloc to 2.2.2
mimalloc 3.0.1 cannot be compiled on Windows, so we don't want to stick
with the 2.x series for the time being.
2025-03-12 14:05:35 +09:00
Rui Ueyama cbd7f74c2e Upgrade bundled xxhash to 0.8.3 2025-03-12 13:38:26 +09:00
Rui Ueyama 3fe25fc69d Upgrade bundled mimalloc to 3.0.1 2025-03-12 13:34:39 +09:00
Rui Ueyama 823ef09585 Upgrade bundled blake3 to 1.6.1 2025-03-12 13:32:26 +09:00
Rui Ueyama f20f57060a Upgrade bundled zstd to 1.5.7 2025-03-12 11:44:12 +09:00
Rui Ueyama 0061d53409 Merge pull request #1425 from christycylee/buck_files
Remove extraneous build files
2025-03-11 10:35:56 +09:00
Rui Ueyama 82affb94d8 Fix out-of-bound memory access
Related to https://github.com/rui314/mold/issues/1426
2025-03-10 14:54:17 +09:00
Rui Ueyama 4f782fb7dc Refactor 2025-03-08 17:13:48 +09:00
Rui Ueyama e9eb6020a6 Skip if textrel is prohibited by default 2025-03-08 10:47:29 +09:00
Christy Lee 23a662cbb9 Remove extraneous build files 2025-03-07 14:23:22 -08:00
Rui Ueyama 32c8f6dd61 Bump mold version to 2.37.1 2025-03-07 14:32:02 +09:00
Rui Ueyama c95476d2fe Fix spurious "dupilcate symbol" error for LTO
Fixes https://github.com/rui314/mold/issues/1421
2025-03-07 13:17:37 +09:00
Rui Ueyama 3981f4f3d4 Bump mold version to 2.37.0 2025-03-05 15:36:27 +09:00
Rui Ueyama d4ad627919 Fix test 2025-03-05 14:22:32 +09:00
Rui Ueyama befe780cf8 Make --discard-section a hidden experimental option 2025-03-05 14:15:19 +09:00
Rui Ueyama 879fe682ee Check if TLSDESC is actually supported 2025-03-05 14:14:38 +09:00
Rui Ueyama 805a8b2b62 Install binary packages if available 2025-03-05 14:14:38 +09:00
Rui Ueyama ec360760d6 Merge pull request #1423 from christycylee/discard-section
Discard section
2025-03-05 14:14:33 +09:00
Christy Lee 30541fc1e8 Add --discard-section feature 2025-03-04 12:49:03 -08:00
Rui Ueyama 9ebd889a73 Use actions/checkout@v4 2025-03-03 17:49:59 +09:00
Rui Ueyama f15bd7537e Fix CI 2025-03-03 11:54:00 +09:00
Rui Ueyama f2ac49824c Fix CI 2025-03-03 10:45:35 +09:00
Rui Ueyama 716fb9c06d Merge pull request #1422 from LarsGullik/lto-dep-file
Remove temporary lto files from dependency-file
2025-03-03 10:45:09 +09:00
Rui Ueyama 50231607f9 Improve comments 2025-03-03 09:53:11 +09:00
Lars Gullik Bjønnes fb7a650186 Mark lto temporary files to not be a dependency
Removes the temporary file created by the compiler
from the dependency list created with --dependency-file.

Without this build systems will never report "Nothing to do",
but always do a re-link of the final object.
2025-03-03 01:10:19 +01:00
Lars Gullik Bjønnes a399548dfa Add test for lto in combination with depencency-file
There should not be any temporary files listed
as dependencies.
2025-03-03 00:39:31 +01:00
Rui Ueyama 11a62121e3 Expand a comment 2025-02-28 16:15:07 +09:00
Rui Ueyama a067b0106b Refactor 2025-02-28 16:15:07 +09:00
Rui Ueyama 7607ad6371 Refactor 2025-02-27 09:14:37 +09:00
Rui Ueyama eab8217962 Ensure that the assembler emits desired relocations 2025-02-27 09:10:13 +09:00
Rui Ueyama f46959819b Run more tests on CI 2025-02-26 17:31:38 +09:00
Rui Ueyama a17202d2b5 Support R_X86_64_CODE_6_GOTTPOFF
R_X86_64_CODE_5_{GOTPCRELX,GOTTPOFF,GOTPC32_TLSDESC} and
R_X86_64_CODE_6_{GOTPCRELX,GOTPC32_TLSDESC} are defined just for
completeness (*), so we've implemented all APX relocations with
this change.

* https://gitlab.com/x86-psABIs/x86-64-ABI/-/merge_requests/53
2025-02-26 16:29:32 +09:00
Rui Ueyama 83152acc8b Support R_X86_64_CODE_4_{GOTPCRELX,GOTTPOFF,GOTPC32_TLSDESC}
These relocations are for Intel APX (Advanced Performance Extension)
that extends the number of general-purpose registers from 16 to 32.
GNU assembler that comes with GCC 14 or newer can compile the test code.
2025-02-26 15:59:04 +09:00
Rui Ueyama 88ea3a1aa5 Update LoongArch cross toolchain 2025-02-25 19:58:50 +09:00
Rui Ueyama 0302affc11 Attempt to fix CI 2025-02-25 13:39:17 +09:00
Rui Ueyama 34986d26df Attempt to fix CI 2025-02-25 13:04:26 +09:00
Rui Ueyama 1fbbcec3da Support R_ARM_THM_JUMP8 2025-02-25 11:32:44 +09:00
Rui Ueyama 9ae7e700fa Use gcr.io instead of docker.io 2025-02-23 22:18:23 +09:00
Rui Ueyama f496c31008 Refactor 2025-02-23 21:52:59 +09:00
Christy Lee 48a84176ac Add --discard-section feature 2025-02-21 13:52:16 -08:00
Rui Ueyama 7fe4bee499 Merge pull request #1418 from christycylee/centos 2025-02-21 08:00:36 +09:00
Christy Lee 015d5388a0 Allow installing dependencies on centos 2025-02-20 13:39:04 -08:00
Rui Ueyama 9306f69a61 Report a "symbol duplication" error for LTO
Fixes https://github.com/rui314/mold/issues/1414
2025-02-17 17:03:42 +09:00
Rui Ueyama b82028d45d Merge pull request #1277 from perseoGI/conan/update-readme 2025-02-13 19:43:17 +09:00
PerseoGI 123faa82dc Update readme redirecting to conan website with mold instructions 2025-02-13 11:23:14 +01:00
Rui Ueyama 93f540a10a Use x86 GitHub runners
It looks like Qemu on ARM64 is unstable on ARM64 runners. I observed
occasional failures with SEGV.
2025-02-13 09:31:32 +09:00
Rui Ueyama 143d447a66 Take sentinel into account when estimating section size 2025-02-09 14:34:56 +09:00
Rui Ueyama 8b089859ee Improve tests 2025-02-09 14:22:54 +09:00
Rui Ueyama 4b754fd944 [ARM32] Emit the sentinel in .ARM.exidx 2025-02-09 13:40:07 +09:00
Rui Ueyama b9f938e801 Improve tests 2025-02-09 11:47:12 +09:00
Rui Ueyama 6e50f3a5e7 Remove a redundant test 2025-02-09 11:38:48 +09:00
Rui Ueyama c2df589e05 Skip a test 2025-02-09 11:18:05 +09:00
Rui Ueyama 247ae9500c Fix tests 2025-02-09 10:43:34 +09:00
Rui Ueyama 85e8d4179e Use ARM64 runners because they are faster than x86 runners
I believe this is just due to the lower number of users on ARM64
runners compared to x86. However, based on my measurements, ARM64
builds all binaries in 1h30m, whereas x86 takes 2h30m.
2025-02-09 10:14:05 +09:00
Rui Ueyama 9185ee4b05 [PPC64V1] Garbage-collect text sections 2025-02-09 10:11:45 +09:00
Rui Ueyama 16f75990f9 [ARM32] Garbage-collect .ARM.exidx sections 2025-02-09 10:11:45 +09:00
Rui Ueyama 34053cc52e Fix tests 2025-02-09 10:11:45 +09:00
Rui Ueyama 810946130d Collect generated files on test failure 2025-02-09 08:48:18 +09:00
Rui Ueyama 33b6f3dee9 Rename a test 2025-02-09 07:35:44 +09:00
Rui Ueyama 14a276ecde Set pipefail to test rigorously
We need to stop using `grep -q` because grep terminates immediately after
finding the first match with `-q`, causing SIGPIPE to the process on the
other side of the pipe.
2025-02-09 07:33:54 +09:00
Rui Ueyama 245d265b4b Simplify 2025-02-08 19:28:39 +09:00
Rui Ueyama 0619541234 Simplify 2025-02-08 18:41:01 +09:00
Rui Ueyama 8f57d2affd Simplify 2025-02-08 18:15:07 +09:00
Rui Ueyama 501f66b24c Report duplicate symbol errors even if --gc-sections is given
Fixes https://github.com/rui314/mold/issues/1414
2025-02-08 13:50:06 +09:00
Rui Ueyama 6ef9ffb81a Fix CI 2025-02-08 13:50:06 +09:00
Rui Ueyama 9ab2c31086 Improve tests 2025-02-08 13:35:43 +09:00
Rui Ueyama 182bd7b68c Refactor 2025-02-08 13:00:08 +09:00
Rui Ueyama 09e205d11e Make $plt symbols local
Related to https://github.com/rui314/mold/issues/1413
2025-02-08 11:46:23 +09:00
Rui Ueyama 7e5e396aae Add a missing entry 2025-02-07 23:14:27 +09:00
Rui Ueyama b8914d0a94 Avoid cache line false sharing 2025-02-07 09:46:20 +09:00
Rui Ueyama 359e5134c9 Refactor 2025-02-05 22:16:03 +09:00
Rui Ueyama 4ed6f48f55 Refactor 2025-02-05 18:53:11 +09:00
Rui Ueyama 48beed9fdf Refactor 2025-02-05 18:20:10 +09:00
Rui Ueyama 42cc55e6d9 Update mold.1 (automated commit) 2025-02-03 02:33:40 +00:00
Rui Ueyama 04103ae60a Fix typo 2025-02-03 11:24:09 +09:00
Rui Ueyama 0966f1858c Refactor 2025-02-03 11:24:05 +09:00
Rui Ueyama 56748ed199 Refactor 2025-02-02 18:54:27 +09:00
Rui Ueyama 11387b3c19 Fix relocations for the large code model 2025-02-02 18:53:46 +09:00
Rui Ueyama 04d71affd1 Define __rela_iplt_{start,end} as absolute symbols if not necessary
Previously, these symbols were defined relative to a section. That is
logically correct, because the runtiem is supposed to apply IREL
relocs between `__rela_iplt_start` and `__rela_iplt_end`, so their
actual locations shouldn't matter if there's no IREL relocs (i.e. they
refer to the same location.)

However, under some circumstances, glibc reads `__rela_iplt_start`
before applying base relocations and `__rela_iplt_end` after applying
them due to compiler instruction reordering.

Therefore, we need to make them absolute symbols that don't require
base relocations.

Fixes https://github.com/rui314/mold/issues/1407
2025-02-02 10:54:33 +09:00
Rui Ueyama 6cb2f31236 Refactor 2025-02-02 10:54:33 +09:00
Rui Ueyama 01392bd53a Use Debian 9 as the base image
It's year of 2025, so a distro from 2017 should be old enough to build
our binary distribution.
2025-01-31 20:25:14 +09:00
Rui Ueyama 7cb0b817f5 Fully quailify container image URLs for better compatibility 2025-01-31 16:00:03 +09:00
Rui Ueyama ea38c1544f Upgrade upload-artifcat action 2025-01-31 11:23:14 +09:00
Rui Ueyama 80a2d24948 Compress debug sections in background
If both --separate-debug-info and --compress-debug-info are given, we want to
compress debug sections in background so that the delay caused by compression
is not visible to users.
2025-01-31 10:26:35 +09:00
Rui Ueyama ded9292ba9 Merge pull request #1409 from 0x2Adr1/compress_debug_sections_separate_debug_file
Honor --compress-debug-sections with --separate-debug-file
2025-01-31 10:21:39 +09:00
Adrien Garin 6d1c96f34b Add --no-detach 2025-01-30 07:31:00 -06:00
Rui Ueyama 0849789fd5 Build loongarch64 binaries 2025-01-30 18:06:31 +09:00
Rui Ueyama 7f771d6ed9 Fix test 2025-01-30 18:05:56 +09:00
Rui Ueyama b8a46ede02 Merge pull request #1406 from sicherha/skip_s390x_tests_with_buggy_gcc
[ELF][S390X] Skip tests that fail due to buggy code produced by GCC
2025-01-30 18:03:46 +09:00
Adrien Garin bab7dd1b2e Honor --compress-debug-sections with --separate-debug-file 2025-01-29 11:54:46 -06:00
Rui Ueyama ebe887f0ae Update README 2025-01-29 13:07:01 +09:00
Rui Ueyama df30516630 Unbreak build-all buildbot 2025-01-29 10:07:28 +09:00
Rui Ueyama e8101a4d8a Use the ARM64 builder to build ARM32 binary
I didn't know if Azure ARM64 servers support ARM32 instructions, but
apparently they do because running ARM32 code is much faster on ARM64
GitHub Actions runners.
2025-01-29 08:37:52 +09:00
Rui Ueyama 6291f1b86c Add an entry for Rocky Linux 2025-01-28 11:34:59 +09:00
Christoph Erhardt a570e8953c [ELF][S390X] Skip tests that fail due to buggy code produced by GCC
`bno-symbolic` and `canonical-plt` still fail with GCC 15.
`rodata-name` started failing with GCC 14.
2025-01-24 21:03:07 +01:00
Rui Ueyama 5cc0e9d0a1 Build ARM64 binary on push 2025-01-24 09:47:34 +09:00
Rui Ueyama 4bb8e03b24 Build an ARM64 binary on a native ARM64 host
This change speeds up building an ARM64 binary on a GitHub Actions runner.
2025-01-22 20:56:10 +09:00
Rui Ueyama bcadb4e30f Refactor 2025-01-21 13:43:28 +09:00
Rui Ueyama a2eda6acc4 [RISC-V] Do not leave a useless ld instruction
ld may cause a fault at runtime, so we want to make sure that that is
replaced with a nop if relaxed.
2025-01-21 13:25:09 +09:00
Rui Ueyama e60e04feb2 Fix Windows build
Cherrypick https://github.com/microsoft/mimalloc/commit/5764845c4d3d9fe77ae3c2fbfcc2beedce368b1d
to fix Windows CI.
2025-01-21 08:01:50 +09:00
Rui Ueyama 3651dba6ef Upgrade bundled mimalloc to 2.1.9 2025-01-20 20:18:10 +09:00
Rui Ueyama c20d027441 Refactor 2025-01-20 20:18:10 +09:00
Rui Ueyama 66b5ed66c6 [ARM32] Increase the default page size to 64 KiB
... to maximize compatibility with broader hardware.
2025-01-20 20:16:38 +09:00
Rui Ueyama 0b747d8334 Merge pull request #1404 from kei-gnu/upgrade-checkout-actions
Upgrade checkout actions
2025-01-20 18:49:48 +09:00
kei-gnu 73f86aeadd upgrade checkout actions 2025-01-20 10:52:05 +09:00
Rui Ueyama ceee495584 Fix R_ARM_THM_JUMP19 2025-01-20 09:22:58 +09:00
Rui Ueyama 3d89c1181b Refactor 2025-01-19 21:31:34 +09:00
Rui Ueyama f014a1d015 Read R_ARM_THM_JUMP19 relocation addends 2025-01-19 21:27:01 +09:00
Rui Ueyama f25e450f2b Show "LTO plugin missing" error correctly
Fixes https://github.com/rui314/mold/issues/1403
2025-01-19 12:09:42 +09:00
Rui Ueyama 1fbc6f3dec Refactor 2025-01-19 10:38:38 +09:00
Rui Ueyama 4679573654 Refactor 2025-01-19 10:32:40 +09:00
Rui Ueyama af608f5651 Fix CI 2025-01-17 23:06:07 +09:00
Rui Ueyama 5a9c465210 Fix CI 2025-01-17 21:41:41 +09:00
Rui Ueyama 1822e47a43 Make -z dynamic-undefined-weak work for executable
Fixes https://github.com/rui314/mold/issues/1401
2025-01-17 19:44:40 +09:00
Rui Ueyama 06f20e9d69 Add new x86-64 relocations
These relocations are for Intel Advanced Performance Extensions.
The real implementation will be added once major compilers start
supporting APX.
2025-01-17 08:49:06 +09:00
Rui Ueyama 8929bd4a8d Fix test
Fixes https://github.com/rui314/mold/issues/1400
2025-01-17 08:40:58 +09:00
Rui Ueyama 3b1e2a9aa3 Rename a function 2025-01-15 11:22:11 +09:00
Rui Ueyama 721eafc7dd Refactor 2025-01-15 09:38:55 +09:00
Rui Ueyama 4de8daf34f Fix typo 2025-01-15 09:38:55 +09:00
Rui Ueyama f7624ee1f9 Simplify 2025-01-15 08:15:35 +09:00
Rui Ueyama 500416f8f6 Improve test 2025-01-15 07:50:56 +09:00
Rui Ueyama c6c12d43f5 Fix CI 2025-01-14 14:08:22 +09:00
Rui Ueyama 7fc2a6e935 Fix CI 2025-01-14 13:25:32 +09:00
Rui Ueyama 9fe8831db3 Fix CI 2025-01-14 12:41:29 +09:00
Rui Ueyama 2bb42badde Do not emit bad dynamic relocations when sections are shrunk 2025-01-14 11:42:40 +09:00
Rui Ueyama 48077dd91a Refactor 2025-01-11 21:28:01 +09:00
Rui Ueyama cf0d750734 Set the basename of an output path to .gnu.version_d 2025-01-11 19:49:36 +09:00
Rui Ueyama 48dd767ad3 Refactor 2025-01-11 19:47:50 +09:00
Rui Ueyama 3c89ad66a2 Refactor 2025-01-11 17:44:55 +09:00
Rui Ueyama 8cd5574bfb Use shrink_to_fit instead of = {} assignment
I'm no C++ expert, but it looks like `vec = {}` is not guaranteed to
release memory.
2025-01-11 14:01:10 +09:00
Rui Ueyama 0ca543e104 Optimize create_output_section() 2025-01-11 13:12:53 +09:00
Rui Ueyama e5f80fd822 Refactor 2025-01-11 13:12:53 +09:00
Rui Ueyama fe8e061427 Refactor
This new logic was found by ChatGPT o1 pro.
2025-01-10 17:34:00 +09:00
Rui Ueyama 05ceb7da0d Revert "Build daily binary for loongarch64"
This reverts commit 12a141b03b to unbreak
buildbots.
2025-01-10 17:26:34 +09:00
Rui Ueyama 3cec2e68be Upgrade Clang 2025-01-10 10:50:44 +09:00
Rui Ueyama 12a141b03b Build daily binary for loongarch64 2025-01-10 09:37:43 +09:00
Rui Ueyama 562361345b Use the latest version of clang 2025-01-10 08:09:58 +09:00
Rui Ueyama 432ffeabf5 Fix typo 2025-01-10 07:59:05 +09:00
Rui Ueyama be0870719f Check if test system supports TLSDESC 2025-01-09 22:12:24 +09:00
Rui Ueyama a66750b1ec Update README.md 2025-01-09 21:06:26 +09:00
Rui Ueyama c3a957c4dc Bump mold version to 2.36.0 2025-01-09 12:53:32 +09:00
Rui Ueyama f193d2b603 Format 2025-01-09 12:53:32 +09:00
Rui Ueyama d2859f597c Simplify 2025-01-09 11:51:58 +09:00
Rui Ueyama 06a21efbdb Improve a test 2025-01-08 17:40:52 +09:00
Rui Ueyama 13aa1685c9 Update mold.1 (automated commit) 2025-01-08 07:00:45 +00:00
Rui Ueyama 55fcd3facf Document --package-metadata 2025-01-08 15:58:11 +09:00
Rui Ueyama 5d454308f2 Remove BitVector and use std::vector<bool>
Even though std::vector<bool> is often critisized for being inconsistent
with other std::vector<T> where T != bool, it suffices for our purpose.
2025-01-08 13:51:21 +09:00
Rui Ueyama a01fe171d0 Refactor 2025-01-08 11:11:16 +09:00
Rui Ueyama 207c4e99f0 Pass --pids-limit=-1 to podman
Without it, fork(2) may fail with EAGAIN inside podman containers.
2025-01-07 16:13:48 +09:00
Rui Ueyama 0dfdc014e2 Change the path of mold binary 2025-01-07 13:24:06 +09:00
Rui Ueyama eec3f6b716 [RISC-V] Fix R_RISCV_HI20 relaxation
We didn't compensate LO12's sign extension when computing the range.
This resulted in a wrong relaxation that breaks the output binary.
2025-01-07 11:41:22 +09:00
Rui Ueyama a483530a3c Refactor 2025-01-07 11:41:22 +09:00
Rui Ueyama 0d1068e460 Refactor 2025-01-06 22:31:06 +09:00
Rui Ueyama fd0fc9a3cf Refactor 2025-01-06 22:27:33 +09:00
Rui Ueyama 40b881b7bb Simplify 2025-01-06 16:41:48 +09:00
Rui Ueyama 35cde381b7 Refactor 2025-01-06 15:41:25 +09:00
Rui Ueyama 6d8c5ca90b Fix symbol size computation 2025-01-06 13:50:15 +09:00
Rui Ueyama aa4d065fde Do not call get_r_delta() unless necessary
get_r_delta performs a binary search on a vector, so it's not cheap.
If we can avoid calling that function, we should.
2025-01-06 13:36:24 +09:00
Rui Ueyama 0af930ae28 Refactor 2025-01-06 11:07:00 +09:00
Rui Ueyama d29fabf084 Refactor 2025-01-05 22:40:46 +09:00
Rui Ueyama 2ee1d47ca8 Do not add useless entries to r_deltas 2025-01-05 20:52:59 +09:00
Rui Ueyama 8e27616934 Include the number of code-shrinking relocations into the output of `--stats' 2025-01-05 20:12:31 +09:00
Rui Ueyama a7b8c6061a Ensure that the first byte of .dynsym is '\0' 2025-01-05 19:48:46 +09:00
Rui Ueyama aef5f3231c Refactor 2025-01-05 15:31:25 +09:00
Rui Ueyama 38ae2990a0 Refactor 2025-01-05 14:53:50 +09:00
Rui Ueyama e6345d5b14 Reflect the result of code-shrinking relaxation to symbol sizes 2025-01-05 13:49:07 +09:00
Rui Ueyama ba35344bd0 Add a comment 2025-01-05 12:50:50 +09:00
Rui Ueyama c33ca0b001 Refactor 2025-01-05 11:09:45 +09:00
Rui Ueyama 77935f1807 Accept very large input text sections 2025-01-04 22:11:24 +09:00
Rui Ueyama 3234d88600 [RISC-V][LoongArch] Reduce memory usage for shrinking sections
Previously, 4 bytes were allocated for each relocation in a text
section to store the number of bytes removed before each relocation.
Now, only the locations where bytes are removed are stored.

This commit improves performance by ~4% when linking clang-19 for RISC-V.
2025-01-04 21:12:02 +09:00
Rui Ueyama 1e8cee85bb Name each thunk symbol unique name
Previously, if symbol `foo` appears more than once in multiple thunks,
they got the same name `foo$thunk`. Now, they are named `foo$thunk0`,
etc.
2025-01-03 17:37:17 +09:00
Rui Ueyama 2352ec647d Fix a range extension thunk creation issue
Previously, we failed to create range extension thunks if we have
a very large input section.
2025-01-03 17:37:17 +09:00
Rui Ueyama 88d5e74825 Refactor 2025-01-03 17:37:17 +09:00
Rui Ueyama d884620f9d Attempt to fix buildbots 2025-01-03 14:28:04 +09:00
Rui Ueyama e55363ec8a Attempt to fix CI 2025-01-03 13:32:00 +09:00
Rui Ueyama a297859c53 Emit an IRELATIVE relocation instead of GLOB_DAT for an ifunc in DSO
Fixes https://github.com/rui314/mold/issues/1385
2025-01-03 12:53:34 +09:00
Rui Ueyama 0a112d0aa9 Refactor 2025-01-03 10:52:37 +09:00
Rui Ueyama 6a342f2c64 Refactor 2025-01-03 09:15:55 +09:00
Rui Ueyama d0bcd35bf5 Add a safety margin when computing branch offsets 2025-01-02 21:34:47 +09:00
Rui Ueyama f609510e3c Refactor 2025-01-02 21:21:25 +09:00
Rui Ueyama 9fc0ace817 Reduce memory usage of range extension thunks
Previously, we allocate 4 byte struct for each relocation to store a
reference to a thunk in case the relocation needs to branch to a thunk.
Most references are null because most relocations don't need thunks.

This commit changes how we manage references to range extension thunks.
Now, thunk addresses are stored to symbols rather than to relocations.

Linking clang-19 for ARM64 is now ~7% faster than before.
2025-01-02 18:09:45 +09:00
Rui Ueyama 89f3ba89da Refactor 2025-01-02 12:05:00 +09:00
Rui Ueyama 09e7616186 Refactor 2025-01-01 08:43:38 +09:00
Rui Ueyama 2342938cea Simplify 2024-12-31 21:43:28 +09:00
Rui Ueyama 047675b762 Unbreak ARM32 buildbot 2024-12-31 17:15:17 +09:00
Rui Ueyama 531e43eddf Do not mark DSOs as needed if they are referenced only indirectly
Fixes https://github.com/rui314/mold/issues/1395
2024-12-31 17:15:15 +09:00
Rui Ueyama 50a75c25e3 Refactor 2024-12-31 17:15:14 +09:00
Rui Ueyama 7a69bd45ac Refactor 2024-12-30 23:18:25 +09:00
Rui Ueyama 006de74706 Merge .rodata.cst.<symbol-name>.cst<N> sections to .rodata.cst<N> 2024-12-30 14:43:09 +09:00
Rui Ueyama 952fdde7ae Add a relocation overflow check 2024-12-30 11:12:17 +09:00
Rui Ueyama dfce2fc8fa [s390x] Reserve _GLOBAL_OFFSET_TABLE_[2]
ELF Application Binary Interface s390x Supplement Version 1.6.1, p.58 says
that "The doublewords at _GLOBAL_OFFSET_TABLE_[1...2] are reserved for
system use."

We had previously reserved GOT[0] and GOT[1], but we did not reserve GOT[2].

It's most likely that GOT[2] is not actually in use, as it appears that
mold-linked programs work without it. But we need to adhere to the psABI.
2024-12-30 09:05:10 +09:00
Rui Ueyama 50fd4beb3e [s390x] Relax GOT-loading LGRL instructions 2024-12-30 07:55:55 +09:00
Rui Ueyama 0b8090994b Simplify 2024-12-29 10:02:29 +09:00
Rui Ueyama 37e6d2f4cb Merge pull request #1394 from sicherha/fix_ifunc-alias_test_on_RHEL10
Fix `arch-x86_64-ifunc-alias` test on RHEL 10
2024-12-28 19:26:21 +09:00
Christoph Erhardt 0c06bef82b Fix arch-x86_64-ifunc-alias test on RHEL 10
On RHEL 10, GCC is configured with the `--with-arch_64=x86-64-v3`
option, which makes it implicitly use the `-march=x86-64-v3`
command-line flag by default.

This breaks the `arch-x86_64-ifunc-alias` test: since the `x86-64-v3`
microarchitecture level includes SSSE3 and AVX2, GCC appears to
interpret the `default` and `ssse3,avx2` targets as the same thing, and
omits generating a resolver function.

Fix the test by forcing GCC's target architecture to the `x86-64`
baseline.
2024-12-28 10:47:07 +01:00
Rui Ueyama 53dbfd96de Make Ctrl-C work for dist.sh 2024-12-28 18:40:15 +09:00
Rui Ueyama 938f145150 Attempt to fix CI 2024-12-27 22:35:00 +09:00
Rui Ueyama 930ba5a7f1 Do not compress .tar.gz with zip 2024-12-27 22:35:00 +09:00
Rui Ueyama 4107c336a8 Simplify 2024-12-27 22:15:25 +09:00
Rui Ueyama d4db31ceaf Merge pull request #1389 from sicherha/improvement_lambdas_and_ranges
Small improvements: lambdas and ranges
2024-12-27 19:33:03 +09:00
Rui Ueyama 30f68106db Merge pull request #1391 from sicherha/debian_11_CI_pipeline
Add CI pipeline for Debian 11
2024-12-27 19:32:24 +09:00
Rui Ueyama e56fc35820 Attempt to unbreak GitHub Actions 2024-12-27 19:27:36 +09:00
Rui Ueyama b8fa19bf24 Merge pull request #1392 from chinrw/dev
Try again to use Podman instead of Docker
2024-12-27 18:30:27 +09:00
Ruowen Qin d9bd1468d0 Try again to use Podman instead of Docker
Switch from Docker to Podman for container builds and remove unused
buildx steps. Also this commit updates GitHub Actions workflows to use
redhat-actions/podman-login and docker/setup-qemu-action@v3, removing
references to Docker Buildx.

Fixes: ec1d7120aa ("Use docker only on GitHub Actions")
Signed-off-by: Ruowen Qin <[email protected]>
2024-12-27 17:11:22 +08:00
Christoph Erhardt b5dfab17da Add CI pipeline for Debian 11
Debian 11 ships GCC 10 and CMake 3.18, which happen to be precisely the
minimum versions required for building mold from source.

Add a CI pipeline to prevent future commits from inadvertently breaking
compatibility with older toolchains.
2024-12-27 08:50:42 +01:00
Christoph Erhardt 7d1c970a39 Make non-capturing lambdas actually non-capturing 2024-12-27 08:37:48 +01:00
Rui Ueyama e6e1cfac31 Handle "L0^A" symbol as local
I don't know about the details, but it looks like "L0^A" is sometimes
used as a local symbol name.
2024-12-27 16:35:43 +09:00
Rui Ueyama 38bca63747 Make the source directory read-only for the build script
So that even a malicious container cannot silently modify our
source files.
2024-12-27 06:29:01 +09:00
Rui Ueyama 606ce43c5b Add loongarch64 support 2024-12-27 06:15:29 +09:00
Rui Ueyama 41413f9d7c Refactor 2024-12-26 20:42:39 +09:00
Rui Ueyama ec1d7120aa Use docker only on GitHub Actions
I don't know how to fix the build breakage on GitHub, so let's keep
using Docker there.
2024-12-26 19:56:26 +09:00
Rui Ueyama bcce7091e7 Run build-x86 GitHub Action on push to any branch 2024-12-26 18:03:55 +09:00
Rui Ueyama e3cb2aa09e Use podman instead of docker
I prefer podman over docker because podman works without the daemon
and doesn't need the root priviledge.
2024-12-26 17:10:03 +09:00
Rui Ueyama 7e7d75fb4b Format 2024-12-26 17:08:52 +09:00
Rui Ueyama e333e435bd Merge pull request #1388 from sicherha/improvement_cleanup
Miscellaneous small cleanups
2024-12-26 17:08:43 +09:00
Christoph Erhardt afd2fab630 Replace putenv() with setenv()
The use of `putenv()` is discouraged for portability reasons:
https://pubs.opengroup.org/onlinepubs/9799919799/functions/putenv.html
2024-12-25 19:17:17 +01:00
Christoph Erhardt 26493c0ca8 Replace stat() with equivalent std::filesystem calls 2024-12-25 19:17:17 +01:00
Christoph Erhardt 9402ca357c Construct std::vector elements in place
This is slightly more efficient than creating the objects and
subsequently copying them into the vector.
2024-12-25 19:17:17 +01:00
Christoph Erhardt 5572679c2d Remove duplicate if-case
Reported by clang-tidy: the same code already exists 50 lines below this
one.
2024-12-25 13:49:42 +01:00
Rui Ueyama 179d5d4b5c Merge pull request #1387 from sicherha/improvement_includes 2024-12-25 20:32:40 +09:00
Christoph Erhardt f169f1c45c Reorganise TBB #includes
Add missing `#include`s and remove unused ones.
2024-12-25 11:41:04 +01:00
Christoph Erhardt e05f3b5362 Include the C++ variant of C headers 2024-12-25 11:41:02 +01:00
Christoph Erhardt a5b3a22517 Remove unused #includes 2024-12-25 11:40:29 +01:00
Rui Ueyama 82263dce4d Merge pull request #1386 from jinghao-jia/main
Restore executable overwriting behavior on Linux 6.13 or later
2024-12-25 18:12:38 +09:00
Jinghao Jia 1a012dda4d Restore executable overwriting behavior on Linux 6.13 or later
Linux upstream has reverted commit 2a010c412853 ("fs: don't block
i_writecount during exec") and the change is merged in the 6.13 cycle.
This means the executable overwriting behavior can be restored on Linux
kernel version 6.13 or later.

Note that there is probably not much we can do for 6.11 / 6.12 kernels.
Although the reversion is backported, many distros do not use upstream
sub-version numbers which means we cannot determine whether the kernel
has this reversion based on its version number.

Signed-off-by: Jinghao Jia <[email protected]>
2024-12-25 01:55:07 -06:00
Rui Ueyama dfe02f41cb Add a test 2024-12-24 17:16:34 +09:00
Rui Ueyama b582ad3589 Format 2024-12-24 17:16:32 +09:00
Rui Ueyama 2d7b6b23e3 [LoongArch] Increase the max page size from 16 KiB to 64 KiB
Even though 16 KiB is the common page size, the Linux kernel for
LoongArch supports 64 KiB pages. Currently, mold-generated binaries
run only on systems with <=16 KiB page.

Note that unlike GNU ld, increasing the maximum page size doesn't
increase the on-disk binary size of generated binaries.
2024-12-22 17:44:53 +09:00
Rui Ueyama 7ddc8f4654 Rename --encoded-package-metadata to --package-metadata
This is an alternative to https://github.com/rui314/mold/pull/1383

The upcoming version of GNU binutils's ld now treats arguments for
`--package-metadata` as percent-encoded strings and decodes them before
writing to .note.package.

Additionally, GNU ld will support `%[<charname>]`, where `<charname>` can be
`comma`, `lbrace`, `rbrace`, `quot`, or `space`. This feature could improve
readability when manually writing command-line arguments.

That said, I don't believe we need to support these metacharacters, for the
following reasons:

1. This option is specifically designed for distribution package maintainers
   to embed metadata into binaries, and is not intended for general use.

2. Command-line arguments for this option are always generated by build
   scripts, not written manually.

If we need these character names, we can add that later.

Furthermore, GNU ld does not appear to report errors for orphan '%'
characters that are not followed by valid hex values; instead, it simply
passes through such characters. I find this behavior fragile. We will report
an error for invalid '%' characters.

With these considerations in mind, I'll just rename the existing
`--encoded-package-metadata` option to `--package-metadata`.
2024-12-22 10:07:23 +09:00
Rui Ueyama 4bbd93f06f Refactor 2024-12-22 10:07:23 +09:00
Rui Ueyama a76d259c0b Merge pull request #1384 from bdrung/ctest-with-logs 2024-12-22 05:27:57 +09:00
Benjamin Drung a557ea1012 ci: run ctest with --output-on-failure
In case ctest fails in the CI, no error logs are printed. This makes
debugging harder.

So run ctest with `--output-on-failure` to ease debugging in case of
failures.
2024-12-21 20:37:31 +01:00
Rui Ueyama 06f6a56043 Merge pull request #1380 from sicherha/fix_system_mimalloc
Add missing `#include "common.h"` - fixes broken `MOLD_USE_SYSTEM_MIMALLOC`
2024-12-20 10:39:39 +09:00
Christoph Erhardt ac9568f17b Disable mimalloc when a sanitizer is used
Combining mimalloc and AddressSanitizer triggers a segmentation fault in
many tests.

Combining mimalloc and ThreadSanitizer leads to duplicate symbols for
the `new` and `delete`
operators, causing the build to fail.
Example message from a failed GitHub Actions build:
```
mold: error: duplicate symbol:
third-party/mimalloc/libmimalloc-debug.a(alloc.c.o):
/usr/lib/llvm-18/lib/clang/18/lib/linux/libclang_rt.tsan_cxx-x86_64.a(tsan_new_delete.cpp.o):
operator delete(void*, std::align_val_t)
```
2024-12-20 00:03:47 +01:00
Christoph Erhardt 9c9145eb6e Add missing #include "common.h"
This allows the compiler to make sure that the implementation of
`set_mimalloc_options()` matches its declaration.

More importantly, it indirectly pulls in `config.h`, where the macros
`MOLD_USE_SYSTEM_MIMALLOC` and `MOLD_USE_MIMALLOC` are conditionally
defined. Without these, the build configuration is ignored.
2024-12-19 22:06:23 +01:00
Rui Ueyama 3cb551424b Fix CI 2024-12-16 17:27:33 +09:00
Rui Ueyama ee8e50a3f7 Bump mold version to 2.35.1 2024-12-15 13:26:50 +09:00
Rui Ueyama 4bbf85c2ea Refactor 2024-12-13 11:13:06 +09:00
Rui Ueyama 6257f12c1f Simplify 2024-12-12 12:45:53 +09:00
Rui Ueyama ea460af09c Fix function prototypes
Fixes https://github.com/rui314/mold/issues/1370
2024-12-12 12:13:29 +09:00
Rui Ueyama a0f9641ae1 Upgrade BLAKE3 to 1.5.5 2024-12-11 20:13:03 +09:00
Rui Ueyama 9204e1dc12 Upgrade OneTBB to 2022.0.0 2024-12-11 20:13:03 +09:00
Rui Ueyama 396666fd48 Preserve the original relocation order as much as possible 2024-12-11 20:13:03 +09:00
Rui Ueyama 74024ea0df Refactor 2024-12-11 15:22:22 +09:00
Rui Ueyama 79aac78581 Refactor 2024-12-09 18:37:01 +09:00
Rui Ueyama 04066d1c1a [RISC-V] Remove GP-relative relocations
These relocations were deleted from the psABI in
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/commit/ad025461defeb610c684427719ce5ac5e8d8d857.
2024-12-09 18:33:30 +09:00
Rui Ueyama 2a78b1b333 Make ICF deterministic
Fixes https://github.com/rui314/mold/issues/1377
2024-12-09 15:13:20 +09:00
Rui Ueyama 27162691b7 Bump mold version to 2.35.0 2024-12-08 14:58:53 +09:00
Rui Ueyama fe6090efff Revert "Upgrade RV32 toolchain"
This reverts commit c7042f416e because it broke CI.
2024-12-05 18:29:47 +09:00
Rui Ueyama c7042f416e Upgrade RV32 toolchain 2024-12-05 16:44:09 +09:00
Rui Ueyama e56b649968 Increase the maximum retry count for our concurrent hashmap
Fixes https://github.com/rui314/mold/issues/1365
2024-12-05 14:17:56 +09:00
Rui Ueyama 0f56a792c3 Upgrade LoongArch toolchain 2024-12-04 16:06:01 +09:00
Rui Ueyama 5bb914553f Fix typo 2024-11-26 13:21:41 +09:00
Rui Ueyama 26d898e9bd Use macos-latest instead of macos-12
macos-12 has been deprecated.
2024-11-26 07:45:07 +09:00
Rui Ueyama eec3bb4478 Simplify 2024-11-26 07:01:50 +09:00
Rui Ueyama fa3026611b Merge pull request #1373 from bl4ckb0ne/test-x86-variants
test: match more x86 arch name variants
2024-11-26 06:59:21 +09:00
Simon Zeni a649156e0f test: match more x86 arch name variants 2024-11-25 11:06:25 -05:00
Rui Ueyama e2e1146a0b [s390x] Fix .hash section
For whatever reason, the entry size for .hash is 8 bytes insetad of 4 on s390x.

Fixes https://github.com/rui314/mold/issues/1372
2024-11-23 13:20:07 +09:00
Rui Ueyama baf4f76f53 Report an error even without NDEBUG=1 if a ConcurrentMap becomes full 2024-11-02 17:05:01 +09:00
Rui Ueyama 85be067813 Try to silence benign mimalloc warning messages
https://github.com/rui314/mold/issues/1367
2024-10-30 13:29:57 +09:00
Rui Ueyama 8e4f7b5383 Do not ovewrite an existing file if running on Linux 6.11.0 or later
https://github.com/rui314/mold/issues/1361
2024-10-29 16:33:45 +09:00
Rui Ueyama a066efa995 Use madvise(2) if available 2024-10-29 16:01:04 +09:00
Rui Ueyama f184021eca Refactor 2024-10-27 16:42:36 +09:00
Rui Ueyama 0841ffcb20 Refactor 2024-10-20 11:33:38 +09:00
Rui Ueyama 9cf1165a34 Fix CI 2024-10-19 11:33:06 +09:00
Rui Ueyama 9aa4ded614 Remove LICENSE.third-party
We don't need to create a copy of license files for third-party software
as we keep an original license file for each subdirectory. It is probably
a bit risky to create copies of license files because it is easy to become
out-of-sync. So I'll just remove this file.
2024-10-19 11:08:12 +09:00
Rui Ueyama e7e764ccf0 Revert "Refactor"
This reverts commit 5ba5b4960f to fix CI.
2024-10-19 09:47:53 +09:00
Rui Ueyama 5ba5b4960f Refactor 2024-10-18 20:57:07 +09:00
Rui Ueyama bb57289385 Refactor 2024-10-18 18:46:23 +09:00
Rui Ueyama 0e28522b5c Fix filler instruction for sh4be
If running in big-endian mode, SH4 processors expects both data and
instructions to be encoded in big-endian. Therefore, nop need to be
reversed for sh4be.
2024-10-17 16:13:46 +09:00
Rui Ueyama 882e7eb15b Support big-endian ARM64 2024-10-17 12:27:12 +09:00
Rui Ueyama 3a0bb87dfe Refactor 2024-10-17 12:27:10 +09:00
Rui Ueyama 832973cef5 Run a test for -z rewrite-endbr on GitHub Actions 2024-10-16 16:22:51 +09:00
Rui Ueyama 0cb9fc613c Support big-endian SH4
This commit adds experimental big-endian SH4 support. Since we already
support little-endian SH4, it wasn't too difficult to support its big-
endian variant.

Some tests related to C++ exceptions fail due to mysterious errors.
I don't have time to debug it, so I'll disable them for now.

Closes https://github.com/rui314/mold/issues/1273
2024-10-15 14:23:54 +09:00
Rui Ueyama 8931cf81f4 Update mold.1 (automated commit) 2024-10-15 04:49:05 +00:00
Rui Ueyama ecdd0f71b6 Update the man page 2024-10-15 13:48:07 +09:00
Rui Ueyama cb9344fab8 Run tests 2024-10-15 13:48:05 +09:00
Rui Ueyama 382e45ea30 Align .gnu.version_r and .gnu.version_d to 4 byte boundaries
They don't contain 8 byte date items, so they don't need to be aligned
to 8 byte boundaries.
2024-10-11 19:51:52 +09:00
Rui Ueyama fdaa13c374 Merge .gnu.build.attributes sections into a single section
It looks like gold does this.
2024-10-11 19:42:14 +09:00
Rui Ueyama fa2411cc08 Refactor 2024-10-10 10:14:36 +09:00
Rui Ueyama a69b332f86 Enable GC-sections and ICF not only for Release but also for RelWithDebInfo 2024-10-09 21:11:55 +09:00
Rui Ueyama 20dccf2b77 Refactor 2024-10-09 15:59:15 +09:00
Rui Ueyama ffd10dd447 Do not include mimalloc-new-delete.h if mimalloc is disabled
Including mimalloc-new-delete.h overrides new and delete operators
with mimalloc. If a user choose not to use mimalloc, we shouldn't
include that file.

https://github.com/rui314/mold/issues/1355
2024-10-09 14:41:36 +09:00
Rui Ueyama ce9cdb1bae [ICF] Handle RELRO sections correctly
When control reaches here, `output_section` is always a nullptr.
2024-10-08 19:35:03 +09:00
Rui Ueyama 979084b593 Use .llvm_addrsig to find address significance for executable sections
This change should allow more executable sections to be merged by ICF.
2024-10-08 17:47:41 +09:00
Rui Ueyama c562c733ce Move target-dependent data members to the end of class definitions
So that the data layouts for other data members become the same.
This makes ICF work better.
2024-10-08 17:45:25 +09:00
Rui Ueyama 3c6f57cc27 Refactor 2024-10-08 14:57:32 +09:00
Rui Ueyama c16d6e8cc0 Remove to_abs_path
This function is not necessary.

I don't know how it could leak memory, but it was reported
that it does in https://github.com/rui314/mold/issues/1355.
2024-10-08 13:01:12 +09:00
Rui Ueyama 91e2daf3bf Create release binaries with Clang instead of GCC
LLVM/Clang works better with mold's --icf=safe because of the presence
of LLVM address significance table.
2024-10-07 19:29:01 +09:00
Rui Ueyama 7204c6dec8 Do not set a value to relr.dyn section's sh_link
The ELF ABI doesn't say anything about sh_link field of SHT_RELR section,
so setting a value to it is simply wrong.
2024-10-06 15:28:36 +09:00
Rui Ueyama 3441cbb636 Enable GC sections and ICF for binary distributions to reduce file size 2024-10-06 09:49:32 +09:00
Rui Ueyama 3070e798f3 Refactor 2024-10-05 20:05:43 +09:00
Rui Ueyama 754354991b Simplify 2024-10-05 15:37:03 +09:00
Rui Ueyama 86fe020558 Fix CI 2024-10-05 15:13:46 +09:00
Rui Ueyama 40e7b983d6 Fix CI 2024-10-05 14:44:22 +09:00
Rui Ueyama f69ecd616d Merge pull request #1353 from praetorian20/main 2024-10-05 13:03:17 +09:00
Rui Ueyama d683f62e01 Add a rule for KDE Neon
Fixes https://github.com/rui314/mold/issues/1354
2024-10-05 12:44:20 +09:00
Ashish Sadanandan 205a091924 Use readelf for compress-debug-sections verification
RHEL 8.7's version of dwarfdump (2018-01-29) is too old to recognize the
SHF_COMPRESSED flag in debug info, so the compress-debug-sections test
fails despite the debug sections being compressed as intended.

To fix this, we will use readelf to dump information about the
executable. The COMPRESSED flag can be verified using this.
2024-10-04 20:44:05 -06:00
Rui Ueyama 7b4d2e77ee Refactor 2024-10-05 10:08:23 +09:00
Rui Ueyama 2c9ba071bc Refactor 2024-10-04 20:47:50 +09:00
Rui Ueyama de65f6bd75 Bump mold version to 2.34.1 2024-10-04 15:00:22 +09:00
Rui Ueyama 186272a425 Handle R_ARM_TARGET1 as a synonym for R_ARM_ABS32
This fixes a regression introduced in 431fc39db0.

Fixes https://github.com/rui314/mold/issues/1351
2024-10-04 14:18:13 +09:00
Rui Ueyama 5fcbf25260 Refactor 2024-10-03 15:19:47 +09:00
Rui Ueyama 16eb5139f1 [RISC-V] Set DT_RISCV_VARIANT_CC if there's a dynamic symbol with STO_RISCV_VARIANT_CC 2024-10-02 20:41:13 +09:00
Rui Ueyama 35be7ba887 Refactor 2024-10-02 20:39:50 +09:00
Rui Ueyama 55f0289a41 Non-SHF_ALLOC sections' addresses should be displayed as zero for --Map 2024-09-30 13:28:22 +09:00
Rui Ueyama 36dd6babe1 Refactor 2024-09-29 19:04:22 +09:00
Rui Ueyama 91b6818b62 Revert "Refactor"
This reverts commit 9e9539144e because
the new code didn't compile on macOS.
2024-09-27 21:45:49 +09:00
Rui Ueyama 9e9539144e Refactor 2024-09-27 17:51:54 +09:00
Rui Ueyama feb9b3eb4e Set 0 to DT_AARCH64_VARIANT_PCS
It looks like only the presence of the entry is important, but by
convention, GNU ld and lld set 0 to a DT_AARCH64_VARIANT_PCS dynamic
entry, so we should follow suit.
2024-09-27 17:51:54 +09:00
Rui Ueyama 4b3fc4e82a Refactor 2024-09-27 17:51:54 +09:00
Rui Ueyama ed7cc1b85a Bump mold version to 2.34.0 2024-09-25 10:01:39 +09:00
Rui Ueyama 3711ddb95e Drop DEC Alpha processor support
Keeping DEC Alpha support is difficult because there are no Alpha
machines available for toolchain developers, and even if there were,
no modern Linux distros still support Alpha. For example, Debian
dropped Alpha support in Debian 6.0. As a result, I cannot test my
linker with real-world programs.

In fact, I believe mold has never been able to link real-world large
programs. Object files for Alpha are compiled with the small code
model by default, so it is not easy to support it in the linker.

There's no means to verify that my implementation is correct for Alpha
because Alpha's psABI has never been published by DEC. The most
"reliable" source of correctness is GNU ld's source code and comments.

Last but not least, I believe there are literally zero mold/Alpha
users. Unlike some other retro computers like the m68k, Alpha doesn't
seem to get much love from the community, perhaps because the
availability of Alpha machines was limited in the first place, even in
the 90s.

Therefore, I'll remove Alpha support now. If there's someone who wants
to keep it, we can resurrect and re-test it.

This change should make the mold binary size a little smaller.
2024-09-24 17:29:34 +09:00
Rui Ueyama 0ee12e4115 Do not strip linker-synthesized symbols when --retain-symbols-file is given
This is an attempt to fix https://github.com/rui314/mold/issues/1346
2024-09-21 17:08:32 +09:00
Rui Ueyama 87c9929df9 Merge pull request #1345 from andreas-schwab/main 2024-09-19 17:32:46 +09:00
Andreas Schwab f5f14d3faa test/absolute-symbols.sh: move symbol address out of the heap
Fixes #1340

Signed-off-by: Andreas Schwab <[email protected]>
2024-09-19 09:53:41 +02:00
Rui Ueyama 5be5ea9ff5 Fix CI 2024-09-18 15:18:44 +09:00
Rui Ueyama f1739bffd0 Refactor 2024-09-18 15:03:54 +09:00
Rui Ueyama 1efbe3f141 Do not lower the priority of DSO symbols than in-archive symbols
This commit reverts 0612ea41f1 and
instead solves the original issue with a different approach.

Now, if a hidden symbol was resolved to a DSO symbol, we'll redo
symbol resolution so that the symbol won't be resolved to the same one
next time.

This should fix our build-all buildbot.
2024-09-17 13:23:17 +09:00
Rui Ueyama 5ba1c36fbb Refactor 2024-09-17 12:27:08 +09:00
Rui Ueyama c217134d97 Refactor 2024-09-17 11:58:42 +09:00
Rui Ueyama fea866d910 Refactor 2024-09-15 19:18:06 +09:00
Rui Ueyama 3b3bc7b41e Make builders runnable by hand 2024-09-15 19:13:45 +09:00
Rui Ueyama 0612ea41f1 Lower the priority of DSO symbols than in-archive symbols
Assume both `foo.a` and `bar.so` define the same symbol `baz`.
If `baz`'s symbol visibility is hidden, it needs to be resolved within
the output file, i.e., from `foo.a`. However, previously, such symbol
was resolved to the one in `bar.so`.

To fix the problem, we'll lower the symbol priority for DSOs.

Fixes https://github.com/rui314/mold/issues/1342
2024-09-15 17:17:43 +09:00
Rui Ueyama 940ac8125c Refactor 2024-09-15 16:22:18 +09:00
Rui Ueyama 583ba8ade1 Fix daily builder 2024-09-15 11:13:56 +09:00
Rui Ueyama 82feeed693 Build mold binary with mold 2024-09-14 19:11:51 +09:00
Rui Ueyama e7e41329ef Refactor 2024-09-14 17:14:30 +09:00
Rui Ueyama 63d990e49a Upgrade upload-artifact github action 2024-09-14 16:00:17 +09:00
Rui Ueyama 8d5b753337 Fix buildbot 2024-09-14 15:10:50 +09:00
Rui Ueyama 431fc39db0 Change how we handle word-size absolute relocations
Word-size absolute relocations, such as R_X86_64_64, are special from
the linker's point of view because only such symbols can be promoted
to dynamic symbols. This patch handles them separately.
2024-09-14 11:59:20 +09:00
Rui Ueyama 7734a93217 Improve a test 2024-09-14 08:04:15 +09:00
Rui Ueyama 806d7409da Refactor 2024-09-07 19:47:18 +09:00
Rui Ueyama 2e326cc006 Refactor 2024-09-07 15:31:56 +09:00
Rui Ueyama e1ace5ea00 Add a FreeBSD test runner 2024-09-02 14:58:30 +09:00
Rui Ueyama 8e010c2095 Update comments 2024-09-01 13:20:50 +09:00
Rui Ueyama 6758b53bff Fix CI 2024-08-31 15:14:26 +09:00
Rui Ueyama f6822fb0f3 Make executables iwth zero-size segments loadable on FreeBSD
Fixes https://github.com/rui314/mold/issues/1338
2024-08-31 14:52:11 +09:00
Rui Ueyama f0d39b2460 Add a rule for FreeBSD 2024-08-31 13:26:52 +09:00
Rui Ueyama a3e69502b0 Refactor 2024-08-30 12:04:56 +09:00
Rui Ueyama 4645248106 Merge pull request #1337 from leleliu008/fix-netbsd-no-alloca.h 2024-08-30 04:58:24 +09:00
leleliu008 80648584cf alloca.h is missing on NetBSD
alloca(3) is defined in stdlib.h on NetBSD

Reference: https://man.netbsd.org/alloca.3

Signed-off-by: leleliu008 <[email protected]>
2024-08-29 20:22:03 +08:00
Rui Ueyama d4c2073cda Fix test
Previous test passed even without 1c321022d0.
2024-08-27 20:09:40 +09:00
Rui Ueyama fe7237e454 Merge pull request #1335 from ywgrit/main 2024-08-27 17:04:46 +09:00
Xin Wang 4da257e6fb LoongArch: change test of R_LARCH_CALL36 with bigger offset 2024-08-27 15:14:56 +08:00
Rui Ueyama ed617fef05 Merge pull request #1170 from mominul/patch-1
Update README.md
2024-08-27 13:19:16 +09:00
Xin Wang 1c321022d0 LoongArch: fix error offset for relaxation of R_LARCH_CALL36 2024-08-26 16:37:09 +08:00
Rui Ueyama ed4cae9354 Add a test 2024-08-24 14:33:13 +09:00
Rui Ueyama 2d390565c1 Fix the section-shrinking relaxation 2024-08-24 13:53:02 +09:00
Rui Ueyama 5e72d88940 Update a document 2024-08-24 13:21:38 +09:00
Rui Ueyama 213f2ecec5 Refactor 2024-08-23 13:45:02 +09:00
Rui Ueyama 23b3778383 Refactor 2024-08-22 18:25:28 +09:00
Rui Ueyama b9776af9b2 Refactor 2024-08-22 17:34:42 +09:00
Rui Ueyama 25794c6003 Refactor 2024-08-22 11:09:55 +09:00
Rui Ueyama 7fa561699e Refactor 2024-08-21 13:57:26 +09:00
Rui Ueyama 127ba25dab [LoongArch] Fix TLSDESC relaxation
R_LARCH_TLS_DESC_PC_HI20 is a GOT-loading relocation, so we need to
compute the distance to the symbol's GOT entry instead of to the
symbol's address.
2024-08-21 13:25:59 +09:00
Rui Ueyama 6a1742ea90 Refacotr 2024-08-21 12:53:16 +09:00
Rui Ueyama e013d27b54 Add a test 2024-08-21 12:40:56 +09:00
Rui Ueyama 37e175b4a4 Merge pull request #1333 from ywgrit/main
LoongArch: apply relaxation to tls desc
2024-08-21 12:12:15 +09:00
Xin Wang 982b901077 LoongArch: apply relaxation to tls desc 2024-08-21 09:44:43 +08:00
Rui Ueyama f0797bb8f2 Improve CI 2024-08-18 14:22:57 +09:00
Rui Ueyama 18f8c425dc Do not use precompiled headers
`-fpch-preprocess` doesn't seem to improve build speed that much,
so don't use it.
2024-08-18 12:56:38 +09:00
Rui Ueyama 194239f8c6 Enable some tests 2024-08-18 12:39:29 +09:00
Rui Ueyama 401e019b05 Add a test
We also don't want to skip empty string because if a user specifies
an empty string, it is their intention whatever that intention is.
2024-08-18 11:38:47 +09:00
cristian64 0c74e8274e Fix infinite loop in split_by_comma_or_colon(). (#1332)
* Fix infinite loop in `split_by_comma_or_colon()`.

The mere presence of a `,` or a `:` in the input string would make the
function enter an infinite loop, effectively freezing the program until
all available memory was exhausted. The string position in the loop was
not incremented past the `,` or the `;` character that had just been
read.

The function is currently used only in the `--exclude-libs` option. The
freeze is reproducible with:

```bash
$ mold --exclude-libs foo,bar
```

The longer the string after the first `,` or `:` is, the faster memory
exhaustion is reached.

* Skip empty parts in `split_by_comma_or_colon()`.
2024-08-18 11:34:12 +09:00
Rui Ueyama cf5d054eb4 Fix CI 2024-08-17 16:17:31 +09:00
Rui Ueyama d9bf693fb8 [LoongArch] Do not emit local symbols by default 2024-08-17 15:09:08 +09:00
Rui Ueyama cf38f81c39 Refactor 2024-08-17 15:09:08 +09:00
Rui Ueyama 2eeb7b411e Simplify 2024-08-17 06:38:31 +09:00
Rui Ueyama ecc7ce022b Skip ELF symbols whose section numbers are the reserved range
Fixes https://github.com/rui314/mold/issues/1331
2024-08-16 13:26:12 +09:00
Rui Ueyama a6b182b065 Refactor 2024-08-16 13:26:12 +09:00
Rui Ueyama e30f01fe93 Refactor 2024-08-16 12:48:16 +09:00
Rui Ueyama ac84d2cdbb Refactor 2024-08-16 11:18:12 +09:00
Rui Ueyama 742ea87d41 Merge .ARM.exidx entries
This change should make our output for ARM32 a little bit smaller.
2024-08-15 17:15:45 +09:00
Rui Ueyama fa546965ec Refactor 2024-08-15 14:11:55 +09:00
Rui Ueyama f68657d201 Refactor 2024-08-15 13:34:41 +09:00
Rui Ueyama 23bc92c66f Emit PT_GNU_PROPERTY program header if .note.gnu.property is present
It looks like Linux kernel reads this program header to enable BTI on ARM64.
2024-08-15 12:57:45 +09:00
Rui Ueyama c2956484ca Refactor 2024-08-15 12:26:29 +09:00
Rui Ueyama 3f593c62ae Remove code for macOS 2024-08-14 21:29:08 +09:00
Rui Ueyama 5b8594979b Reorganize directory structure 2024-08-14 20:52:09 +09:00
Rui Ueyama 7571305cd1 Fix CI 2024-08-14 20:06:28 +09:00
Rui Ueyama 0631f9750e Fix a Docker image build failure
Fixes https://github.com/rui314/mold/issues/1330
2024-08-14 19:10:40 +09:00
Rui Ueyama b51d6ab5cb Avoid LD_PRELOAD 2024-08-14 19:10:40 +09:00
Rui Ueyama 93c4f35615 Fix minor issues 2024-08-14 17:38:53 +09:00
Rui Ueyama 510b2d68c0 Reorganize directory structure 2024-08-14 16:23:25 +09:00
Rui Ueyama d6d8d2e494 [LoongArch] Fix symbol value computation for the extreme code model
The LoongArch extreme code model uses the following instructions to
materialize a 64-bit value in a regiter.

  pcalau12i $t1, %pc_hi20(g2)
  addi.d    $t0, $zero, %pc_lo12(g2)
  lu32i.d   $t0, %pc64_lo20(g2)
  lu52i.d   $t0, $t0, %pc64_hi12(g2)

The previous relocation formula expected that `page(pc)` would be
consistent for all the instructions, but this assumption is incorrect
if the instruction sequence crosses a 4 KiB boundary.

Now, the LoongArch psABI requires that the machine instructions must be
consecutive, and the relocations for lu32.d and lu52i.d uses
`page(pc - 8)` and `page(pc - 16)` instead of `page(pc)`.

This psABI change gave me an impression that the LoongArch's extreme code
model was poorly designed and inadequately tested. If these instructions
must be consecutive, only a single relocation referring to the beginning
of the instructions would suffice, which relocates all the following four
instructions at once.

Maybe we do not need to support the relocations for the extreme code model
because the code model was buggy at the spec level, which suggests that no
one is using them. But I'm not sure if it is safe to remove them, so let's
just follow the psABI change.
2024-08-14 13:52:12 +09:00
Rui Ueyama 9e0c1c81dd Refactor 2024-08-13 20:52:38 +09:00
Rui Ueyama ac5031198c Simplify 2024-08-13 20:18:50 +09:00
Rui Ueyama 11d5426781 Update mold.1 (automated commit) 2024-08-13 09:22:48 +00:00
Rui Ueyama afc52eea70 Accept --build-id=fast for the sake of compatibility with lld
Fixes https://github.com/rui314/mold/issues/1328
2024-08-13 18:20:56 +09:00
Rui Ueyama 6248d964fb Refactor 2024-08-13 18:15:20 +09:00
Rui Ueyama addb5f1675 [SPARC] Relax TLS GD/LD relocations 2024-08-13 17:18:01 +09:00
Rui Ueyama ed22d76ef0 Refactor 2024-08-13 14:46:53 +09:00
Rui Ueyama a73e9b2dac Update comments 2024-08-13 11:27:45 +09:00
Rui Ueyama 5dfa1cf07c Update comments 2024-08-10 09:51:13 +09:00
Rui Ueyama 4164285a13 Fix CI 2024-08-09 22:10:53 +09:00
Rui Ueyama 1f53bd5bfa Attempt to fix CI 2024-08-09 18:22:07 +09:00
Rui Ueyama a32d294df8 Update comments 2024-08-09 18:02:14 +09:00
Rui Ueyama dbaa6d7341 [LoongArch] Support TLSDESC 2024-08-09 17:49:12 +09:00
Rui Ueyama 23a9e7261d Refactor 2024-08-09 15:04:04 +09:00
Rui Ueyama 5a9919b433 Fix a crash bug
Fixes https://github.com/rui314/mold/issues/1326
2024-08-09 12:38:56 +09:00
Rui Ueyama 1d24b70996 Use crc32() instead of crc32_z()
Old zlib does not support crc32_z. The difference between the two is
that crc32() takes an input size as an unsigned int while crc32_z()
takes as size_t. For our use case, size is guranteed to be short, so
crc32() is safe to use.
2024-08-08 20:06:19 +09:00
Rui Ueyama f18d1c1ffc Attempt to fix build failure on 32 bit Alpine distros
Fixes https://github.com/rui314/mold/issues/1323
2024-08-08 10:09:41 +09:00
Rui Ueyama 1ab637cea4 Bump mold version to 2.33.0 2024-08-07 12:32:13 +09:00
Rui Ueyama 573b8a45df Update mold.1 (automated commit) 2024-08-06 05:26:45 +00:00
Rui Ueyama 0bf00a4247 Remove --encoded-package-metadata from docs
It's not clear whether or not the option would be added to the GNU linker,
so I"ll make this a hidden option for now.

https://github.com/rui314/mold/pull/1308
2024-08-06 14:25:45 +09:00
Rui Ueyama 43d9536a05 Fix an assertion failure
https://github.com/rui314/mold/issues/573
2024-08-05 20:06:45 +09:00
Rui Ueyama 893ec1b362 Format 2024-08-05 15:08:57 +09:00
Rui Ueyama 1aa966c2ad Refactor 2024-08-05 13:26:03 +09:00
ywgrit 121f917884 LoongArch: support relaxation of pcalau12i/ld.d to pcalau12i/addi.d or pcaddi (#1322)
[LoongArch] support relaxation of pcalau12i/ld.d to pcalau12i/addi.d
2024-08-05 12:26:03 +09:00
Rui Ueyama 17a9481522 Merge .sdata, .srodata and .sbss
RISC-V uses these sections for small data/rodata/bss.
2024-08-03 14:58:27 +09:00
Rui Ueyama 06e8bf8933 Update mold.1 (automated commit) 2024-08-03 03:15:37 +00:00
Rui Ueyama f7af61f80c Update man page 2024-08-03 12:14:59 +09:00
Rui Ueyama 25b34bb472 Update mold.1 (automated commit) 2024-08-02 11:02:16 +00:00
Rui Ueyama 22f85c1b6f Rename --remove-landing-pad -z rewrite-endbr 2024-08-02 19:18:03 +09:00
Rui Ueyama dd8d9714cb dSupport --dynamic-list-data
Fixes https://github.com/rui314/mold/issues/1320
2024-08-01 18:51:34 +09:00
Rui Ueyama 982f07168d Refactor 2024-08-01 11:51:28 +09:00
Rui Ueyama 218d89b005 Rename a test 2024-07-31 21:00:12 +09:00
Rui Ueyama bada67bb39 Handle R_LARCH_CALL36 as a function-calling relocation 2024-07-31 20:58:45 +09:00
Rui Ueyama db03248e72 Fix comments 2024-07-31 20:05:52 +09:00
Rui Ueyama 47c092aac0 Remove range extension support from LoongArch
It looks like emitting PCADDU18I + JIRL instead of just BL for function
calls is a way to go, so we don't want to bother to maintain range
extension thunks for the psABI.

Since no other linkers support range extension thunks for LoongArch other
than us, removing the support should be OK.
2024-07-31 19:23:03 +09:00
Rui Ueyama 4e6b4c43cd Refactor 2024-07-31 19:09:56 +09:00
Rui Ueyama 2c6f379d69 Support R_LARCH_CALL36 2024-07-31 17:51:34 +09:00
Rui Ueyama bf79ae1190 Refactor 2024-07-31 14:55:05 +09:00
Rui Ueyama 96fda95624 Add a test for LoongArch relaxation 2024-07-31 11:48:04 +09:00
Rui Ueyama b71bfccb46 Improve pcalau12i+addi.d relaxation
This should unbreak our buildbots.
2024-07-31 11:36:32 +09:00
Rui Ueyama a4dc015c0d Update comments 2024-07-31 10:39:22 +09:00
ywgrit 74b359fe62 [LoongArch] Support relax of pcalau12i/addi.d->pcaddi (#1318)
Relax pcalau12i/addi.d to pcaddi
2024-07-31 10:39:06 +09:00
Rui Ueyama 197a74ded6 Add comments 2024-07-29 16:03:35 +09:00
Rui Ueyama 9aa4fcc6cd Show skipped tests
I don't know why, but the previous regular expression didn't match with
a line consisting just with "skipped".
2024-07-29 13:41:34 +09:00
Rui Ueyama 36915416dd Run a binary under Intel SDE CPU emulator with CET enabled 2024-07-29 13:16:12 +09:00
Rui Ueyama 411dcfd0b1 Fix comments 2024-07-29 13:13:54 +09:00
Rui Ueyama 35564d5f2c Update mold.1 (automated commit) 2024-07-29 02:00:32 +00:00
Rui Ueyama bba20b3fe7 Fix typos 2024-07-29 10:59:12 +09:00
Rui Ueyama 55e516da6f Update mold.1 (automated commit) 2024-07-29 01:55:03 +00:00
Rui Ueyama 2c3dcc4370 Document --remove-landing-pads 2024-07-29 10:52:31 +09:00
Rui Ueyama 1fb6dd3b46 Rename -z rewrite-endbr --remove-landing-pads
This is because we want to support it for non-x86-64 targets.
Using the x86 instruction name as a parameter name is not desirable.
2024-07-29 10:52:21 +09:00
Rui Ueyama ed7eec5969 Fix -z rewrite-endbr
Previously, functions that are referred to only by section symbols were
not considered address-taken, and we always rewrote endbr64s with NOPs for
such functions. That caused a runtime fault when such function was called
indirectly.

Now, endbr64s for such symbols are retained.

I tried to build clang-19 as a release build with and without `-z
rewrite-endbr` and counted the number of endbr64 instructions in each
binary. Here is the result:

  Before: 110,615
  After:   91,799

So the feature reduces the number of gadgets by 17%. Both binaries worked
fine with Intel SDE CPU emulator with `-cet 1 -cet_raise 1`, so I think
it's finally working as expected.

I also tried to build mold itself with and without the feature. Here is
the result:

  Before: 27,430
  After:  17,725

This is a 35% reduction. I confirmed that mold built with `-z rewrite-endbr`
can self-host under Intel SDE.

The rewrite_endbr pass is extremely fast. It took only 7 milliseconds
for a ~210 MiB clang-19 binary on my Threadripper 7990X machine. We
may want to consider enabling it by default at some point.
2024-07-29 09:06:26 +09:00
Rui Ueyama 3f7236d268 Refactor 2024-07-28 19:42:05 +09:00
Rui Ueyama 766ce80297 Update comments 2024-07-28 17:01:13 +09:00
Rui Ueyama 61af6f3c59 Report an error for an enormously large r_addend 2024-07-27 16:14:20 +09:00
Rui Ueyama 464f7655d6 Use break instead of nop as a filler 2024-07-27 14:50:38 +09:00
Rui Ueyama d1674d86ac Add comments 2024-07-27 14:50:38 +09:00
Rui Ueyama 36e5b4b949 Relax R_LARCH_TLS_LE_{HI20_R,ADD_R,LO12_R} 2024-07-27 11:03:45 +09:00
Rui Ueyama 98a7cff0c5 Handle R_LARCH_RELAX 2024-07-27 11:03:45 +09:00
Rui Ueyama 34ed39c873 Refactor 2024-07-27 11:03:43 +09:00
Rui Ueyama 2bbba15068 Create canonical PLTs for PIEs
We cannot guarantee pointer equality for some corner cases without them.
2024-07-24 10:56:39 +09:00
Rui Ueyama a478db77c2 Do not obtain an unnecessary lock 2024-07-22 12:05:19 +09:00
Rui Ueyama d829e425df Add a missing #include
https://github.com/microsoft/mimalloc/pull/917
2024-07-22 11:06:37 +09:00
Rui Ueyama f5b2b82295 Skip a test if LD_PRELOAD doesn't work
May be related to the following issues:

  https://github.com/rui314/mold/issues/1305
  https://github.com/rui314/mold/issues/1309
2024-07-22 10:39:32 +09:00
Rui Ueyama e3876ddbd4 Refactor 2024-07-22 08:47:25 +09:00
Rui Ueyama b8556e37bb Fix CI 2024-07-21 20:18:31 +09:00
Rui Ueyama bbfaa0bb30 Fix CI 2024-07-21 19:47:12 +09:00
Rui Ueyama 5606087776 Support -z x86-64-v[234] 2024-07-21 18:51:28 +09:00
Rui Ueyama b919548b27 Refactor 2024-07-21 16:42:39 +09:00
Rui Ueyama ed7414e1c7 Update mold.1 (automated commit) 2024-07-21 06:16:56 +00:00
Rui Ueyama fa1237fc08 Document --no-allow-shlib-undefined 2024-07-21 15:14:48 +09:00
Rui Ueyama 1dc99aaade Fix CI 2024-07-21 13:26:58 +09:00
Rui Ueyama 3001f02b55 Support --no-allow-shlib-undefined
Fixes https://github.com/rui314/mold/issues/1313
2024-07-21 13:06:57 +09:00
Rui Ueyama 39442dbdf0 Refactor 2024-07-20 16:05:37 +09:00
Rui Ueyama 6c4ca4e40c Attempt to fix CI 2024-07-20 15:06:15 +09:00
Rui Ueyama bdca85e15a Make Integer's constructor constexpr 2024-07-20 14:54:58 +09:00
Rui Ueyama 1ccc77ac7f Refactor 2024-07-20 13:38:03 +09:00
Rui Ueyama b99ed6e7ae Refactor 2024-07-20 13:10:33 +09:00
Rui Ueyama 6ee6410948 Build and test on more Linux distros 2024-07-19 18:03:57 +09:00
Rui Ueyama d187dac602 Install binary packages in Gentoo 2024-07-19 18:03:57 +09:00
Rui Ueyama 2b4c365161 Run tests on Alpine Linux with musl libc 2024-07-19 15:46:15 +09:00
Rui Ueyama 70ea7ade7f Do not use the extended regexp
It looks like Busybox's grep doesn't support regexp backreferences.

Fixes https://github.com/rui314/mold/issues/1309
2024-07-19 15:23:52 +09:00
Rui Ueyama 56a94d2693 Fix a test on musl
Fixes https://github.com/rui314/mold/issues/1309
2024-07-19 14:54:33 +09:00
Rui Ueyama 14a4b05950 Always create a .eh_frame section even if it's empty
glibc's `__register_frame_info_bases` expect that it can read a terminator
if a .eh_frame is empty. Removing the section completely caused a crash bug
on Alpine Linux with musl as libc.

Specifically, mold-generated binaries crash if all the following
condition were met:

 1. musl is used as a libc
 2. .eh_frame is empty
 3. libgcc_s.so is linked

Prior to 06b592683c, condition (3) was not
usually satisfied, so we didn't notice this issue. Now, the shared library
is linked against almost all output files because the file is referenced by
`__deregister_frame_info`.

https://github.com/bminor/glibc/blob/910aae6e5a2196938fc30fa54dd1e96f16774ce7/sysdeps/generic/unwind-dw2-fde.c#L105-L107

Fixes https://github.com/rui314/mold/issues/1309
2024-07-19 14:51:00 +09:00
Rui Ueyama 14d525c1a1 Refactor 2024-07-18 21:04:55 +09:00
Rui Ueyama 5b7ae06bde Update mold.1 (automated commit) 2024-07-18 06:53:23 +00:00
Rui Ueyama e67f5e62ae Fix update-manpage workflow 2024-07-18 15:51:56 +09:00
Rui Ueyama bae9598190 Refactor 2024-07-18 13:54:16 +09:00
Rui Ueyama 9acda6f939 Make update-manpage workflow runnable by hand 2024-07-18 13:06:08 +09:00
Rui Ueyama 7b52f34c38 Make test scripts more verbose 2024-07-18 12:57:32 +09:00
Rui Ueyama 9874a9daa7 Document --encoded-package-metadata
https://github.com/rui314/mold/pull/1308
2024-07-18 11:57:48 +09:00
Rui Ueyama aecf39d459 Do not crash when an empty argument is given 2024-07-18 11:57:48 +09:00
Rui Ueyama 9eacc638f3 Improve a test 2024-07-18 11:57:48 +09:00
Rui Ueyama 0c34ec1f50 Refactor 2024-07-18 09:39:01 +09:00
Rui Ueyama a778f9c5f4 Merge pull request #1308 from bdrung/encoded-package-metadata
Add --encoded-package-metadata
2024-07-17 22:23:44 +09:00
Rui Ueyama d7b079c4fb Refactor 2024-07-17 21:26:36 +09:00
Benjamin Drung cfe60991a5 Add --encoded-package-metadata
Specifying the compiler flag `-Wl,--package-metadata=<JSON>` might not
work, because the shells might eat the quotation marks and the compiler
splits the JSON at the commas.

Ubuntu tried to using a specs file to set `--package-metadata` but that
turned out to be too fragile. autopkgtests might use the compiler flags
but the needed environment variables are not set in the test
environment. Debugging a crash of an application build with the -spec
parameter lacks the environment variables. People like to iteratively
continue building the software in the build directory while hacking on
the package and then have no environment variable set.

So introduce a `--encoded-package-metadata` linker flag that takes a
percent-encoded JSON. Percent-encoding is used because it is a
standard and simple to implement.

Bug-Ubutru: https://bugs.launchpad.net/bugs/2071468
Signed-off-by: Benjamin Drung <[email protected]>
2024-07-17 14:14:48 +02:00
Rui Ueyama 2acf634340 Fix typo 2024-07-17 18:50:48 +09:00
Rui Ueyama 09f1008186 Handle R_LARCH_TLS_LE_HI20_R and R_LARCH_TLS_LE_LO12_R 2024-07-17 18:36:12 +09:00
Rui Ueyama baf9ae9038 Fix a test on Debian
If the default linker doesn't complain, just skip the test.

Fixes https://github.com/rui314/mold/issues/1301
2024-07-16 12:00:08 +09:00
Rui Ueyama cdcb428f26 Remove dead code 2024-07-15 13:41:57 +09:00
Rui Ueyama 36bc198b9a Refactor 2024-07-15 13:36:07 +09:00
Rui Ueyama 428a9c4228 Skip a test on m68k
https://github.com/rui314/mold/pull/1304
2024-07-15 11:19:06 +09:00
Rui Ueyama 666b18b851 Use __attribute__((aligned)) instead of a C macro 2024-07-15 11:06:11 +09:00
Rui Ueyama f180ac21c8 Fix build on non-m68k hosts
https://github.com/rui314/mold/pull/1304
2024-07-14 21:49:58 +09:00
Rui Ueyama 0b1be96a13 Merge pull request #1304 from chewi/m68k-alignment
Align Chuck, InputSection, and SectionFragment to at least 4 bytes
2024-07-14 21:44:35 +09:00
James Le Cuirot 1f444a21d2 Align Chuck, InputSection, and SectionFragment to at least 4 bytes
There are already static_asserts to check this, so it is almost always
true anyway, but not on m68k, which aligns to 2 bytes by default.
2024-07-14 10:22:04 +01:00
Rui Ueyama 4eeebf0221 Define Script class and move linker script-related code there 2024-07-14 16:41:03 +09:00
Rui Ueyama b2a1e4b83e Move reader-related attributes to ReaderContext 2024-07-14 11:10:59 +09:00
Rui Ueyama 08938402c9 Rename a variable 2024-07-13 18:19:11 +09:00
Rui Ueyama 4c569c8c8c Accept --library as a synonym for -l
No one has complained about it so far, but according to the GNU ld manual,
`--library` is a synonym for `-l`.
2024-07-13 18:18:35 +09:00
Rui Ueyama 03a2105d39 Change how we resolve relocations referring to mergeable sections
Previously, we find a string in a mergeable string section by offset
after adding a relocation addend to a symbol value. Now, we find a
string just by a symbol value and then add a relocation addend later.

Assume a mergeable section containing two strings as an example.

  foo\0bar\0
  ^    ^
  .L1  .L2

Previously, a relocation referring to .L2 - 1 referred to the end of
.L1. Now, it refers to one byte before .L2.

Section symbol is an exception to this rule, because it seems it's
what the compiler is assuming. The rules seem arbitrary, but it looks
like we need them for the compatibility with existing object files.

Fixes https://github.com/rui314/mold/issues/1300
2024-07-13 17:25:44 +09:00
Rui Ueyama d2870ac574 Upgrade bundled mimalloc to 2.1.7 2024-07-11 15:38:18 +09:00
Rui Ueyama 8497b7e1dc Upgrade OneTBB in ./third-party to version 2021.13.0 2024-07-11 15:38:18 +09:00
Rui Ueyama 0e2b6b010d Update mold.1 (automated commit) 2024-07-11 04:39:27 +00:00
Rui Ueyama 84263109b1 Revert "Allow numbers other than 1 for MOLD_JOBS"
This reverts commit 863893057e because
it's unstable.
2024-07-11 13:36:55 +09:00
Rui Ueyama 162bcca5df Update mold.1 (automated commit) 2024-07-11 02:10:41 +00:00
Rui Ueyama 2fb3c9da57 Update man page 2024-07-11 11:09:03 +09:00
Rui Ueyama 83ee4962b1 Fix test flakiness for SH4 2024-07-11 09:58:16 +09:00
Rui Ueyama e03f74a357 Fix CI
Fixes https://github.com/rui314/mold/issues/1299
2024-07-10 15:52:46 +09:00
Rui Ueyama 8c1b25b3aa Obtain a file lock with flock() while creating a separate debug file 2024-07-09 17:49:39 +09:00
Rui Ueyama 315dfd3244 Fix CI 2024-07-09 15:25:53 +09:00
Rui Ueyama 02ca830f42 Refactor 2024-07-09 15:07:41 +09:00
Rui Ueyama 18da5b654e Add --no-detach to write to a separate debug file in the foreground
--detach is the default.
2024-07-09 12:06:41 +09:00
Rui Ueyama 97a1e218c5 Simplify crc32_solve()
The code was originally written by Pete Cawley
https://gist.github.com/corsix/bdfc8f2f1dc0f28de39f74de9bf4f060
2024-07-09 10:15:26 +09:00
Rui Ueyama f9e4cb1a7f Add a missing #include 2024-07-08 10:43:40 +09:00
Rui Ueyama 60760a892a Attempt to fix CI 2024-07-08 09:59:07 +09:00
Rui Ueyama 596ffa959a Add --separate-debug-info
This option is to separate debug info to a different file. The debug
info file's filename is stored to the main output file's .gnu_debuglink
section. gdb can read the section contents and followg the link to
find debug info in another file.

Fixes https://github.com/rui314/mold/issues/1294
2024-07-08 09:28:32 +09:00
Rui Ueyama cd3b817f13 Make notify_parent idempotent 2024-07-07 22:49:35 +09:00
Rui Ueyama 19de40fed4 Refactor 2024-07-07 22:45:17 +09:00
Rui Ueyama e78e12b15b Refactor 2024-07-07 22:24:42 +09:00
Rui Ueyama 5b4377842b Fix CI 2024-07-07 21:50:53 +09:00
Rui Ueyama 16ee9f48a4 Fix CI 2024-07-07 17:58:21 +09:00
Rui Ueyama 8e3679e4e5 Refactor 2024-07-06 13:11:09 +09:00
Rui Ueyama 7089304046 Simplify 2024-07-05 19:47:33 +09:00
Rui Ueyama 5ab3708e9f Merge pull request #1298 from yujincheng08/patch-1
Fix cross-compile lto on MacOS
2024-07-05 19:45:10 +09:00
Rui Ueyama aa939d7a6a Temporarily disable ASAN tests
It looks like starting with Clang 18.1.3, ASAN began reporting an
error on OneTBB.
2024-07-05 18:27:00 +09:00
Rui Ueyama a63bbcd7b1 Attempt to fix CI 2024-07-05 17:58:13 +09:00
Rui Ueyama 339f9c485a Attempt to fix CI 2024-07-05 17:42:32 +09:00
LoveSy 4905f6d3f2 Use macos-12 because macos-11 has been removed
https://github.blog/changelog/2024-05-20-actions-upcoming-changes-to-github-hosted-macos-runners/#macos-11-deprecation-and-removal
2024-07-05 15:32:37 +08:00
Rui Ueyama cb8cc1c8f0 Refactor 2024-07-05 16:00:09 +09:00
Rui Ueyama c6b54532e9 Refactor 2024-07-04 16:17:25 +09:00
Rui Ueyama 3936134823 Refactor 2024-07-04 15:08:37 +09:00
Rui Ueyama 55ca05bab6 Refactor 2024-07-04 13:59:29 +09:00
LoveSy 7305a5c5dd Fix cross-compile lto on MacOS 2024-07-03 21:55:22 +08:00
Rui Ueyama b145377c39 Merge pull request #1296 from marxin/improve-gdb-index-test
Improve --gdb-index test by using readelf --debug=gdb_index
2024-07-01 17:15:51 +09:00
Martin Liska 08f2a74be7 Improve --gdb-index test by using readelf --debug=gdb_index 2024-06-30 16:07:47 +02:00
Rui Ueyama 82fb10fe26 Fix a test 2024-06-28 15:57:10 +09:00
Rui Ueyama 34f4d654d3 Add an entry for AlmaLinnux 2024-06-28 15:54:39 +09:00
Rui Ueyama 091395df33 Link with -ldl for dlopen()
Fixes https://github.com/rui314/mold/issues/1293
2024-06-28 11:00:37 +09:00
Rui Ueyama a38be94064 Simplify 2024-06-27 19:51:06 +09:00
Rui Ueyama cf1c02d097 Bump mold version to 2.32.1 2024-06-27 14:04:26 +09:00
Rui Ueyama 3dbe2ac704 Do not write relocation addends for RISC-V
Probably due to a bug in glibc, static PIE binaries crash on startup
in some environment.

Fixes https://github.com/rui314/mold/issues/1291
2024-06-27 13:09:58 +09:00
Rui Ueyama 35d67a3fab Add a missing #include for getenv() 2024-06-26 15:10:54 +09:00
Rui Ueyama f60fada438 Refactor 2024-06-24 16:09:35 +09:00
Rui Ueyama c419dcde1b Merge pull request #1289 from yujincheng08/main
ci: add msys2
2024-06-24 09:49:34 +09:00
LoveSy ebfe4f13c6 fix mingw compiles 2024-06-23 15:40:28 +00:00
LoveSy 8f9b59cbe6 ci: add msys2 2024-06-23 23:21:50 +08:00
Rui Ueyama 63f26708fd Unbreak daily buildbot 2024-06-23 12:38:59 +09:00
Rui Ueyama 07b09a66d3 Attempt to fix CI 2024-06-22 13:51:30 +09:00
Rui Ueyama 4cc61361f3 Refactor 2024-06-22 13:08:43 +09:00
Rui Ueyama 5535e7a6b7 Style change 2024-06-22 10:56:36 +09:00
Rui Ueyama 06b592683c Make weak undefined symbols to mark shared libraries as "needed"
It is not clearly defined when undefined weak symbols are resolved.
It looks like there are two possible approaches:

 1. Promote all weak undefined symbols to dynamic ones so that they'll
    have another chance to be resolved at load-time, or

 2. Promote weak undefined symbols to dynamic ones only when there are
    definitions in other DSOs at link-time.

(1) provides the maximum flexibility. For example, consider a main program
that has a weak undefined symbol `foo` and there's no DSO that defines it
at link-time. In (1), `foo` gets promoted to a dynamic symbol, so that one
of its depending DSO is upgraded to define `foo`, the main executable's
`foo` is resolved to that symbol at load-time. On the other hand, in (2),
`foo` would have already been converted to an absolute symbol at address
zero at link-time, so you need to rebuild the main executable to use the
new definition of `foo` in the shared library.

However, (1) is not compatible with copy relocations. This is because we
need to know the size of the symbol when creating a copy relocation, but
that information is not available unless we have a definition. It's also
not compatible with canonical PLTs because canonical PLTs have non-zero
addresses and therefore weak undefined symbols would always be resolved to
non-zero addresses.

As a workaround, GNU ld promotes weak undefs to dynamic symbols only when
they don't need copy relocations or canonical PLTs. In other words, weak
undef's behavior is different between -fPIC and -fno-PIC. In the former
case, they become dynamic symbols, and vice versa.

I don't think that workaround is a good one. So, mold took the second
approach.

There is, however, another thing to consider. What if we can find a
defined symbol in a DSO that is specified as `-as-needed`? Previously,
mold did not mark the library as "needed" and converted the weak undef
into an absolute symbol.

However, libstdc++ assumes that if weak undef symbol
`__pthread_key_create` is not resolved, it assumes that multi-threading is
not used in the executable, which resulted in a mis-detection with mold.

Therefore, this patch changes the mold's behavior so that it makes weak
undefs to keep DSOs "needed".

Fixes https://github.com/rui314/mold/issues/1286
2024-06-22 10:46:17 +09:00
Rui Ueyama 0b5e5dcbb5 Fix -Wunused-result 2024-06-22 10:46:17 +09:00
Rui Ueyama b1695c6018 Merge pull request #1287 from yujincheng08/lto
Support lto on mingw
2024-06-22 07:54:25 +09:00
LoveSy 48ee8e8257 Handle stackoverflow 2024-06-21 17:42:33 +08:00
LoveSy 50bf031d49 Support lto on mingw 2024-06-21 17:42:27 +08:00
Rui Ueyama bf05198d53 Merge pull request #1285 from yujincheng08/patch-2 2024-06-20 11:17:47 +09:00
Rui Ueyama f9a37e9dd4 Remove dead code
get_self_path() is used by `mold -run`, which is not available on
macOS or Windows.
2024-06-20 09:31:44 +09:00
LoveSy cf9e59954b Use __has_include instead 2024-06-19 23:50:40 +08:00
LoveSy 7cce297318 Only define STDERR_FILENO when not defined 2024-06-19 23:48:14 +08:00
Rui Ueyama 1f94716fce Attempt to fix CI 2024-06-18 22:40:52 +09:00
Rui Ueyama 863893057e Allow numbers other than 1 for MOLD_JOBS 2024-06-18 18:28:28 +09:00
Rui Ueyama b83b1950c0 Merge pull request #1281 from ziyao233/outgoing/fix-static-flag 2024-06-17 08:10:20 +09:00
Yao Zi d2774b0da9 Use $CXX to detect cxxflags
Use $CXX instead of $CC to detect whether C++ programs could be built
statically. Add function test_cxxflags for convenience.

Fix test error in an environment where C++ programs cannot be built
statically, with error like:

  mold: fatal: library not found: c++

Signed-off-by: Yao Zi <[email protected]>
2024-06-16 20:16:34 +00:00
PerseoGI cd7287f0de Update README adding Conan usage instructions 2024-06-13 11:26:14 +02:00
Rui Ueyama cff858bad5 Attempt to fix CI 2024-06-13 16:47:50 +09:00
Rui Ueyama 7669a3ce67 Attempt to fix CI 2024-06-13 16:11:48 +09:00
Rui Ueyama 3ad387f20c Refactor 2024-06-13 13:33:45 +09:00
Rui Ueyama af6d356468 Attempt to fix CI 2024-06-11 14:49:53 +09:00
Rui Ueyama 768f1dffdd Attempt to fix CI 2024-06-11 14:43:54 +09:00
Rui Ueyama 2a25c81834 Refactor 2024-06-11 13:37:21 +09:00
Rui Ueyama 24b8941f60 Refactor 2024-06-10 20:36:40 +09:00
LoveSy 841a186346 Fix corrupted .riscv.attributes ISA string (#1276)
Fix corrupted .riscv.attributes ISA string
extension name can contain digits
2024-06-10 19:56:40 +09:00
Muhammad Mominul Huque f05e32df4a Update README.md
GCC doesn't accept a path as an argument to `-fuse-ld`. `-fuse-ld=mold` is enough.

Tested with
```
gcc (GCC) 13.2.1 20231205 (Red Hat 13.2.1-6)
```
2023-12-31 18:21:30 +06:00
1160 changed files with 38985 additions and 23164 deletions

No files matched your search

+20 -14
View File
@@ -5,14 +5,22 @@ on:
- cron: '0 0 * * *'
release:
types: [ published ]
workflow_dispatch:
jobs:
build-tarballs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ x86_64, aarch64, arm, riscv64, ppc64le, s390x ]
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
permissions:
contents: read
@@ -20,26 +28,24 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: tarballs
path: mold-*.tar.gz
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
+44
View File
@@ -0,0 +1,44 @@
name: Build native tarballs
on:
push:
workflow_dispatch:
jobs:
build-tarballs:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
runs-on: ${{ matrix.os }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
-36
View File
@@ -1,36 +0,0 @@
name: Build x86 tarball
on:
push:
branches: [ main ]
jobs:
build-tarball:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build a tarball
run: ./dist.sh
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: tarball
path: mold-*.tar.gz
+89 -81
View File
@@ -1,42 +1,32 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
jobs:
build-clang:
build-sanitizers:
strategy:
matrix:
target:
# Disable PCH for the default configuration. This prevents relying on implicit includes.
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
- ''
- '-DMOLD_USE_ASAN=On'
- '-DMOLD_USE_TSAN=On'
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: rui314/setup-mold@staging
- name: install-build-deps
run: sudo ./install-build-deps.sh
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- run: sudo ./install-build-deps.sh
- name: build
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
sudo apt-get install -y clang++-12
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
mkdir build
cd build
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
cmake --build . -j$(nproc)
- name: test
run: |
cd build
ctest -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-clang
@@ -44,98 +34,116 @@ jobs:
build
!build/CMakeFiles
build-gcc:
runs-on: ubuntu-20.04
container: gcc:11.1.0
build-multi-archs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: install-build-deps
shell: bash
run: |
# Install cross toolchains
./install-build-deps.sh
./install-cross-tools.sh
# Install a RV32 toolchain from third party since it's not available
# as an Ubuntu package.
mkdir /rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /rv32 --strip-components=1 -xzf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
done
# Install a LoongArch toolchain
mkdir /larch
wget -O- -q https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
ln -sf /larch/target /usr/loongarch64-linux-gnu
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /usr/loongarch64-linux-gnu/lib64/
for i in objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
echo '/larch/bin/loongarch64-unknown-linux-gnu-gcc -L/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-gcc
echo '/larch/bin/loongarch64-unknown-linux-gnu-g++ -L/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-g++
chmod 755 /usr/bin/loongarch64-linux-gnu-{gcc,g++}
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
- name: ccache
uses: hendrikmuhs/ccache-action@v1
sudo ./install-build-deps.sh
sudo ./install-cross-tools.sh
sudo .github/workflows/install-extras.sh
- name: build
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- name: test
run: |
cd build
ctest -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-gcc
name: test-results-multi-archs
path: |
build
!build/CMakeFiles
build-distros:
strategy:
matrix:
distro:
- alpine
- archlinux
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
- fedora
- gentoo/stage3
- opensuse/tumbleweed
- ubuntu:22.04
- ubuntu:25.04
runs-on: ubuntu-latest
container: ${{ matrix.distro }}
steps:
- uses: actions/checkout@v4
- run: ./install-build-deps.sh
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-${{ matrix.distro }}
path: |
build
!build/CMakeFiles
build-macos:
runs-on: macos-11
strategy:
matrix:
target:
# Disable PCH for the default configuration. This prevents relying on implicit includes.
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
- '-DMOLD_USE_ASAN=On'
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- uses: actions/checkout@v4
- name: build
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
mkdir build
cd build
cmake ${{ matrix.target }} ..
cmake ..
cmake --build . -j$(sysctl -n hw.physicalcpu)
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: build
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
build-msys:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
- name: build
shell: msys2 {0}
run: |
mkdir build
cd build
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
cmake --build . -j $(nproc)
build-freebsd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and test
uses: vmactions/freebsd-vm@v1
with:
usesh: true
run: |
./install-build-deps.sh
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
ctest --output-on-failure -j$(nproc)
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash -x
apt-get update
apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
done
# Install a LoongArch toolchain
mkdir /larch
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- -q https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
chmod 755 /usr/bin/sh4aeb-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /sh4aeb/bin/sh4aeb-linux-$i /usr/bin/sh4aeb-linux-gnu-$i
done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
+3 -4
View File
@@ -1,5 +1,3 @@
# This file is generated by ChatGPT
name: Update manpage
on:
@@ -8,6 +6,7 @@ on:
- 'docs/mold.md'
branches:
- main
workflow_dispatch:
jobs:
update-manpage:
@@ -15,10 +14,10 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install ronn
run: sudo apt-get install -y ronn
run: sudo apt-get update && sudo apt-get install -y ronn
- name: Generate mold.1 from mold.md
run: ronn --roff docs/mold.md
+1
View File
@@ -8,3 +8,4 @@ gentoo
/mold-*.tar.gz
/build*
/mold
/dist
+90 -76
View File
@@ -44,27 +44,38 @@
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.32.0)
project(mold VERSION 2.37.1)
include(CMakeDependentOption)
include(CheckSymbolExists)
include(GNUInstallDirs)
add_executable(mold)
target_compile_features(mold PRIVATE cxx_std_20)
if(MINGW)
target_link_libraries(mold PRIVATE dl)
else()
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
endif()
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
add_link_options(-fuse-ld=mold -Wl,--gdb-index)
endif()
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
add_executable(mold)
target_compile_features(mold PRIVATE cxx_std_20)
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
endif()
endif()
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
target_compile_options(mold PRIVATE
-fno-exceptions
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-ffunction-sections
-fdata-sections
-Wall
-Wextra
-Wno-sign-compare
@@ -169,7 +180,7 @@ endif()
# be stable on 32-bit targets.
cmake_dependent_option(
MOLD_USE_MIMALLOC "Use mimalloc" ON
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD" OFF)
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
@@ -182,7 +193,6 @@ if(MOLD_USE_MIMALLOC)
if(MOLD_USE_SYSTEM_MIMALLOC)
find_package(mimalloc REQUIRED)
target_link_libraries(mold PRIVATE mimalloc)
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
else()
function(mold_add_mimalloc)
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
@@ -247,7 +257,7 @@ if(NOT APPLE AND NOT WIN32)
# Remove the default `lib` prefix
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
endif()
# If atomics doesn't work by default, add -latomic.
@@ -273,21 +283,22 @@ if(NOT APPLE AND NOT MSVC)
endif()
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
# Create a .cc file containing the current git hash for `mold --version`.
add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-P ${CMAKE_SOURCE_DIR}/common/update-git-hash.cmake
DEPENDS common/update-git-hash.cmake
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS git-hash.cc
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(common/config.h.in config.h)
configure_file(lib/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
@@ -299,33 +310,48 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# compiler instances. This is hacky but greatly reduces compile time
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
S390X SPARC64 M68K SH4 ALPHA LOONGARCH32 LOONGARCH64)
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64 ARC)
list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/arch-loongarch.cc
elf/arch-riscv.cc
elf/cmdline.cc
elf/gc-sections.cc
elf/gdb-index.cc
elf/icf.cc
elf/input-files.cc
elf/input-sections.cc
elf/linker-script.cc
elf/main.cc
elf/mapfile.cc
elf/output-chunks.cc
elf/passes.cc
elf/relocatable.cc
elf/subprocess.cc
elf/thunks.cc
elf/tls.cc
src/arch-arm64.cc
src/arch-loongarch.cc
src/arch-riscv.cc
src/arch-sh4.cc
src/cmdline.cc
src/filetype.cc
src/gc-sections.cc
src/gdb-index.cc
src/icf.cc
src/input-files.cc
src/input-sections.cc
src/linker-script.cc
src/main.cc
src/mapfile.cc
src/output-chunks.cc
src/passes.cc
src/relocatable.cc
src/shrink-sections.cc
src/thunks.cc
src/tls.cc
)
if(WIN32)
list(APPEND MOLD_ELF_TEMPLATE_FILES elf/lto-win32.cc)
if(WIN32 AND NOT MINGW)
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-win32.cc)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES elf/lto-unix.cc)
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-unix.cc)
endif()
if(WIN32)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/output-file-win32.cc
src/subprocess-win32.cc
)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/output-file-unix.cc
src/subprocess-unix.cc
)
endif()
function(mold_instantiate_templates SOURCE TARGET)
@@ -347,58 +373,47 @@ endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
common/compress.cc
common/demangle.cc
common/filepath.cc
common/glob.cc
common/hyperloglog.cc
common/malloc.cc
common/multi-glob.cc
common/perf.cc
common/random.cc
common/tar.cc
elf/arch-alpha.cc
elf/arch-arm32.cc
elf/arch-arm64.cc
elf/arch-i386.cc
elf/arch-m68k.cc
elf/arch-ppc32.cc
elf/arch-ppc64v1.cc
elf/arch-ppc64v2.cc
elf/arch-s390x.cc
elf/arch-sh4.cc
elf/arch-sparc64.cc
elf/arch-x86-64.cc
elf/config.cc
elf/elf.cc
git-hash.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/mimalloc.cc
lib/multi-glob.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/arch-arc.cc
src/arch-arm32.cc
src/arch-i386.cc
src/arch-m68k.cc
src/arch-ppc32.cc
src/arch-ppc64v1.cc
src/arch-ppc64v2.cc
src/arch-s390x.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/config.cc
src/elf.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
common/jobs-win32.cc
common/mapped-file-win32.cc
common/signal-win32.cc
lib/jobs-win32.cc
lib/mapped-file-win32.cc
lib/signal-win32.cc
)
else()
target_sources(mold PRIVATE
common/jobs-unix.cc
common/mapped-file-unix.cc
common/signal-unix.cc
lib/jobs-unix.cc
lib/mapped-file-unix.cc
lib/signal-unix.cc
)
endif()
# Add frequently included header files for pre-compiling.
# target_precompile_headers is supported by CMake 3.16.0 or newer.
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
# to enable caching
if(NOT MSVC)
target_compile_options(mold PRIVATE -fpch-preprocess)
endif()
endif()
include(CTest)
if(BUILD_TESTING)
@@ -413,7 +428,7 @@ if(BUILD_TESTING)
endif()
if(${UNIX})
add_subdirectory(test/elf)
add_subdirectory(test)
endif()
endif()
@@ -421,7 +436,6 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES "LICENSE.third-party" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
function(mold_install_relative_symlink OLD NEW)
install(CODE "
+16 -8
View File
@@ -24,8 +24,7 @@ free to [file a bug report](https://github.com/rui314/mold/issues).
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, SH-4, and DEC
Alpha.
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
## Why does linking speed matter?
@@ -77,12 +76,12 @@ installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
For other cmake options, see the comments in `CMakeLists.txt`.
If you are not using a recent enough Linux distribution, or if `cmake` does
not work for you for any reason, you can use Docker to build mold in a Docker
environment. To do so, run `./dist.sh` in this directory instead of using
`cmake`. The shell script will pull a Docker image, build mold and auxiliary
not work for you for any reason, you can use Podman to build mold in a
container. To do so, run `./dist.sh` in this directory instead of using
`cmake`. The shell script will pull a container image, build mold and auxiliary
files inside it, and package them into a single tar file named
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere and
use the mold executable within it.
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
and use the mold executable in it.
## How to use
@@ -133,7 +132,7 @@ may be able to remove the `linker = "clang"` line.
```toml
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
If you want to use mold for all projects, add the above snippet to
@@ -159,6 +158,14 @@ If you want to use mold for all projects, add the above snippet to
</details>
<details><summary>If you are using Conan package manager</summary>
You can configure [Conan](https://github.com/conan-io) to download the latest
version of `mold` and use it as the linker when building your dependencies and
projects from source. Please see the instructions [here](https://conan.io/center/recipes/mold).
</details>
<details><summary>mold -run</summary>
It is sometimes very hard to pass an appropriate command line option to `cc`
@@ -265,3 +272,4 @@ the following people and organizations who have sponsored $128/month or more:
- [Bryant Biggs](https://github.com/bryantbiggs)
- [kraptor23](https://github.com/kraptor23)
- [Jinkyu Yi](https://github.com/jincreator)
- [Pedro Navarro](https://github.com/pedronavf)
Executable
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash -x
set -e
apt-get install -y git bzip2 ninja-build pkg-config libglib2.0-dev python3-distutils-extra libpixman-1-dev
cd /
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/qemu.git
mkdir /qemu/build
cd /qemu/build
../configure --static --extra-cflags=-Wno-error
ninja
ln -s /qemu/build/qemu-arc /qemu/build/qemu-arc64 /usr/local/bin
apt-get install -y libgmp-dev libmpfr-dev texinfo bison yacc flex
cd /
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/binutils-gdb.git
cd /binutils-gdb
mkdir build
cd build
../configure --target=arc-linux
make -j$(nproc)
apt-get install -y mold
mkdir /build
cd /build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DMOLD_USE_MOLD=1 /mold
ninja
TRIPLE=arc-linux-gnu bash -x /mold/test/hello-dynamic.sh
-68
View File
@@ -1,68 +0,0 @@
#include "common.h"
#include <filesystem>
#include <sys/stat.h>
#ifdef __APPLE__
# include <mach-o/dyld.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
#endif
namespace mold {
std::string get_realpath(std::string_view path) {
std::error_code ec;
std::filesystem::path link = std::filesystem::read_symlink(path, ec);
if (ec)
return std::string(path);
return (filepath(path) / ".." / link).lexically_normal().string();
}
// Removes redundant '/..' or '/.' from a given path.
// The transformation is done purely by lexical processing.
// This function does not access file system.
std::string path_clean(std::string_view path) {
return filepath(path).lexically_normal().string();
}
std::filesystem::path to_abs_path(std::filesystem::path path) {
if (path.is_absolute())
return path.lexically_normal();
return (std::filesystem::current_path() / path).lexically_normal();
}
// Returns the path of the mold executable itself
std::string get_self_path() {
#if __APPLE__
char path[8192];
u32 size = sizeof(path);
if (_NSGetExecutablePath(path, &size)) {
std::cerr << "_NSGetExecutablePath failed\n";
exit(1);
}
return path;
#elif __FreeBSD__
// /proc may not be mounted on FreeBSD. The proper way to get the
// current executable's path is to use sysctl(2).
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t size;
sysctl(mib, 4, NULL, &size, NULL, 0);
std::string path;
path.resize(size);
sysctl(mib, 4, path.data(), &size, NULL, 0);
return path;
#else
return std::filesystem::read_symlink("/proc/self/exe").string();
#endif
}
} // namespace mold
-221
View File
@@ -1,221 +0,0 @@
// This file defines integral types for file input/output. We need to use
// these types instead of the plain integers (such as uint32_t or int32_t)
// when reading from/writing to an mmap'ed file area for the following
// reasons:
//
// 1. mold is always a cross linker and should not depend on what host it
// is running on. Users should be able to run mold on a big-endian
// SPARC machine to create a little-endian RV64 binary, for example.
//
// 2. Even though data members in all ELF data strucutres are naturally
// aligned, they are not guaranteed to be aligned on memory. Because
// archive file (.a file) aligns each member only to a 2 byte boundary,
// anything larger than 2 bytes may be unaligned in an mmap'ed memory.
// Unaligned access is an undefined behavior in C/C++, so we shouldn't
// cast an arbitrary pointer to a uint32_t, for example, to read a
// 32-bits value.
//
// The data types defined in this file don't depend on host byte order and
// don't do unaligned access.
#pragma once
#include <bit>
#include <cstdint>
#include <cstring>
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __LITTLE_ENDIAN__ 1
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__ 1
# else
# error "unknown host byte order"
# endif
#endif
namespace mold {
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
template <typename T>
static inline T bswap(T val) {
switch (sizeof(T)) {
case 2: return __builtin_bswap16(val);
case 4: return __builtin_bswap32(val);
case 8: return __builtin_bswap64(val);
default: __builtin_unreachable();
}
}
template <typename T, int SIZE = sizeof(T)>
class LittleEndian {
public:
LittleEndian() = default;
LittleEndian(T x) { *this = x; }
operator T() const {
if constexpr (sizeof(T) == SIZE) {
T x;
memcpy(&x, val, sizeof(T));
if constexpr (std::endian::native == std::endian::big)
x = bswap(x);
return x;
} else {
static_assert(SIZE == 3);
return (val[2] << 16) | (val[1] << 8) | val[0];
}
}
LittleEndian &operator=(T x) {
if constexpr (sizeof(T) == SIZE) {
if constexpr (std::endian::native == std::endian::big)
x = bswap(x);
memcpy(val, &x, sizeof(T));
} else {
static_assert(SIZE == 3);
val[2] = x >> 16;
val[1] = x >> 8;
val[0] = x;
}
return *this;
}
LittleEndian &operator++() {
return *this = *this + 1;
}
LittleEndian operator++(int) {
T ret = *this;
*this = *this + 1;
return ret;
}
LittleEndian &operator--() {
return *this = *this - 1;
}
LittleEndian operator--(int) {
T ret = *this;
*this = *this - 1;
return ret;
}
LittleEndian &operator+=(T x) {
return *this = *this + x;
}
LittleEndian &operator-=(T x) {
return *this = *this - x;
}
LittleEndian &operator&=(T x) {
return *this = *this & x;
}
LittleEndian &operator|=(T x) {
return *this = *this | x;
}
private:
u8 val[SIZE];
};
using il16 = LittleEndian<i16>;
using il32 = LittleEndian<i32>;
using il64 = LittleEndian<i64>;
using ul16 = LittleEndian<u16>;
using ul24 = LittleEndian<u32, 3>;
using ul32 = LittleEndian<u32>;
using ul64 = LittleEndian<u64>;
template <typename T, int SIZE = sizeof(T)>
class BigEndian {
public:
BigEndian() = default;
BigEndian(T x) { *this = x; }
operator T() const {
if constexpr (sizeof(T) == SIZE) {
T x;
memcpy(&x, val, sizeof(T));
if constexpr (std::endian::native == std::endian::little)
x = bswap(x);
return x;
} else {
static_assert(SIZE == 3);
return (val[0] << 16) | (val[1] << 8) | val[2];
}
}
BigEndian &operator=(T x) {
if constexpr (sizeof(T) == SIZE) {
if constexpr (std::endian::native == std::endian::little)
x = bswap(x);
memcpy(val, &x, sizeof(T));
} else {
static_assert(SIZE == 3);
val[0] = x >> 16;
val[1] = x >> 8;
val[2] = x;
}
return *this;
}
BigEndian &operator++() {
return *this = *this + 1;
}
BigEndian operator++(int) {
T ret = *this;
*this = *this + 1;
return ret;
}
BigEndian &operator--() {
return *this = *this - 1;
}
BigEndian operator--(int) {
T ret = *this;
*this = *this - 1;
return ret;
}
BigEndian &operator+=(T x) {
return *this = *this + x;
}
BigEndian &operator-=(T x) {
return *this = *this - x;
}
BigEndian &operator&=(T x) {
return *this = *this & x;
}
BigEndian &operator|=(T x) {
return *this = *this | x;
}
private:
u8 val[SIZE];
};
using ib16 = BigEndian<i16>;
using ib32 = BigEndian<i32>;
using ib64 = BigEndian<i64>;
using ub16 = BigEndian<u16>;
using ub24 = BigEndian<u32, 3>;
using ub32 = BigEndian<u32>;
using ub64 = BigEndian<u64>;
} // namespace mold
-5
View File
@@ -1,5 +0,0 @@
#include "config.h"
#ifdef USE_SYSTEM_MIMALLOC
# include <mimalloc-new-delete.h>
#endif
-149
View File
@@ -1,149 +0,0 @@
#include "common.h"
#include <fcntl.h>
#include <filesystem>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
namespace mold {
inline u32 get_umask() {
u32 orig_umask = umask(0);
umask(orig_umask);
return orig_umask;
}
template <typename Context>
static std::pair<i64, char *>
open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
char *path2 = (char *)save_string(ctx, tmpl).data();
i64 fd = mkstemp(path2);
if (fd == -1)
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
// Reuse an existing file if exists and writable because on Linux,
// writing to an existing file is much faster than creating a fresh
// file and writing to it.
if (ctx.overwrite_output_file && rename(path.c_str(), path2) == 0) {
::close(fd);
fd = ::open(path2, O_RDWR | O_CREAT, perm);
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
return {fd, path2};
unlink(path2);
fd = ::open(path2, O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
}
if (fchmod(fd, (perm & ~get_umask())) == -1)
Fatal(ctx) << "fchmod failed: " << errno_string();
#ifdef __linux__
if (fallocate(fd, 0, 0, filesize) == 0)
return {fd, path2};
#endif
if (ftruncate(fd, filesize) == -1)
Fatal(ctx) << "ftruncate failed: " << errno_string();
return {fd, path2};
}
template <typename Context>
class MemoryMappedOutputFile : public OutputFile<Context> {
public:
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, true) {
std::tie(this->fd, output_tmpfile) =
open_or_create_file(ctx, path, filesize, perm);
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
}
~MemoryMappedOutputFile() {
if (fd2 != -1)
::close(fd2);
}
void close(Context &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
if (this->buf2.empty()) {
::close(this->fd);
} else {
FILE *out = fdopen(this->fd, "w");
fseek(out, 0, SEEK_END);
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
fclose(out);
}
// If an output file already exists, open a file and then remove it.
// This is the fastest way to unlink a file, as it does not make the
// system to immediately release disk blocks occupied by the file.
fd2 = ::open(this->path.c_str(), O_RDONLY);
if (fd2 != -1)
unlink(this->path.c_str());
if (rename(output_tmpfile, this->path.c_str()) == -1)
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
output_tmpfile = nullptr;
}
private:
int fd2 = -1;
};
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
bool is_special = false;
if (path == "-") {
is_special = true;
} else {
struct stat st;
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
is_special = true;
}
OutputFile<Context> *file;
if (is_special)
file = new MallocOutputFile(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
#ifdef MADV_HUGEPAGE
// Enable transparent huge page for an output memory-mapped file.
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
// but it can make the linker ~10% faster. You can try it by creating
// a tmpfs with the following commands
//
// $ mkdir tmp
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
//
// and then specifying a path under the directory as an output file.
madvise(file->buf, filesize, MADV_HUGEPAGE);
#endif
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile>(file);
}
} // namespace mold
-103
View File
@@ -1,103 +0,0 @@
#include "common.h"
#include <fcntl.h>
#include <filesystem>
#include <windows.h>
namespace mold {
template <typename Context>
class MemoryMappedOutputFile : public OutputFile<Context> {
public:
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, true) {
// TODO: use intermediate temporary file for output.
DWORD file_attrs =
(perm & 0200) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY;
file_handle =
CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, CREATE_ALWAYS, file_attrs, nullptr);
if (file_handle == INVALID_HANDLE_VALUE)
Fatal(ctx) << "cannot open " << path << ": " << GetLastError();
HANDLE mapping_handle = CreateFileMapping(
file_handle, nullptr, PAGE_READWRITE, 0, filesize, nullptr);
if (!mapping_handle)
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
this->buf =
(u8 *)MapViewOfFile(mapping_handle, FILE_MAP_WRITE, 0, 0, filesize);
CloseHandle(mapping_handle);
if (!this->buf)
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
}
~MemoryMappedOutputFile() {
if (file_handle != INVALID_HANDLE_VALUE)
CloseHandle(file_handle);
}
void close(Context &ctx) override {
Timer t(ctx, "close_file");
UnmapViewOfFile(this->buf);
if (!this->buf2.empty()) {
if (SetFilePointer(file_handle, 0, nullptr, FILE_END) ==
INVALID_SET_FILE_POINTER)
Fatal(ctx) << this->path
<< ": SetFilePointer failed: " << GetLastError();
DWORD written;
if (!WriteFile(file_handle, this->buf2.data(), this->buf2.size(),
&written, nullptr))
Fatal(ctx) << this->path << ": WriteFile failed: " << GetLastError();
}
CloseHandle(file_handle);
file_handle = INVALID_HANDLE_VALUE;
}
private:
HANDLE file_handle;
};
template <typename Context>
std::unique_ptr<OutputFile<Context>>
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
bool is_special = false;
if (path == "-") {
is_special = true;
} else {
HANDLE file_handle =
CreateFileA(path.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file_handle != INVALID_HANDLE_VALUE) {
if (GetFileType(file_handle) != FILE_TYPE_DISK)
is_special = true;
CloseHandle(file_handle);
}
}
OutputFile<Context> *file;
if (is_special)
file = new MallocOutputFile(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<Context>>(file);
}
} // namespace mold
-5
View File
@@ -1,5 +0,0 @@
#if _WIN32
# include "output-file-win32.h"
#else
# include "output-file-unix.h"
#endif
+99 -52
View File
@@ -1,7 +1,7 @@
#!/bin/bash
#
# This script creates a mold binary distribution. The output is written in
# this directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
# `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
# `mold-1.0.3-x86_64-linux.tar.gz`).
#
# The mold executable created by this script is statically linked to
@@ -18,25 +18,29 @@
# commit with release tags by rebuilding the binaries yourself.
#
# Debian provides snapshot.debian.org to host all historical binary
# packages. We use it to construct Docker images pinned to a
# packages. We use it to construct Podman images pinned to a
# particular timestamp.
#
# We aim to use a reasonably old Debian version because we'll dynamically
# link glibc to mold, and a binary linked against a newer version of glibc
# won't work on a system with an older version of glibc.
#
# We need GCC 10 or newer to build mold. If GCC 10 is not available on an
# old Debian version, we'll build it ourselves.
# We want to build mold with Clang rather than GCC because mold's
# Identical Code Folding works best with the LLVM address significance
# table (.llvm_addrsig). Building a release binary with GCC yields a
# slightly larger binary than Clang's.
#
# You may need to run the following command to use Docker with Qemu:
# We need a recent version of Clang to build mold. If it's not available
# via apt-get, we'll build it ourselves.
#
# $ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# You may need to install qemu-user-static package to build non-native
# binaries.
set -e -x
cd "$(dirname $0)"
usage() {
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
exit 1
}
@@ -56,97 +60,135 @@ case $# in
usage
esac
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || usage
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
dest=mold-$version-$arch-linux
# Create a Podman image.
if [ "$GITHUB_REPOSITORY" = '' ]; then
image=mold-builder-$arch
docker_build="docker build --platform linux/$arch -t $image -"
image_build="podman build --arch $arch -t $image -"
else
# If this script is running on GitHub Actions, we want to cache
# the created Docker image in GitHub's Docker repostiory.
# the created container image in GitHub's container repostiory.
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
docker_build="docker buildx build --platform linux/$arch -t $image --push --cache-to type=inline --cache-from type=registry,ref=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch -"
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
fi
# Create a Docker image.
case $arch in
x86_64)
# Debian 8 (Jessie) released in April 2015
cat <<EOF | $docker_build
FROM debian:jessie-20210326@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
# Debian 9 (Stretch) released in June 2017.
# We use a Google-provided mirror (gcr.io) of the official Docker hub
# (docker.io) because docker.io has a strict rate limit policy.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends wget bzip2 file make autoconf gcc g++ libssl-dev && \
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev && \
rm -rf /var/lib/apt/lists
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
wget -O- --no-check-certificate --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build GCC 10
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/gcc/gcc-10.5.0/gcc-10.5.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir isl gmp mpc mpfr && \
wget -O- --no-check-certificate https://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2 | tar xjf - --strip-components=1 -C isl && \
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/gmp/gmp-6.1.2.tar.bz2 | tar xjf - --strip-components=1 -C gmp && \
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/mpc/mpc-1.2.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --no-check-certificate https://ftpmirror.gnu.org/gnu/mpfr/mpfr-4.1.0.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
make -j\$(nproc) && \
make install && \
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
rm -rf /build
# Build GNU binutils 2.43
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
./configure --prefix=/usr && \
make -j\$(nproc) && \
make install && \
rm -fr /build
# Build Python 3.12.7
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 19
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
cmake --build . -j\$(nproc) && \
cmake --install . --strip && \
rm -rf /build
EOF
;;
aarch64 | arm | ppc64le | s390x)
# Debian 10 (Bullseye) released in July 2019
# Debian 11 (Bullseye) released in August 2021
#
# We don't want to build GCC for these targets with Qemu becuase
# We don't want to build Clang for these targets with Qemu becuase
# that'd take extremely long time. Also I believe old build machines
# are usually x86-64.
[ $arch = aarch64 ] && digest=d5ed76c5265576982e6599b6f12392290d9b52b315b19b28b640aaba6e8af002
[ $arch = arm ] && digest=bede2623dae269454c5b6dd4af15a10810a5f4ef75963d4eb6531628f98bd633
[ $arch = ppc64le ] && digest=255f385e735469493b3465befad59a16f9d46f41d0b50e4fa6d5928c5ee7702a
[ $arch = s390x ] && digest=96fb9ce5d3ce7f3dab7c34c18edfee093904cbc7fc19162dbcca22b2cc273b9d
cat <<EOF | $docker_build
FROM debian:bullseye-20231030@sha256:$digest
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
ln -sf /usr/bin/gcc-10 /usr/bin/cc && \
ln -sf /usr/bin/g++-10 /usr/bin/c++ && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake && \
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
riscv64)
cat <<EOF | $docker_build
FROM riscv64/debian:sid-20240311@sha256:8c02dbe4faa999b588e873cc1759dd9b340f39daf4d7aabdb2c1a87cdc586459
cat <<EOF | $image_build
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-12 g++-12 cmake && \
ln -sf /usr/bin/gcc-12 /usr/bin/cc && \
ln -sf /usr/bin/g++-12 /usr/bin/c++ && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake && \
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
loongarch64)
# LoongArch build is not reproducible yet
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
*)
usage
;;
esac
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
dest=mold-$version-$arch-linux
# Source tarballs available on GitHub don't contain .git history.
# Clone the repo if missing.
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
@@ -156,17 +198,22 @@ esac
timestamp="$(git log -1 --format=%ci)"
# Build mold in a container.
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" $image bash -c "
mkdir -p dist
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
set -e
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=On /mold
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
cmake --build . -j\$(nproc)
ctest -j\$(nproc)
cmake --install .
cmake -DMOLD_USE_MOLD=1 .
cmake --build . -j\$(nproc)
ctest --output-on-failure -j\$(nproc)
cmake --install . --prefix $dest --strip
find $dest -print | xargs touch --no-dereference --date='$timestamp'
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /mold/$dest.tar.gz
cp mold /mold
chown $(id -u):$(id -g) /mold/$dest.tar.gz /mold/mold
sha256sum /mold/$dest.tar.gz
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
"
+5
View File
@@ -1,3 +1,8 @@
[This document was written in 2020, and the contents are outdated.
Specifically, we no longer believe that object preloading is a good
idea. That being said, most of the points in this document still hold
even today. Therefore, I'll keep this document as-is.]
## Design and implementation of mold
For the rest of this documentation, I'll explain the design and the
+46 -18
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "MOLD" "1" "May 2024" ""
.TH "MOLD" "1" "February 2025" ""
.SH "NAME"
\fBmold\fR \- a modern linker
.SH "SYNOPSIS"
@@ -10,9 +10,9 @@
.SS "How to use"
See \fIhttps://github\.com/rui314/mold#how\-to\-use\fR\.
.SS "Compatibility"
\fBMold\fR is designed to be a drop\-in replacement for the GNU linkers for linking user\-land programs\. If your user\-land program cannot be built due to missing command\-line options, please file a bug at \fIhttps://github\.com/rui314/mold/issues\fR\.
\fBmold\fR is designed to be a drop\-in replacement for the GNU linkers for linking user\-land programs\. If your user\-land program cannot be built due to missing command\-line options, please file a bug at \fIhttps://github\.com/rui314/mold/issues\fR\.
.P
Mold supports a very limited set of linker script features, which is just sufficient to read \fB/usr/lib/x86_64\-linux\-gnu/libc\.so\fR on Linux systems (on Linux, that file is contrary to its name not a shared library but an ASCII linker script that loads a real \fBlibc\.so\fR file\.)
\fBmold\fR supports a very limited set of linker script features, which is just sufficient to read \fB/usr/lib/x86_64\-linux\-gnu/libc\.so\fR on Linux systems (on Linux, that file is contrary to its name not a shared library but an ASCII linker script that loads a real \fBlibc\.so\fR file\.)
.P
Beyond that, we have no plan to support any additional linker script features\. The linker script is an ad\-hoc, over\-designed, complex language which we believe needs to be replaced by a simpler mechanism\. We have a plan to add a replacement for the linker script to \fBmold\fR instead\.
.SS "Archive symbol resolution"
@@ -40,13 +40,11 @@ Typically, an ELF module that exports a symbol also imports the same symbol\. Su
.P
Let's take \fBmalloc\fR as an example\. Assume that you define your version of \fBmalloc\fR in your main executable file\. Then, all \fBmalloc\fR calls from any module are resolved to your function instead of the one in libc, because the executable is always at the beginning of the dynamic symbol search list\. Note that even \fBmalloc\fR calls within libc are resolved to your definition since libc exports and imports \fBmalloc\fR\. Therefore, by defining \fBmalloc\fR yourself, you can overwrite a library function, and the \fBmalloc\fR in libc becomes dead code\.
.P
These Unix semantics are tricky and sometimes considered harmful\. For example, assume that you accidentally define \fBatoi\fR as a global function in your executable that behaves completely differently from the one in the C standard\. Then, all \fBatoi\fR function calls from any modules (even function calls within libc) are redirected to your function instead of the one in libc, which will very likely cause a problem\.
.P
That is a somewhat surprising consequence for an accidental name conflict\. On the other hand, this semantic is sometimes useful because it allows users to override library functions without rebuilding modules containing them\.
These Unix semantics are tricky and sometimes considered harmful\. For example, assume that you accidentally define \fBatoi\fR as a global function in your executable that behaves completely differently from the one in the C standard\. Then, all \fBatoi\fR function calls from any modules (even function calls within libc) are redirected to your function instead of the one in libc, which will very likely cause a problem\. That is a somewhat surprising consequence for an accidental name conflict\. On the other hand, this semantic is sometimes useful because it allows users to override library functions without rebuilding modules containing them\.
.P
Whether good or bad, you should keep these semantics in mind to understand Unix linkers' behaviors\.
.SS "Build reproducibility"
\fBmold\fR's output is deterministic\. That is, if you pass the same object files and the same command\-line options to the same version of \fBmold\fR, it is guaranteed that \fBmold\fR produces the bit\-by\-bit identical output\. The linker's internal randomness, such as the timing of thread scheduling or iteration orders of hash tables, doesn't affect the output\.
\fBmold\fR's output is deterministic\. That is, if you pass the same object files and the same command\-line options to the same version of \fBmold\fR, it is guaranteed that \fBmold\fR produces the bit\-for\-bit identical output\. The linker's internal randomness, such as the timing of thread scheduling or iteration orders of hash tables, doesn't affect the output\.
.P
\fBmold\fR does not have any host\-specific default settings\. This is contrary to the GNU linkers, for which some configurable values, such as system\-dependent library search paths, are hard\-coded\. \fBmold\fR depends only on its command\-line arguments\.
.SH "OPTION NOTATIONS"
@@ -67,6 +65,9 @@ Synonym for \fB\-\-color\-diagnostics=auto\fR\.
\fB\-\-no\-color\-diagnostics\fR
Synonym for \fB\-\-color\-diagnostics=never\fR\.
.TP
\fB\-\-detach\fR, \fB\-\-no\-detach\fR
Permit or do not permit mold to create a debug info file in the background\.
.TP
\fB\-\-fork\fR, \fB\-\-no\-fork\fR
Spawn a child process and let it do the actual linking\. When linking a large program, the OS kernel can take a few hundred milliseconds to terminate a \fBmold\fR process\. \fB\-\-fork\fR hides that latency\. By default, it does fork\.
.TP
@@ -94,7 +95,16 @@ This option is useful for finding bugs that depend on the initialization order o
By reversing the order of input sections using \fB\-\-reverse\-sections\fR, you can easily test that your program works in the reversed initialization order\.
.TP
\fB\-\-run\fR \fIcommand\fR \fIarg\fR\|\.\|\.\|\.
Run \fIcommand\fR with \fBmold\fR \fB/usr/bin/ld\fR\. Specifically, \fBmold\fR runs a given command with the \fBLD_PRELOAD\fR environment set to intercept exec(3) family functions and replaces \fBargv[0]\fR with itself if it is \fBld\fR, \fBld\.gold\fR, or \fBld\.lld\fR\.
Run \fIcommand\fR with \fBmold\fR as \fB/usr/bin/ld\fR\. Specifically, \fBmold\fR runs a given command with the \fBLD_PRELOAD\fR environment set to intercept exec(3) family functions and replaces \fBargv[0]\fR with itself if it is \fBld\fR, \fBld\.gold\fR, or \fBld\.lld\fR\.
.TP
\fB\-\-separate\-debug\-file\fR, \fB\-\-separate\-debug\-file\fR=\fIfile\fR
Bundle debug info sections into a separate file instead of embedding them in an output executable or a shared library\. mold creates a debug info file in the background by default, so that you can start running your executable as soon as possible\.
.IP
By default, the debug info file is created in the same directory as is the output file, with the \fB\.dbg\fR file extension\. That filename is embedded into the output file so that \fBgdb\fR can automatically find the debug info file for the output file\. For more info about gdb features related to separate debug files, see \fIhttps://sourceware\.org/gdb/current/onlinedocs/gdb\.html/Separate\-Debug\-Files\.html\fR\.
.IP
mold holds a file lock with flock(2) while creating a debug info file in the background\.
.IP
If you don't want to create a debug info file in the background, pass the \fB\-\-no\-detach\fR option\.
.TP
\fB\-\-shuffle\-sections\fR, \fB\-\-shuffle\-sections\fR=\fInumber\fR
Randomize the output by shuffling the order of input sections before assigning them the offsets in the output file\. If a \fInumber\fR is given, it's used as a seed for the random number generator, so that the linker produces the same output for the same seed\. If no seed is given, a random number is used as a seed\.
@@ -119,6 +129,17 @@ Use multiple threads\. By default, \fBmold\fR uses as many threads as the number
.TP
\fB\-\-quick\-exit\fR, \fB\-\-no\-quick\-exit\fR
Use or do not use \fBquick_exit\fR to exit\.
.TP
\fB\-z rewrite\-endbr\fR, \fB\-z norewrite\-endbr\fR
As a security measure, some CPU instruction sets have recently gained a feature to protect control flow integrity by disallowing indirect branches by default\. If the feature is enabled, the instruction that is executed immediately after an indirect branch must be an branch target marker instruction, or a CPU\-level fault will raise\. The marker instruction is also known as "landing pad" instruction, to which indirect branches can land\. This feature makes ROP attacks harder to conduct\.
.IP
To use the feature, a function whose pointer is taken needs to begin with a landing pad because a function call via a function pointer is compiled to an indirect branch\. On the other hand, if a function is called only directly (i\.e\. referred to only by \fIdirect\fR branch instructions), it doesn't have to begin with it\.
.IP
By default, the compiler always emits a landing pad at the beginning of each global function because it doesn't know whether or not the function's pointer is taken in another translation unit\. As a result, the resulting binary has more attack surface than necessary\.
.IP
If \fB\-\-rewrite\-endbr\fR is given, mold conducts a whole program analysis to identify functions whose addresses are actually taken and rewrites landing pads with no\-ops for non\-address\-taken functions, reducing the attack surface\.
.IP
This feature is currently available only on x86\-64\.
.SH "GNU\-COMPATIBLE OPTIONS"
.TP
\fB\-\-help\fR
@@ -227,13 +248,20 @@ Alias for \fB\-\-section\-start=\.text=\fR\fIaddress\fR\.
\fB\-\-allow\-multiple\-definition\fR
Normally, the linker reports an error if there are more than one definition of a symbol\. This option changes the default behavior so that it doesn't report an error for duplicate definitions and instead use the first definition\.
.TP
\fB\-\-allow\-shlib\-undefined\fR, \fB\-\-no\-allow\-shlib\-undefined\fR
Even if mold succeeds in linking a main executable without undefined symbol errors, you may still encounter symbol lookup errors at runtime because the dynamic linker cannot find some symbols in shared libraries in any ELF module\. This occurs because mold ignores undefined symbols in shared libraries by default\.
.IP
If you pass \fB\-\-no\-allow\-shlib\-undefined\fR, mold verifies that undefined symbols in shared libraries given to the linker can be resolved at link\-time\. In other words, this converts the runtime error to a link\-time error\.
.IP
Note that you need to pass all shared libraries, including indirectly dependent ones, to the linker as arguments for \fB\-l\fR\. If a shared library depends on a library that's not passed to the linker, the verification will be skipped for that file\.
.TP
\fB\-\-as\-needed\fR, \fB\-\-no\-as\-needed\fR
By default, shared libraries given to the linker are unconditionally added to the list of required libraries in an output file\. However, shared libraries after \fB\-\-as\-needed\fR are added to the list only when at least one symbol is actually used by the output file\. In other words, shared libraries after \fB\-\-as\-needed\fR are not added to the list of needed libraries if they are not needed by a program\.
.IP
The \fB\-\-no\-as\-needed\fR option restores the default behavior for subsequent files\.
.TP
\fB\-\-build\-id\fR=[ \fBmd5\fR | \fBsha1\fR | \fBsha256\fR | \fBuuid\fR | \fB0x\fR\fIhexstring\fR | \fBnone\fR ]
Create a \fB\.note\.gnu\.build\-id\fR section containing a byte string to uniquely identify an output file\. \fBsha256\fR compute a 256\-bit cryptographic hash of an output file and set it to build\-id\. \fBmd5\fR and \fBsha1\fR compute the same hash but truncate it to 128 and 160 bits, respectively, before setting it to build\-id\. \fBuuid\fR sets a random 128\-bit UUID\. \fB0x\fR\fIhexstring\fR sets \fIhexstring\fR\.
\fB\-\-build\-id\fR=[ \fBmd5\fR | \fBsha1\fR | \fBsha256\fR | \fBfast\fR | \fBuuid\fR | \fB0x\fR\fIhexstring\fR | \fBnone\fR ]
Create a \fB\.note\.gnu\.build\-id\fR section containing a byte string to uniquely identify an output file\. \fBsha256\fR compute a 256\-bit cryptographic hash of an output file and set it to build\-id\. \fBmd5\fR and \fBsha1\fR compute the same hash but truncate it to 128 and 160 bits, respectively, before setting it to build\-id\. \fBuuid\fR sets a random 128\-bit UUID\. \fB0x\fR\fIhexstring\fR sets \fIhexstring\fR\. \fBfast\fR is a synonym for \fBsha256\fR\.
.TP
\fB\-\-build\-id\fR
Synonym for \fB\-\-build\-id=sha256\fR\.
@@ -272,10 +300,8 @@ The \fB\-\-emit\-relocs\fR instructs the linker to leave relocation sections in
\fB\-\-enable\-new\-dtags\fR, \fB\-\-disable\-new\-dtags\fR
By default, \fBmold\fR emits \fBDT_RUNPATH\fR for \fB\-\-rpath\fR\. If you pass \fB\-\-disable\-new\-dtags\fR, \fBmold\fR emits \fBDT_RPATH\fR for \fB\-\-rpath\fR instead\.
.TP
\fB\-\-execute\-only\fR
Traditionally, most processors require both executable and readable bits to 1 to make the page executable, which allows machine code to be read as data at runtime\. This is actually what an attacker often does after gaining a limited control of a process to find pieces of machine code they can use to gain the full control of the process\. As a mitigation, some recent processors allows "execute\-only" pages\. If a page is execute\-only, you can call a function there as long as you know its address but can't read it as data\.
.IP
This option marks text segments execute\-only\. This option currently works only on some ARM64 processors\.
\fB\-\-execute\-only\fR:
.TP
\fB\-\-exclude\-libs\fR=\fIlibraries\fR \|\.\|\.\|\.
Mark all symbols in the given \fIlibraries\fR hidden\.
@@ -327,14 +353,16 @@ Report undefined symbols (even with \fB\-\-shared\fR)\.
\fB\-\-noinhibit\-exec\fR
Create an output file even if errors occur\.
.TP
\fB\-\-package\-metadata\fR=\fIpercent\-encoded\-string\fR
Embed a specified string into the \fB\.note\.package\fR section\. This option is designed for build scripts that generate binary packages, such as \fB\.rpm\fR or \fB\.deb\fR, to include package metadata in each executable\. It simplifies the process of identifying the corresponding package for a given executable or core file\.
.IP
An argument to this option is treated as percent\-encoded and decoded before being inserted into the section, allowing you to avoid the use of the comma (\fB,\fR) character in the argument\. This is useful because the compiler replaces all occurrences of commas in \fB\-Wl,\fR with spaces before forwarding them to the linker\. Note that \fBmold\fR always interprets the argument as percent\-encoded, so you also need to escape all occurrences of \fB%\fR as \fB%25\fR\.
.TP
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBnone\fR ]
If \fBrelr\fR is specified, all \fBR_*_RELATIVE\fR relocations are put into \fB\.relr\.dyn\fR section instead of \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section\. Since \fB\.relr\.dyn\fR section uses a space\-efficient encoding scheme, specifying this flag can reduce the size of the output\. This is typically most effective for position\-independent executable\.
.IP
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2022, only ChromeOS, Android and Fuchsia support it\.
.TP
\fB\-\-package\-metadata\fR=\fIstring\fR
Embed \fIstring\fR to a \fB\.note\.package\fR section\. This option is intended to be used by a package management command such as rpm(8) to embed metadata regarding a package to each executable file\.
.TP
\fB\-\-pie\fR, \fB\-\-pic\-executable\fR, \fB\-\-no\-pie\fR, \fB\-\-no\-pic\-executable\fR
Create a position\-independent executable\.
.TP
@@ -499,7 +527,7 @@ Mark DSO to be initialized first at runtime\.
\fB\-z interpose\fR
Mark object to interpose all DSOs but executable\.
.TP
\fB\-(\fR, \fB\-)\fR, \fB\-EL\fR, \fB\-O\fR\fInumber\fR, \fB\-\-allow\-shlib\-undefined\fR, \fB\-\-dc\fR, \fB\-\-dp\fR, \fB\-\-end\-group\fR, \fB\-\-no\-add\-needed\fR, \fB\-\-no\-allow\-shlib\-undefined\fR, \fB\-\-no\-copy\-dt\-needed\-entries\fR, \fB\-\-nostdlib\fR, \fB\-\-rpath\-link=Ar dir\fR, \fB\-\-sort\-common\fR, \fB\-\-sort\-section\fR, \fB\-\-start\-group\fR, \fB\-\-warn\-constructors\fR, \fB\-\-warn\-once\fR, \fB\-\-fix\-cortex\-a53\-835769\fR, \fB\-\-fix\-cortex\-a53\-843419\fR, \fB\-z combreloc\fR, \fB\-z common\-page\-size\fR, \fB\-z nocombreloc\fR
\fB\-(\fR, \fB\-)\fR, \fB\-EL\fR, \fB\-O\fR\fInumber\fR, \fB\-\-dc\fR, \fB\-\-dp\fR, \fB\-\-end\-group\fR, \fB\-\-no\-add\-needed\fR, \fB\-\-no\-copy\-dt\-needed\-entries\fR, \fB\-\-nostdlib\fR, \fB\-\-rpath\-link=Ar dir\fR, \fB\-\-sort\-common\fR, \fB\-\-sort\-section\fR, \fB\-\-start\-group\fR, \fB\-\-warn\-constructors\fR, \fB\-\-warn\-once\fR, \fB\-\-fix\-cortex\-a53\-835769\fR, \fB\-\-fix\-cortex\-a53\-843419\fR, \fB\-z combreloc\fR, \fB\-z common\-page\-size\fR, \fB\-z nocombreloc\fR
Ignored
.SH "ENVIRONMENT VARIABLES"
.TP
+110 -30
View File
@@ -13,12 +13,12 @@ See <https://github.com/rui314/mold#how-to-use>.
### Compatibility
**Mold** is designed to be a drop-in replacement for the GNU linkers for
`mold` is designed to be a drop-in replacement for the GNU linkers for
linking user-land programs. If your user-land program cannot be built due to
missing command-line options, please file a bug at
<https://github.com/rui314/mold/issues>.
Mold supports a very limited set of linker script features, which is just
`mold` supports a very limited set of linker script features, which is just
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
Linux, that file is contrary to its name not a shared library but an ASCII
linker script that loads a real `libc.so` file.)
@@ -104,11 +104,10 @@ assume that you accidentally define `atoi` as a global function in your
executable that behaves completely differently from the one in the C standard.
Then, all `atoi` function calls from any modules (even function calls within
libc) are redirected to your function instead of the one in libc, which will
very likely cause a problem.
That is a somewhat surprising consequence for an accidental name conflict. On
the other hand, this semantic is sometimes useful because it allows users to
override library functions without rebuilding modules containing them.
very likely cause a problem. That is a somewhat surprising consequence for an
accidental name conflict. On the other hand, this semantic is sometimes useful
because it allows users to override library functions without rebuilding
modules containing them.
Whether good or bad, you should keep these semantics in mind to understand
Unix linkers' behaviors.
@@ -117,7 +116,7 @@ Unix linkers' behaviors.
`mold`'s output is deterministic. That is, if you pass the same object files
and the same command-line options to the same version of `mold`, it is
guaranteed that `mold` produces the bit-by-bit identical output. The linker's
guaranteed that `mold` produces the bit-for-bit identical output. The linker's
internal randomness, such as the timing of thread scheduling or iteration
orders of hash tables, doesn't affect the output.
@@ -152,6 +151,9 @@ but as `-o magic`.
* `--no-color-diagnostics`:
Synonym for `--color-diagnostics=never`.
* `--detach`, `--no-detach`:
Permit or do not permit mold to create a debug info file in the background.
* `--fork`, `--no-fork`:
Spawn a child process and let it do the actual linking. When linking a large
program, the OS kernel can take a few hundred milliseconds to terminate a
@@ -198,10 +200,29 @@ but as `-o magic`.
easily test that your program works in the reversed initialization order.
* `--run` _command_ _arg_...:
Run _command_ with `mold` `/usr/bin/ld`. Specifically, `mold` runs a given
command with the `LD_PRELOAD` environment set to intercept exec(3) family
functions and replaces `argv[0]` with itself if it is `ld`, `ld.gold`, or
`ld.lld`.
Run _command_ with `mold` as `/usr/bin/ld`. Specifically, `mold` runs a
given command with the `LD_PRELOAD` environment set to intercept exec(3)
family functions and replaces `argv[0]` with itself if it is `ld`,
`ld.gold`, or `ld.lld`.
* `--separate-debug-file`, `--separate-debug-file`=_file_:
Bundle debug info sections into a separate file instead of embedding them in
an output executable or a shared library. mold creates a debug info file in
the background by default, so that you can start running your executable as
soon as possible.
By default, the debug info file is created in the same directory as is the
output file, with the `.dbg` file extension. That filename is embedded into
the output file so that `gdb` can automatically find the debug info file for
the output file. For more info about gdb features related to separate debug
files, see
<https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html>.
mold holds a file lock with flock(2) while creating a debug info file in the
background.
If you don't want to create a debug info file in the background, pass the
`--no-detach` option.
* `--shuffle-sections`, `--shuffle-sections`=_number_:
Randomize the output by shuffling the order of input sections before
@@ -246,6 +267,33 @@ but as `-o magic`.
* `--quick-exit`, `--no-quick-exit`:
Use or do not use `quick_exit` to exit.
* `-z rewrite-endbr`, `-z norewrite-endbr`:
As a security measure, some CPU instruction sets have recently gained a
feature to protect control flow integrity by disallowing indirect branches
by default. If the feature is enabled, the instruction that is executed
immediately after an indirect branch must be an branch target marker
instruction, or a CPU-level fault will raise. The marker instruction is also
known as "landing pad" instruction, to which indirect branches can land.
This feature makes ROP attacks harder to conduct.
To use the feature, a function whose pointer is taken needs to begin with a
landing pad because a function call via a function pointer is compiled to an
indirect branch. On the other hand, if a function is called only directly
(i.e. referred to only by _direct_ branch instructions), it doesn't have to
begin with it.
By default, the compiler always emits a landing pad at the beginning of each
global function because it doesn't know whether or not the function's
pointer is taken in another translation unit. As a result, the resulting
binary has more attack surface than necessary.
If `--rewrite-endbr` is given, mold conducts a whole program analysis
to identify functions whose addresses are actually taken and rewrites
landing pads with no-ops for non-address-taken functions, reducing the
attack surface.
This feature is currently available only on x86-64.
## GNU-COMPATIBLE OPTIONS
* `--help`:
@@ -390,6 +438,23 @@ but as `-o magic`.
report an error for duplicate definitions and instead use the first
definition.
* `--allow-shlib-undefined`, `--no-allow-shlib-undefined`:
Even if mold succeeds in linking a main executable without undefined symbol
errors, you may still encounter symbol lookup errors at runtime because the
dynamic linker cannot find some symbols in shared libraries in any ELF
module. This occurs because mold ignores undefined symbols in shared
libraries by default.
If you pass `--no-allow-shlib-undefined`, mold verifies that undefined
symbols in shared libraries given to the linker can be resolved at
link-time. In other words, this converts the runtime error to a link-time
error.
Note that you need to pass all shared libraries, including indirectly
dependent ones, to the linker as arguments for `-l`. If a shared library
depends on a library that's not passed to the linker, the verification will
be skipped for that file.
* `--as-needed`, `--no-as-needed`:
By default, shared libraries given to the linker are unconditionally added
to the list of required libraries in an output file. However, shared
@@ -401,13 +466,13 @@ but as `-o magic`.
The `--no-as-needed` option restores the default behavior for subsequent
files.
* `--build-id`=[ `md5` | `sha1` | `sha256` | `uuid` | `0x`_hexstring_ | `none` ]:
* `--build-id`=[ `md5` | `sha1` | `sha256` | `fast` | `uuid` | `0x`_hexstring_ | `none` ]:
Create a `.note.gnu.build-id` section containing a byte string to uniquely
identify an output file. `sha256` compute a 256-bit cryptographic hash of an
output file and set it to build-id. `md5` and `sha1` compute the same hash
but truncate it to 128 and 160 bits, respectively, before setting it to
build-id. `uuid` sets a random 128-bit UUID. `0x`_hexstring_ sets
_hexstring_.
_hexstring_. `fast` is a synonym for `sha256`.
* `--build-id`:
Synonym for `--build-id=sha256`.
@@ -463,17 +528,22 @@ but as `-o magic`.
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
* `--execute-only`:
Traditionally, most processors require both executable and readable bits to
1 to make the page executable, which allows machine code to be read as data
at runtime. This is actually what an attacker often does after gaining a
limited control of a process to find pieces of machine code they can use to
gain the full control of the process. As a mitigation, some recent
processors allows "execute-only" pages. If a page is execute-only, you can
call a function there as long as you know its address but can't read it as
data.
This option marks text segments execute-only. This option currently works
only on some ARM64 processors.
Traditionally, setting the executable bit to 1 for a memory page implies
that the page also become readable, which allows machine code to be read
as data at runtime. That is actually what an attacker often does after
gaining a limited control of a process to find pieces of machine code
they can use to gain the full control of the process. As a mitigation,
recent processors including some ARM64 ones allows "execute-only" pages.
If a page is execute-only, you can call a function there as long as you
know its address but can't read it as data.
This option marks text segments as execute-only by setting just the "X"
bit instead of "RX". Note that on most systems, the absence of the "R"
bit in the text segment serves just as a hint. If you run a program
linked with `--execute-only` on a processor that doesn't support
execute-only pages, your executable will likely still function normally,
but the text segment will remain readable.
* `--exclude-libs`=_libraries_ ...:
Mark all symbols in the given _libraries_ hidden.
@@ -547,6 +617,21 @@ but as `-o magic`.
* `--noinhibit-exec`:
Create an output file even if errors occur.
* `--package-metadata`=_percent-encoded-string_:
Embed a specified string into the `.note.package` section. This option
is designed for build scripts that generate binary packages, such as
`.rpm` or `.deb`, to include package metadata in each executable. It
simplifies the process of identifying the corresponding package for a
given executable or core file.
An argument to this option is treated as percent-encoded and decoded
before being inserted into the section, allowing you to avoid the use of
the comma (`,`) character in the argument. This is useful because the
compiler replaces all occurrences of commas in `-Wl,` with spaces before
forwarding them to the linker. Note that `mold` always interprets the
argument as percent-encoded, so you also need to escape all occurrences
of `%` as `%25`.
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
@@ -558,11 +643,6 @@ but as `-o magic`.
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2022, only
ChromeOS, Android and Fuchsia support it.
* `--package-metadata`=_string_:
Embed _string_ to a `.note.package` section. This option is intended to be
used by a package management command such as rpm(8) to embed metadata
regarding a package to each executable file.
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
Create a position-independent executable.
@@ -809,7 +889,7 @@ but as `-o magic`.
* `-z interpose`:
Mark object to interpose all DSOs but executable.
* `-(`, `-)`, `-EL`, `-O`_number_, `--allow-shlib-undefined`, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-allow-shlib-undefined`, `--no-copy-dt-needed-entries`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
* `-(`, `-)`, `-EL`, `-O`_number_, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-copy-dt-needed-entries`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
Ignored
## ENVIRONMENT VARIABLES
-330
View File
@@ -1,330 +0,0 @@
// Alpha is a 64-bit RISC ISA developed by DEC (Digital Equipment
// Corporation) in the early '90s. It aimed to be an ISA that would last
// 25 years. DEC expected Alpha would become 1000x faster during that time
// span. Since the ISA was developed from scratch for future machines,
// it's 64-bit from the beginning. There's no 32-bit variant.
//
// DEC ported its own Unix (Tru64) to Alpha. Microsoft also ported Windows
// NT to it. But it wasn't a huge commercial success.
//
// DEC was acquired by Compaq in 1997. In the late '90s, Intel and
// Hewlett-Packard were advertising that their upcoming Itanium processor
// would achieve significantly better performance than RISC processors, so
// Compaq decided to discontinue the Alpha processor line to switch to
// Itanium. Itanium resulted in a miserable failure, but it still suceeded
// to wipe out several RISC processors just by promising overly optimistic
// perf numbers. Alpha as an ISA would probably have been fine after 25
// years since its introduction (which is 1992 + 25 = 2017), but the
// company and its market didn't last that long.
//
// From the linker's point of view, there are a few peculiarities in its
// psABI as shown below:
//
// - Alpha lacks PC-relative memory load/store instructions, so it uses
// register-relative load/store instructions in position-independent
// code. Specifically, GP (which is an alias for $r29) is always
// maintained to refer to .got+0x8000, and global variables' addresses
// are loaded in a GP-relative manner.
//
// - It looks like even function addresses are first loaded to register
// in a GP-relative manner before calling it. We can relax it to
// convert the instruction sequence with a direct branch instruction,
// but by default, object files don't use a direct branch to call a
// function. Therefore, by default, we don't need to create a PLT.
// Any function call is made by first reading its address from GOT and
// jump to the address.
#include "mold.h"
namespace mold::elf {
using E = ALPHA;
// A 32-bit immediate can be materialized in a register with a "load high"
// and a "load low" instruction sequence. The first instruction sets the
// upper 16 bits in a register, and the second one set the lower 16
// bits. When doing so, they sign-extend an immediate. Therefore, if the
// 15th bit of an immediate happens to be 1, setting a "low half" value
// negates the upper 16 bit values that has already been set in a
// register. To compensate that, we need to add 0x8000 when setting the
// upper 16 bits.
static u32 hi(u32 val) {
return bits(val + 0x8000, 31, 16);
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ALPHA_SREL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
u64 GP = ctx.got->shdr.sh_addr + 0x8000;
switch (rel.r_type) {
case R_ALPHA_REFQUAD:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_ALPHA_GPREL32:
*(ul32 *)loc = S + A - GP;
break;
case R_ALPHA_LITERAL:
if (A)
*(ul16 *)loc = ctx.extra.got->get_addr(sym, A) - GP;
else
*(ul16 *)loc = GOT + G - GP;
break;
case R_ALPHA_BRSGP:
*(ul32 *)loc |= bits(S + A - P - 4, 22, 0);
break;
case R_ALPHA_GPDISP:
*(ul16 *)loc = hi(GP - P);
*(ul16 *)(loc + A) = GP - P;
break;
case R_ALPHA_SREL32:
*(ul32 *)loc = S + A - P;
break;
case R_ALPHA_GPRELHIGH:
*(ul16 *)loc = hi(S + A - GP);
break;
case R_ALPHA_GPRELLOW:
*(ul16 *)loc = S + A - GP;
break;
case R_ALPHA_TLSGD:
*(ul16 *)loc = sym.get_tlsgd_addr(ctx) - GP;
break;
case R_ALPHA_TLSLDM:
*(ul16 *)loc = ctx.got->get_tlsld_addr(ctx) - GP;
break;
case R_ALPHA_DTPRELHI:
*(ul16 *)loc = hi(S + A - ctx.dtp_addr);
break;
case R_ALPHA_DTPRELLO:
*(ul16 *)loc = S + A - ctx.dtp_addr;
break;
case R_ALPHA_GOTTPREL:
*(ul16 *)loc = sym.get_gottp_addr(ctx) + A - GP;
break;
case R_ALPHA_TPRELHI:
*(ul16 *)loc = hi(S + A - ctx.tp_addr);
break;
case R_ALPHA_TPRELLO:
*(ul16 *)loc = S + A - ctx.tp_addr;
break;
case R_ALPHA_LITUSE:
case R_ALPHA_HINT:
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_ALPHA_REFLONG:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_ALPHA_REFQUAD:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
switch (rel.r_type) {
case R_ALPHA_REFQUAD:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ALPHA_LITERAL:
if (rel.r_addend)
ctx.extra.got->add_symbol(sym, rel.r_addend);
else
sym.flags |= NEEDS_GOT;
break;
case R_ALPHA_SREL32:
scan_pcrel(ctx, sym, rel);
break;
case R_ALPHA_BRSGP:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_ALPHA_TLSGD:
sym.flags |= NEEDS_TLSGD;
break;
case R_ALPHA_TLSLDM:
ctx.needs_tlsld = true;
break;
case R_ALPHA_GOTTPREL:
sym.flags |= NEEDS_GOTTP;
break;
case R_ALPHA_TPRELHI:
case R_ALPHA_TPRELLO:
check_tlsle(ctx, sym, rel);
break;
case R_ALPHA_GPREL32:
case R_ALPHA_LITUSE:
case R_ALPHA_GPDISP:
case R_ALPHA_HINT:
case R_ALPHA_GPRELHIGH:
case R_ALPHA_GPRELLOW:
case R_ALPHA_DTPRELHI:
case R_ALPHA_DTPRELLO:
break;
default:
Fatal(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
// entry for an external symbol with a non-zero addend. This is an unusual
// request which is not found in any other targets.
//
// Referring an external symbol with a non-zero addend is a bad practice
// because we need to create as many dynamic relocations as the number of
// distinctive addends for the same symbol.
//
// We don't want to mess up the implementation of the common GOT section
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
// entry for an R_ALPHA_LITERAL reloc with a non-zero addend is created
// not in .got but in .alpha_got.
//
// Since .alpha_got entries are accessed relative to GP, .alpha_got
// needs to be close enough to .got. It's actually placed next to .got.
void AlphaGotSection::add_symbol(Symbol<E> &sym, i64 addend) {
assert(addend);
std::scoped_lock lock(mu);
entries.push_back({&sym, addend});
}
bool operator<(const AlphaGotSection::Entry &a, const AlphaGotSection::Entry &b) {
return std::tuple(a.sym->file->priority, a.sym->sym_idx, a.addend) <
std::tuple(b.sym->file->priority, b.sym->sym_idx, b.addend);
};
u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
auto it = std::lower_bound(entries.begin(), entries.end(), Entry{&sym, addend});
assert(it != entries.end());
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
}
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) const {
i64 n = 0;
for (const Entry &e : entries)
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
n++;
return n;
}
void AlphaGotSection::finalize() {
sort(entries);
remove_duplicates(entries);
shdr.sh_size = entries.size() * sizeof(Word<E>);
}
void AlphaGotSection::copy_buf(Context<E> &ctx) {
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
reldyn_offset);
for (i64 i = 0; i < entries.size(); i++) {
Entry &e = entries[i];
u64 P = this->shdr.sh_addr + sizeof(Word<E>) * i;
ul64 *buf = (ul64 *)(ctx.buf + this->shdr.sh_offset + sizeof(Word<E>) * i);
if (e.sym->is_imported) {
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
} else {
*buf = e.sym->get_addr(ctx) + e.addend;
if (ctx.arg.pic && !e.sym->is_absolute())
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, *buf);
}
}
}
} // namespace mold::elf
-444
View File
@@ -1,444 +0,0 @@
// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually
// a shared object file but an ASCII text file containing a linker
// script to include a "real" libc.so file. Therefore, we need to
// support a (very limited) subset of the linker script language.
#include "mold.h"
#include <cctype>
#include <iomanip>
namespace mold::elf {
template <typename E>
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
static std::string_view get_line(std::string_view input, const char *pos) {
assert(input.data() <= pos);
assert(pos < input.data() + input.size());
i64 start = input.rfind('\n', pos - input.data());
if (start == input.npos)
start = 0;
else
start++;
i64 end = input.find('\n', pos - input.data());
if (end == input.npos)
end = input.size();
return input.substr(start, end - start);
}
template <typename E>
class SyntaxError {
public:
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
std::string_view contents = ctx.script_file->get_contents();
std::string_view line = get_line(contents, errpos.data());
i64 lineno = 1;
for (i64 i = 0; contents.data() + i < line.data(); i++)
if (contents[i] == '\n')
lineno++;
std::string label = ctx.script_file->name + ":" +
std::to_string(lineno) + ": ";
i64 indent = strlen("mold: fatal: ") + label.size();
i64 column = errpos.data() - line.data();
out << label << line << "\n"
<< std::string(indent + column, ' ') << "^ ";
}
template <typename T> SyntaxError &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
[[noreturn]] ~SyntaxError() = default;
Fatal<Context<E>> out;
};
template <typename E>
static std::vector<std::string_view>
tokenize(Context<E> &ctx, std::string_view input) {
std::vector<std::string_view> vec;
while (!input.empty()) {
if (isspace(input[0])) {
input = input.substr(1);
continue;
}
if (input.starts_with("/*")) {
i64 pos = input.find("*/", 2);
if (pos == std::string_view::npos)
SyntaxError(ctx, input) << "unclosed comment";
input = input.substr(pos + 2);
continue;
}
if (input[0] == '#') {
i64 pos = input.find("\n", 1);
if (pos == std::string_view::npos)
break;
input = input.substr(pos + 1);
continue;
}
if (input[0] == '"') {
i64 pos = input.find('"', 1);
if (pos == std::string_view::npos)
SyntaxError(ctx, input) << "unclosed string literal";
vec.push_back(input.substr(0, pos + 1));
input = input.substr(pos + 1);
continue;
}
i64 pos = input.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$/\\~=+[]*?-!^:");
if (pos == 0)
pos = 1;
else if (pos == input.npos)
pos = input.size();
vec.push_back(input.substr(0, pos));
input = input.substr(pos);
}
return vec;
}
template <typename E>
static std::span<std::string_view>
skip(Context<E> &ctx, std::span<std::string_view> tok, std::string_view str) {
if (tok.empty())
Fatal(ctx) << ctx.script_file->name << ": expected '" << str
<< "', but got EOF";
if (tok[0] != str)
SyntaxError(ctx, tok[0]) << "expected '" << str << "'";
return tok.subspan(1);
}
static std::string_view unquote(std::string_view s) {
if (s.size() > 0 && s[0] == '"') {
assert(s[s.size() - 1] == '"');
return s.substr(1, s.size() - 2);
}
return s;
}
template <typename E>
static std::span<std::string_view>
read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
tok = skip(ctx, tok, "(");
while (!tok.empty() && tok[0] != ")")
tok = tok.subspan(1);
if (tok.empty())
Fatal(ctx) << ctx.script_file->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
std::filesystem::path sysroot = to_abs_path(ctx.arg.sysroot);
std::string rel = to_abs_path(path).lexically_relative(sysroot).string();
return rel != "." && !rel.starts_with("../");
}
template <typename E>
static MappedFile *
resolve_path(Context<E> &ctx, std::string_view tok, bool check_target) {
std::string str(unquote(tok));
auto open = [&](const std::string &path) -> MappedFile * {
MappedFile *mf = open_file(ctx, path);
if (!mf)
return nullptr;
if (check_target) {
std::string_view target = get_machine_type(ctx, mf);
if (!target.empty() && target != E::target_name) {
Warn(ctx) << path << ": skipping incompatible file: " << target
<< " (e_machine " << (int)E::e_machine << ")";
return nullptr;
}
}
return mf;
};
// GNU ld prepends the sysroot if a pathname starts with '/' and the
// script being processed is in the sysroot. We do the same.
if (str.starts_with('/') && is_in_sysroot(ctx, ctx.script_file->name))
return must_open_file(ctx, ctx.arg.sysroot + str);
if (str.starts_with('=')) {
std::string path;
if (ctx.arg.sysroot.empty())
path = str.substr(1);
else
path = ctx.arg.sysroot + str.substr(1);
return must_open_file(ctx, path);
}
if (str.starts_with("-l"))
return find_library(ctx, str.substr(2));
if (!str.starts_with('/'))
if (MappedFile *mf = open(path_clean(ctx.script_file->name + "/../" + str)))
return mf;
if (MappedFile *mf = open(str))
return mf;
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + str;
if (MappedFile *mf = open(path))
return mf;
}
SyntaxError(ctx, tok) << "library not found: " << str;
}
template <typename E>
static std::span<std::string_view>
read_group(Context<E> &ctx, std::span<std::string_view> tok) {
tok = skip(ctx, tok, "(");
while (!tok.empty() && tok[0] != ")") {
if (tok[0] == "AS_NEEDED") {
bool orig = ctx.as_needed;
ctx.as_needed = true;
tok = read_group(ctx, tok.subspan(1));
ctx.as_needed = orig;
continue;
}
MappedFile *mf = resolve_path(ctx, tok[0], true);
read_file(ctx, mf);
tok = tok.subspan(1);
}
if (tok.empty())
Fatal(ctx) << ctx.script_file->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
void parse_linker_script(Context<E> &ctx, MappedFile *mf) {
ctx.script_file = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
while (!tok.empty()) {
if (tok[0] == "OUTPUT_FORMAT") {
tok = read_output_format(ctx, tok.subspan(1));
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
tok = read_group(ctx, tok.subspan(1));
} else if (tok[0] == "VERSION") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script(ctx, tok);
tok = skip(ctx, tok, "}");
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
get_symbol(ctx, unquote(tok[2])));
tok = tok.subspan(4);
} else if (tok[0] == ";") {
tok = tok.subspan(1);
} else {
SyntaxError(ctx, tok[0]) << "unknown linker script token";
}
}
}
template <typename E>
std::string_view
get_script_output_type(Context<E> &ctx, MappedFile *mf) {
ctx.script_file = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
if (tok[2] == "elf64-x86-64")
return X86_64::target_name;
if (tok[2] == "elf32-i386")
return I386::target_name;
}
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
tok[1] == "(")
if (MappedFile *mf = resolve_path(ctx, tok[2], false))
return get_machine_type(ctx, mf);
return "";
}
static bool read_label(std::span<std::string_view> &tok,
std::string label) {
if (tok.size() >= 1 && tok[0] == label + ":") {
tok = tok.subspan(1);
return true;
}
if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") {
tok = tok.subspan(2);
return true;
}
return false;
}
template <typename E>
static void
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
std::string_view ver_str, u16 ver_idx,
bool is_global, bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (read_label(tok, "global")) {
is_global = true;
continue;
}
if (read_label(tok, "local")) {
is_global = false;
continue;
}
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script_commands( ctx, tok, ver_str, ver_idx, is_global, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, is_global, true);
}
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}
if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), ctx.script_file->name,
ver_str, ver_idx, is_cpp});
} else {
ctx.version_patterns.push_back({unquote(tok[0]), ctx.script_file->name,
ver_str, VER_NDX_LOCAL, is_cpp});
}
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "}")
return;
tok = skip(ctx, tok, ";");
}
}
template <typename E>
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
while (!tok.empty() && tok[0] != "}") {
std::string_view ver_str;
u16 ver_idx;
if (tok[0] == "{") {
ver_str = "global";
ver_idx = VER_NDX_GLOBAL;
} else {
ver_str = tok[0];
ver_idx = next_ver++;
ctx.arg.version_definitions.push_back(std::string(tok[0]));
tok = tok.subspan(1);
}
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_str, ver_idx, true, false);
tok = skip(ctx, tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
tok = skip(ctx, tok, ";");
}
}
template <typename E>
void parse_version_script(Context<E> &ctx, MappedFile *mf) {
ctx.script_file = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
read_version_script(ctx, tok);
if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";
}
template <typename E>
void read_dynamic_list_commands(Context<E> &ctx,
std::vector<DynamicPattern> &result,
std::span<std::string_view> &tok,
bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, result, tok, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, result, tok, true);
}
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}
result.push_back({unquote(tok[0]), "", is_cpp});
tok = skip(ctx, tok.subspan(1), ";");
}
}
template <typename E>
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
std::string_view contents =
must_open_file(ctx, std::string(path))->get_contents();
std::vector<std::string_view> vec = tokenize(ctx, contents);
std::span<std::string_view> tok = vec;
std::vector<DynamicPattern> result;
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, result, tok, false);
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";
for (DynamicPattern &p : result)
p.source = path;
return result;
}
using E = MOLD_TARGET;
template void parse_linker_script(Context<E> &, MappedFile *);
template std::string_view get_script_output_type(Context<E> &, MappedFile *);
template void parse_version_script(Context<E> &, MappedFile *);
template std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);
} // namespace mold::elf
-300
View File
@@ -1,300 +0,0 @@
// RISC instructions are usually up to 4 bytes long, so the immediates of
// their branch instructions are naturally smaller than 32 bits. This is
// contrary to x86-64 on which branch instructions take 4 bytes immediates
// and can jump to anywhere within PC ± 2 GiB.
//
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
// ARM64's ±128 MiB, for example. If a branch target is further than that,
// we need to let it branch to a linker-synthesized code sequence that
// construct a full 32 bit address in a register and jump there. That
// linker-synthesized code is called "thunk".
//
// The function in this file creates thunks.
//
// Note that although thunks play an important role in an executable, they
// don't take up too much space in it. For example, among the clang-16's
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
// need more thunks on ARM32 whose branch range is shorter than ARM64.
// That said, the total size of thunks still isn't that much. Therefore,
// we don't need to try too hard to reduce thunk size to the absolute
// minimum.
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || \
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
using E = MOLD_TARGET;
// Returns a branch reach in bytes for a given target.
static consteval i64 max_distance() {
// ARM64's branch has 26 bits immediate. The immediate is padded with
// implicit two-bit zeros because all instructions are 4 bytes aligned
// and therefore the least two bits are always zero. So the branch
// operand is effectively 28 bits long. That means the branch range is
// [-2^27, 2^27) or PC ± 128 MiB.
//
// LoongArch's BR instruction also takes a 26 bit immediate.
if (is_arm64<E> || is_loongarch<E>)
return 1 << 27;
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
// aligned to 2, so it's effectively 25 bits. It's [-2^24, 2^24) or PC ±
// 16 MiB.
//
// ARM32's non-Thumb branches have twice longer range than its Thumb
// counterparts, but we conservatively use the Thumb's limitation.
if (is_arm32<E>)
return 1 << 24;
// PPC's branch has 24 bits immediate, and the instructions are aligned
// to 4, therefore the reach is [-2^25, 2^25) or PC ± 32 MiB.
assert(is_ppc<E>);
return 1 << 25;
}
// We create thunks for each 12.8/1.6/3.2 MiB code block for
// ARM64/ARM32/PPC, respectively.
static constexpr i64 batch_size = max_distance() / 10;
// We assume that a single thunk group is smaller than 900 KiB.
static constexpr i64 max_thunk_size = 900 * 1024;
static_assert(max_thunk_size / E::thunk_size < ThunkRef::MAX_SYM_IDX);
template <typename E>
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
// We create thunks with a pessimistic assumption that all
// out-of-section relocations would be out-of-range.
InputSection<E> *isec2 = sym.get_input_section();
if (!isec2 || isec.output_section != isec2->output_section)
return false;
// Even if the target is the same section, we branch to its PLT
// if it has one. So a symbol with a PLT is also considered an
// out-of-section reference.
if (sym.has_plt(ctx))
return false;
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
if (isec2->offset == -1)
return false;
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are within their ranges.
if constexpr (is_arm32<E>) {
bool is_thumb = sym.get_addr(ctx) & 1;
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb))
return false;
}
// PowerPC before Power9 lacks PC-relative load/store instructions.
// Functions compiled for Power9 or earlier assume that r2 points to
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
// We need to a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>) {
if (rel.r_type == R_PPC64_REL24 && !sym.esym().preserves_r2())
return false;
if (rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().uses_toc())
return false;
}
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx, NO_OPD);
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -max_distance() <= val && val < max_distance();
}
static void reset_thunk(Thunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
sym->extra.thunk_idx = -1;
sym->extra.thunk_sym_idx = -1;
sym->flags = 0;
}
}
// Scan relocations to collect symbols that need thunks.
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
Thunk<E> &thunk, i64 thunk_idx) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<ThunkRef> &thunk_refs = isec.extra.thunk_refs;
thunk_refs.resize(rels.size());
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (!is_func_call_rel(rel))
continue;
// Skip if the symbol is undefined. apply_reloc() will report an error.
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.file)
continue;
// Skip if the destination is within reach.
if (is_reachable(ctx, isec, sym, rel))
continue;
// This relocation needs a thunk. If the symbol is already in a
// previous thunk, reuse it.
if (sym.extra.thunk_idx != -1) {
thunk_refs[i].thunk_idx = sym.extra.thunk_idx;
thunk_refs[i].sym_idx = sym.extra.thunk_sym_idx;
continue;
}
// Otherwise, add the symbol to the current thunk if it's not
// added already.
thunk_refs[i].thunk_idx = thunk_idx;
thunk_refs[i].sym_idx = -1;
if (sym.flags.exchange(-1) == 0) {
std::scoped_lock lock(thunk.mu);
thunk.symbols.push_back(&sym);
}
}
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
std::span<InputSection<E> *> m = members;
if (m.empty())
return;
// Initialize input sections with a dummy offset so that we can
// distinguish sections that have got an address with the one who
// haven't.
for (InputSection<E> *isec : m)
isec->offset = -1;
// We create thunks from the beginning of the section to the end.
// We manage progress using four offsets which increase monotonically.
// The locations they point to are always A <= B <= C <= D.
//
// Input sections between B and C are in the current batch.
//
// A is the input section with the smallest address than can reach
// from the current batch.
//
// D is the input section with the largest address such that the thunk
// is reachable from the current batch if it's inserted right before D.
//
// ................................ <input sections> ............
// A B C D
// ^ We insert a thunk for the current batch just before D
// <---> The current batch, which is smaller than BATCH_SIZE
// <--------> Smaller than MAX_DISTANCE
// <--------> Smaller than MAX_DISTANCE
// <-------------> Reachable from the current batch
i64 a = 0;
i64 b = 0;
i64 c = 0;
i64 d = 0;
i64 offset = 0;
// The smallest thunk index that is reachable from the current batch.
i64 t = 0;
while (b < m.size()) {
// Move D foward as far as we can jump from B to a thunk at D.
auto d_thunk_end = [&] {
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
return align_to(d_end, Thunk<E>::alignment) + max_thunk_size;
};
while (d < m.size() &&
(b == d || d_thunk_end() <= m[b]->offset + max_distance())) {
offset = align_to(offset, 1 << m[d]->p2align);
m[d]->offset = offset;
offset += m[d]->sh_size;
d++;
}
// Move C forward so that C is apart from B by BATCH_SIZE. We want
// to make sure that there's at least one section between B and C
// to ensure progress.
c = b + 1;
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
c++;
// Move A forward so that A is reachable from C.
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
while (a < b && m[a]->offset + max_distance() < c_offset)
a++;
// Erase references to out-of-range thunks.
while (t < thunks.size() && thunks[t]->offset < m[a]->offset)
reset_thunk(*thunks[t++]);
// Create a new thunk and place it at D.
offset = align_to(offset, Thunk<E>::alignment);
i64 thunk_idx = thunks.size();
Thunk<E> *thunk = new Thunk<E>(*this, offset);
thunks.emplace_back(thunk);
// Scan relocations between B and C to collect symbols that need
// entries in the new thunk.
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
[&](InputSection<E> *isec) {
scan_rels(ctx, *isec, *thunk, thunk_idx);
});
// Now that we know the number of symbols in the thunk, we can compute
// the thunk's size.
assert(thunk->size() < max_thunk_size);
offset += thunk->size();
// Sort symbols added to the thunk to make the output deterministic.
sort(thunk->symbols, [](Symbol<E> *a, Symbol<E> *b) {
return std::tuple{a->file->priority, a->sym_idx} <
std::tuple{b->file->priority, b->sym_idx};
});
// Assign offsets within the thunk to the symbols.
for (i64 i = 0; Symbol<E> *sym : thunk->symbols) {
sym->extra.thunk_idx = thunk_idx;
sym->extra.thunk_sym_idx = i++;
}
// Scan relocations again to fix symbol offsets in the last thunk.
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
[&](InputSection<E> *isec) {
std::span<Symbol<E> *> syms = isec->file.symbols;
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
std::span<ThunkRef> thunk_refs = isec->extra.thunk_refs;
for (i64 i = 0; i < rels.size(); i++)
if (thunk_refs[i].thunk_idx == thunk_idx)
thunk_refs[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
});
// Move B forward to point to the begining of the next batch.
b = c;
}
while (t < thunks.size())
reset_thunk(*thunks[t++]);
this->shdr.sh_size = offset;
for (InputSection<E> *isec : members)
this->shdr.sh_addralign =
std::max<u32>(this->shdr.sh_addralign, 1 << isec->p2align);
}
} // namespace mold::elf
#endif
+21 -18
View File
@@ -7,42 +7,45 @@ set -e
set -x
case "$ID-$VERSION_ID" in
ubuntu-20.* | pop-20.*)
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian | neon)
apt-get update
apt-get install -y cmake gcc g++ g++-10
apt-get install -y cmake gcc g++ clang gdb
;;
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get update
apt-get install -y cmake gcc g++
fedora | amzn | rhel | centos)
dnf install -y gcc-g++ cmake glibc-static libstdc++-static diffutils util-linux tar
;;
fedora-* | amzn-* | rhel-*)
dnf install -y gcc-g++ cmake glibc-static libstdc++-static diffutils util-linux
rocky)
dnf install -y gcc-g++ cmake diffutils util-linux
;;
opensuse-leap-*)
zypper install -y make cmake gcc-c++ gcc11-c++ glibc-devel-static tar diffutils util-linux
;;
opensuse-tumbleweed-*)
opensuse-*)
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
;;
gentoo-*)
gentoo)
emerge-webrsync
emerge dev-build/cmake
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
;;
arch-* | archarm-* | artix-* | endeavouros-*)
arch | archarm | artix | endeavouros)
pacman -Sy --needed --noconfirm base-devel cmake util-linux
;;
void-*)
void)
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
;;
alpine-*)
alpine)
apk update
apk add bash make linux-headers cmake gcc g++
;;
clear-linux-*)
clear-linux-os)
swupd update
swupd bundle-add c-basic diffutils
;;
almalinux)
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
;;
freebsd)
pkg update
pkg install -y cmake bash binutils gcc
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
+3 -3
View File
@@ -9,9 +9,9 @@ set -x
#
# Feel free to send me a PR if your OS is not on this list.
case "$ID-$VERSION_ID" in
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,arc}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
+8 -12
View File
@@ -26,7 +26,6 @@
#pragma once
#include "common.h"
#include "filetype.h"
namespace mold {
@@ -76,7 +75,7 @@ struct ArHdr {
}
};
template <typename Context, typename MappedFile>
template <typename Context>
std::vector<MappedFile *>
read_thin_archive_members(Context &ctx, MappedFile *mf) {
u8 *begin = mf->data;
@@ -116,7 +115,7 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
continue;
std::string path = name.starts_with('/') ?
name : (filepath(mf->name).parent_path() / name).string();
name : (path_dirname(mf->name) / name).string();
vec.push_back(must_open_file(ctx, path));
vec.back()->thin_parent = mf;
data = body;
@@ -124,7 +123,7 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
return vec;
}
template <typename Context, typename MappedFile>
template <typename Context>
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
@@ -162,16 +161,13 @@ std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf)
return vec;
}
template <typename Context, typename MappedFile>
template <typename Context>
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
case FileType::AR:
std::string_view str = mf->get_contents();
if (str.starts_with("!<arch>\n"))
return read_fat_archive_members(ctx, mf);
case FileType::THIN_AR:
return read_thin_archive_members(ctx, mf);
default:
unreachable();
}
assert(str.starts_with("!<thin>\n"));
return read_thin_archive_members(ctx, mf);
}
} // namespace mold
+72 -139
View File
@@ -1,7 +1,9 @@
#pragma once
#include "config.h"
#include "integers.h"
#include <array>
#include <atomic>
#include <bit>
#include <bitset>
@@ -58,11 +60,10 @@ public:
namespace mold {
namespace ranges = std::ranges;
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
template <typename Context> class OutputFile;
inline char *output_tmpfile;
inline u8 *output_buffer_start = nullptr;
@@ -71,11 +72,10 @@ inline u8 *output_buffer_end = nullptr;
extern std::string mold_git_hash;
std::string errno_string();
std::string get_self_path();
void cleanup();
void install_signal_handler();
static u64 combine_hash(u64 a, u64 b) {
inline u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
@@ -251,6 +251,12 @@ struct Atomic : std::atomic<T> {
}
};
//
// mimalloc.cc
//
void set_mimalloc_options();
//
// perf.cc
//
@@ -313,7 +319,7 @@ class Timer {
public:
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
record = new TimerRecord(name, parent ? parent->record : nullptr);
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
ctx.timer_records.emplace_back(record);
}
Timer(const Timer &) = delete;
@@ -330,23 +336,6 @@ private:
TimerRecord *record;
};
//
// Bit vector
//
class BitVector {
public:
BitVector() = default;
BitVector(u32 size) : vec((size + 7) / 8) {}
void resize(u32 size) { vec.resize((size + 7) / 8); }
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
private:
std::vector<u8> vec;
};
//
// Utility functions
//
@@ -384,8 +373,14 @@ inline u64 bits(u64 val, u64 hi, u64 lo) {
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
}
inline i64 sign_extend(u64 val, i64 size) {
return (i64)(val << (63 - size)) >> (63 - size);
// Cast val to a signed N bit integer.
// For example, sign_extend(x, 32) == (i32)x for any integer x.
inline i64 sign_extend(u64 val, i64 n) {
return (i64)(val << (64 - n)) >> (64 - n);
}
inline bool is_int(u64 val, i64 n) {
return sign_extend(val, n) == val;
}
template <typename T, typename Compare = std::less<T>>
@@ -404,9 +399,9 @@ void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
std::memory_order_relaxed));
}
template <typename T, typename U>
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
template <typename T>
inline void append(std::vector<T> &x, const auto &y) {
x.insert(x.end(), y.begin(), y.end());
}
template <typename T>
@@ -422,14 +417,6 @@ inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
return ret;
}
inline void sort(auto &vec) {
std::stable_sort(vec.begin(), vec.end());
}
inline void sort(auto &vec, auto less) {
std::stable_sort(vec.begin(), vec.end(), less);
}
template <typename T>
inline void remove_duplicates(std::vector<T> &vec) {
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
@@ -442,10 +429,9 @@ inline i64 write_string(void *buf, std::string_view str) {
}
template <typename T>
inline i64 write_vector(void *buf, const std::vector<T> &vec) {
i64 sz = vec.size() * sizeof(T);
memcpy(buf, vec.data(), sz);
return sz;
inline void write_vector(void *buf, const std::vector<T> &vec) {
if (!vec.empty())
memcpy(buf, vec.data(), vec.size() * sizeof(T));
}
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
@@ -497,6 +483,23 @@ inline u64 read_uleb(u8 *buf) {
return read_uleb(&tmp);
}
inline i64 read_sleb(u8 **buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *(*buf)++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return sign_extend(val, shift);
}
inline i64 read_sleb(u8 *buf) {
u8 *tmp = buf;
return read_sleb(&tmp);
}
inline u64 read_uleb(std::string_view *str) {
u8 *start = (u8 *)str->data();
u8 *ptr = start;
@@ -525,23 +528,6 @@ inline void overwrite_uleb(u8 *loc, u64 val) {
*loc = val & 0b0111'1111;
}
template <typename Context>
std::string_view save_string(Context &ctx, const std::string &str) {
u8 *buf = new u8[str.size() + 1];
memcpy(buf, str.data(), str.size());
buf[str.size()] = '\0';
ctx.string_pool.push_back(std::unique_ptr<u8[]>(buf));
return {(char *)buf, str.size()};
}
inline bool remove_prefix(std::string_view &s, std::string_view prefix) {
if (s.starts_with(prefix)) {
s = s.substr(prefix.size());
return true;
}
return false;
}
static inline void pause() {
#if defined(__x86_64__)
asm volatile("pause");
@@ -608,14 +594,14 @@ public:
#endif
}
std::pair<T *, bool> insert(std::string_view key, u32 hash, const T &val) {
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
assert(has_single_bit(nbuckets));
i64 begin = hash & (nbuckets - 1);
u64 begin = hash & (nbuckets - 1);
u64 mask = nbuckets / NUM_SHARDS - 1;
for (i64 i = 0; i < MAX_RETRY; i++) {
i64 idx = (begin & ~mask) | ((begin + i) & mask);
u64 idx = (begin & ~mask) | ((begin + i) & mask);
Entry &ent = entries[idx];
// It seems avoiding compare-and-swap is faster overall at least
@@ -653,8 +639,8 @@ public:
return {&ent.value, false};
}
assert(false && "ConcurrentMap is full");
return {nullptr, false};
std::cerr << "ConcurrentMap is full\n";
abort();
}
i64 get_idx(T *value) const {
@@ -712,12 +698,12 @@ public:
return flatten(vec);
}
static constexpr i64 MIN_NBUCKETS = 2048;
static constexpr i64 MIN_NBUCKETS = 4096;
static constexpr i64 NUM_SHARDS = 16;
static constexpr i64 MAX_RETRY = 128;
static constexpr i64 MAX_RETRY = 256;
Entry *entries = nullptr;
i64 nbuckets = 0;
u64 nbuckets = 0;
};
//
@@ -726,74 +712,6 @@ public:
void get_random_bytes(u8 *buf, i64 size);
//
// output-file.h
//
template <typename Context>
class OutputFile {
public:
static std::unique_ptr<OutputFile<Context>>
open(Context &ctx, std::string path, i64 filesize, i64 perm);
virtual void close(Context &ctx) = 0;
virtual ~OutputFile() = default;
u8 *buf = nullptr;
std::vector<u8> buf2;
std::string path;
i64 fd = -1;
i64 filesize = 0;
bool is_mmapped = false;
bool is_unmapped = false;
protected:
OutputFile(std::string path, i64 filesize, bool is_mmapped)
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
};
template <typename Context>
class MallocOutputFile : public OutputFile<Context> {
public:
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<Context>(path, filesize, false), ptr(new u8[filesize]),
perm(perm) {
this->buf = ptr.get();
}
void close(Context &ctx) override {
Timer t(ctx, "close_file");
FILE *fp;
if (this->path == "-") {
fp = stdout;
} else {
#ifdef _WIN32
int pmode = (perm & 0200) ? (_S_IREAD | _S_IWRITE) : _S_IREAD;
i64 fd = _open(this->path.c_str(), _O_RDWR | _O_CREAT | _O_BINARY, pmode);
#else
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
#endif
if (fd == -1)
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
#ifdef _WIN32
fp = _fdopen(fd, "wb");
#else
fp = fdopen(fd, "w");
#endif
}
fwrite(this->buf, this->filesize, 1, fp);
if (!this->buf2.empty())
fwrite(this->buf2.data(), this->buf2.size(), 1, fp);
fclose(fp);
}
private:
std::unique_ptr<u8[]> ptr;
i64 perm;
};
//
// hyperloglog.cc
//
@@ -877,13 +795,19 @@ private:
// filepath.cc
//
std::filesystem::path filepath(const auto &path) {
return {path, std::filesystem::path::format::generic_format};
inline std::filesystem::path path_dirname(std::string_view path) {
return std::filesystem::path(path).parent_path();
}
std::string get_realpath(std::string_view path);
std::string path_clean(std::string_view path);
std::filesystem::path to_abs_path(std::filesystem::path path);
inline std::string path_filename(std::string_view path) {
return std::filesystem::path(path).filename().string();
}
inline std::string path_clean(std::string_view path) {
return std::filesystem::path(path).lexically_normal().string();
}
std::string get_self_path();
//
// demangle.cc
@@ -899,6 +823,13 @@ std::optional<std::string_view> demangle_rust(std::string_view name);
void acquire_global_lock();
void release_global_lock();
//
// crc32.cc
//
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
std::vector<u8> crc32_solve(u32 current, u32 desired);
//
// compress.cc
//
@@ -963,6 +894,8 @@ class MappedFile {
public:
~MappedFile() { unmap(); }
void unmap();
void close_fd();
void reopen_fd(const std::string &path);
template <typename Context>
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size) {
@@ -972,7 +905,7 @@ public:
mf->size = size;
mf->parent = this;
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
ctx.mf_pool.emplace_back(mf);
return mf;
}
@@ -1031,7 +964,7 @@ MappedFile *open_file(Context &ctx, std::string path) {
Fatal(ctx) << error;
if (mf)
ctx.mf_pool.push_back(std::unique_ptr<MappedFile>(mf));
ctx.mf_pool.emplace_back(mf);
return mf;
}
File renamed without changes.
+3
View File
@@ -2,3 +2,6 @@
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
#cmakedefine HAVE_MADVISE 1
#cmakedefine HAVE_UNAME 1
#cmakedefine MOLD_USE_MIMALLOC 1
#cmakedefine MOLD_USE_SYSTEM_MIMALLOC 1
+60
View File
@@ -0,0 +1,60 @@
#include "common.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
namespace mold {
// This function "forges" a CRC. That is, given the current and a desired
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
// the original data to yield the desired CRC. Trailing garbage is ignored
// by many bianry file formats, so you can create a file with a desired
// CRC using crc32_solve(). We need it for --separate-debug-file.
std::vector<u8> crc32_solve(u32 current, u32 desired) {
constexpr u32 poly = 0xedb88320;
u32 x = ~desired;
// Each iteration computes x = (x * x^-1) mod poly.
for (i64 i = 0; i < 32; i++) {
x = std::rotl(x, 1);
x ^= (x & 1) * (poly << 1);
}
x ^= ~current;
std::vector<u8> out(4);
out[0] = x;
out[1] = x >> 8;
out[2] = x >> 16;
out[3] = x >> 24;
return out;
}
// Compute a CRC for given data in parallel
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
struct Shard {
u8 *buf;
i64 len;
u32 crc;
};
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
std::vector<Shard> shards;
while (len > 0) {
i64 sz = std::min(len, shard_size);
shards.push_back({buf, sz, 0});
buf += sz;
len -= sz;
}
tbb::parallel_for_each(shards, [](Shard &shard) {
shard.crc = crc32(0, shard.buf, shard.len);
});
for (Shard &shard : shards)
crc = crc32_combine(crc, shard.crc, shard.len);
return crc;
}
} // namespace mold
File renamed without changes.
+42
View File
@@ -0,0 +1,42 @@
#include "common.h"
#include <filesystem>
#include <sys/stat.h>
#ifdef __APPLE__
# include <mach-o/dyld.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
#endif
namespace mold {
// Returns the path of the mold executable itself
std::string get_self_path() {
#if __APPLE__ || _WIN32
fprintf(stderr, "mold: get_self_path is not supported");
exit(1);
#elif __FreeBSD__
// /proc may not be mounted on FreeBSD. The proper way to get the
// current executable's path is to use sysctl(2).
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t size;
sysctl(mib, 4, NULL, &size, NULL, 0);
std::string path;
path.resize(size);
sysctl(mib, 4, path.data(), &size, NULL, 0);
return path;
#else
return std::filesystem::read_symlink("/proc/self/exe").string();
#endif
}
} // namespace mold
+16 -15
View File
@@ -1,14 +1,14 @@
#!/bin/bash
#
# This test script takes a Gentoo package name and tries to build it
# using mold in a Docker environment. We chose Gentoo Linux as a test
# using mold in a Podman environment. We chose Gentoo Linux as a test
# target, because its source-based package allows us to build programs
# locally and run their test suites without any hassle.
#
# You can get a complete list of Gentoo packages availalbe for testing
# with the following command:
#
# docker run --rm mold-gentoo emerge --color n -s '' | \
# podman run --rm mold-gentoo emerge --color n -s '' | \
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
package="$1"
@@ -20,47 +20,48 @@ fi
set -x
# Create a Docker image
if ! docker image ls mold-gentoo | grep -q mold-gentoo; then
# Create a Podman image
if ! podman image ls mold-gentoo | grep -q mold-gentoo; then
set -e
cat <<EOF | docker build -t mold-gentoo -
FROM gentoo/stage3
cat <<EOF | podman build -t mold-gentoo -
FROM docker.io/gentoo/stage3
RUN emerge-webrsync
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary"' >> /etc/portage/make.conf && \
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary -perl"' >> /etc/portage/make.conf && \
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
echo 'FEATURES="\${FEATURE} noclean nostrip ccache -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf
RUN emerge gdb lld clang vim emacs strace ccache xeyes dev-build/cmake dev-vcs/git && rm -rf /var/tmp/portage
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache xeyes dev-build/cmake dev-vcs/git && \
rm -rf /var/tmp/portage
EOF
set +e
fi
git_hash=$(./mold --version | perl -ne '/\((\w+)/; print $1;')
git_hash=$(./dist/mold --version | perl -ne '/\((\w+)/; print $1;')
if [ "$package" = dev-libs/concurrencykit ]; then
echo "Skipping known broken package: $package"
exit 0
fi
# Build a given package in Docker
cmd1='(cd /usr/bin; ln -sf /mold/mold $(realpath ld))'
# Build a given package in Podman
cmd1='(cd /usr/bin; ln -sf /mold/dist/mold $(realpath ld))'
cmd2="MAKEOPTS=-'j$(nproc) --load-average=100' emerge --onlydeps $package"
cmd3="MAKEOPTS='-j$(nproc) --load-average=100' FEATURES=test emerge $package"
filename=`echo "$package" | sed 's!/!_!g'`
docker="docker run --rm --cap-add=SYS_PTRACE -v `pwd`:/mold -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 3h"
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 3h"
dir=gentoo/$git_hash
mkdir -p "$dir"/success "$dir"/failure
$docker nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
$podman nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
if [ $? = 0 ]; then
mv "$dir"/"$filename".mold "$dir"/success
else
mv "$dir"/"$filename".mold "$dir"/failure
fi
$docker nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
$podman nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
if [ $? = 0 ]; then
mv "$dir"/"$filename".ld "$dir"/success
else
+5 -5
View File
@@ -19,7 +19,7 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
vec.push_back({BRACKET});
vec.emplace_back(BRACKET);
std::bitset<256> &bitset = vec.back().bitset;
bool negate = false;
@@ -74,22 +74,22 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
break;
}
case '?':
vec.push_back({QUESTION});
vec.emplace_back(QUESTION);
break;
case '*':
vec.push_back({STAR});
vec.emplace_back(STAR);
break;
case '\\':
if (pat.empty())
return {};
if (vec.empty() || vec.back().kind != STRING)
vec.push_back({STRING});
vec.emplace_back(STRING);
vec.back().str += pat[0];
pat = pat.substr(1);
break;
default:
if (vec.empty() || vec.back().kind != STRING)
vec.push_back({STRING});
vec.emplace_back(STRING);
vec.back().str += c;
break;
}
File renamed without changes.
+144
View File
@@ -0,0 +1,144 @@
// This file defines integral types for file input/output. We need to use
// these types instead of the plain integers (such as uint32_t or int32_t)
// when reading from/writing to an mmap'ed file area for the following
// reasons:
//
// 1. mold is always a cross linker and should not depend on what host it
// is running on. For example, users should be able to run mold on a
// big-endian SPARC machine to create a little-endian RV64 binary.
//
// 2. Even though data members in all ELF data strucutres are naturally
// aligned, they are not guaranteed to be aligned on memory because of
// archive files. Archive files (.a files) align each member only to a
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
// in an mmap'ed memory. Misaligned access is an undefined behavior in
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
// example, to read a 32 bit value.
//
// The data types defined in this file don't depend on host byte order and
// don't do unaligned access. Note that modern compilers are smart enough
// to recognize shift and bitwise OR patterns and compile them into a
// single load instruction.
#pragma once
#include <cstdint>
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __LITTLE_ENDIAN__ 1
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__ 1
# else
# error "unknown host byte order"
# endif
#endif
namespace mold {
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
template <typename T, bool is_le, int size = sizeof(T)>
class Integer {
public:
constexpr Integer() = default;
constexpr Integer(T x) requires (is_le && size == 2)
: buf{(u8)x, (u8)(x >> 8)} {}
constexpr Integer(T x) requires (is_le && size == 3)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16)} {}
constexpr Integer(T x) requires (is_le && size == 4)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24)} {}
constexpr Integer(T x) requires (is_le && size == 8)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24),
(u8)(x >> 32), (u8)(x >> 40), (u8)(x >> 48), (u8)(x >> 56)} {}
constexpr Integer(T x) requires (!is_le && size == 2)
: buf{(u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 3)
: buf{(u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 4)
: buf{(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 8)
: buf{(u8)(x >> 56), (u8)(x >> 48), (u8)(x >> 40), (u8)(x >> 32),
(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
operator T() const {
if constexpr (is_le) {
if constexpr (size == 2)
return buf[1] << 8 | buf[0];
else if constexpr (size == 3)
return buf[2] << 16 | buf[1] << 8 | buf[0];
else if constexpr (size == 4)
return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
else
return (u64)buf[7] << 56 | (u64)buf[6] << 48 |
(u64)buf[5] << 40 | (u64)buf[4] << 32 |
(u64)buf[3] << 24 | (u64)buf[2] << 16 |
(u64)buf[1] << 8 | (u64)buf[0];
} else {
if constexpr (size == 2)
return buf[0] << 8 | buf[1];
else if constexpr (size == 3)
return buf[0] << 16 | buf[1] << 8 | buf[2];
else if constexpr (size == 4)
return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
else
return (u64)buf[0] << 56 | (u64)buf[1] << 48 |
(u64)buf[2] << 40 | (u64)buf[3] << 32 |
(u64)buf[4] << 24 | (u64)buf[5] << 16 |
(u64)buf[6] << 8 | (u64)buf[7];
}
}
Integer &operator=(T x) {
new (this) Integer(x);
return *this;
}
Integer &operator++() { return *this = *this + 1; }
Integer operator++(int) { return ++*this - 1; }
Integer &operator--() { return *this = *this - 1; }
Integer operator--(int) { return --*this + 1; }
Integer &operator+=(T x) { return *this = *this + x; }
Integer &operator-=(T x) { return *this = *this - x; }
Integer &operator&=(T x) { return *this = *this & x; }
Integer &operator|=(T x) { return *this = *this | x; }
private:
u8 buf[size];
};
using il16 = Integer<i16, true>;
using il32 = Integer<i32, true>;
using il64 = Integer<i64, true>;
using ul16 = Integer<u16, true>;
using ul24 = Integer<u32, true, 3>;
using ul32 = Integer<u32, true>;
using ul64 = Integer<u64, true>;
using ib16 = Integer<i16, false>;
using ib32 = Integer<i32, false>;
using ib64 = Integer<i64, false>;
using ub16 = Integer<u16, false>;
using ub24 = Integer<u32, false, 3>;
using ub32 = Integer<u32, false>;
using ub64 = Integer<u64, false>;
} // namespace mold
+1 -1
View File
@@ -11,9 +11,9 @@
#include "common.h"
#include <cstdlib>
#include <fcntl.h>
#include <pwd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
File renamed without changes.
@@ -32,9 +32,20 @@ MappedFile *open_file_impl(const std::string &path, std::string &error) {
void MappedFile::unmap() {
if (size == 0 || parent || !data)
return;
munmap(data, size);
data = nullptr;
}
void MappedFile::close_fd() {
if (fd == -1)
return;
close(fd);
fd = -1;
}
void MappedFile::reopen_fd(const std::string &path) {
if (fd == -1)
fd = open(path.c_str(), O_RDONLY);
}
} // namespace mold
@@ -64,4 +64,18 @@ void MappedFile::unmap() {
data = nullptr;
}
void MappedFile::close_fd() {
if (fd == INVALID_HANDLE_VALUE)
return;
CloseHandle(fd);
fd = INVALID_HANDLE_VALUE;
}
void MappedFile::reopen_fd(const std::string &path) {
if (fd == INVALID_HANDLE_VALUE)
fd = CreateFileA(path.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
}
} // namespace mold
+24
View File
@@ -0,0 +1,24 @@
#include "common.h"
// Including mimalloc-new-delete.h overrides new/delete operators.
// We need it only when we are using mimalloc as a dynamic library.
#if MOLD_USE_SYSTEM_MIMALLOC
# include <mimalloc-new-delete.h>
#endif
// Silence mimalloc warning messages that users can just ignore.
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
namespace mold {
void set_mimalloc_options() {
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
}
}
#else
namespace mold {
void set_mimalloc_options() {}
}
#endif
+2 -2
View File
@@ -96,12 +96,12 @@ bool MultiGlob::add(std::string_view pat, i64 val) {
assert(!is_compiled);
assert(!pat.empty());
strings.push_back(std::string(pat));
strings.emplace_back(pat);
// Complex glob pattern
if (!is_simple_pattern(pat)) {
if (std::optional<Glob> glob = Glob::compile(pat)) {
globs.push_back({std::move(*glob), val});
globs.emplace_back(std::move(*glob), val);
return true;
}
return false;
+3 -6
View File
@@ -3,6 +3,7 @@
#include <functional>
#include <iomanip>
#include <ios>
#include <tbb/concurrent_vector.h>
#ifndef _WIN32
#include <sys/resource.h>
@@ -16,9 +17,7 @@ i64 Counter::get_value() {
}
void Counter::print() {
sort(instances, [](Counter *a, Counter *b) {
return a->get_value() > b->get_value();
});
ranges::stable_sort(instances, {}, [](Counter *x) { return x->get_value(); });
for (Counter *c : instances)
std::cout << std::setw(20) << std::right << c->name
@@ -79,9 +78,7 @@ static void print_rec(TimerRecord &rec, i64 indent) {
std::string(indent * 2, ' ').c_str(),
rec.name.c_str());
sort(rec.children, [](TimerRecord *a, TimerRecord *b) {
return a->start < b->start;
});
ranges::stable_sort(rec.children, {}, &TimerRecord::start);
for (TimerRecord *child : rec.children)
print_rec(*child, indent + 1);
View File
File renamed without changes.
+1 -1
View File
@@ -1,6 +1,6 @@
#include "common.h"
#include <signal.h>
#include <csignal>
#include <tbb/version.h>
#ifdef __FreeBSD__
@@ -38,6 +38,10 @@ static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
static const char msg[] =
"mold: failed to write to an output file. Disk full?\n";
(void)!_write(_fileno(stderr), msg, sizeof(msg) - 1);
} else if (rec->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
static const char msg[] =
"mold: stack overflow\n";
(void)!_write(_fileno(stderr), msg, sizeof(msg) - 1);
}
cleanup();
View File
File renamed without changes.
+2
View File
@@ -1,3 +1,5 @@
// This file contains functions to create a tar file.
#include "common.h"
#ifdef _WIN32
File renamed without changes.
+285
View File
@@ -0,0 +1,285 @@
// r31 is blink. r30 is I believe a scratch register.
//
// Programs might use one of several branch, jump, and link instructions
// to control execution flow through direct and indirect function calls
// and branching. For function calling, the conditional branch-and-link
// instruction has a maximum branch range of ±1 MiB, and the target
// address is 32-bit-aligned. The unconditional branch-and-link format has
// a maximum branch range of ±16 MiB.
//
// https://github.com/foss-for-synopsys-dwc-arc-processors/arc-ABI-manual/blob/master/ARCv2_ABI.pdf
#include "mold.h"
namespace mold {
using E = ARC;
// An integer-like class for the mixed-endian encoding
class M32 {
public:
operator u32() const {
return (buf[1] << 24) | (buf[0] << 16) | (buf[3] << 8) | buf[2];
}
M32 &operator=(u32 x) {
buf[0] = x >> 16;
buf[1] = x >> 24;
buf[2] = x;
buf[3] = x >> 8;
return *this;
}
M32 &operator&=(u32 x) { return *this = *this & x; }
M32 &operator|=(u32 x) { return *this = *this | x; }
u8 buf[4];
};
static void write_disp7u(u8 *loc, u32 val) {
*(ul16 *)loc |= (bits(val, 6, 3) << 4) | bits(val, 2, 0);
}
static void write_disp9(u8 *loc, u32 val) {
*(M32 *)loc |= bits(val, 8, 0);
}
static void write_disp9ls(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 7, 0) << 16) | (bit(val, 8) << 15);
}
static void write_disp9s(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 8, 0);
}
static void write_disp10u(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 9, 0);
}
static void write_disp13s(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 12, 0);
}
static void write_disp21h(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 1) << 17) | (bits(val, 20, 11) << 6);
}
static void write_disp21w(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 2) << 18) | (bits(val, 20, 11) << 6);
}
static void write_disp25h(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 1) << 17) | (bits(val, 20, 11) << 6) |
bits(val, 24, 21);
}
static void write_disp25w(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 10, 2) << 18) | (bits(val, 20, 11) << 6) |
bits(val, 24, 21);
}
static void write_disps9(u8 *loc, u32 val) {
*(ul16 *)loc |= bits(val, 10, 2);
}
static void write_disps12(u8 *loc, u32 val) {
*(M32 *)loc |= (bits(val, 5, 0) << 6) | bits(val, 11, 6);
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul16 insn[] = {
0x2730, 0x7f8b, 0, 0, // ld r11, [pcl,0]
0x2730, 0x7f8a, 0, 0, // ld r10, [pcl,0]
0x2020, 0x0280, // j [r10]
0, 0, // (address of GOTPLT)
};
memcpy(buf, insn, sizeof(insn));
*(M32 *)(buf + 4) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
*(M32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
*(M32 *)(buf + 20) = ctx.gotplt->shdr.sh_addr;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul16 insn[] = {
0x2730, 0x7f8c, 0, 0, // ld r12, [pcl,0]
0x2021, 0x0300, // j.d [r12]
0x240a, 0x1fc0, // mov r12,pcl
};
memcpy(buf, insn, sizeof(insn));
*(M32 *)(buf + 4) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx);
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul16 insn[] = {
0x2730, 0x7f8c, 0, 0, // ld r12, [pcl,0]
0x2021, 0x0300, // j.d [r12]
0x240a, 0x1fc0, // mov r12,pcl
};
memcpy(buf, insn, sizeof(insn));
*(M32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx);
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ARC_32:
*(ul32 *)loc = val;
break;
case R_ARC_32_PCREL:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_ARC_32:
*(ul32 *)loc = S + A;
break;
case R_ARC_32_ME:
*(M32 *)loc = S + A;
break;
case R_ARC_S25H_PCREL:
case R_ARC_S25H_PCREL_PLT:
write_disp25h(loc, align_to(S + A - P, 2));
break;
case R_ARC_S25W_PCREL:
case R_ARC_S25W_PCREL_PLT:
write_disp25w(loc, align_to(S + A - P, 4));
break;
case R_ARC_PC32:
*(M32 *)loc = S + A - P;
break;
case R_ARC_32_PCREL:
*(ul32 *)loc = S + A - P;
break;
case R_ARC_GOTPC32:
*(M32 *)loc = GOT + G + A - P;
break;
case R_ARC_TLS_LE_32:
*(M32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARC_TLS_IE_GOT:
*(M32 *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_ARC_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
switch (rel.r_type) {
case R_ARC_32:
break;
case R_ARC_32_ME:
scan_absrel(ctx, sym, rel);
break;
case R_ARC_S25H_PCREL:
case R_ARC_S25W_PCREL:
case R_ARC_32_PCREL:
case R_ARC_PC32:
scan_pcrel(ctx, sym, rel);
break;
case R_ARC_S25H_PCREL_PLT:
case R_ARC_S25W_PCREL_PLT:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_ARC_GOTPC32:
sym.flags |= NEEDS_GOT;
break;
case R_ARC_TLS_LE_32:
check_tlsle(ctx, sym, rel);
break;
case R_ARC_TLS_IE_GOT:
sym.flags |= NEEDS_GOTTP;
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return 0x406;
}
} // namespace mold
+220 -159
View File
@@ -34,19 +34,19 @@
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
namespace mold::elf {
namespace mold {
using E = ARM32;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
ul32 *arm = (ul32 *)loc;
ul16 *thm = (ul16 *)loc;
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_TARGET1:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
@@ -57,77 +57,104 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
return *(il32 *)loc;
return (il32)*arm;
case R_ARM_THM_JUMP8:
return sign_extend(thm[0], 8) << 1;
case R_ARM_THM_JUMP11:
return sign_extend(*(ul16 *)loc, 10) << 1;
return sign_extend(thm[0], 11) << 1;
case R_ARM_THM_JUMP19: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/B--Branch-
u32 S = bit(thm[0], 10);
u32 J2 = bit(thm[1], 11);
u32 J1 = bit(thm[1], 13);
u32 imm6 = bits(thm[0], 5, 0);
u32 imm11 = bits(thm[1], 10, 0);
u32 val = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
return sign_extend(val, 21);
}
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL: {
u32 S = bit(*(ul16 *)loc, 10);
u32 J1 = bit(*(ul16 *)(loc + 2), 13);
u32 J2 = bit(*(ul16 *)(loc + 2), 11);
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/BL--BLX--immediate---Branch-with-Link-and-optional-Exchange--immediate--
u32 S = bit(thm[0], 10);
u32 J1 = bit(thm[1], 13);
u32 J2 = bit(thm[1], 11);
u32 I1 = !(J1 ^ S);
u32 I2 = !(J2 ^ S);
u32 imm10 = bits(*(ul16 *)loc, 9, 0);
u32 imm11 = bits(*(ul16 *)(loc + 2), 10, 0);
u32 imm10 = bits(thm[0], 9, 0);
u32 imm11 = bits(thm[1], 10, 0);
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
return sign_extend(val, 24);
return sign_extend(val, 25);
}
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_TLS_CALL:
return sign_extend(*(ul32 *)loc, 23) << 2;
return sign_extend(*arm, 24) << 2;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS: {
u32 imm12 = bits(*(ul32 *)loc, 11, 0);
u32 imm4 = bits(*(ul32 *)loc, 19, 16);
return sign_extend((imm4 << 12) | imm12, 15);
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOV--MOVS--immediate---Move--immediate--
u32 imm4 = bits(*arm, 19, 16);
u32 imm12 = bits(*arm, 11, 0);
u32 val = (imm4 << 12) | imm12;
return sign_extend(val, 16);
}
case R_ARM_PREL31:
return sign_extend(*(ul32 *)loc, 30);
return sign_extend(*arm, 31);
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS: {
u32 imm4 = bits(*(ul16 *)loc, 3, 0);
u32 i = bit(*(ul16 *)loc, 10);
u32 imm3 = bits(*(ul16 *)(loc + 2), 14, 12);
u32 imm8 = bits(*(ul16 *)(loc + 2), 7, 0);
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOVT--Move-Top-
u32 imm4 = bits(thm[0], 3, 0);
u32 i = bit(thm[0], 10);
u32 imm3 = bits(thm[1], 14, 12);
u32 imm8 = bits(thm[1], 7, 0);
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
return sign_extend(val, 15);
return sign_extend(val, 16);
}
default:
return 0;
}
}
static void write_mov_imm(u8 *loc, u32 val) {
static void write_arm_mov(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
}
static void write_thm_b_imm(u8 *loc, u32 val) {
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
u32 sign = bit(val, 24);
static void write_thm_b21(u8 *loc, u32 val) {
u32 S = bit(val, 20);
u32 J2 = bit(val, 19);
u32 J1 = bit(val, 18);
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
static void write_thm_b25(u8 *loc, u32 val) {
u32 S = bit(val, 24);
u32 I1 = bit(val, 23);
u32 I2 = bit(val, 22);
u32 J1 = !I1 ^ sign;
u32 J2 = !I2 ^ sign;
u32 J1 = !I1 ^ S;
u32 J2 = !I2 ^ S;
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (sign << 10) | imm10;
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
static void write_thm_mov_imm(u8 *loc, u32 val) {
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
static void write_thm_mov(u8 *loc, u32 val) {
u32 imm4 = bits(val, 15, 12);
u32 i = bit(val, 11);
u32 imm3 = bits(val, 10, 8);
@@ -145,7 +172,6 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
break;
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_TARGET1:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
@@ -156,16 +182,20 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
*(ul32 *)loc = val;
break;
case R_ARM_THM_JUMP8:
*(ul16 *)loc = (*(ul16 *)loc & 0xff00) | bits(val, 8, 1);
break;
case R_ARM_THM_JUMP11:
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL:
write_thm_b_imm(loc, val);
write_thm_b25(loc, val);
break;
case R_ARM_CALL:
case R_ARM_JUMP24:
@@ -176,7 +206,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS:
write_mov_imm(loc, val);
write_arm_mov(loc, val);
break;
case R_ARM_PREL31:
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
@@ -185,7 +215,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS:
write_thm_mov_imm(loc, val);
write_thm_mov(loc, val);
break;
default:
unreachable();
@@ -194,7 +224,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
@@ -209,7 +239,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static const ul32 plt_entry[] = {
static constexpr ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
@@ -247,30 +277,18 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
}
// ARM and Thumb branch instructions can jump within ±16 MiB.
static bool is_jump_reachable(i64 val) {
return sign_extend(val, 24) == val;
static Thunk<E> &get_reachable_thunk(OutputSection<E> &osec, u64 addr) {
auto it = std::upper_bound(osec.thunks.begin(), osec.thunks.end(), addr,
[](u64 addr, std::unique_ptr<Thunk<E>> &thunk) {
return addr < thunk->get_addr();
});
return **it;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
auto get_tls_trampoline_addr = [&, i = 0](u64 addr) mutable {
for (; i < output_section->thunks.size(); i++) {
i64 disp = output_section->shdr.sh_addr + output_section->thunks[i]->offset -
addr;
if (is_jump_reachable(disp))
return disp;
}
unreachable();
};
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
@@ -279,13 +297,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
@@ -293,13 +304,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto get_thumb_thunk_addr = [&] { return get_thunk_addr(i); };
auto get_arm_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto get_thumb_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
auto get_tlsdesc_trampoline_addr = [&] {
return get_reachable_thunk(*output_section, P).get_addr();
};
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_ARM_REL32:
*(ul32 *)loc = S + A - P;
@@ -312,21 +330,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
// THM_CALL relocation refers either BL or BLX instruction.
// THM_CALL relocation refers to either BL or BLX instruction.
// They are different in only one bit. We need to use BL if
// the jump target is Thumb. Otherwise, use BLX.
i64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T) {
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
} else {
write_thm_b_imm(loc, align_to(val, 4));
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
}
i64 val1 = S + A - P;
i64 val2 = align_to(S + A - P, 4);
if (T && is_int(val1, 25)) {
*(ul16 *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, val1);
} else if (!T && is_int(val2, 25)) {
*(ul16 *)(loc + 2) &= ~0x1000; // BLX
write_thm_b25(loc, val2);
} else {
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
*(ul16 *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
}
break;
}
@@ -356,8 +374,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
u64 val = S + A - P;
if (is_jump_reachable(val)) {
i64 val = S + A - P;
if (is_int(val, 26)) {
if (T) {
*(ul32 *)loc = 0xfa00'0000; // BLX
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
@@ -383,8 +401,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// immediate; it takes only a register. So if mode switch is
// required, we jump to a linker-synthesized thunk which does the
// job with a longer code sequence.
u64 val = S + A - P;
if (!is_jump_reachable(val) || T)
i64 val = S + A - P;
if (T || !is_int(val, 26))
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
@@ -397,30 +415,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP8:
check(S + A - P, -(1 << 8), 1 << 8);
*(ul16 *)loc &= 0xff00;
*(ul16 *)loc |= bits(S + A - P, 8, 1);
break;
case R_ARM_THM_JUMP11:
assert(T);
check(S + A - P, -(1 << 11), 1 << 11);
*(ul16 *)loc &= 0xf800;
*(ul16 *)loc |= bits(S + A - P, 11, 1);
break;
case R_ARM_THM_JUMP19: {
i64 val = S + A - P;
check(val, -(1 << 19), 1 << 19);
// sign:J2:J1:imm6:imm11:'0'
u32 sign = bit(val, 20);
u32 J2 = bit(val, 19);
u32 J1 = bit(val, 18);
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
*(ul16 *)loc &= 0b1111'1011'1100'0000;
*(ul16 *)loc |= (sign << 10) | imm6;
*(ul16 *)(loc + 2) &= 0b1101'0000'0000'0000;
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
case R_ARM_THM_JUMP19:
check(S + A - P, -(1 << 20), 1 << 20);
write_thm_b21(loc, S + A - P);
break;
}
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0x8000'f3af; // NOP
@@ -429,20 +437,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
u64 val = S + A - P;
if (!is_jump_reachable(val) || !T)
i64 val = S + A - P;
if (!T || !is_int(val, 25))
val = get_thumb_thunk_addr() + A - P;
write_thm_b_imm(loc, val);
write_thm_b25(loc, val);
break;
}
case R_ARM_MOVW_PREL_NC:
write_mov_imm(loc, ((S + A) | T) - P);
write_arm_mov(loc, ((S + A) | T) - P);
break;
case R_ARM_MOVW_ABS_NC:
write_mov_imm(loc, (S + A) | T);
write_arm_mov(loc, (S + A) | T);
break;
case R_ARM_THM_MOVW_PREL_NC:
write_thm_mov_imm(loc, ((S + A) | T) - P);
write_thm_mov(loc, ((S + A) | T) - P);
break;
case R_ARM_PREL31:
check(S + A - P, -(1LL << 30), 1LL << 30);
@@ -450,19 +458,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
break;
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov_imm(loc, (S + A) | T);
write_thm_mov(loc, (S + A) | T);
break;
case R_ARM_MOVT_PREL:
write_mov_imm(loc, (S + A - P) >> 16);
write_arm_mov(loc, (S + A - P) >> 16);
break;
case R_ARM_THM_MOVT_PREL:
write_thm_mov_imm(loc, (S + A - P) >> 16);
write_thm_mov(loc, (S + A - P) >> 16);
break;
case R_ARM_MOVT_ABS:
write_mov_imm(loc, (S + A) >> 16);
write_arm_mov(loc, (S + A) >> 16);
break;
case R_ARM_THM_MOVT_ABS:
write_thm_mov_imm(loc, (S + A) >> 16);
write_thm_mov(loc, (S + A) >> 16);
break;
case R_ARM_TLS_GD32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
@@ -489,19 +497,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// .L2: .word foo + . - .L1
// R_ARM_TLS_GOTDESC
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// ldr r0, .L2
// .L1: ldr r0, [pc, r0]
// ...
// .L2: .word foo(gottpoff) + . - .L1
//
// or to the following for executable.
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// ldr r0, .L2
// .L1: nop
// ...
// .L2: .word foo(tpoff)
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// ldr r0, .L2
// .L1: ldr r0, [pc, r0]
// ...
// .L2: .word foo(gottpoff) + . - .L1
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
@@ -513,8 +523,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
// BL <tls_trampoline>
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
*(ul32 *)loc = 0xeb00'0000; // bl 0
*(ul32 *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
@@ -523,8 +533,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
write_thm_b_imm(loc, val);
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
write_thm_b25(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else if (sym.has_gottp(ctx)) {
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
@@ -584,8 +594,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -600,12 +608,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVT_ABS:
case R_ARM_TARGET1:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ARM_MOVW_ABS_NC:
case R_ARM_THM_MOVW_ABS_NC:
scan_absrel(ctx, sym, rel);
@@ -644,9 +646,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
break;
case R_ARM_ABS32:
case R_ARM_TARGET1:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVT_ABS:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_THM_JUMP8:
case R_ARM_THM_JUMP11:
case R_ARM_THM_JUMP19:
case R_ARM_MOVW_PREL_NC:
@@ -666,7 +673,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
// TLS trampoline code. ARM32's TLSDESC is designed so that this
// common piece of code is factored out from object files to reduce
// output size. Since no one provide, the linker has to synthesize it.
static ul32 hdr[] = {
constexpr ul32 hdr[] = {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
@@ -675,7 +682,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
// This is a range extension and mode switch thunk.
// It has two entry points: +0 for Thumb and +4 for ARM.
const u8 entry[] = {
constexpr u8 entry[] = {
// .thumb
0x78, 0x47, // bx pc # jumps to 1f
0xc0, 0x46, // nop
@@ -708,6 +715,54 @@ u64 get_eflags(Context<E> &ctx) {
return EF_ARM_EABI_VER5;
}
void create_arm_exidx_section(Context<E> &ctx) {
for (i64 i = 0; i < ctx.chunks.size(); i++) {
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
if (osec && osec->shdr.sh_type == SHT_ARM_EXIDX) {
auto *sec = new Arm32ExidxSection(*osec);
ctx.extra.exidx = sec;
ctx.chunks[i] = sec;
ctx.chunk_pool.emplace_back(sec);
for (InputSection<E> *isec : osec->members)
isec->is_alive = false;
break;
}
}
}
void Arm32ExidxSection::compute_section_size(Context<E> &ctx) {
output_section.compute_section_size(ctx);
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
}
void Arm32ExidxSection::update_shdr(Context<E> &ctx) {
// .ARM.exidx's sh_link should be set to the .text section index.
// Runtime doesn't care about it, but the binutils's strip command does.
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
this->shdr.sh_link = chunk->shndx;
}
void Arm32ExidxSection::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
void Arm32ExidxSection::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
// Returns the end of the text segment
static u64 get_text_end(Context<E> &ctx) {
u64 ret = 0;
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->shdr.sh_flags & SHF_EXECINSTR)
ret = std::max<u64>(ret, chunk->shdr.sh_addr + chunk->shdr.sh_size);
return ret;
}
// ARM executables use an .ARM.exidx section to look up an exception
// handling record for the current instruction pointer. The table needs
// to be sorted by their addresses.
@@ -716,14 +771,8 @@ u64 get_eflags(Context<E> &ctx) {
// I don't know why only ARM uses the different mechanism, but it's
// likely that it's due to some historical reason.
//
// This function sorts .ARM.exidx records.
void fixup_arm_exidx_section(Context<E> &ctx) {
Timer t(ctx, "fixup_arm_exidx_section");
OutputSection<E> *osec = find_section(ctx, SHT_ARM_EXIDX);
if (!osec)
return;
// This function returns contents of .ARM.exidx.
std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
// .ARM.exidx records consists of a signed 31-bit relative address
// and a 32-bit value. The relative address indicates the start
// address of a function that the record covers. The value is one of
@@ -736,37 +785,56 @@ void fixup_arm_exidx_section(Context<E> &ctx) {
//
// CANTUNWIND is value 1. The most significant bit is set in (2) but
// not in (3). So we can distinguished them just by looking at a value.
const u32 EXIDX_CANTUNWIND = 1;
const u32 CANTUNWIND = 1;
struct Entry {
ul32 addr;
ul32 val;
};
if (osec->shdr.sh_size % sizeof(Entry))
Fatal(ctx) << "invalid .ARM.exidx section size";
// We reserve one extra slot for the sentinel
i64 num_entries = output_section.shdr.sh_size / sizeof(Entry) + 1;
std::vector<u8> buf(num_entries * sizeof(Entry));
Entry *ent = (Entry *)buf.data();
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
// Write section contents to the buffer
output_section.shdr.sh_addr = this->shdr.sh_addr;
output_section.write_to(ctx, buf.data());
// Fill in sentinel fields
u64 sentinel_addr = this->shdr.sh_addr + sizeof(Entry) * (num_entries - 1);
ent[num_entries - 1].addr = get_text_end(ctx) - sentinel_addr;
ent[num_entries - 1].val = CANTUNWIND;
// Entry's addresses are relative to themselves. In order to sort
// records by addresses, we first translate them so that the addresses
// records by address, we first translate them so that the addresses
// are relative to the beginning of the section.
auto is_relative = [](u32 val) {
return val != EXIDX_CANTUNWIND && !(val & 0x8000'0000);
return val != CANTUNWIND && !(val & 0x8000'0000);
};
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
ent[i].addr = sign_extend(ent[i].addr, 31) + offset;
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
std::sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
return a.addr < b.addr;
});
// Remove duplicate adjacent entries. That is, if two adjacent functions
// have the same compact unwind info or are both CANTUNWIND, we can
// merge them into a single address range.
auto it = std::unique(ent, ent + num_entries,
[](const Entry &a, const Entry &b) {
return a.val == b.val;
});
num_entries = it - ent;
buf.resize(num_entries * sizeof(Entry));
// Make addresses relative to themselves.
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
@@ -775,14 +843,7 @@ void fixup_arm_exidx_section(Context<E> &ctx) {
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
});
// .ARM.exidx's sh_link should be set to the .text section index.
// Runtime doesn't care about it, but the binutils's strip command does.
if (ctx.shdr) {
if (Chunk<E> *text = find_section(ctx, ".text")) {
osec->shdr.sh_link = text->shndx;
ctx.shdr->copy_buf(ctx);
}
}
return buf;
}
} // namespace mold::elf
} // namespace mold
+46 -58
View File
@@ -17,11 +17,13 @@
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
#if MOLD_ARM64LE || MOLD_ARM64BE
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = ARM64;
using E = MOLD_TARGET;
static void write_adrp(u8 *buf, u64 val) {
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
@@ -46,7 +48,7 @@ static u64 page(u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
0x9000'0010, // adrp x16, .got.plt[2]
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
@@ -68,7 +70,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, .got.plt[n]
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
0x9100'0210, // add x16, x16, .got.plt[n]
@@ -86,7 +88,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, GOT[n]
0xf940'0211, // ldr x17, [x16, GOT[n]]
0xd61f'0220, // br x17
@@ -110,13 +112,13 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
case R_NONE:
break;
case R_AARCH64_ABS64:
*(ul64 *)loc = val;
*(U64<E> *)loc = val;
break;
case R_AARCH64_PREL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
case R_AARCH64_PREL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -145,11 +147,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -158,22 +155,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_ADD_ABS_LO12_NC:
@@ -242,7 +235,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
i + 1 < rels.size()) {
i64 val = S + A - P - 4;
const ElfRel<E> &rel2 = rels[i + 1];
if (sign_extend(val, 20) == val &&
if (is_int(val, 21) &&
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
rel2.r_sym == rel.r_sym &&
rel2.r_offset == rel.r_offset + 4 &&
@@ -281,14 +274,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
i64 val = S + A - P;
if (val < -(1 << 27) || (1 << 27) <= val)
val = get_thunk_addr(i) + A - P;
if (!is_int(val, 28))
val = sym.get_thunk_addr(ctx, P) + A - P;
*(ul32 *)loc |= bits(val, 27, 2);
break;
}
case R_AARCH64_PLT32:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = S + A - P;
*(U32<E> *)loc = S + A - P;
break;
case R_AARCH64_CONDBR19:
case R_AARCH64_LD_PREL_LO19:
@@ -296,15 +289,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
break;
case R_AARCH64_PREL16:
check(S + A - P, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = S + A - P;
check(S + A - P, -(1LL << 15), 1LL << 16);
*(U16<E> *)loc = S + A - P;
break;
case R_AARCH64_PREL32:
check(S + A - P, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = S + A - P;
*(U32<E> *)loc = S + A - P;
break;
case R_AARCH64_PREL64:
*(ul64 *)loc = S + A - P;
*(U64<E> *)loc = S + A - P;
break;
case R_AARCH64_LD64_GOT_LO12_NC:
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
@@ -383,19 +376,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// blr x1
// R_AARCH64_TLSDESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// nop
// nop
// adrp x0, :gottprel:foo
// ldr x0, [x0, :gottprel_lo12:foo]
//
// or to the following for executable.
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// nop
// nop
// movz x0, :tls_offset_hi:foo, lsl #16
// movk x0, :tls_offset_lo:foo
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// nop
// nop
// adrp x0, :gottprel:foo
// ldr x0, [x0, :gottprel_lo12:foo]
if (sym.has_tlsdesc(ctx)) {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
@@ -450,13 +445,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -464,19 +452,21 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
*(U64<E> *)loc = *val;
else
*(ul64 *)loc = S + A;
*(U64<E> *)loc = S + A;
break;
case R_AARCH64_ABS32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
case R_AARCH64_ABS32:
check(S + A, 0, 1LL << 32);
*(U32<E> *)loc = S + A;
break;
}
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
@@ -488,8 +478,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -505,9 +493,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_AARCH64_ABS64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_AARCH64_MOVW_UABS_G3:
scan_absrel(ctx, sym, rel);
break;
@@ -567,6 +552,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
check_tlsle(ctx, sym, rel);
break;
case R_AARCH64_ABS64:
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
case R_AARCH64_CONDBR19:
@@ -603,7 +589,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0xd61f'0200, // br x16
@@ -626,4 +612,6 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
#endif
+54 -71
View File
@@ -35,7 +35,7 @@
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = I386;
@@ -108,25 +108,24 @@ template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0x51, // push %ecx
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 7) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
*(ul32 *)(buf + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0x51, // push %ecx
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, // (padding)
0x51, // push %ecx
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr + 4;
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
}
}
@@ -134,24 +133,22 @@ template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, // (padding)
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, // (padding)
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
}
}
@@ -159,20 +156,18 @@ template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx);
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
}
}
@@ -226,7 +221,7 @@ static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
}
// Relax LD to LE
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32: {
@@ -235,18 +230,17 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
*(ul32 *)(loc + 5) = tls_size;
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
0x90, // nop
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
*(ul32 *)(loc + 6) = tls_size;
break;
}
default:
@@ -286,11 +280,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -299,19 +288,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_386_8:
check(S + A, 0, 1 << 8);
@@ -322,7 +308,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = S + A;
break;
case R_386_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_386_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
@@ -374,7 +359,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_386_TLS_LDM:
if (ctx.got->has_tlsld(ctx))
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
else
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_386_TLS_LDO_32:
@@ -392,16 +377,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// mov foo@GOTTPOFF(%ebx), %eax
// nop
//
// or to the following for executable.
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// mov $foo@TPOFF, %eax
// nop
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// mov foo@GOTTPOFF(%ebx), %eax
// nop
//
// We allow the following alternative code sequence too because
// LLVM emits such code.
//
@@ -410,6 +397,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// mov %reg, %eax
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// Note that the compiler always uses the local-exec TLS model
// for -fno-pic, so TLSDESC code is always PIC (i.e. uses %ebx to
// store the address of GOT.)
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
} else if (sym.has_gottp(ctx)) {
@@ -453,13 +444,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -468,6 +452,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : get_addend(*this, rel);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_386_8:
check(S + A, 0, 1 << 8);
@@ -518,8 +506,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -549,9 +535,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_386_16:
scan_absrel(ctx, sym, rel);
break;
case R_386_32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_386_PC8:
case R_386_PC16:
case R_386_PC32:
@@ -581,8 +564,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_386_TLS_GD:
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static)
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
i++;
else
sym.flags |= NEEDS_TLSGD;
@@ -590,7 +572,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_386_TLS_LDM:
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld = true;
@@ -601,6 +583,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
break;
case R_386_32:
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
@@ -612,4 +595,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
+432 -99
View File
@@ -10,21 +10,22 @@
// bootstrapping the entire ecosystem for LoongArch, sending patches to
// Linux, GCC, LLVM, etc.
//
// All instructions are 4 bytes long in LoongArch and aligned to 4-byte
// boundaries. It has 32 general-purpose registers. Among these, $t0 - $t8
// (aliases for $r12 - $r20) are temporary registers that we can use in
// our PLT and range extension thunks.
// Speaking of the ISA, all instructions are 4 byte long and aligned to 4
// byte boundaries in LoongArch. It has 32 general-purpose registers.
// Among these, $t0 - $t8 (aliases for $r12 - $r20) are temporary
// registers that we can use in our PLT.
//
// The psABI defines a few linker relaxations. We haven't supported them
// yet.
// Just like RISC-V, LoongArch supports section-shrinking relaxations.
// That is, it allows linkers to rewrite certain instruction sequences to
// shorter ones. Sections are not an atomic unit of copying.
//
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
// https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc
#if MOLD_LOONGARCH64 || MOLD_LOONGARCH32
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = MOLD_TARGET;
@@ -50,7 +51,7 @@ static u64 hi20(u64 val, u64 pc) {
return bits(page(val + 0x800) - page(pc), 31, 12);
}
static u64 hi64(u64 val, u64 pc) {
static u64 higher(u64 val, u64 pc) {
// A PC-relative 64-bit address is materialized with the following
// instructions for the large code model:
//
@@ -64,21 +65,18 @@ static u64 hi64(u64 val, u64 pc) {
// ADDI.D adds a sign-extended 12 bit value to a register. LU32I.D and
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
//
// Compensating all the sign-extensions is a bit complicated.
u64 x = page(val) - page(pc);
if (val & 0x800)
x += 0x1000 - 0x1'0000'0000;
if (x & 0x8000'0000)
x += 0x1'0000'0000;
return x;
// Compensating all the sign-extensions is a bit complicated. The
// psABI gave the following formula.
val = val + 0x8000'0000 + ((val & 0x800) ? (0x1000 - 0x1'0000'0000) : 0);
return page(val) - page(pc - 8);
}
static u64 higher20(u64 val, u64 pc) {
return bits(hi64(val, pc), 51, 32);
return bits(higher(val, pc), 51, 32);
}
static u64 highest12(u64 val, u64 pc) {
return bits(hi64(val, pc), 63, 52);
return bits(higher(val, pc), 63, 52);
}
static void write_k12(u8 *loc, u32 val) {
@@ -113,9 +111,49 @@ static void write_d10k16(u8 *loc, u32 val) {
*(ul32 *)loc |= bits(val, 25, 16);
}
static u32 get_rd(u32 insn) {
return bits(insn, 4, 0);
}
static u32 get_rj(u32 insn) {
return bits(insn, 9, 5);
}
static void set_rj(u8 *loc, u32 rj) {
assert(rj < 32);
*(ul32 *)loc &= 0b111111'1111111111111111'00000'11111;
*(ul32 *)loc |= rj << 5;
}
// Returns true if isec's i'th relocation refers to the following
// relaxable instructioon pair.
//
// pcalau12i $t0, 0 # R_LARCH_GOT_PC_HI20, R_LARCH_RELAX
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12, R_LARCH_RELAX
static bool is_relaxable_got_load(Context<E> &ctx, InputSection<E> &isec, i64 i) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
Symbol<E> &sym = *isec.file.symbols[rels[i].r_sym];
u8 *buf = (u8 *)isec.contents.data();
if (ctx.arg.relax &&
sym.is_pcrel_linktime_const(ctx) &&
i + 3 < rels.size() &&
rels[i + 1].r_type == R_LARCH_RELAX &&
rels[i + 2].r_type == R_LARCH_GOT_PC_LO12 &&
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
rels[i + 3].r_type == R_LARCH_RELAX) {
u32 insn1 = *(ul32 *)(buf + rels[i].r_offset);
u32 insn2 = *(ul32 *)(buf + rels[i].r_offset + 4);
bool is_ld_d = (insn2 & 0xffc0'0000) == 0x28c0'0000;
return get_rd(insn1) == get_rd(insn2) && get_rd(insn2) == get_rj(insn2) &&
is_ld_d;
}
return false;
}
template <>
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
static const ul32 insn_64[] = {
constexpr ul32 insn_64[] = {
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
0x0011'bdad, // sub.d $t1, $t1, $t3
0x28c0'01cf, // ld.d $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
@@ -126,7 +164,7 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
0x4c00'01e0, // jr $t3
};
static const ul32 insn_32[] = {
constexpr ul32 insn_32[] = {
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
0x0011'3dad, // sub.w $t1, $t1, $t3
0x2880'01cf, // ld.w $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
@@ -146,18 +184,18 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
write_k12(buf + 16, gotplt);
}
static const ul32 plt_entry_64[] = {
static constexpr ul32 plt_entry_64[] = {
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
0x28c0'01ef, // ld.d $t3, $t3, %lo12([email protected])
0x4c00'01ed, // jirl $t1, $t3, 0
0x0340'0000, // nop
0x002a'0000, // break
};
static const ul32 plt_entry_32[] = {
static constexpr ul32 plt_entry_32[] = {
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
0x2880'01ef, // ld.w $t3, $t3, %lo12([email protected])
0x4c00'01ed, // jirl $t1, $t3, 0
0x0340'0000, // nop
0x002a'0000, // break
};
template <>
@@ -232,11 +270,9 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
std::span<RelocDelta> deltas = extra.r_deltas;
i64 k = 0;
u8 *buf = (u8 *)contents.data();
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
@@ -246,54 +282,60 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
rel.r_type == R_LARCH_ALIGN)
continue;
i64 removed_bytes = 0;
i64 r_delta = 0;
if (!deltas.empty()) {
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
k++;
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
removed_bytes = get_removed_bytes(deltas, k);
if (k > 0)
r_delta = deltas[k - 1].delta;
}
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto check_branch = [&](i64 val, i64 lo, i64 hi) {
if (val & 0b11)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
check(val, lo, hi);
};
i64 r_offset = rel.r_offset - r_delta;
u8 *loc = base + r_offset;
// Unlike other psABIs, the LoongArch ABI uses the same relocation
// types to refer to GOT entries for thread-local symbols and regular
// ones. Therefore, G may refer to a TLSGD or a regular GOT slot
// depending on the symbol type.
//
// Note that as of August 2023, both GCC and Clang treat TLSLD relocs
// as if they were TLSGD relocs for LoongArch, which is a clear bug.
// We need to handle TLSLD relocs as synonyms for TLSGD relocs for the
// sake of bug compatibility.
auto get_got_idx = [&] {
if (sym.has_tlsgd(ctx))
return sym.get_tlsgd_idx(ctx);
return sym.get_got_idx(ctx);
};
// Note that even though LoongArch defines relocations for TLSLD, TLSLD
// is not actually supported on it. GCC and LLVM emit identical machine
// code for -ftls-model=global-dynamic and -ftls-model=local-dynamic,
// and we need to handle TLSLD relocations as equivalent to TLSGD
// relocations. This is clearly a compiler bug, but it's too late to
// fix. The only way to fix it would be to define a new set of
// relocations for true TLSLD and deprecate the current ones. But it
// appears that migrating to TLSDESC is a better choice, so it's
// unlikely to happen.
i64 got_idx =
sym.has_tlsgd(ctx) ? sym.get_tlsgd_idx(ctx) : sym.get_got_idx(ctx);
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = get_got_idx() * sizeof(Word<E>);
u64 P = get_addr() + r_offset;
u64 G = got_idx * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto check_branch = [&](i64 val, i64 lo, i64 hi) {
check(val, lo, hi);
if (val & 0b11)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
};
switch (rel.r_type) {
case R_LARCH_32:
if constexpr (E::is_64)
*(ul32 *)loc = S + A;
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_LARCH_64:
assert(E::is_64);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
*(ul32 *)loc = S + A;
break;
case R_LARCH_B16:
check_branch(S + A - P, -(1 << 17), 1 << 17);
@@ -303,13 +345,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check_branch(S + A - P, -(1 << 22), 1 << 22);
write_d5k16(loc, (S + A - P) >> 2);
break;
case R_LARCH_B26: {
i64 val = S + A - P;
if (val < -(1 << 27) || (1 << 27) <= val)
val = get_thunk_addr(i) + A - P;
write_d10k16(loc, val >> 2);
case R_LARCH_B26:
check_branch(S + A - P, -(1 << 27), 1 << 27);
write_d10k16(loc, (S + A - P) >> 2);
break;
}
case R_LARCH_ABS_LO12:
write_k12(loc, S + A);
break;
@@ -328,12 +367,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// It is contrary to the psABI document, but GNU ld has special
// code to handle it, so we accept it too.
if ((*(ul32 *)loc & 0xfc00'0000) == 0x4c00'0000)
write_k16(loc, sign_extend(S + A, 11) >> 2);
write_k16(loc, sign_extend(S + A, 12) >> 2);
else
write_k12(loc, S + A);
break;
case R_LARCH_PCALA_HI20:
write_j20(loc, hi20(S + A, P));
if (removed_bytes == 0) {
write_j20(loc, hi20(S + A, P));
} else {
// Rewrite pcalau12i + addi.d with pcaddi
assert(removed_bytes == 4);
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, (S + A - P) >> 2);
i += 3;
}
break;
case R_LARCH_PCALA64_LO20:
write_j20(loc, higher20(S + A, P));
@@ -345,7 +392,37 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_k12(loc, GOT + G + A);
break;
case R_LARCH_GOT_PC_HI20:
write_j20(loc, hi20(GOT + G + A, P));
if (removed_bytes == 0) {
// If the PC-relative symbol address is known at link-time, we can
// rewrite the following GOT load
//
// pcalau12i $t0, 0 # R_LARCH_GOT_PC_HI20
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12
//
// with the following address materialization
//
// pcalau12i $t0, 0
// addi.d $t0, $t0, 0
if (is_relaxable_got_load(ctx, *this, i)) {
i64 dist = compute_distance(ctx, sym, *this, rel);
if (is_int(dist, 32)) {
u32 rd = get_rd(*(ul32 *)loc);
*(ul32 *)(loc + 4) = 0x02c0'0000 | (rd << 5) | rd; // addi.d
write_j20(loc, hi20(S + A, P));
write_k12(loc + 4, S + A);
i += 3;
break;
}
}
write_j20(loc, hi20(GOT + G + A, P));
} else {
// Rewrite pcalau12i + ld.d with pcaddi
assert(removed_bytes == 4);
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, (S + A - P) >> 2);
i += 3;
}
break;
case R_LARCH_GOT64_PC_LO20:
write_j20(loc, higher20(GOT + G + A, P));
@@ -401,13 +478,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_LARCH_TLS_IE64_HI12:
write_k12(loc, (sym.get_gottp_addr(ctx) + A) >> 52);
break;
case R_LARCH_TLS_LD_PC_HI20:
case R_LARCH_TLS_GD_PC_HI20:
case R_LARCH_TLS_LD_PC_HI20:
check(sym.get_tlsgd_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
write_j20(loc, hi20(sym.get_tlsgd_addr(ctx) + A, P));
break;
case R_LARCH_TLS_LD_HI20:
case R_LARCH_TLS_GD_HI20:
case R_LARCH_TLS_LD_HI20:
write_j20(loc, (sym.get_tlsgd_addr(ctx) + A) >> 12);
break;
case R_LARCH_ADD6:
@@ -446,12 +523,131 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_LARCH_64_PCREL:
*(ul64 *)loc = S + A - P;
break;
case R_LARCH_CALL36:
if (removed_bytes == 0) {
i64 val = S + A - P;
check_branch(val, -(1LL << 37) - 0x20000, (1LL << 37) - 0x20000);
write_j20(loc, (val + 0x20000) >> 18);
write_k16(loc + 4, val >> 2);
} else {
// Rewrite PCADDU18I + JIRL to B or BL
assert(removed_bytes == 4);
if (get_rd(*(ul32 *)(buf + rel.r_offset + 4)) == 0)
*(ul32 *)loc = 0x5000'0000; // B
else
*(ul32 *)loc = 0x5400'0000; // BL
write_d10k16(loc, (S + A - P) >> 2);
}
break;
case R_LARCH_ADD_ULEB128:
overwrite_uleb(loc, read_uleb(loc) + S + A);
break;
case R_LARCH_SUB_ULEB128:
overwrite_uleb(loc, read_uleb(loc) - S - A);
break;
case R_LARCH_TLS_DESC_PC_HI20:
// LoongArch TLSDESC uses the following code sequence to materialize
// a TP-relative address in a0.
//
// pcalau12i $a0, 0
// R_LARCH_TLS_DESC_PC_HI20 foo
// addi.[dw] $a0, $a0, 0
// R_LARCH_TLS_DESC_PC_LO12 foo
// ld.d $ra, $a0, 0
// R_LARCH_TLS_DESC_LD foo
// jirl $ra, $ra, 0
// R_LARCH_TLS_DESC_CALL foo
//
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// <deleted>
// <deleted>
// lu12i.w $a0, foo@TPOFF
// addi.w $a0, $a0, foo@TPOFF
//
// or to the following if the TP offset is small enough.
//
// <deleted>
// <deleted>
// <deleted>
// ori $a0, $zero, foo@TPOFF
//
// If the TP-relative address is known at process startup time, we
// may relax the instructions to the following.
//
// <deleted>
// <deleted>
// pcalau12i $a0, foo@GOTTP
// ld.[dw] $a0, $a0, foo@GOTTP
//
// If we don't know anything about the symbol, we can still relax
// the first two instructions to a single pcaddi as shown below.
//
// <deleted>
// pcaddi $a0, foo@GOTDESC
// ld.d $ra, $a0, 0
// jirl $ra, $ra, 0
if (sym.has_tlsdesc(ctx) && removed_bytes == 0)
write_j20(loc, hi20(sym.get_tlsdesc_addr(ctx) + A, P));
break;
case R_LARCH_TLS_DESC_PC_LO12:
if (sym.has_tlsdesc(ctx) && removed_bytes == 0) {
i64 dist = sym.get_tlsdesc_addr(ctx) + A - P;
if (is_int(dist, 22)) {
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, dist >> 2);
} else {
write_k12(loc, sym.get_tlsdesc_addr(ctx) + A);
}
}
break;
case R_LARCH_TLS_DESC_LD:
if (sym.has_tlsdesc(ctx) || removed_bytes == 4) {
// Do nothing
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0x1a00'0004; // pcalau12i $a0, 0
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
} else {
*(ul32 *)loc = 0x1400'0004; // lu12i.w $a0, 0
write_j20(loc, (S + A + 0x800 - ctx.tp_addr) >> 12);
}
break;
case R_LARCH_TLS_DESC_CALL:
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else if (sym.has_gottp(ctx)) {
if (E::is_64)
*(ul32 *)loc = 0x28c0'0084; // ld.d $a0, $a0, 0
else
*(ul32 *)loc = 0x2880'0084; // ld.w $a0, $a0, 0
write_k12(loc, sym.get_gottp_addr(ctx) + A);
} else {
i64 val = S + A - ctx.tp_addr;
if (0 <= val && val < 0x1000)
*(ul32 *)loc = 0x0380'0004; // ori $a0, $zero, 0
else
*(ul32 *)loc = 0x0280'0084; // addi.w $a0, $a0, 0
write_k12(loc, val);
}
break;
case R_LARCH_TLS_LE_HI20_R:
if (removed_bytes == 0)
write_j20(loc, (S + A + 0x800 - ctx.tp_addr) >> 12);
break;
case R_LARCH_TLS_LE_LO12_R: {
i64 val = S + A - ctx.tp_addr;
write_k12(loc, val);
// Rewrite `addi.d $t0, $t0, <offset>` with `addi.d $t0, $tp, <offset>`
// if the offset is directly accessible using tp. tp is r2.
if (is_int(val, 12))
set_rj(loc, 2);
break;
}
case R_LARCH_64:
case R_LARCH_TLS_LE_ADD_R:
break;
default:
unreachable();
}
@@ -551,8 +747,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -576,15 +770,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_LARCH_32:
if constexpr (E::is_64)
scan_absrel(ctx, sym, rel);
else
scan_dyn_absrel(ctx, sym, rel);
break;
case R_LARCH_64:
assert(E::is_64);
scan_dyn_absrel(ctx, sym, rel);
break;
case R_LARCH_B26:
case R_LARCH_PCALA_HI20:
case R_LARCH_CALL36:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
@@ -596,10 +785,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_LARCH_TLS_IE_PC_HI20:
sym.flags |= NEEDS_GOTTP;
break;
case R_LARCH_TLS_LD_PC_HI20:
case R_LARCH_TLS_GD_PC_HI20:
case R_LARCH_TLS_LD_HI20:
case R_LARCH_TLS_LD_PC_HI20:
case R_LARCH_TLS_GD_HI20:
case R_LARCH_TLS_LD_HI20:
sym.flags |= NEEDS_TLSGD;
break;
case R_LARCH_32_PCREL:
@@ -610,8 +799,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_LARCH_TLS_LE_LO12:
case R_LARCH_TLS_LE64_LO20:
case R_LARCH_TLS_LE64_HI12:
case R_LARCH_TLS_LE_HI20_R:
case R_LARCH_TLS_LE_LO12_R:
check_tlsle(ctx, sym, rel);
break;
case R_LARCH_TLS_DESC_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_LARCH_64:
case R_LARCH_B16:
case R_LARCH_B21:
case R_LARCH_ABS_HI20:
@@ -645,6 +840,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_LARCH_SUB64:
case R_LARCH_ADD_ULEB128:
case R_LARCH_SUB_ULEB128:
case R_LARCH_TLS_DESC_PC_HI20:
case R_LARCH_TLS_DESC_PC_LO12:
case R_LARCH_TLS_DESC_LD:
case R_LARCH_TLS_LE_ADD_R:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -653,29 +852,163 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
static const ul32 insn[] = {
0x1e00'000c, // pcaddu18i $t0, 0
0x4c00'0180, // jirl $zero, $t0, 0
};
void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<RelocDelta> &deltas = isec.extra.r_deltas;
i64 r_delta = 0;
u8 *buf = (u8 *)isec.contents.data();
static_assert(E::thunk_size == sizeof(insn));
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &r = rels[i];
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
auto remove = [&](i64 d) {
r_delta += d;
deltas.push_back(RelocDelta{r.r_offset, r_delta});
};
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
// A R_LARCH_ALIGN relocation refers to the beginning of a nop
// sequence. We need to remove some or all of them so that the
// instruction that immediately follows that is aligned to a specified
// boundary. To allow that, a R_LARCH_ALIGN relocation that requests
// 2^n alignment refers to 2^n - 4 bytes of nop instructions.
if (r.r_type == R_LARCH_ALIGN) {
// The actual rule for storing the alignment size is a bit weird.
// In particular, the most significant 56 bits of r_addend is
// sometimes used to store the upper limit of the alignment,
// allowing the instruction that follows nops _not_ to be aligned at
// all. I think that's a spec bug, so we don't want to support that.
i64 alignment;
if (r.r_sym) {
if (r.r_addend >> 8)
Fatal(ctx) << isec << ": ternary R_LARCH_ALIGN is not supported: " << i;
alignment = 1 << r.r_addend;
} else {
if (!has_single_bit(r.r_addend + 4))
Fatal(ctx) << isec << ": R_LARCH_ALIGN: invalid alignment requirement: "
<< i;
alignment = r.r_addend + 4;
}
memcpy(buf, insn, sizeof(insn));
write_j20(buf, (S - P + 0x20000) >> 18);
write_k16(buf + 4, (S - P) >> 2);
u64 P = isec.get_addr() + r.r_offset - r_delta;
u64 desired = align_to(P, alignment);
u64 actual = P + alignment - 4;
if (desired != actual)
remove(actual - desired);
continue;
}
buf += sizeof(insn);
P += sizeof(insn);
// Handling other relocations is optional.
if (!ctx.arg.relax || i == rels.size() - 1 ||
rels[i + 1].r_type != R_LARCH_RELAX)
continue;
// Skip linker-synthesized symbols because their final addresses
// are not fixed yet.
if (sym.file == ctx.internal_obj)
continue;
switch (r.r_type) {
case R_LARCH_TLS_LE_HI20_R:
case R_LARCH_TLS_LE_ADD_R:
// LoongArch uses the following three instructions to access
// TP ± 2 GiB.
//
// lu12i.w $t0, 0 # R_LARCH_TLS_LE_HI20_R
// add.d $t0, $t0, $tp # R_LARCH_TLS_LE_ADD_R
// addi.d $t0, $t0, 0 # R_LARCH_TLS_LE_LO12_R
//
// If the thread-local variable is within TP ± 2 KiB, we can
// relax them into the following single instruction.
//
// addi.d $t0, $tp, <tp-offset>
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
is_int(val, 12))
remove(4);
break;
case R_LARCH_PCALA_HI20:
// The following two instructions are used to materialize a
// PC-relative address with a 32 bit displacement.
//
// pcalau12i $t0, 0 # R_LARCH_PCALA_HI20
// addi.d $t0, $t0, 0 # R_LARCH_PCALA_LO12
//
// If the displacement is within ±2 MiB, we can relax them to
// the following instruction.
//
// pcaddi $t0, <offset>
if (i + 3 < rels.size() &&
rels[i + 2].r_type == R_LARCH_PCALA_LO12 &&
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
rels[i + 3].r_type == R_LARCH_RELAX) {
i64 dist = compute_distance(ctx, sym, isec, r);
u32 insn1 = *(ul32 *)(buf + rels[i].r_offset);
u32 insn2 = *(ul32 *)(buf + rels[i].r_offset + 4);
bool is_addi_d = (insn2 & 0xffc0'0000) == 0x02c0'0000;
if ((dist & 0b11) == 0 && is_int(dist, 22) &&
is_addi_d && get_rd(insn1) == get_rd(insn2) &&
get_rd(insn2) == get_rj(insn2))
remove(4);
}
break;
case R_LARCH_CALL36:
// A CALL36 relocation referes to the following instruction pair
// to jump to PC ± 128 GiB.
//
// pcaddu18i $t0, 0 # R_LARCH_CALL36
// jirl $zero/$ra, $t0, 0
//
// If the displacement is PC ± 128 MiB, we can use B or BL instead.
// Note that $zero is $r0 and $ra is $r1.
if (i64 dist = compute_distance(ctx, sym, isec, r);
is_int(dist, 28))
if (u32 jirl = *(ul32 *)(buf + rels[i].r_offset + 4);
get_rd(jirl) == 0 || get_rd(jirl) == 1)
remove(4);
break;
case R_LARCH_GOT_PC_HI20:
// The following two instructions are used to load a symbol address
// from the GOT.
//
// pcalau12i $t0, 0 # R_LARCH_GOT_PC_HI20
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12
//
// If the PC-relative symbol address is known at link-time, we can
// relax them to the following instruction.
//
// pcaddi $t0, <offset>
if (is_relaxable_got_load(ctx, isec, i))
if (i64 dist = compute_distance(ctx, sym, isec, r);
is_int(dist, 22))
remove(4);
break;
case R_LARCH_TLS_DESC_PC_HI20:
if (sym.has_tlsdesc(ctx)) {
u64 P = isec.get_addr() + r.r_offset;
i64 dist = sym.get_tlsdesc_addr(ctx) + r.r_addend - P;
if (is_int(dist, 22))
remove(4);
} else {
remove(4);
}
break;
case R_LARCH_TLS_DESC_PC_LO12:
if (!sym.has_tlsdesc(ctx))
remove(4);
break;
case R_LARCH_TLS_DESC_LD:
if (!sym.has_tlsdesc(ctx) && !sym.has_gottp(ctx))
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
0 <= val && val < 0x1000)
remove(4);
break;
}
}
isec.sh_size -= r_delta;
}
} // namespace mold::elf
} // namespace mold
#endif
+15 -28
View File
@@ -1,10 +1,10 @@
// This file contains code for the Motorola 68000 series microprocessors,
// which is often abbreviated as m68k. Running a Unix-like system on a
// m68k-based machine today is probably a retro-computing hobby activity,
// but the processor was a popular choice to build Unix computers during
// '80s. Early Sun workstations for example used m68k. Macintosh until
// 1994 were based on m68k as well until they switched to PowerPC (and
// then to x86 and to ARM.)
// m68k-based machine today is a retro-computing hobby activity, but the
// processor was a popular choice to build Unix computers during '80s.
// Early Sun workstations for example used m68k. Macintosh until 1994 were
// based on m68k as well until they switched to PowerPC (and then to x86
// and to ARM.)
//
// From the linker's point of view, it is not hard to support m68k. It's
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
@@ -16,7 +16,7 @@
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = M68K;
@@ -78,11 +78,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -91,11 +86,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
check_range(ctx, i, val, lo, hi);
};
auto write16 = [&](u64 val) {
@@ -118,15 +116,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*loc = val;
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_68K_32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_68K_16:
write16(S + A);
@@ -251,8 +242,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
@@ -266,9 +255,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
switch (rel.r_type) {
case R_68K_32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_68K_16:
case R_68K_8:
scan_absrel(ctx, sym, rel);
@@ -312,6 +298,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_68K_TLS_LE8:
check_tlsle(ctx, sym, rel);
break;
case R_68K_32:
case R_68K_TLS_LDO32:
case R_68K_TLS_LDO16:
case R_68K_TLS_LDO8:
@@ -322,4 +309,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
+13 -24
View File
@@ -42,7 +42,7 @@
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = PPC32;
@@ -54,7 +54,7 @@ static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ub32 insn[] = {
constexpr ub32 insn[] = {
// Get the address of this PLT section
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
@@ -88,7 +88,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
loc[5] |= lo(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
}
static const ub32 plt_entry[] = {
static constexpr ub32 plt_entry[] = {
// Get the address of this PLT entry
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
@@ -148,11 +148,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
for (i64 i = 0; i < rels.size(); i++) {
@@ -170,10 +165,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_PPC_ADDR32:
case R_PPC_UADDR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_PPC_ADDR14:
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
break;
@@ -222,15 +213,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC_REL24:
case R_PPC_LOCAL24PC: {
i64 val = S + A - P;
if (sign_extend(val, 25) != val)
val = get_thunk_addr(i) - P;
if (!is_int(val, 26))
val = sym.get_thunk_addr(ctx, P) - P;
*(ub32 *)loc |= bits(val, 25, 2) << 2;
break;
}
case R_PPC_PLTREL24: {
i64 val = S - P;
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) - P;
if (sym.has_plt(ctx) || !is_int(val, 26))
val = sym.get_thunk_addr(ctx, P) - P;
*(ub32 *)loc |= bits(val, 25, 2) << 2;
break;
}
@@ -275,6 +266,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC_GOT_TPREL16:
*(ub16 *)loc = sym.get_gottp_addr(ctx) - GOT;
break;
case R_PPC_ADDR32:
case R_PPC_UADDR32:
case R_PPC_TLS:
case R_PPC_TLSGD:
case R_PPC_TLSLD:
@@ -323,8 +316,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -339,10 +330,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_PPC_ADDR32:
case R_PPC_UADDR32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PPC_ADDR14:
case R_PPC_ADDR16:
case R_PPC_UADDR16:
@@ -391,6 +378,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC_TPREL16_HA:
check_tlsle(ctx, sym, rel);
break;
case R_PPC_ADDR32:
case R_PPC_UADDR32:
case R_PPC_LOCAL24PC:
case R_PPC_TLS:
case R_PPC_TLSGD:
@@ -409,7 +398,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
static const ub32 local_thunk[] = {
constexpr ub32 local_thunk[] = {
// Get this thunk's address
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
@@ -450,4 +439,4 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
+29 -49
View File
@@ -50,7 +50,7 @@
#include <algorithm>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
namespace mold {
using E = PPC64V1;
@@ -68,7 +68,7 @@ static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ub32 insn[] = {
constexpr ub32 insn[] = {
0x7d88'02a6, // mflr r12
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
@@ -101,7 +101,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
// call to the PLT entry jumps to. So we need to strictly follow the PLT
// section layout as the loader expect it to be.
if (idx < 0x8000) {
static const ub32 insn[] = {
constexpr ub32 insn[] = {
0x3800'0000, // li r0, PLT_INDEX
0x4b00'0000, // b plt0
};
@@ -110,7 +110,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
loc[0] |= idx;
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
} else {
static const ub32 insn[] = {
constexpr ub32 insn[] = {
0x3c00'0000, // lis r0, PLT_INDEX@high
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
0x4b00'0000, // b plt0
@@ -154,11 +154,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -167,13 +162,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
@@ -181,12 +169,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
u64 TOC = ctx.extra.TOC->value;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_ADDR64:
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_PPC64_TOC:
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, &dynrel);
break;
case R_PPC64_TOC16_HA:
*(ub16 *)loc = ha(S + A - TOC);
@@ -203,8 +191,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_PPC64_REL24: {
i64 val = sym.get_addr(ctx, NO_OPD) + A - P;
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) + A - P;
if (sym.has_plt(ctx) || !is_int(val, 26))
val = sym.get_thunk_addr(ctx, P) + A - P;
check(val, -(1 << 25), 1 << 25);
*(ub32 *)loc |= bits(val, 25, 2) << 2;
@@ -277,6 +265,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_GOT_TPREL16_LO_DS:
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_ADDR64:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TLS:
@@ -301,13 +290,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -315,6 +297,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
@@ -322,12 +308,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ub64 *)loc = S + A;
break;
case R_PPC64_ADDR32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_PPC64_ADDR32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_PPC64_DTPREL64:
*(ub64 *)loc = S + A - ctx.dtp_addr;
break;
@@ -341,8 +325,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -362,10 +344,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_PPC_OPD;
switch (rel.r_type) {
case R_PPC64_ADDR64:
case R_PPC64_TOC:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
sym.flags |= NEEDS_GOTTP;
break;
@@ -387,6 +365,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_TPREL16_LO_DS:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_ADDR64:
case R_PPC64_TOC:
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
@@ -421,7 +401,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is .plt.got, we save the current r2, read an
// address of a function descriptor from .got, restore %r2 and jump
// to the function.
static const ub32 pltgot_thunk[] = {
constexpr ub32 pltgot_thunk[] = {
// Store the caller's %r2
0xf841'0028, // std %r2, 40(%r1)
@@ -439,7 +419,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
};
// If the destination is .plt, read a function descriptor from .got.plt.
static const ub32 plt_thunk[] = {
constexpr ub32 plt_thunk[] = {
// Store the caller's %r2
0xf841'0028, // std %r2, 40(%r1)
@@ -458,7 +438,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is a non-imported function, we directly jump
// to the function entry address.
static const ub32 local_thunk[] = {
constexpr ub32 local_thunk[] = {
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
@@ -519,16 +499,16 @@ get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
return &*it;
}
namespace {
struct OpdSymbol {
bool operator<(const OpdSymbol &x) const { return r_offset < x.r_offset; }
u64 r_offset = 0;
Symbol<E> *sym = nullptr;
};
}
static Symbol<E> *
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
if (it == syms.end())
return nullptr;
if (it->r_offset != offset)
@@ -601,7 +581,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
Symbol<E> *sym2 = file->symbols[rel->r_sym];
if (sym2->get_type() != STT_SECTION)
Fatal(ctx) << *file << ": bad relocation in .opd referring " << *sym2;
Fatal(ctx) << *file << ": bad relocation in .opd referring to " << *sym2;
opd_syms.push_back({sym->value, sym});
@@ -610,7 +590,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
}
// Sort symbols so that get_opd_sym_at() can do binary search.
sort(opd_syms);
ranges::stable_sort(opd_syms, {}, &OpdSymbol::r_offset);
// Rewrite relocations so that they directly refer to .opd.
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
@@ -689,4 +669,4 @@ void PPC64OpdSection::copy_buf(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
+25 -45
View File
@@ -82,7 +82,7 @@
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = PPC64V2;
@@ -106,7 +106,7 @@ static void write34(u8 *loc, u64 x) {
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
constexpr ul32 insn[] = {
// Get PC
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4 // obtain PC
@@ -172,7 +172,7 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
i64 val = sym.esym().ppc_local_entry;
i64 val = sym.esym().ppc64_local_entry;
assert(val <= 7);
if (val == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
@@ -186,11 +186,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -206,16 +201,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
u64 TOC = ctx.extra.TOC->value;
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
auto r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
auto no_r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 8; };
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (name() == ".toc")
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_PPC64_TOC16_HA:
*(ul16 *)loc = ha(S + A - TOC);
break;
@@ -227,7 +216,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_REL24:
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
if (sym.has_plt(ctx) || !sym.esym().ppc64_preserves_r2()) {
i64 val = r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
@@ -238,18 +227,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
} else {
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
if (sign_extend(val, 25) != val)
if (!is_int(val, 26))
val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
break;
case R_PPC64_REL24_NOTOC:
if (sym.has_plt(ctx) || sym.esym().uses_toc()) {
if (sym.has_plt(ctx) || sym.esym().ppc64_uses_toc()) {
i64 val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
} else {
i64 val = S + A - P;
if (sign_extend(val, 25) != val)
if (!is_int(val, 26))
val = no_r2save_thunk_addr() + A - P;
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
@@ -337,6 +326,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_TPREL34:
write34(loc, S + A - ctx.tp_addr);
break;
case R_PPC64_ADDR64:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTSEQ_NOTOC:
case R_PPC64_PLTCALL:
@@ -344,6 +334,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
case R_PPC64_ENTRY:
break;
default:
unreachable();
@@ -363,13 +354,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -377,6 +361,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
@@ -384,12 +372,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ul64 *)loc = S + A;
break;
case R_PPC64_ADDR32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
case R_PPC64_ADDR32:
check(S + A, 0, 1LL << 32);
*(ul32 *)loc = S + A;
break;
}
case R_PPC64_DTPREL64:
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
@@ -403,8 +389,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -419,12 +403,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_PPC64_ADDR64:
if (name() == ".toc")
scan_toc_rel(ctx, sym, rel);
else
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
case R_PPC64_GOT_TPREL_PCREL34:
sym.flags |= NEEDS_GOTTP;
@@ -458,6 +436,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_TPREL34:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_ADDR64:
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
@@ -484,6 +463,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_DTPREL16_LO:
case R_PPC64_DTPREL16_LO_DS:
case R_PPC64_DTPREL34:
case R_PPC64_ENTRY:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -495,7 +475,7 @@ template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is PLT, we read an address from .got.plt or .got
// and jump there.
static const ul32 plt_thunk[] = {
constexpr ul32 plt_thunk[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
@@ -504,7 +484,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
0x4e80'0420, // bctr
};
static const ul32 plt_thunk_power10[] = {
constexpr ul32 plt_thunk_power10[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x0410'0000, // pld r12, foo@gotplt@pcrel
@@ -515,7 +495,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
// If the destination is a non-imported function, we directly jump
// to its local entry point.
static const ul32 local_thunk[] = {
constexpr ul32 local_thunk[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x3d82'0000, // addis r12, r2, foo@toc@ha
@@ -524,7 +504,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
0x4e80'0420, // bctr
};
static const ul32 local_thunk_power10[] = {
constexpr ul32 local_thunk_power10[] = {
0xf841'0018, // std r2, 24(r1)
0x6000'0000, // nop
0x0610'0000, // pla r12, foo@pcrel
@@ -677,4 +657,4 @@ u64 get_eflags(Context<E> &ctx) {
return 2;
}
} // namespace mold::elf
} // namespace mold
+216 -422
View File
@@ -12,71 +12,18 @@
// From the linker's point of view, the RISC-V's psABI is unique because
// sections in input object files can be shrunk while being copied to the
// output file. That is contrary to other psABIs in which sections are an
// atomic unit of copying. Let me explain it in more details.
//
// Since RISC-V instructions are 16-bit or 32-bit long, there's no way to
// embed a very large immediate into a branch instruction. In fact, JAL
// (jump and link) instruction can jump to only within PC ± 1 MiB because
// its immediate is only 21 bits long. If the destination is out of its
// reach, we need to use two instructions instead; the first instruction
// being AUIPC which sets upper 20 bits to a register and the second being
// JALR with a 12-bit immediate and the register. Combined, they specify a
// 32 bits displacement.
//
// Other RISC ISAs have the same limitation, and they solved the problem by
// letting the linker create so-called "range extension thunks". It works as
// follows: the compiler optimistically emits single jump instructions for
// function calls. If the linker finds that a branch target is out of reach,
// it emits a small piece of machine code near the branch instruction and
// redirect the branch to the linker-synthesized code. The code constructs a
// full 32-bit address in a register and jump to the destination. That
// linker-synthesized code is called "range extension thunks" or just
// "thunks".
//
// The RISC-V psABI is unique that it works the other way around. That is,
// for RISC-V, the compiler always emits two instructions (AUIPC + JAL) for
// function calls. If the linker finds the destination is reachable with a
// single instruction, it replaces the two instructions with the one and
// shrink the section size by one instruction length, instead of filling the
// gap with a nop.
//
// With the presence of this relaxation, sections can no longer be
// considered as an atomic unit. If we delete 4 bytes from the middle of a
// section, all contents after that point needs to be shifted by 4. Symbol
// values and relocation offsets have to be adjusted accordingly if they
// refer to past the deleted bytes.
//
// In mold, we use `r_deltas` to memorize how many bytes have be adjusted
// for relocations. For symbols, we directly mutate their `value` member.
//
// RISC-V object files tend to have way more relocations than those for
// other targets. This is because all branches, including ones that jump
// within the same section, are explicitly expressed with relocations.
// Here is why we need them: all control-flow statements such as `if` or
// `for` are implemented using branch instructions. For other targets, the
// compiler doesn't emit relocations for such branches because they know
// at compile-time exactly how many bytes has to be skipped. That's not
// true to RISC-V because the linker may delete bytes between a branch and
// its destination. Therefore, all branches including in-section ones have
// to be explicitly expressed with relocations.
//
// Note that this mechanism only shrink sections and never enlarge, as
// the compiler always emits the longest instruction sequence. This
// makes the linker implementation a bit simpler because we don't need
// to worry about oscillation.
// atomic unit of copying. See file comments in shrink-sections.cc for
// details.
//
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
#if MOLD_RV64LE || MOLD_RV64BE || MOLD_RV32LE || MOLD_RV32BE
#include "elf.h"
#include "mold.h"
#include <regex>
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
namespace mold {
using E = MOLD_TARGET;
@@ -139,9 +86,13 @@ static void set_rs1(u8 *loc, u32 rs1) {
*(ul32 *)loc |= rs1 << 15;
}
static u32 get_rd(u8 *loc) {
return bits(*(u32 *)loc, 11, 7);
};
template <>
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
static const ul32 insn_64[] = {
constexpr ul32 insn_64[] = {
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
0x0003'be03, // ld t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
@@ -152,7 +103,7 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
0x000e'0067, // jr t3
};
static const ul32 insn_32[] = {
constexpr ul32 insn_32[] = {
0x0000'0397, // auipc t2, %pcrel_hi(.got.plt)
0x41c3'0333, // sub t1, t1, t3 # .plt entry + hdr + 12
0x0003'ae03, // lw t3, %pcrel_lo(1b)(t2) # _dl_runtime_resolve
@@ -172,14 +123,14 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
write_itype(buf + 16, gotplt - plt);
}
static const ul32 plt_entry_64[] = {
static constexpr ul32 plt_entry_64[] = {
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
0x000e'3e03, // ld t3, %pcrel_lo(1b)(t3)
0x000e'0367, // jalr t1, t3
0x0010'0073, // ebreak
};
static const ul32 plt_entry_32[] = {
static constexpr ul32 plt_entry_32[] = {
0x0000'0e17, // auipc t3, %pcrel_hi([email protected])
0x000e'2e03, // lw t3, %pcrel_lo(1b)(t3)
0x000e'0367, // jalr t1, t3
@@ -249,79 +200,113 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
}
static inline bool is_hi20(const ElfRel<E> &rel) {
u32 ty = rel.r_type;
return ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20 ||
ty == R_RISCV_TLSDESC_HI20;
// RISC-V generally uses the AUIPC + ADDI/LW/SW/etc instruction pair
// to access the AUIPC's address ± 2 GiB. AUIPC materializes the most
// significant 52 bits in a PC-relative manner, and the following
// instruction specifies the remaining least significant 12 bits.
// There are several HI20 and LO12 relocation types for them.
//
// LO12 relocations need to materialize an address relative to AUIPC's
// address, not relative to the instruction that the relocation
// directly refers to.
//
// The problem here is that the instruction pair may not always be
// adjacent. We need a mechanism to find a paired AUIPC for a given
// LO12 relocation. For this purpose, the compiler creates a local
// symbol for each location to which HI20 refers, and the LO12
// relocation refers to that symbol.
//
// This function returns a paired HI20 relocation for a given LO12.
// Since the instructions are typically adjacent, we do a linear
// search.
static const ElfRel<E> &
find_paired_reloc(Context<E> &ctx, InputSection<E> &isec,
std::span<const ElfRel<E>> rels,
Symbol<E> &sym, i64 i) {
auto is_hi20 = [](u32 ty) {
return ty == R_RISCV_GOT_HI20 || ty == R_RISCV_TLS_GOT_HI20 ||
ty == R_RISCV_TLS_GD_HI20 || ty == R_RISCV_PCREL_HI20 ||
ty == R_RISCV_TLSDESC_HI20;
};
u64 value = sym.esym().st_value;
if (value <= rels[i].r_offset) {
for (i64 j = i - 1; j >= 0; j--)
if (is_hi20(rels[j].r_type) && value == rels[j].r_offset)
return rels[j];
} else {
for (i64 j = i + 1; j < rels.size(); j++)
if (is_hi20(rels[j].r_type) && value == rels[j].r_offset)
return rels[j];
}
Fatal(ctx) << isec << ": paired relocation is missing: " << i;
}
// Returns true if isec's i'th relocation refers to the following
// GOT-load instructioon pair, which is an expeanded form of
// `la t0, foo` pseudo assembly instruction.
//
// .L0
// auipc t0, 0 # R_RISCV_GOT_HI20(foo), R_RISCV_RELAX
// ld t0, 0(t0) # R_RISCV_PCREL_LO12_I(.L0), R_RISCV_RELAX
static bool is_got_load_pair(Context<E> &ctx, InputSection<E> &isec,
std::span<const ElfRel<E>> rels, i64 i) {
u8 *buf = (u8 *)isec.contents.data();
return i + 3 < rels.size() &&
rels[i].r_type == R_RISCV_GOT_HI20 &&
rels[i + 1].r_type == R_RISCV_RELAX &&
rels[i + 2].r_type == R_RISCV_PCREL_LO12_I &&
rels[i + 3].r_type == R_RISCV_RELAX &&
rels[i].r_offset == rels[i + 2].r_offset - 4 &&
rels[i].r_offset == isec.file.symbols[rels[i + 2].r_sym]->value &&
get_rd(buf + rels[i].r_offset) == get_rd(buf + rels[i + 2].r_offset);
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
u64 GP = ctx.__global_pointer ? ctx.__global_pointer->get_addr(ctx) : 0;
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
auto get_r_delta = [&](i64 idx) {
return extra.r_deltas.empty() ? 0 : extra.r_deltas[idx];
};
std::span<RelocDelta> deltas = extra.r_deltas;
i64 k = 0;
u8 *buf = (u8 *)contents.data();
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_RISCV_RELAX)
continue;
i64 removed_bytes = 0;
i64 r_delta = 0;
if (!deltas.empty()) {
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
k++;
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
removed_bytes = get_removed_bytes(deltas, k);
if (k > 0)
r_delta = deltas[k - 1].delta;
}
Symbol<E> &sym = *file.symbols[rel.r_sym];
i64 r_offset = rel.r_offset - get_r_delta(i);
i64 removed_bytes = get_r_delta(i + 1) - get_r_delta(i);
i64 r_offset = rel.r_offset - r_delta;
u8 *loc = base + r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto find_paired_reloc = [&] {
if (sym.value <= rels[i].r_offset - get_r_delta(i)) {
for (i64 j = i - 1; j >= 0; j--)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset - get_r_delta(j))
return j;
} else {
for (i64 j = i + 1; j < rels.size(); j++)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset - get_r_delta(j))
return j;
}
Fatal(ctx) << *this << ": paired relocation is missing: " << i;
};
auto get_rd = [&](i64 offset) {
// Returns the rd register of an R/I/U/J-type instruction.
return bits(*(ul32 *)(contents.data() + offset), 11, 7);
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_RISCV_32:
if constexpr (E::is_64)
*(U32<E> *)loc = S + A;
else
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
assert(E::is_64);
*(U32<E> *)loc = S + A;
break;
case R_RISCV_64:
assert(E::is_64);
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_RISCV_BRANCH:
check(S + A - P, -(1 << 12), 1 << 12);
@@ -334,7 +319,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
i64 val = S + A - P;
i64 rd = get_rd(rel.r_offset + 4);
i64 rd = get_rd(buf + rel.r_offset + 4);
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
@@ -368,47 +353,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// pair to load a symbol value from the GOT. If the symbol value
// is actually a link-time constant, we can materialize the value
// directly into a register to eliminate a memory load.
i64 rd = get_rd(rel.r_offset);
i64 rd = get_rd(buf + rel.r_offset);
switch (removed_bytes) {
case 6:
if (removed_bytes == 6) {
// c.li <rd>, val
*(ul16 *)loc = 0b010'0'00000'00000'01 | (rd << 7);
write_citype(loc, sym.get_addr(ctx));
i += 3;
break;
case 4:
} else if (removed_bytes == 4) {
// addi <rd>, zero, val
*(ul32 *)loc = 0b0010011 | (rd << 7);
write_itype(loc, sym.get_addr(ctx));
i += 3;
break;
case 0:
if (ctx.arg.relax &&
sym.is_pcrel_linktime_const(ctx) &&
i + 3 < rels.size() &&
rels[i + 1].r_type == R_RISCV_RELAX &&
rels[i + 2].r_type == R_RISCV_PCREL_LO12_I &&
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
file.symbols[rels[i + 2].r_sym]->value == r_offset &&
rels[i + 3].r_type == R_RISCV_RELAX) {
i64 val = S + A - P;
if (rd == get_rd(rel.r_offset + 4) && (i32)val == val) {
// auipc <rd>, %hi20(val)
write_utype(loc, val);
} else {
assert(removed_bytes == 0);
// addi <rd>, <rd>, %lo12(val)
*(ul32 *)(loc + 4) = 0b0010011 | (rd << 15) | (rd << 7);
write_itype(loc + 4, val);
i += 3;
break;
}
i64 val = S + A - P;
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
is_got_load_pair(ctx, *this, rels, i) && is_int(val, 32)) {
// auipc <rd>, %hi20(val)
write_utype(loc, val);
// addi <rd>, <rd>, %lo12(val)
*(ul32 *)(loc + 4) = 0b0010011 | (rd << 15) | (rd << 7);
write_itype(loc + 4, val);
i += 3;
} else {
write_utype(loc, G + GOT + A - P);
}
write_utype(loc, G + GOT + A - P);
break;
default:
unreachable();
}
break;
}
@@ -422,50 +394,38 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_utype(loc, S + A - P);
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S:
case R_RISCV_GPREL_LO12_I:
case R_RISCV_GPREL_LO12_S: {
i64 idx2 = find_paired_reloc();
const ElfRel<E> &rel2 = rels[idx2];
case R_RISCV_PCREL_LO12_S: {
const ElfRel<E> &rel2 = find_paired_reloc(ctx, *this, rels, sym, i);
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
auto write =
(rel.r_type == R_RISCV_PCREL_LO12_I) ? write_itype : write_stype;
u64 S = sym2.get_addr(ctx);
u64 A = rel2.r_addend;
u64 P = get_addr() + rel2.r_offset - get_r_delta(idx2);
u64 P = get_addr() + rel2.r_offset - get_r_delta(*this, rel2.r_offset);
u64 G = sym2.get_got_idx(ctx) * sizeof(Word<E>);
u64 val;
switch (rel2.r_type) {
case R_RISCV_GOT_HI20:
val = G + GOT + A - P;
write(loc, G + GOT + A - P);
break;
case R_RISCV_TLS_GOT_HI20:
val = sym2.get_gottp_addr(ctx) + A - P;
write(loc, sym2.get_gottp_addr(ctx) + A - P);
break;
case R_RISCV_TLS_GD_HI20:
val = sym2.get_tlsgd_addr(ctx) + A - P;
write(loc, sym2.get_tlsgd_addr(ctx) + A - P);
break;
case R_RISCV_PCREL_HI20:
val = S + A - P;
write(loc, S + A - P);
break;
case R_RISCV_GPREL_HI20:
val = S + A - GP;
break;
default:
unreachable();
}
if (rel.r_type == R_RISCV_PCREL_LO12_I ||
rel.r_type == R_RISCV_GPREL_LO12_I)
write_itype(loc, val);
else
write_stype(loc, val);
break;
}
case R_RISCV_HI20:
if (removed_bytes == 2) {
// Rewrite LUI with C.LUI
i64 rd = get_rd(rel.r_offset);
i64 rd = get_rd(buf + rel.r_offset);
*(ul16 *)loc = 0b011'0'00000'00000'01 | (rd << 7);
write_citype(loc, (S + A + 0x800) >> 12);
} else if (removed_bytes == 0) {
@@ -483,7 +443,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(x0)` if the address is
// accessible relative to the zero register because if that's the
// case, corresponding LUI might have been removed by relaxation.
if (sign_extend(S + A, 11) == S + A)
if (is_int(S + A, 12))
set_rs1(loc, 0);
break;
case R_RISCV_TPREL_HI20:
@@ -507,7 +467,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// Rewrite `lw t1, 0(t0)` with `lw t1, 0(tp)` if the address is
// directly accessible using tp. tp is x4.
if (sign_extend(val, 11) == val)
if (is_int(val, 12))
set_rs1(loc, 4);
break;
}
@@ -546,7 +506,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// <deleted>
// lui a0, %tpoff_hi(a0)
// addi a0, a0, %tpoff_lo(a0)
if (removed_bytes == 0)
//
// If the code-shrinking relaxation is disabled, we may leave
// original useless instructions instead of deleting them, but we
// accept that because relaxations are enabled by default.
if (sym.has_tlsdesc(ctx) && removed_bytes == 0)
write_utype(loc, sym.get_tlsdesc_addr(ctx) + A - P);
break;
case R_RISCV_TLSDESC_LOAD_LO12:
@@ -555,17 +519,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (removed_bytes == 4)
break;
i64 idx2 = find_paired_reloc();
const ElfRel<E> &rel2 = rels[idx2];
const ElfRel<E> &rel2 = find_paired_reloc(ctx, *this, rels, sym, i);
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
u64 S = sym2.get_addr(ctx);
u64 A = rel2.r_addend;
u64 P = get_addr() + rel2.r_offset - get_r_delta(idx2);
u64 P = get_addr() + rel2.r_offset - get_r_delta(*this, rel2.r_offset);
switch (rel.r_type) {
case R_RISCV_TLSDESC_LOAD_LO12:
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
if (sym2.has_tlsdesc(ctx))
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
else
*(ul32 *)loc = 0x13; // nop
break;
case R_RISCV_TLSDESC_ADD_LO12:
if (sym2.has_tlsdesc(ctx)) {
@@ -587,7 +553,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_itype(loc, sym2.get_gottp_addr(ctx) + A - P);
} else {
i64 val = S + A - ctx.tp_addr;
if (sign_extend(val, 11) == val)
if (is_int(val, 12))
*(ul32 *)loc = 0x513; // addi a0,zero,<lo12>
else
*(ul32 *)loc = 0x50513; // addi a0,a0,<lo12>
@@ -647,10 +613,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check(S + A - P, -(1 << 11), 1 << 11);
write_cjtype(loc, S + A - P);
break;
case R_RISCV_GPREL_HI20:
check(S + A - GP, -(1LL << 31), 1LL << 31);
write_utype(loc, S + A - GP);
break;
case R_RISCV_SUB6:
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
break;
@@ -764,38 +726,9 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
}
}
template <>
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
// If a section is not relaxed, we can copy it as a one big chunk.
if (extra.r_deltas.empty()) {
copy_contents(ctx, buf);
return;
}
// A relaxed section is copied piece-wise.
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 pos = 0;
for (i64 i = 0; i < rels.size(); i++) {
i64 delta = extra.r_deltas[i + 1] - extra.r_deltas[i];
if (delta == 0)
continue;
assert(delta > 0);
const ElfRel<E> &r = rels[i];
memcpy(buf, contents.data() + pos, r.r_offset - pos);
buf += r.r_offset - pos;
pos = r.r_offset + delta;
}
memcpy(buf, contents.data() + pos, contents.size() - pos);
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -813,17 +746,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_32:
if constexpr (E::is_64)
scan_absrel(ctx, sym, rel);
else
scan_dyn_absrel(ctx, sym, rel);
break;
case R_RISCV_HI20:
scan_absrel(ctx, sym, rel);
break;
case R_RISCV_64:
if constexpr (!E::is_64)
Fatal(ctx) << *this << ": R_RISCV_64 cannot be used on RV32";
scan_dyn_absrel(ctx, sym, rel);
break;
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
case R_RISCV_PLT32:
@@ -852,10 +778,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_TPREL_ADD:
check_tlsle(ctx, sym, rel);
break;
case R_RISCV_GPREL_HI20:
if (ctx.arg.shared)
Fatal(ctx) << *this << ": R_RISCV_GPREL_HI20 may not be used with -shared";
break;
case R_RISCV_64:
case R_RISCV_BRANCH:
case R_RISCV_JAL:
case R_RISCV_PCREL_LO12_I:
@@ -876,8 +799,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_ALIGN:
case R_RISCV_RVC_BRANCH:
case R_RISCV_RVC_JUMP:
case R_RISCV_GPREL_LO12_I:
case R_RISCV_GPREL_LO12_S:
case R_RISCV_RELAX:
case R_RISCV_SUB6:
case R_RISCV_SET6:
@@ -901,9 +822,9 @@ u64 get_eflags(Context<E> &ctx) {
if (objs.empty())
return 0;
u32 ret = objs[0]->get_ehdr().e_flags;
u32 ret = objs[0]->get_eflags();
for (i64 i = 1; i < objs.size(); i++) {
u32 flags = objs[i]->get_ehdr().e_flags;
u32 flags = objs[i]->get_eflags();
if (flags & EF_RISCV_RVC)
ret |= EF_RISCV_RVC;
@@ -918,47 +839,26 @@ u64 get_eflags(Context<E> &ctx) {
return ret;
}
static bool is_resizable(InputSection<E> *isec) {
return isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
(isec->shdr().sh_flags & SHF_EXECINSTR);
}
// Returns the distance between a relocated place and a symbol.
static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel) {
// We handle absolute symbols as if they were infinitely far away
// because `shrink_section` may increase a distance between a branch
// instruction and an absolute symbol. Branching to an absolute
// location is extremely rare in real code, though.
if (sym.is_absolute())
return INT32_MAX;
// Likewise, relocations against weak undefined symbols won't be relaxed.
if (sym.esym().is_undef_weak())
return INT32_MAX;
// Compute a distance between the relocated place and the symbol.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
i64 P = isec.get_addr() + rel.r_offset;
return S + A - P;
}
// Scan relocations to shrink sections.
static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc) {
// Scan relocations to shrink a given section.
template <>
void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
isec.extra.r_deltas.resize(rels.size() + 1);
std::vector<RelocDelta> &deltas = isec.extra.r_deltas;
i64 r_delta = 0;
u8 *buf = (u8 *)isec.contents.data();
auto get_rd = [&](i64 offset) {
return bits(*(ul32 *)(isec.contents.data() + offset), 11, 7);
};
i64 delta = 0;
// True if we can use 2-byte instructions. This is usually true on
// Unix because RV64GC is generally considered the baseline hardware.
bool use_rvc = isec.file.get_eflags() & EF_RISCV_RVC;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &r = rels[i];
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
isec.extra.r_deltas[i] = delta;
auto remove = [&](i64 d) {
r_delta += d;
deltas.push_back(RelocDelta{r.r_offset, r_delta});
};
// Handling R_RISCV_ALIGN is mandatory.
//
@@ -968,11 +868,11 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
if (r.r_type == R_RISCV_ALIGN) {
// The total bytes of NOPs is stored to r_addend, so the next
// instruction is r_addend away.
u64 loc = isec.get_addr() + r.r_offset - delta;
u64 next_loc = loc + r.r_addend;
u64 alignment = bit_ceil(r.r_addend + 1);
assert(alignment <= (1 << isec.p2align));
delta += next_loc - align_to(loc, alignment);
u64 P = isec.get_addr() + r.r_offset - r_delta;
u64 desired = align_to(P, bit_ceil(r.r_addend));
u64 actual = P + r.r_addend;
if (desired != actual)
remove(actual - desired);
continue;
}
@@ -989,20 +889,6 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
if (sym.file == ctx.internal_obj)
continue;
auto find_paired_reloc = [&] {
if (sym.value <= rels[i].r_offset) {
for (i64 j = i - 1; j >= 0; j--)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset)
return j;
} else {
for (i64 j = i + 1; j < rels.size(); j++)
if (is_hi20(rels[j]) && sym.value == rels[j].r_offset)
return j;
}
Fatal(ctx) << isec << ": paired relocation is missing: " << i;
};
switch (r.r_type) {
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
@@ -1013,63 +899,51 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
if (dist & 1)
break;
i64 rd = get_rd(r.r_offset + 4);
i64 rd = get_rd(buf + r.r_offset + 4);
if (use_rvc && rd == 0 && sign_extend(dist, 11) == dist) {
if (use_rvc && rd == 0 && is_int(dist, 12)) {
// If rd is x0 and the jump target is within ±2 KiB, we can use
// C.J, saving 6 bytes.
delta += 6;
} else if (use_rvc && !E::is_64 && rd == 1 && sign_extend(dist, 11) == dist) {
remove(6);
} else if (use_rvc && !E::is_64 && rd == 1 && is_int(dist, 12)) {
// If rd is x1 and the jump target is within ±2 KiB, we can use
// C.JAL. This is RV32 only because C.JAL is RV32-only instruction.
delta += 6;
} else if (sign_extend(dist, 20) == dist) {
remove(6);
} else if (is_int(dist, 21)) {
// If the jump target is within ±1 MiB, we can use JAL.
delta += 4;
remove(4);
}
break;
}
case R_RISCV_GOT_HI20: {
case R_RISCV_GOT_HI20:
// A GOT_HI20 followed by a PCREL_LO12_I is used to load a value from
// GOT. If the loaded value is a link-time constant, we can rewrite
// the instructions to directly materialize the value, eliminating a
// memory load.
if (sym.is_absolute() &&
i + 3 < rels.size() &&
rels[i + 1].r_type == R_RISCV_RELAX &&
rels[i + 2].r_type == R_RISCV_PCREL_LO12_I &&
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
isec.file.symbols[rels[i + 2].r_sym]->value == rels[i].r_offset &&
rels[i + 3].r_type == R_RISCV_RELAX) {
i64 rd = get_rd(r.r_offset);
if (rd == get_rd(r.r_offset + 4)) {
u64 val = sym.get_addr(ctx) + r.r_addend;
if (use_rvc && rd != 0 && sign_extend(val, 5) == val) {
// Replace AUIPC + LD with C.LI.
delta += 6;
} else if (sign_extend(val, 11) == val) {
// Replace AUIPC + LD with ADDI.
delta += 4;
}
if (sym.is_absolute() && is_got_load_pair(ctx, isec, rels, i)) {
u64 val = sym.get_addr(ctx) + r.r_addend;
if (use_rvc && is_int(val, 6) && get_rd(buf + r.r_offset) != 0) {
// Replace AUIPC + LD with C.LI.
remove(6);
} else if (is_int(val, 12)) {
// Replace AUIPC + LD with ADDI.
remove(4);
}
}
break;
}
case R_RISCV_HI20: {
u64 val = sym.get_addr(ctx) + r.r_addend;
i64 rd = get_rd(r.r_offset);
i64 rd = get_rd(buf + r.r_offset);
if (sign_extend(val, 11) == val) {
if (is_int(val, 12)) {
// We can replace `lui t0, %hi(foo)` and `add t0, t0, %lo(foo)`
// instruction pair with `add t0, x0, %lo(foo)` if foo's bits
// [32:11] are all one or all zero.
delta += 4;
} else if (use_rvc && rd != 0 && rd != 2 && sign_extend(val, 17) == val) {
remove(4);
} else if (use_rvc && rd != 0 && rd != 2 && is_int(val + 0x800, 18)) {
// If the upper 20 bits can actually be represented in 6 bits,
// we can use C.LUI instead of LUI.
delta += 2;
remove(2);
}
break;
}
@@ -1095,84 +969,34 @@ static void shrink_section(Context<E> &ctx, InputSection<E> &isec, bool use_rvc)
//
// Here, we remove `lui` and `add` if the offset is within ±2 KiB.
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
sign_extend(val, 11) == val)
delta += 4;
is_int(val, 12))
remove(4);
break;
case R_RISCV_TLSDESC_HI20:
if (!sym.has_tlsdesc(ctx))
delta += 4;
remove(4);
break;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12: {
const ElfRel<E> &rel2 = rels[find_paired_reloc()];
const ElfRel<E> &rel2 = find_paired_reloc(ctx, isec, rels, sym, i);
Symbol<E> &sym2 = *isec.file.symbols[rel2.r_sym];
if (r.r_type == R_RISCV_TLSDESC_LOAD_LO12) {
if (!sym2.has_tlsdesc(ctx))
delta += 4;
remove(4);
} else {
assert(r.r_type == R_RISCV_TLSDESC_ADD_LO12);
if (!sym2.has_tlsdesc(ctx) && !sym2.has_gottp(ctx))
if (i64 val = sym2.get_addr(ctx) + rel2.r_addend - ctx.tp_addr;
sign_extend(val, 11) == val)
delta += 4;
is_int(val, 12))
remove(4);
}
break;
}
}
}
isec.extra.r_deltas[rels.size()] = delta;
isec.sh_size -= delta;
}
// Shrink sections by interpreting relocations.
//
// This operation seems to be optional, because by default longest
// instructions are being used. However, calling this function is actually
// mandatory because of R_RISCV_ALIGN. R_RISCV_ALIGN is a directive to the
// linker to align the location referred to by the relocation to a
// specified byte boundary. We at least have to interpret them to satisfy
// the alignment constraints.
template <>
i64 riscv_resize_sections<E>(Context<E> &ctx) {
Timer t(ctx, "riscv_resize_sections");
// True if we can use the 2-byte instructions. This is usually true on
// Unix because RV64GC is generally considered the baseline hardware.
bool use_rvc = get_eflags(ctx) & EF_RISCV_RVC;
// Find all the relocations that can be relaxed.
// This step should only shrink sections.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (is_resizable(isec.get()))
shrink_section(ctx, *isec, use_rvc);
});
// Fix symbol values.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file != file)
continue;
InputSection<E> *isec = sym->get_input_section();
if (!isec || isec->extra.r_deltas.empty())
continue;
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
auto it = std::lower_bound(rels.begin(), rels.end(), sym->value,
[&](const ElfRel<E> &r, u64 val) {
return r.r_offset < val;
});
sym->value -= isec->extra.r_deltas[it - rels.begin()];
}
});
// Re-compute section offset again to finalize them.
compute_section_sizes(ctx);
return set_osec_offsets(ctx);
isec.sh_size -= r_delta;
}
// ISA name handlers
@@ -1190,8 +1014,8 @@ i64 riscv_resize_sections<E>(Context<E> &ctx) {
// Each extension consists of a name, a major version and a minor version.
// For example, "m2p0" indicates the "m" extension of version 2.0. "p" is
// just a separator. Versions are often omitted in documents, but they are
// mandatory in .riscv.attributes. Likewise, abbreviations as "g" (which
// is short for "IMAFD") are not allowed in .riscv.attributes.
// mandatory in .riscv.attributes. Likewise, abbreviations such as "G"
// (which is short for "IMAFD") are not allowed in .riscv.attributes.
//
// Each RISC-V object file contains an ISA string enumerating extensions
// used by the object file. We need to merge input objects' ISA strings
@@ -1217,7 +1041,7 @@ struct Extn {
//
// This function returns true if the first extension name should precede
// the second one as per the rule.
static bool extn_name_less(const Extn &e1, const Extn &e2) {
static bool extn_name_less(std::string_view x, std::string_view y) {
auto get_single_letter_rank = [](char c) -> i64 {
std::string_view exts = "iemafdqlcbkjtpvnh";
size_t pos = exts.find_first_of(c);
@@ -1239,57 +1063,26 @@ static bool extn_name_less(const Extn &e1, const Extn &e2) {
}
};
return std::tuple{get_rank(e1.name), e1.name} <
std::tuple{get_rank(e2.name), e2.name};
}
static bool extn_version_less(const Extn &e1, const Extn &e2) {
return std::tuple{e1.major, e1.minor} <
std::tuple{e2.major, e2.minor};
}
static std::optional<Extn> read_extn_string(std::string_view &str) {
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
static std::regex re(R"(^([a-z]+)(\d+)p(\d+))", flags);
std::cmatch m;
if (std::regex_search(str.data(), str.data() + str.size(), m, re)) {
str = str.substr(m.length());
return Extn{m[1], (i64)std::stoul(m[2]), (i64)std::stoul(m[3])};
}
return {};
return std::tuple{get_rank(x), x} < std::tuple{get_rank(y), y};
}
static std::vector<Extn> parse_arch_string(std::string_view str) {
if (str.size() < 5)
return {};
// Parse the base part
std::string_view base = str.substr(0, 5);
if (base != "rv32i" && base != "rv32e" && base != "rv64i" && base != "rv64e")
return {};
str = str.substr(4);
std::optional<Extn> extn = read_extn_string(str);
if (!extn)
return {};
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
static std::regex re(R"(^([a-z]|[a-z][a-z0-9]*[a-z])(\d+)p(\d+)(_|$))", flags);
std::vector<Extn> vec;
extn->name = base;
vec.push_back(*extn);
// Parse extensions
while (!str.empty()) {
if (str[0] != '_')
for (;;) {
std::cmatch m;
if (!std::regex_search(str.data(), str.data() + str.size(), m, re))
return {};
str = str.substr(1);
std::optional<Extn> extn = read_extn_string(str);
if (!extn)
return {};
vec.push_back(*extn);
vec.push_back(Extn{m[1], (i64)std::stoul(m[2]), (i64)std::stoul(m[3])});
if (m[4].length() == 0)
return vec;
str = str.substr(m.length());
}
return vec;
}
static std::vector<Extn> merge_extensions(std::span<Extn> x, std::span<Extn> y) {
@@ -1302,10 +1095,13 @@ static std::vector<Extn> merge_extensions(std::span<Extn> x, std::span<Extn> y)
// Merge ISA extension strings
while (!x.empty() && !y.empty()) {
if (x[0].name == y[0].name) {
vec.push_back(extn_version_less(x[0], y[0]) ? y[0] : x[0]);
if (std::tuple{x[0].major, x[0].minor} < std::tuple{y[0].major, y[0].minor})
vec.push_back(y[0]);
else
vec.push_back(x[0]);
x = x.subspan(1);
y = y.subspan(1);
} else if (extn_name_less(x[0], y[0])) {
} else if (extn_name_less(x[0].name, y[0].name)) {
vec.push_back(x[0]);
x = x.subspan(1);
} else {
@@ -1314,19 +1110,17 @@ static std::vector<Extn> merge_extensions(std::span<Extn> x, std::span<Extn> y)
}
}
vec.insert(vec.end(), x.begin(), x.end());
vec.insert(vec.end(), y.begin(), y.end());
append(vec, x);
append(vec, y);
return vec;
}
static std::string to_string(std::span<Extn> v) {
std::string str = v[0].name + std::to_string(v[0].major) + "p" +
std::to_string(v[0].minor);
for (i64 i = 1; i < v.size(); i++)
str += "_" + v[i].name + std::to_string(v[i].major) + "p" +
std::to_string(v[i].minor);
return str;
std::ostringstream os;
os << v[0].name << v[0].major << 'p' << v[0].minor;
for (Extn &e : v.subspan(1))
os << '_' << e.name << e.major << 'p' << e.minor;
return os.str();
}
//
@@ -1408,9 +1202,9 @@ void RiscvAttributesSection<E>::update_shdr(Context<E> &ctx) {
template <>
void RiscvAttributesSection<E>::copy_buf(Context<E> &ctx) {
memcpy(ctx.buf + this->shdr.sh_offset, contents.data(), contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
} // namespace mold::elf
} // namespace mold
#endif
+48 -55
View File
@@ -33,11 +33,11 @@
// we need to add TP to a return value before use. I don't know why it is
// different, but that is the way it is.
//
// https://github.com/rui314/psabi/blob/main/s390x.pdf
// https://github.com/IBM/s390x-abi/releases/download/v1.6.1/lzsabi_s390x.pdf
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = S390X;
@@ -116,11 +116,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -129,31 +124,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
check(val, lo, hi);
// R_390_*DBL relocs should never refer a symbol at an odd address
if (val & 1)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
// R_390_*DBL relocs should never refer to a symbol at an odd address
check(val, lo, hi);
if (val & 1)
Error(ctx) << *this << ": misaligned symbol " << sym
<< " for relocation " << rel;
};
switch (rel.r_type) {
case R_390_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_390_8:
check(S + A, 0, 1 << 8);
@@ -176,9 +166,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
case R_390_PLT64:
*(ub64 *)loc = S + A;
break;
case R_390_PC12DBL:
case R_390_PLT12DBL:
check_dbl(S + A - P, -(1 << 12), 1 << 12);
@@ -193,6 +180,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc = S + A - P;
break;
case R_390_PC64:
case R_390_PLT64:
*(ub64 *)loc = S + A - P;
break;
case R_390_PC16DBL:
@@ -256,7 +244,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc = (GOT + A - P) >> 1;
break;
case R_390_GOTENT:
check(GOT + G + A - P, -(1LL << 32), 1LL << 32);
// If we can relax a GOT-loading LGRL to an address-materializing
// LARL, do that. The format of LGRL is 0xc 0x4 <reg> 0x8 followed
// by a 32-bit offset. LARL is 0xc 0x0 <reg> 0x0.
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx)) {
u64 op = *(ub16 *)(loc - 2);
u64 val = S + A - P;
if ((op & 0xff0f) == 0xc408 && A == 2 && (val & 1) == 0 &&
is_int(val, 33)) {
*(ub16 *)(loc - 2) = 0xc000 | (op & 0x00f0);
*(ub32 *)loc = val >> 1;
break;
}
}
check_dbl(GOT + G + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (GOT + G + A - P) >> 1;
break;
case R_390_TLS_LE32:
@@ -303,22 +304,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_390_TLS_LDM32:
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
else
*(ub32 *)loc = ctx.dtp_addr - ctx.tp_addr;
break;
case R_390_TLS_LDM64:
if (ctx.got->has_tlsld(ctx))
*(ub64 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
break;
case R_390_TLS_LDO32:
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc = S + A - ctx.dtp_addr;
else
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_LDO64:
if (ctx.got->has_tlsld(ctx))
*(ub64 *)loc = S + A - ctx.dtp_addr;
else
*(ub64 *)loc = S + A - ctx.tp_addr;
*(ub64 *)loc = ctx.dtp_addr - ctx.tp_addr;
break;
case R_390_TLS_LDCALL:
if (!ctx.got->has_tlsld(ctx)) {
@@ -327,6 +320,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
memcpy(loc, insn, sizeof(insn));
}
break;
case R_390_TLS_LDO32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
case R_390_TLS_LDO64:
*(ub64 *)loc = S + A - ctx.dtp_addr;
break;
default:
unreachable();
}
@@ -345,13 +344,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -359,6 +351,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_390_32:
check(S + A, 0, 1LL << 32);
@@ -385,8 +381,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -401,9 +395,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_390_64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_390_8:
case R_390_12:
case R_390_16:
@@ -411,8 +402,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_32:
scan_absrel(ctx, sym, rel);
break;
case R_390_PC12DBL:
case R_390_PC16:
case R_390_PC16DBL:
case R_390_PC24DBL:
case R_390_PC32:
case R_390_PC32DBL:
case R_390_PC64:
@@ -457,8 +450,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// We always want to relax calls to __tls_get_offset() in statically-
// linked executables because __tls_get_offset() in libc.a just calls
// abort().
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static) {
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// Do nothing
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
@@ -468,7 +460,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64:
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared)) {
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
// Do nothing
} else {
ctx.needs_tlsld = true;
@@ -478,6 +470,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_390_TLS_LE64:
check_tlsle(ctx, sym, rel);
break;
case R_390_64:
case R_390_TLS_LDO32:
case R_390_TLS_LDO64:
case R_390_TLS_GDCALL:
@@ -489,4 +482,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
+73 -79
View File
@@ -58,11 +58,13 @@
// output from the linker contains lots of text relocations. That's not
// a problem with embedded programming, I guess.
#if MOLD_SH4LE || MOLD_SH4BE
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = SH4;
using E = MOLD_TARGET;
// Even though SH-4 uses RELA-type relocations, addends are stored to
// relocated places for some reason.
@@ -84,7 +86,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_SH_GOTOFF:
case R_SH_GOTPC:
case R_SH_GOTPLT32:
return *(ul32 *)loc;
return *(U32<E> *)loc;
default:
return 0;
}
@@ -108,102 +110,102 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_SH_GOTOFF:
case R_SH_GOTPC:
case R_SH_GOTPLT32:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
}
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x02, 0xd2, // mov.l 1f, r2
0xcc, 0x32, // add r12, r2
0x22, 0x50, // mov.l @(8, r2), r0
0x21, 0x52, // mov.l @(4, r2), r2
0x2b, 0x40, // jmp @r0
0x00, 0xe0, // mov #0, r0
0, 0, 0, 0, // 1: .long GOTPLT
constexpr U16<E> insn[] = {
0xd202, // mov.l 1f, r2
0x32cc, // add r12, r2
0x5022, // mov.l @(8, r2), r0
0x5221, // mov.l @(4, r2), r2
0x402b, // jmp @r0
0xe000, // mov #0, r0
0, 0, // 1: .long GOTPLT
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0x02, 0xd2, // mov.l 1f, r2
0x22, 0x50, // mov.l @(8, r2), r0
0x21, 0x52, // mov.l @(4, r2), r2
0x2b, 0x40, // jmp @r0
0x00, 0xe0, // mov #0, r0
0x09, 0x00, // nop
0, 0, 0, 0, // 1: .long GOTPLT
constexpr U16<E> insn[] = {
0xd202, // mov.l 1f, r2
0x5022, // mov.l @(8, r2), r0
0x5221, // mov.l @(4, r2), r2
0x402b, // jmp @r0
0xe000, // mov #0, r0
0x0009, // nop
0, 0, // 1: .long GOTPLT
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
*(U32<E> *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
}
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0xce, 0x00, // mov.l @(r0, r12), r0
0x2b, 0x40, // jmp @r0
0x01, 0xd1, // mov.l 2f, r1
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x00ce, // mov.l @(r0, r12), r0
0x402b, // jmp @r0
0xd101, // mov.l 2f, r1
0, 0, // 1: .long GOTPLT_ENTRY
0, 0, // 2: .long INDEX_IN_RELPLT
};
static_assert(sizeof(insn) == E::plt_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
} else {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0x02, 0x60, // mov.l @r0, r0
0x2b, 0x40, // jmp @r0
0x01, 0xd1, // mov.l 2f, r1
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x6002, // mov.l @r0, r0
0x402b, // jmp @r0
0xd101, // mov.l 2f, r1
0, 0, // 1: .long GOTPLT_ENTRY
0, 0, // 2: .long INDEX_IN_RELPLT
};
static_assert(sizeof(insn) == E::plt_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx);
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx);
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0xce, 0x00, // mov.l @(r0, r12), r0
0x2b, 0x40, // jmp @r0
0x09, 0x00, // nop
0, 0, 0, 0, // 1: .long GOT_ENTRY
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x00ce, // mov.l @(r0, r12), r0
0x402b, // jmp @r0
0x0009, // nop
0, 0, // 1: .long GOT_ENTRY
};
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0x01, 0xd0, // mov.l 1f, r0
0x02, 0x60, // mov.l @r0, r0
0x2b, 0x40, // jmp @r0
0x09, 0x00, // nop
0, 0, 0, 0, // 1: .long GOT_ENTRY
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0x6002, // mov.l @r0, r0
0x402b, // jmp @r0
0x0009, // nop
0, 0, // 1: .long GOT_ENTRY
};
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
*(U32<E> *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
}
}
@@ -216,10 +218,10 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
case R_NONE:
break;
case R_SH_DIR32:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
break;
case R_SH_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -230,11 +232,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -251,35 +248,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_SH_DIR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_SH_REL32:
case R_SH_PLT32:
*(ul32 *)loc = S + A - P;
*(U32<E> *)loc = S + A - P;
break;
case R_SH_GOT32:
*(ul32 *)loc = G;
*(U32<E> *)loc = G;
break;
case R_SH_GOTPC:
*(ul32 *)loc = GOT + A - P;
*(U32<E> *)loc = GOT + A - P;
break;
case R_SH_GOTOFF:
*(ul32 *)loc = S + A - GOT;
*(U32<E> *)loc = S + A - GOT;
break;
case R_SH_TLS_GD_32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LD_32:
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_SH_TLS_IE_32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LE_32:
*(ul32 *)loc = S + A - ctx.tp_addr;
*(U32<E> *)loc = S + A - ctx.tp_addr;
break;
default:
unreachable();
@@ -309,9 +305,9 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_SH_DIR32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
*(U32<E> *)loc = *val;
else
*(ul32 *)loc = S + A;
*(U32<E> *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
@@ -323,8 +319,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
@@ -338,9 +332,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
switch (rel.r_type) {
case R_SH_DIR32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_SH_REL32:
scan_pcrel(ctx, sym, rel);
break;
@@ -363,6 +354,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SH_TLS_LE_32:
check_tlsle(ctx, sym, rel);
break;
case R_SH_DIR32:
case R_SH_GOTPC:
case R_SH_GOTOFF:
case R_SH_TLS_LDO_32:
@@ -373,4 +365,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
} // namespace mold
#endif
+94 -80
View File
@@ -51,14 +51,11 @@
// address. That also means each function needs to execute the above piece
// of code to become position-independent.
//
// This scheme is very similar to i386. That may not be a coincidence
// because the i386 ELF psABI is created by Sun Microsystems too.
//
// https://github.com/rui314/psabi/blob/main/sparc.pdf
#include "mold.h"
namespace mold::elf {
namespace mold {
using E = SPARC64;
@@ -142,11 +139,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -155,23 +147,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = (get_addr() + rel.r_offset);
u64 G = (sym.get_got_idx(ctx) * sizeof(Word<E>));
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_SPARC_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_SPARC_5:
check(S + A, 0, 1 << 5);
*(ub32 *)loc |= bits(S + A, 4, 0);
@@ -359,27 +345,75 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(S + A, 11, 0);
break;
case R_SPARC_TLS_GD_HI22:
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
if (sym.has_tlsgd(ctx)) {
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
} else if (sym.has_gottp(ctx)) {
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
} else {
*(ub32 *)loc |= bits(~(S + A - ctx.tp_addr), 31, 10);
}
break;
case R_SPARC_TLS_GD_LO10:
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
if (sym.has_tlsgd(ctx)) {
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
} else if (sym.has_gottp(ctx)) {
u32 rd = bits(*(ub32 *)loc, 29, 25);
*(ub32 *)loc = 0x8010'2000 | (rd << 25) | (rd << 14); // or %reg, $0, %reg
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
} else {
u32 rd = bits(*(ub32 *)loc, 29, 25);
*(ub32 *)loc = 0x8018'2000 | (rd << 25) | (rd << 14); // xor %reg, $0, %reg
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
}
break;
case R_SPARC_TLS_GD_ADD:
if (sym.has_tlsgd(ctx)) {
// do nothing
} else if (sym.has_gottp(ctx)) {
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
*(ub32 *)loc = 0xd05d'c000 | rs2; // ldx [ %l7 + %reg ], %o0
} else {
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
*(ub32 *)loc = 0x9001'c000 | rs2; // add %g7, %reg, %o0
}
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL: {
u64 addr;
if (ctx.arg.is_static)
addr = ctx.extra.tls_get_addr_sec->shdr.sh_addr;
else
addr = ctx.extra.tls_get_addr_sym->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
if (sym.has_tlsgd(ctx)) {
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
} else if (sym.has_gottp(ctx)) {
*(ub32 *)loc = 0x9001'c008; // add %g7, %o0, %o0
} else {
*(ub32 *)loc = 0x0100'0000; // nop
}
break;
}
case R_SPARC_TLS_LDM_HI22:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
else
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
break;
case R_SPARC_TLS_LDM_LO10:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
else
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
break;
case R_SPARC_TLS_LDM_ADD:
if (ctx.got->has_tlsld(ctx)) {
// do nothing
} else {
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
*(ub32 *)loc = 0x9021'c000 | rs2; // sub %g7, %reg, %o0
}
break;
case R_SPARC_TLS_LDM_CALL:
if (ctx.got->has_tlsld(ctx)) {
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
} else {
*(ub32 *)loc = 0x0100'0000; // nop
}
break;
case R_SPARC_TLS_LDO_HIX22:
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 31, 10);
@@ -402,8 +436,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_SPARC_SIZE32:
*(ub32 *)loc = sym.esym().st_size + A;
break;
case R_SPARC_TLS_GD_ADD:
case R_SPARC_TLS_LDM_ADD:
case R_SPARC_64:
case R_SPARC_TLS_LDO_ADD:
case R_SPARC_TLS_IE_LD:
case R_SPARC_TLS_IE_LDX:
@@ -427,13 +460,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
@@ -441,6 +467,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_SPARC_64:
case R_SPARC_UA64:
@@ -450,12 +480,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
*(ub64 *)loc = S + A;
break;
case R_SPARC_32:
case R_SPARC_UA32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
case R_SPARC_UA32:
check(S + A, 0, 1LL << 32);
*(ub32 *)loc = S + A;
break;
}
case R_SPARC_TLS_DTPOFF32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
@@ -471,8 +499,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -487,9 +513,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_SPARC_64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_SPARC_8:
case R_SPARC_5:
case R_SPARC_6:
@@ -554,24 +577,36 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_SPARC_TLS_GD_HI22:
sym.flags |= NEEDS_TLSGD;
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
sym.flags |= NEEDS_GOTTP;
} else {
sym.flags |= NEEDS_TLSGD;
}
break;
case R_SPARC_TLS_LDM_HI22:
ctx.needs_tlsld = true;
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
} else {
ctx.needs_tlsld = true;
}
break;
case R_SPARC_TLS_IE_HI22:
sym.flags |= NEEDS_GOTTP;
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (!ctx.arg.is_static)
if (Symbol<E> &sym = *ctx.extra.tls_get_addr_sym; sym.is_imported)
sym.flags |= NEEDS_PLT;
if (Symbol<E> *sym = ctx.extra.tls_get_addr; sym->is_imported)
sym->flags |= NEEDS_PLT;
break;
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
check_tlsle(ctx, sym, rel);
break;
case R_SPARC_64:
case R_SPARC_GOTDATA_OP_LOX10:
case R_SPARC_GOTDATA_OP:
case R_SPARC_GOTDATA_LOX10:
@@ -594,25 +629,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// __tls_get_addr is not defined by libc.a, so we can't use that function
// in statically-linked executables. This section provides a replacement.
void SparcTlsGetAddrSection::copy_buf(Context<E> &ctx) {
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
static const ub32 insn[] = {
0x0300'0000, // sethi %hi(TP_SIZE), %g1
0x8210'6000, // or %g1, %lo(TP_SIZE), %g1
0x8221'c001, // sub %g7, %g1, %g1
0xd05a'2008, // ldx [ %o0 + 8 ], %o0
0x81c3'e008, // retl
0x9000'4008, // add %g1, %o0, %o0
};
assert(this->shdr.sh_size == sizeof(insn));
memcpy(buf, insn, sizeof(insn));
buf[0] |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
buf[1] |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
}
} // namespace mold::elf
} // namespace mold
+304 -171
View File
@@ -23,12 +23,13 @@
// TLS block as TP (with some addend). As a result, offsets from TP to
// thread-local variables (TLVs) in the main executable are all negative.
//
// https://github.com/rui314/psabi/blob/main/x86-64.pdf
// https://github.com/rui314/psabi/blob/main/i386.pdf
// https://gitlab.com/x86-psABIs/x86-64-ABI
#include "mold.h"
namespace mold::elf {
#include <tbb/parallel_for_each.h>
namespace mold {
using E = X86_64;
@@ -126,98 +127,165 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
}
}
static u32 relax_gotpcrelx(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
static u32 relax_gotpcrelx(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTPCRELX) {
switch ((loc[-2] << 8) | loc[-1]) {
case 0xff15: return 0x40e8; // call *0(%rip) -> call 0
case 0xff25: return 0x40e9; // jmp *0(%rip) -> jmp 0
}
} else {
assert(rel.r_type == R_X86_64_REX_GOTPCRELX ||
rel.r_type == R_X86_64_CODE_4_GOTPCRELX);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488b05: return 0x8d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
case 0x488b0d: return 0x8d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
case 0x488b15: return 0x8d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
case 0x488b1d: return 0x8d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
case 0x488b25: return 0x8d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
case 0x488b2d: return 0x8d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
case 0x488b35: return 0x8d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
case 0x488b3d: return 0x8d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
case 0x4c8b05: return 0x8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
case 0x4c8b0d: return 0x8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
case 0x4c8b15: return 0x8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
case 0x4c8b1d: return 0x8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
case 0x4c8b25: return 0x8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
case 0x4c8b2d: return 0x8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
case 0x4c8b35: return 0x8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
case 0x4c8b3d: return 0x8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
}
}
return 0;
}
static u32 relax_rex_gotpcrelx(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
static u32 relax_gottpoff(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTTPOFF) {
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
}
} else {
assert(rel.r_type == R_X86_64_CODE_4_GOTTPOFF);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488b05: return 0x18c7c0; // mov 0(%rip), %r16 -> mov $0, %r16
case 0x488b0d: return 0x18c7c1; // mov 0(%rip), %r17 -> mov $0, %r17
case 0x488b15: return 0x18c7c2; // mov 0(%rip), %r18 -> mov $0, %r18
case 0x488b1d: return 0x18c7c3; // mov 0(%rip), %r19 -> mov $0, %r19
case 0x488b25: return 0x18c7c4; // mov 0(%rip), %r20 -> mov $0, %r20
case 0x488b2d: return 0x18c7c5; // mov 0(%rip), %r21 -> mov $0, %r21
case 0x488b35: return 0x18c7c6; // mov 0(%rip), %r22 -> mov $0, %r22
case 0x488b3d: return 0x18c7c7; // mov 0(%rip), %r23 -> mov $0, %r23
case 0x4c8b05: return 0x19c7c0; // mov 0(%rip), %r24 -> mov $0, %r24
case 0x4c8b0d: return 0x19c7c1; // mov 0(%rip), %r25 -> mov $0, %r25
case 0x4c8b15: return 0x19c7c2; // mov 0(%rip), %r26 -> mov $0, %r26
case 0x4c8b1d: return 0x19c7c3; // mov 0(%rip), %r27 -> mov $0, %r27
case 0x4c8b25: return 0x19c7c4; // mov 0(%rip), %r28 -> mov $0, %r28
case 0x4c8b2d: return 0x19c7c5; // mov 0(%rip), %r29 -> mov $0, %r29
case 0x4c8b35: return 0x19c7c6; // mov 0(%rip), %r30 -> mov $0, %r30
case 0x4c8b3d: return 0x19c7c7; // mov 0(%rip), %r31 -> mov $0, %r31
}
}
return 0;
}
static u32 relax_gottpoff(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
static u32 relax_tlsdesc_to_ie(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTPC32_TLSDESC) {
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x488b05; // lea 0(%rip), %rax -> mov 0(%rip), %rax
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %rcx -> mov 0(%rip), %rcx
case 0x488d15: return 0x488b15; // lea 0(%rip), %rdx -> mov 0(%rip), %rdx
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %rbx -> mov 0(%rip), %rbx
case 0x488d25: return 0x488b25; // lea 0(%rip), %rsp -> mov 0(%rip), %rsp
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %rbp -> mov 0(%rip), %rbp
case 0x488d35: return 0x488b35; // lea 0(%rip), %rsi -> mov 0(%rip), %rsi
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %rdi -> mov 0(%rip), %rdi
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r8 -> mov 0(%rip), %r8
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r9 -> mov 0(%rip), %r9
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r10 -> mov 0(%rip), %r10
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r11 -> mov 0(%rip), %r11
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r12 -> mov 0(%rip), %r12
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r13 -> mov 0(%rip), %r13
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r14 -> mov 0(%rip), %r14
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r15 -> mov 0(%rip), %r15
}
} else {
assert(rel.r_type == R_X86_64_CODE_4_GOTPC32_TLSDESC);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x488b05; // lea 0(%rip), %r16 -> mov 0(%rip), %r16
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %r17 -> mov 0(%rip), %r17
case 0x488d15: return 0x488b15; // lea 0(%rip), %r18 -> mov 0(%rip), %r18
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %r19 -> mov 0(%rip), %r19
case 0x488d25: return 0x488b25; // lea 0(%rip), %r20 -> mov 0(%rip), %r20
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %r21 -> mov 0(%rip), %r21
case 0x488d35: return 0x488b35; // lea 0(%rip), %r22 -> mov 0(%rip), %r22
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %r23 -> mov 0(%rip), %r23
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r24 -> mov 0(%rip), %r24
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r25 -> mov 0(%rip), %r25
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r26 -> mov 0(%rip), %r26
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r27 -> mov 0(%rip), %r27
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r28 -> mov 0(%rip), %r28
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r29 -> mov 0(%rip), %r29
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r30 -> mov 0(%rip), %r30
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r31 -> mov 0(%rip), %r31
}
}
return 0;
}
static u32 relax_tlsdesc_to_ie(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488d05: return 0x488b05; // lea 0(%rip), %rax -> mov 0(%rip), %rax
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %rcx -> mov 0(%rip), %rcx
case 0x488d15: return 0x488b15; // lea 0(%rip), %rdx -> mov 0(%rip), %rdx
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %rbx -> mov 0(%rip), %rbx
case 0x488d25: return 0x488b25; // lea 0(%rip), %rsp -> mov 0(%rip), %rsp
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %rbp -> mov 0(%rip), %rbp
case 0x488d35: return 0x488b35; // lea 0(%rip), %rsi -> mov 0(%rip), %rsi
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %rdi -> mov 0(%rip), %rdi
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r8 -> mov 0(%rip), %r8
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r9 -> mov 0(%rip), %r9
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r10 -> mov 0(%rip), %r10
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r11 -> mov 0(%rip), %r11
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r12 -> mov 0(%rip), %r12
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r13 -> mov 0(%rip), %r13
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r14 -> mov 0(%rip), %r14
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r15 -> mov 0(%rip), %r15
}
return 0;
}
static u32 relax_tlsdesc_to_le(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
static u32 relax_tlsdesc_to_le(u8 *loc, const ElfRel<E> &rel) {
if (rel.r_type == R_X86_64_GOTPC32_TLSDESC) {
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
}
} else {
assert(rel.r_type == R_X86_64_CODE_4_GOTPC32_TLSDESC);
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
case 0x488d05: return 0x18c7c0; // lea 0(%rip), %r16 -> mov $0, %r16
case 0x488d0d: return 0x18c7c1; // lea 0(%rip), %r17 -> mov $0, %r17
case 0x488d15: return 0x18c7c2; // lea 0(%rip), %r18 -> mov $0, %r18
case 0x488d1d: return 0x18c7c3; // lea 0(%rip), %r19 -> mov $0, %r19
case 0x488d25: return 0x18c7c4; // lea 0(%rip), %r20 -> mov $0, %r20
case 0x488d2d: return 0x18c7c5; // lea 0(%rip), %r21 -> mov $0, %r21
case 0x488d35: return 0x18c7c6; // lea 0(%rip), %r22 -> mov $0, %r22
case 0x488d3d: return 0x18c7c7; // lea 0(%rip), %r23 -> mov $0, %r23
case 0x4c8d05: return 0x19c7c0; // lea 0(%rip), %r24 -> mov $0, %r24
case 0x4c8d0d: return 0x19c7c1; // lea 0(%rip), %r25 -> mov $0, %r25
case 0x4c8d15: return 0x19c7c2; // lea 0(%rip), %r26 -> mov $0, %r26
case 0x4c8d1d: return 0x19c7c3; // lea 0(%rip), %r27 -> mov $0, %r27
case 0x4c8d25: return 0x19c7c4; // lea 0(%rip), %r28 -> mov $0, %r28
case 0x4c8d2d: return 0x19c7c5; // lea 0(%rip), %r29 -> mov $0, %r29
case 0x4c8d35: return 0x19c7c6; // lea 0(%rip), %r30 -> mov $0, %r30
case 0x4c8d3d: return 0x19c7c7; // lea 0(%rip), %r31 -> mov $0, %r31
}
}
return 0;
}
@@ -225,7 +293,7 @@ static u32 relax_tlsdesc_to_le(u8 *loc) {
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. We can do this when we know the thread-local variable's TP-
// relative address at link-time.
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
static void relax_gd_to_le(u8 *loc, const ElfRel<E> &rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
@@ -269,7 +337,7 @@ static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
}
}
static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
static void relax_gd_to_ie(u8 *loc, const ElfRel<E> &rel, u64 val) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
@@ -299,10 +367,10 @@ static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
}
// Rewrite a function call to __tls_get_addr to a cheaper instruction
// sequence. The difference from relax_gd_to_le is that we are
// materializing a Dynamic Thread Pointer for the current ELF module
// instead of an address for a particular thread-local variable.
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
// sequence. The difference from relax_gd_to_le is that we are materializing
// the address of the beginning of TLS block instead of an address of a
// particular thread-local variable.
static void relax_ld_to_le(u8 *loc, const ElfRel<E> &rel, i64 tls_size) {
switch (rel.r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
@@ -311,10 +379,9 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
//
// The instructions are so short that we cannot rewrite them with
// "mov %fs:0, %rax" which is 9 bytes long. We use a shorter code
// sequence instead. Since "xor %eax, %eax" zero-clears %rax, the
// meaning is equivalent.
// Because the original instruction sequence is so short that we need a
// little bit of code golfing here. "mov %fs:0, %rax" is 9 byte long, so
// xor + mov is shorter. Note that `xor %eax, %eax` zero-clears %eax.
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
@@ -331,13 +398,12 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x48, 0x31, 0xc0, // xor %rax, %rax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x90, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = tls_size;
*(ul32 *)(loc + 6) = tls_size;
break;
}
case R_X86_64_PLTOFF64: {
@@ -368,11 +434,6 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
@@ -381,11 +442,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
u64 GOT = ctx.gotplt->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
check_range(ctx, i, val, lo, hi);
};
auto write32 = [&](u64 val) {
@@ -398,12 +462,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = val;
};
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
u64 GOT = ctx.gotplt->shdr.sh_addr;
switch (rel.r_type) {
case R_X86_64_8:
check(S + A, 0, 1 << 8);
@@ -420,7 +478,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write32s(S + A);
break;
case R_X86_64_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
break;
case R_X86_64_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
@@ -460,30 +517,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc = G + GOT + A - P;
break;
case R_X86_64_GOTPCRELX:
case R_X86_64_REX_GOTPCRELX:
case R_X86_64_CODE_4_GOTPCRELX:
// We always want to relax GOTPCRELX relocs even if --no-relax
// was given because some static PIE runtime code depends on these
// relaxations.
if (sym.is_pcrel_linktime_const(ctx)) {
u32 insn = relax_gotpcrelx(loc - 2);
i64 val = S + A - P;
if (insn && (i32)val == val) {
if (sym.is_pcrel_linktime_const(ctx) && is_int(S + A - P, 32)) {
if (u32 insn = relax_gotpcrelx(loc, rel)) {
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = val;
break;
}
}
write32s(G + GOT + A - P);
break;
case R_X86_64_REX_GOTPCRELX:
if (sym.is_pcrel_linktime_const(ctx)) {
u32 insn = relax_rex_gotpcrelx(loc - 3);
i64 val = S + A - P;
if (insn && (i32)val == val) {
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = val;
*(ul32 *)loc = S + A - P;
break;
}
}
@@ -516,18 +559,22 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc = S + A - ctx.tp_addr;
break;
case R_X86_64_GOTTPOFF:
case R_X86_64_CODE_4_GOTTPOFF:
if (sym.has_gottp(ctx)) {
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_gottpoff(loc - 3);
u32 insn = relax_gottpoff(loc, rel);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S - ctx.tp_addr);
assert(A == -4);
}
break;
case R_X86_64_CODE_6_GOTTPOFF:
write32s(sym.get_gottp_addr(ctx) + A - P);
break;
case R_X86_64_GOTPC32_TLSDESC:
case R_X86_64_CODE_4_GOTPC32_TLSDESC:
// x86-64 TLSDESC uses the following code sequence to materialize
// a TP-relative address in %rax.
//
@@ -536,16 +583,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// call *(%rax)
// R_X86_64_TLSDESC_CALL foo
//
// We may relax the instructions to the following for non-dlopen'd DSO
//
// mov foo@GOTTPOFF(%rip), %rax
// nop
//
// or to the following for executable.
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// mov $foo@TPOFF, %rax
// nop
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// mov foo@GOTTPOFF(%rip), %rax
// nop
//
// We allow the following alternative code sequence too because
// LLVM emits such code.
//
@@ -557,17 +606,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (sym.has_tlsdesc(ctx)) {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
} else if (sym.has_gottp(ctx)) {
u32 insn = relax_tlsdesc_to_ie(loc - 3);
u32 insn = relax_tlsdesc_to_ie(loc, rel);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
Fatal(ctx) << *this << ": illegal instruction sequence for " << rel;
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_tlsdesc_to_le(loc - 3);
u32 insn = relax_tlsdesc_to_le(loc, rel);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
Fatal(ctx) << *this << ": illegal instruction sequence for " << rel;
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
@@ -575,9 +624,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
break;
case R_X86_64_TLSDESC_CALL:
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else {
if (!sym.has_tlsdesc(ctx)) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
@@ -619,11 +666,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
check_range(ctx, i, val, lo, hi);
};
auto write32 = [&](u64 val) {
@@ -636,13 +687,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = val;
};
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_X86_64_8:
check(S + A, 0, 1 << 8);
@@ -707,8 +751,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
@@ -743,9 +785,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_32S:
scan_absrel(ctx, sym, rel);
break;
case R_X86_64_64:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_X86_64_PC8:
case R_X86_64_PC16:
case R_X86_64_PC32:
@@ -760,6 +799,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_GOTPCREL64:
case R_X86_64_GOTPCRELX:
case R_X86_64_REX_GOTPCRELX:
case R_X86_64_CODE_4_GOTPCRELX:
sym.flags |= NEEDS_GOT;
break;
case R_X86_64_PLT32:
@@ -768,8 +808,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_PLT;
break;
case R_X86_64_TLSGD:
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
ctx.arg.is_static) {
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
i++;
@@ -783,18 +822,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_TLSLD:
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld = true;
break;
case R_X86_64_GOTTPOFF:
if (ctx.arg.relax && relax_gottpoff(loc - 3) &&
sym.is_tprel_linktime_const(ctx)) {
// do nothing
} else {
case R_X86_64_CODE_4_GOTTPOFF:
if (!ctx.arg.relax || !sym.is_tprel_linktime_const(ctx) ||
!relax_gottpoff(loc, rel))
sym.flags |= NEEDS_GOTTP;
}
break;
case R_X86_64_CODE_6_GOTTPOFF:
sym.flags |= NEEDS_GOTTP;
break;
case R_X86_64_TLSDESC_CALL:
scan_tlsdesc(ctx, sym);
@@ -803,12 +843,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_TPOFF64:
check_tlsle(ctx, sym, rel);
break;
case R_X86_64_64:
case R_X86_64_GOTOFF64:
case R_X86_64_DTPOFF32:
case R_X86_64_DTPOFF64:
case R_X86_64_SIZE32:
case R_X86_64_SIZE64:
case R_X86_64_GOTPC32_TLSDESC:
case R_X86_64_CODE_4_GOTPC32_TLSDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
@@ -816,4 +858,95 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
} // namespace mold::elf
// Intel CET is a relatively new CPU feature to enhance security by
// protecting control flow integrity. If the feature is enabled, indirect
// branches (i.e. branch instructions that take a register instead of an
// immediate) must land on a "landing pad" instruction, or a CPU-level fault
// will raise. That prevents an attacker to branch to a middle of a random
// function, making ROP or JOP much harder to conduct.
//
// On x86-64, the landing pad instruction is ENDBR64. That is actually a
// repurposed NOP instruction to provide binary compatibility with older
// hardware that doesn't support CET.
//
// The problem here is that the compiler always emits a landing pad at the
// beginning fo a global function because it doesn't know whether or not the
// function's address is taken in other translation units. As a result, the
// resulting binary contains more landing pads than necessary.
//
// This function rewrites a landing pad with a nop if the function's address
// was not actually taken. We can do what the compiler cannot because we
// know about all translation units.
void rewrite_endbr(Context<E> &ctx) {
Timer t(ctx, "rewrite_endbr");
constexpr u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
constexpr u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
// Rewrite all endbr64 instructions referred to by function symbols with
// NOPs. We handle only global symbols because the compiler doesn't emit
// an endbr64 for a file-scoped function in the first place if its address
// is not taken within the file.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file == file && sym->esym().st_type == STT_FUNC) {
if (InputSection<E> *isec = sym->get_input_section();
isec && (isec->shdr().sh_flags & SHF_EXECINSTR)) {
if (OutputSection<E> *osec = isec->output_section) {
u8 *buf = ctx.buf + osec->shdr.sh_offset + isec->offset + sym->value;
if (memcmp(buf, endbr64, 4) == 0)
memcpy(buf, nop, 4);
}
}
}
}
});
auto write_back = [&](InputSection<E> *isec, i64 offset) {
// If isec has an endbr64 at a given offset, copy that instruction to
// the output buffer, possibly overwriting a nop written in the above
// loop.
if (isec && isec->output_section &&
(isec->shdr().sh_flags & SHF_EXECINSTR) &&
0 <= offset && offset <= isec->contents.size() - 4 &&
memcmp(isec->contents.data() + offset, endbr64, 4) == 0)
memcpy(ctx.buf + isec->output_section->shdr.sh_offset + isec->offset + offset,
endbr64, 4);
};
// Write back endbr64 instructions if they are referred to by address-taking
// relocations.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC)) {
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
if (!is_func_call_rel(rel)) {
Symbol<E> *sym = file->symbols[rel.r_sym];
if (sym->esym().st_type == STT_SECTION)
write_back(sym->get_input_section(), rel.r_addend);
else
write_back(sym->get_input_section(), sym->value);
}
}
}
}
});
// We record addresses of some symbols in the ELF header, .dynamic or in
// .dynsym. We need to retain endbr64s for such symbols.
auto keep = [&](Symbol<E> *sym) {
if (sym)
write_back(sym->get_input_section(), sym->value);
};
keep(ctx.arg.entry);
keep(ctx.arg.init);
keep(ctx.arg.fini);
if (ctx.dynsym)
for (Symbol<E> *sym : ctx.dynsym->symbols)
if (sym && sym->is_exported)
keep(sym);
}
} // namespace mold
+269 -129
View File
@@ -1,23 +1,27 @@
#include "mold.h"
#include <filesystem>
#include <random>
#include <regex>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <system_error>
#include <tbb/global_control.h>
#include <unordered_set>
#ifdef _WIN32
# define isatty _isatty
# define STDERR_FILENO (_fileno(stderr))
#else
# include <unistd.h>
#if __has_include(<sys/utsname.h>)
# include <sys/utsname.h>
#endif
namespace mold::elf {
#if __has_include(<unistd.h>)
# include <unistd.h>
#else
# define isatty _isatty
# define STDERR_FILENO (_fileno(stderr))
#endif
inline const char helpmsg[] = R"(
namespace mold {
static const char helpmsg[] = R"(
Options:
--help Report usage information
-v, --version Report version information
@@ -37,6 +41,8 @@ Options:
-N, --omagic Do not page align data; do not make text readonly
--no-omagic
-O NUMBER Ignored
-P AUDITLIB, --depaudit AUDITLIB
Set DT_DEPAUDIT to the specified value
-S, --strip-debug Strip .debug_* sections
-T FILE, --script FILE Read linker script
-X, --discard-locals Discard temporary local symbols
@@ -44,7 +50,8 @@ Options:
-f SHLIB, --auxiliary SHLIB Set DT_AUXILIARY to the specified value
-h LIBNAME, --soname LIBNAME
Set shared library name
-l LIBNAME Search for a given library
-l LIBNAME, --library LIBNAME
Search for a given library
-m TARGET Set target
-o FILE, --output FILE Set output filename
-q, --emit-relocs Leaves relocation sections in the output
@@ -71,7 +78,8 @@ Options:
--no-apply-dynamic-relocs
--as-needed Only set DT_NEEDED if used
--no-as-needed
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
--audit LIBNAME Set DT_AUDIT to the specified value
--build-id [none,md5,sha1,sha256,fast,uuid,HEXSTRING]
Generate build ID
--no-build-id
--chroot DIR Set a given path to the root directory
@@ -85,11 +93,14 @@ Options:
--defsym=SYMBOL=VALUE Define a symbol alias
--demangle Demangle C++ symbols in log messages (default)
--no-demangle
--detach Create separate debug info file in the background (default)
--no-detach
--enable-new-dtags Emit DT_RUNPATH for --rpath (default)
--disable-new-dtags Emit DT_RPATH for --rpath
--execute-only Make executable segments unreadable
--dp Ignored
--dynamic-list=FILE Read a list of dynamic symbols (implies -Bsymbolic)
--dynamic-list-data Add data symbols to dynamic symbols
--eh-frame-hdr Create .eh_frame_hdr section
--no-eh-frame-hdr
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries as hidden
@@ -119,7 +130,8 @@ Options:
--oformat=binary Omit ELF, section, and program headers
--pack-dyn-relocs=[relr,none]
Pack dynamic relocations
--package-metadata=STRING Set a given string to .note.package
--package-metadata=PERCENT_ENCODED_STRING
Set a given string to .note.package
--perf Print performance statistics
--pie, --pic-executable Create a position-independent executable
--no-pie, --no-pic-executable
@@ -143,6 +155,8 @@ Options:
--rpath-link DIR Ignored
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
--section-start=SECTION=ADDR Set address for section
--separate-debug-file[=FILE] Separate debug info to the specified file
--no-separate-debug-file
--shared, --Bshareable Create a shared library
--shuffle-sections[=SEED] Randomize the output by shuffling input sections
--sort-common Ignored
@@ -209,13 +223,15 @@ Options:
-z stack-size=VALUE Set the size of the stack segment
-z relro Make some sections read-only after relocation (default)
-z norelro
-z rewrite-endbr Rewrite indirect branch target instructions with NOPs
-z norewrite-endbr
-z rodynamic Make the .dynamic section read-only
-z text Report error if DT_TEXTREL is set
-z notext
-z textoff
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf64-loongarch elf32-loongarch
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha elf64loongarch elf32loongarch)";
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf64-bigaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-loongarch elf32-loongarch
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64elf aarch64linux aarch64elfb aarch64linuxb elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux shelf_linux elf64loongarch elf32loongarch)";
template <typename E>
static std::vector<std::string_view>
@@ -309,7 +325,7 @@ static i64 get_default_thread_count() {
return std::min(n, 32);
}
static inline std::string_view string_trim(std::string_view str) {
static std::string_view string_trim(std::string_view str) {
size_t pos = str.find_first_not_of(" \t");
if (pos == str.npos)
return "";
@@ -365,6 +381,15 @@ static i64 parse_number(Context<E> &ctx, std::string opt,
return ret;
}
static char from_hex(char c) {
if ('0' <= c && c <= '9')
return c - '0';
if ('a' <= c && c <= 'f')
return c - 'a' + 10;
assert('A' <= c && c <= 'F');
return c - 'A' + 10;
}
template <typename E>
static std::vector<u8> parse_hex_build_id(Context<E> &ctx, std::string_view arg) {
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
@@ -373,23 +398,34 @@ static std::vector<u8> parse_hex_build_id(Context<E> &ctx, std::string_view arg)
if (!std::regex_match(arg.begin(), arg.end(), re))
Fatal(ctx) << "invalid build-id: " << arg;
arg = arg.substr(2);
auto fn = [](char c) {
if ('0' <= c && c <= '9')
return c - '0';
if ('a' <= c && c <= 'f')
return c - 'a' + 10;
assert('A' <= c && c <= 'F');
return c - 'A' + 10;
};
std::vector<u8> vec;
for (i64 i = 0; i < arg.size(); i += 2)
vec.push_back((fn(arg[i]) << 4) | fn(arg[i + 1]));
for (i64 i = 2; i < arg.size(); i += 2)
vec.push_back((from_hex(arg[i]) << 4) | from_hex(arg[i + 1]));
return vec;
}
template <typename E>
static std::string
parse_package_metadata(Context<E> &ctx, std::string_view arg) {
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
static std::regex re(R"(([^%]|%[0-9a-fA-F][0-9a-fA-F])*)", flags);
if (!std::regex_match(arg.begin(), arg.end(), re))
Fatal(ctx) << "--package-metadata: invalid string: " << arg;
std::ostringstream out;
while (!arg.empty()) {
if (arg[0] == '%') {
out << (char)((from_hex(arg[1]) << 4) | from_hex(arg[2]));
arg = arg.substr(3);
} else {
out << arg[0];
arg = arg.substr(1);
}
}
return out.str();
}
static std::vector<std::string_view>
split_by_comma_or_colon(std::string_view str) {
std::vector<std::string_view> vec;
@@ -401,7 +437,7 @@ split_by_comma_or_colon(std::string_view str) {
break;
}
vec.push_back(str.substr(0, pos));
str = str.substr(pos);
str = str.substr(pos + 1);
}
return vec;
}
@@ -410,8 +446,7 @@ template <typename E>
static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
MappedFile *mf = must_open_file(ctx, std::string(path));
std::string_view data((char *)mf->data, mf->size);
ctx.arg.retain_symbols_file.reset(new std::unordered_set<std::string_view>);
std::vector<Symbol<E> *> vec;
while (!data.empty()) {
size_t pos = data.find('\n');
@@ -427,14 +462,15 @@ static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
name = string_trim(name);
if (!name.empty())
ctx.arg.retain_symbols_file->insert(name);
vec.push_back(get_symbol(ctx, name));
}
ctx.arg.retain_symbols_file = std::move(vec);
}
static bool is_file(std::string_view path) {
struct stat st;
return stat(std::string(path).c_str(), &st) == 0 &&
(st.st_mode & S_IFMT) != S_IFDIR;
static bool is_file(const std::filesystem::path& path) {
std::error_code error;
return !std::filesystem::is_directory(path, error) && !error;
}
template <typename E>
@@ -510,6 +546,33 @@ parse_defsym_value(Context<E> &ctx, std::string_view s) {
return get_symbol(ctx, s);
}
// Parses a kernel version string, e.g. "6.8.0-47-generic".
static std::tuple<int, int, int>
parse_kernel_version(std::string str) {
auto flags = std::regex_constants::optimize | std::regex_constants::ECMAScript;
static std::regex re(R"(^(\d+)\.(\d+)\.(\d+))", flags);
std::smatch m;
if (!std::regex_search(str, m, re))
return {0, 0, 0};
return {std::stoi(m[1]), std::stoi(m[2]), std::stoi(m[3])};
}
// Version 6.11 and 6.12 of the Linux kernel does not return ETXTBSY for
// open(2) on an executable file that is currently running. This function
// returns true if we are running on a Linux kernel older than 6.11 or newer
// than 6.12.
static bool returns_etxtbsy() {
#if HAVE_UNAME
struct utsname buf;
if (uname(&buf) == 0 && strcmp(buf.sysname, "Linux") == 0) {
std::tuple<int, int, int> ver = parse_kernel_version(buf.release);
return ver < std::tuple{6, 11, 0} || std::tuple{6, 13, 0} <= ver;
}
#endif
return false;
}
template <typename E>
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::span<std::string_view> args = ctx.cmdline_args;
@@ -526,8 +589,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::optional<SeparateCodeKind> z_separate_code;
std::optional<bool> report_undefined;
std::optional<bool> z_relro;
std::optional<bool> z_dynamic_undefined_weak;
std::optional<std::string> separate_debug_file;
std::optional<u64> shuffle_sections_seed;
std::unordered_set<std::string_view> rpaths;
std::vector<std::string_view> version_scripts;
auto add_rpath = [&](std::string_view arg) {
if (rpaths.insert(arg).second) {
@@ -537,21 +603,24 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
}
};
// RISC-V object files contains lots of local symbols, so by default
// we discard them. This is compatible with GNU ld.
if constexpr (is_riscv<E>)
// RISC-V and LoongArch object files contains lots of local symbols,
// so by default we discard them. This is compatible with GNU ld.
if constexpr (is_riscv<E> || is_loongarch<E>)
ctx.arg.discard_locals = true;
// It looks like the SPARC's dynamic linker takes both RELA's r_addend
// and the value at the relocated place. So we don't want to write
// values to relocated places.
if constexpr (is_sparc<E>)
ctx.arg.apply_dynamic_relocs = false;
// For some reason, SH4 always stores relocation addends to
// relocated places even though its RELA.
if constexpr (is_sh4<E>)
ctx.arg.apply_dynamic_relocs = true;
// We generally don't need to write addends to relocated places if the
// relocation type is RELA because RELA records contain addends.
// However, there are too much code that wrongly assumes that addends
// are written to both RELA records and relocated places, so we write
// addends to relocated places by default. There are a few exceptions:
//
// - It looks like the SPARC's dynamic linker takes both RELA's r_addend
// and the value at the relocated place. So we don't want to write
// values to relocated places.
//
// - Static PIE binaries crash on startup in some RISC-V environment if
// we write addends to relocated places.
ctx.arg.apply_dynamic_relocs = !is_sparc<E> && !is_riscv<E>;
auto read_arg = [&](std::string name) {
for (const std::string &opt : add_dashes(name)) {
@@ -644,54 +713,62 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("V")) {
Out(ctx) << get_mold_version()
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
<< " aarch64linux\n armelf_linux_eabi\n elf64lriscv\n"
<< " aarch64elf\n aarch64linux\n aarch64elfb\n"
<< " aarch64linuxb\n armelf_linux_eabi\n elf64lriscv\n"
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
<< " elf64alpha\n elf64loongarch\n elf32loongarch";
<< " shelf_linux\n elf64loongarch\n elf32loongarch\n"
<< " arclinux";
version_shown = true;
} else if (read_arg("mllvm")) {
ctx.arg.plugin_opt.emplace_back(arg);
} else if (read_arg("m")) {
if (arg == "elf_x86_64") {
ctx.arg.emulation = X86_64::target_name;
ctx.arg.emulation = X86_64::name;
} else if (arg == "elf_i386") {
ctx.arg.emulation = I386::target_name;
} else if (arg == "aarch64linux") {
ctx.arg.emulation = ARM64::target_name;
ctx.arg.emulation = I386::name;
} else if (arg == "aarch64elf" || arg == "aarch64linux") {
ctx.arg.emulation = ARM64LE::name;
} else if (arg == "aarch64elfb" || arg == "aarch64linuxb") {
ctx.arg.emulation = ARM64BE::name;
} else if (arg == "armelf_linux_eabi") {
ctx.arg.emulation = ARM32::target_name;
ctx.arg.emulation = ARM32::name;
} else if (arg == "elf64lriscv") {
ctx.arg.emulation = RV64LE::target_name;
ctx.arg.emulation = RV64LE::name;
} else if (arg == "elf64briscv") {
ctx.arg.emulation = RV64BE::target_name;
ctx.arg.emulation = RV64BE::name;
} else if (arg == "elf32lriscv") {
ctx.arg.emulation = RV32LE::target_name;
ctx.arg.emulation = RV32LE::name;
} else if (arg == "elf32briscv") {
ctx.arg.emulation = RV32BE::target_name;
ctx.arg.emulation = RV32BE::name;
} else if (arg == "elf32ppc" || arg == "elf32ppclinux") {
ctx.arg.emulation = PPC32::target_name;
ctx.arg.emulation = PPC32::name;
} else if (arg == "elf64ppc") {
ctx.arg.emulation = PPC64V1::target_name;
ctx.arg.emulation = PPC64V1::name;
} else if (arg == "elf64lppc") {
ctx.arg.emulation = PPC64V2::target_name;
ctx.arg.emulation = PPC64V2::name;
} else if (arg == "elf64_s390") {
ctx.arg.emulation = S390X::target_name;
ctx.arg.emulation = S390X::name;
} else if (arg == "elf64_sparc") {
ctx.arg.emulation = SPARC64::target_name;
ctx.arg.emulation = SPARC64::name;
} else if (arg == "m68kelf") {
ctx.arg.emulation = M68K::target_name;
} else if (arg == "shlelf_linux") {
ctx.arg.emulation = SH4::target_name;
} else if (arg == "elf64alpha") {
ctx.arg.emulation = ALPHA::target_name;
ctx.arg.emulation = M68K::name;
} else if (arg == "shlelf" || arg == "shlelf_linux") {
ctx.arg.emulation = SH4LE::name;
} else if (arg == "shelf" || arg == "shelf_linux") {
ctx.arg.emulation = SH4BE::name;
} else if (arg == "elf64loongarch") {
ctx.arg.emulation = LOONGARCH64::target_name;
ctx.arg.emulation = LOONGARCH64::name;
} else if (arg == "elf32loongarch") {
ctx.arg.emulation = LOONGARCH32::target_name;
ctx.arg.emulation = LOONGARCH32::name;
} else if (arg == "arclinux") {
ctx.arg.emulation = ARC::name;
} else {
Fatal(ctx) << "unknown -m argument: " << arg;
}
} else if (read_flag("end-lib")) {
remaining.push_back("--end-lib");
remaining.emplace_back("--end-lib");
} else if (read_flag("export-dynamic") || read_flag("E")) {
ctx.arg.export_dynamic = true;
} else if (read_flag("no-export-dynamic")) {
@@ -721,11 +798,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("print-map") || read_flag("M")) {
ctx.arg.print_map = true;
} else if (read_flag("Bstatic") || read_flag("dn") || read_flag("static")) {
ctx.arg.is_static = true;
remaining.push_back("--Bstatic");
ctx.arg.static_ = true;
remaining.emplace_back("--Bstatic");
} else if (read_flag("Bdynamic") || read_flag("dy")) {
ctx.arg.is_static = false;
remaining.push_back("--Bdynamic");
ctx.arg.static_ = false;
remaining.emplace_back("--Bdynamic");
} else if (read_flag("shared") || read_flag("Bshareable")) {
ctx.arg.shared = true;
} else if (read_arg("spare-dynamic-tags")) {
@@ -734,7 +811,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.spare_program_headers
= parse_number(ctx, "spare-program-headers", arg);
} else if (read_flag("start-lib")) {
remaining.push_back("--start-lib");
remaining.emplace_back("--start-lib");
} else if (read_flag("start-stop")) {
ctx.arg.start_stop = true;
} else if (read_arg("dependency-file")) {
@@ -753,6 +830,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.demangle = true;
} else if (read_flag("no-demangle")) {
ctx.arg.demangle = false;
} else if (read_flag("detach")) {
ctx.arg.detach = true;
} else if (read_flag("no-detach")) {
ctx.arg.detach = false;
} else if (read_flag("default-symver")) {
ctx.arg.default_symver = true;
} else if (read_flag("noinhibit-exec")) {
@@ -773,7 +854,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("filler")) {
ctx.arg.filler = parse_hex(ctx, "filler", arg);
} else if (read_arg("L") || read_arg("library-path")) {
ctx.arg.library_paths.push_back(std::string(arg));
ctx.arg.library_paths.emplace_back(arg);
} else if (read_arg("sysroot")) {
ctx.arg.sysroot = arg;
} else if (read_arg("unique")) {
@@ -815,9 +896,16 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else {
Fatal(ctx) << "invalid --hash-style argument: " << arg;
}
} else if (read_arg("soname") ||
read_arg("h")) {
} else if (read_arg("soname") || read_arg("h")) {
ctx.arg.soname = arg;
} else if (read_arg("audit")) {
if (!ctx.arg.audit.empty())
ctx.arg.audit += ':';
ctx.arg.audit += std::string(arg);
} else if (read_arg("depaudit") || read_arg("P")) {
if (!ctx.arg.depaudit.empty())
ctx.arg.depaudit += ':';
ctx.arg.depaudit += std::string(arg);
} else if (read_flag("allow-multiple-definition")) {
ctx.arg.allow_multiple_definition = true;
} else if (read_flag("apply-dynamic-relocs")) {
@@ -860,7 +948,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
read_z_flag("nopack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = false;
} else if (read_arg("package-metadata")) {
ctx.arg.package_metadata = arg;
ctx.arg.package_metadata = parse_package_metadata(ctx, arg);
} else if (read_flag("stats")) {
ctx.arg.stats = true;
Counter::enabled = true;
@@ -904,7 +992,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.wrap.insert(arg);
} else if (read_flag("omagic") || read_flag("N")) {
ctx.arg.omagic = true;
ctx.arg.is_static = true;
ctx.arg.static_ = true;
} else if (read_flag("no-omagic")) {
ctx.arg.omagic = false;
} else if (read_arg("oformat")) {
@@ -1000,6 +1088,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.z_origin = true;
} else if (read_z_flag("nodefaultlib")) {
ctx.arg.z_nodefaultlib = true;
} else if (read_eq("separate-debug-file")) {
separate_debug_file = arg;
} else if (read_flag("separate-debug-file")) {
separate_debug_file = "";
} else if (read_flag("no-separate-debug-file")) {
separate_debug_file.reset();
} else if (read_z_flag("separate-loadable-segments")) {
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
} else if (read_z_flag("separate-code")) {
@@ -1009,17 +1103,27 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_z_arg("stack-size")) {
ctx.arg.z_stack_size = parse_number(ctx, "-z stack-size", arg);
} else if (read_z_flag("dynamic-undefined-weak")) {
ctx.arg.z_dynamic_undefined_weak = true;
z_dynamic_undefined_weak = true;
} else if (read_z_flag("nodynamic-undefined-weak")) {
ctx.arg.z_dynamic_undefined_weak = false;
z_dynamic_undefined_weak = false;
} else if (read_z_flag("sectionheader")) {
ctx.arg.z_sectionheader = true;
} else if (read_z_flag("nosectionheader")) {
ctx.arg.z_sectionheader = false;
} else if (read_z_flag("rewrite-endbr")) {
ctx.arg.z_rewrite_endbr = true;
} else if (read_z_flag("rodynamic")) {
ctx.arg.z_rodynamic = true;
} else if (read_z_flag("x86-64-v2")) {
ctx.arg.z_x86_64_isa_level |= GNU_PROPERTY_X86_ISA_1_V2;
} else if (read_z_flag("x86-64-v3")) {
ctx.arg.z_x86_64_isa_level |= GNU_PROPERTY_X86_ISA_1_V3;
} else if (read_z_flag("x86-64-v4")) {
ctx.arg.z_x86_64_isa_level |= GNU_PROPERTY_X86_ISA_1_V4;
} else if (read_z_flag("rewrite-endbr")) {
if constexpr (!is_x86_64<E>)
Fatal(ctx) << "-z rewrite-endbr is supported only on x86-64";
ctx.arg.z_rewrite_endbr = true;
} else if (read_z_flag("norewrite-endbr")) {
ctx.arg.z_rewrite_endbr = false;
} else if (read_flag("nmagic")) {
ctx.arg.nmagic = true;
} else if (read_flag("no-nmagic")) {
@@ -1040,6 +1144,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.print_gc_sections = true;
} else if (read_flag("no-print-gc-sections")) {
ctx.arg.print_gc_sections = false;
} else if (read_arg("discard-section")) {
ctx.arg.discard_section.insert(arg);
} else if (read_arg("no-discard-section")) {
ctx.arg.discard_section.erase(arg);
} else if (read_arg("icf")) {
if (arg == "all") {
ctx.arg.icf = true;
@@ -1070,25 +1178,23 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("plugin")) {
ctx.arg.plugin = arg;
} else if (read_arg("plugin-opt")) {
ctx.arg.plugin_opt.push_back(std::string(arg));
ctx.arg.plugin_opt.emplace_back(arg);
} else if (read_flag("lto-cs-profile-generate")) {
ctx.arg.plugin_opt.push_back("cs-profile-generate");
ctx.arg.plugin_opt.emplace_back("cs-profile-generate");
} else if (read_arg("lto-cs-profile-file")) {
ctx.arg.plugin_opt.push_back("cs-profile-path=" + std::string(arg));
} else if (read_flag("lto-debug-pass-manager")) {
ctx.arg.plugin_opt.push_back("debug-pass-manager");
ctx.arg.plugin_opt.emplace_back("debug-pass-manager");
} else if (read_flag("disable-verify")) {
ctx.arg.plugin_opt.push_back("disable-verify");
ctx.arg.plugin_opt.emplace_back("disable-verify");
} else if (read_flag("lto-emit-asm")) {
ctx.arg.plugin_opt.push_back("emit-asm");
} else if (read_arg("thinlto-jobs")) {
ctx.arg.plugin_opt.push_back("jobs=" + std::string(arg));
ctx.arg.plugin_opt.emplace_back("emit-asm");
} else if (read_flag("no-legacy-pass-manager")) {
ctx.arg.plugin_opt.push_back("legacy-pass-manager");
ctx.arg.plugin_opt.emplace_back("legacy-pass-manager");
} else if (read_arg("lto-partitions")) {
ctx.arg.plugin_opt.push_back("lto-partitions=" + std::string(arg));
} else if (read_flag("no-lto-legacy-pass-manager")) {
ctx.arg.plugin_opt.push_back("new-pass-manager");
ctx.arg.plugin_opt.emplace_back("new-pass-manager");
} else if (read_arg("lto-obj-path")) {
ctx.arg.plugin_opt.push_back("obj-path=" + std::string(arg));
} else if (read_arg("opt-remarks-filename")) {
@@ -1101,7 +1207,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("opt-remarks-passes")) {
ctx.arg.plugin_opt.push_back("opt-remarks-passes=" + std::string(arg));
} else if (read_flag("opt-remarks-with_hotness")) {
ctx.arg.plugin_opt.push_back("opt-remarks-with-hotness");
ctx.arg.plugin_opt.emplace_back("opt-remarks-with-hotness");
} else if (args[0].starts_with("-lto-O")) {
ctx.arg.plugin_opt.push_back("O" + std::string(args[0].substr(6)));
args = args.subspan(1);
@@ -1114,13 +1220,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_arg("lto-sample-profile")) {
ctx.arg.plugin_opt.push_back("sample-profile=" + std::string(arg));
} else if (read_flag("save-temps")) {
ctx.arg.plugin_opt.push_back("save-temps");
ctx.arg.plugin_opt.emplace_back("save-temps");
} else if (read_flag("thinlto-emit-imports-files")) {
ctx.arg.plugin_opt.push_back("thinlto-emit-imports-files");
ctx.arg.plugin_opt.emplace_back("thinlto-emit-imports-files");
} else if (read_arg("thinlto-index-only")) {
ctx.arg.plugin_opt.push_back("thinlto-index-only=" + std::string(arg));
} else if (read_flag("thinlto-index-only")) {
ctx.arg.plugin_opt.push_back("thinlto-index-only");
ctx.arg.plugin_opt.emplace_back("thinlto-index-only");
} else if (read_arg("thinlto-object-suffix-replace")) {
ctx.arg.plugin_opt.push_back("thinlto-object-suffix-replace=" +
std::string(arg));
@@ -1177,7 +1283,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (arg == "sha1") {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 20;
} else if (arg == "sha256") {
} else if (arg == "sha256" || arg == "fast") {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 32;
} else if (arg.starts_with("0x") || arg.starts_with("0X")) {
@@ -1199,6 +1305,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.auxiliary.push_back(arg);
} else if (read_arg("filter") || read_arg("F")) {
ctx.arg.filter.push_back(arg);
} else if (read_flag("allow-shlib-undefined")) {
ctx.arg.allow_shlib_undefined = true;
} else if (read_flag("no-allow-shlib-undefined")) {
ctx.arg.allow_shlib_undefined = false;
} else if (read_arg("O")) {
} else if (read_flag("EB")) {
} else if (read_flag("EL")) {
@@ -1216,8 +1326,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("enable-new-dtags")) {
} else if (read_flag("disable-new-dtags")) {
} else if (read_flag("nostdlib")) {
} else if (read_flag("allow-shlib-undefined")) {
} else if (read_flag("no-allow-shlib-undefined")) {
} else if (read_flag("no-add-needed")) {
} else if (read_flag("no-call-graph-profile-sort")) {
} else if (read_flag("no-copy-dt-needed-entries")) {
@@ -1241,38 +1349,35 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_z_arg("common-page-size")) {
} else if (read_flag("no-keep-memory")) {
} else if (read_arg("max-cache-size")) {
} else if (read_flag("mmap-output-file")) {
} else if (read_flag("no-mmap-output-file")) {
} else if (read_arg("version-script")) {
// --version-script is treated as positional arguments even though
// they are actually not positional. This is because linker scripts
// (a positional argument) can also specify a version script, and
// it's better to consolidate parsing in read_input_files. In
// particular, version scripts can modify ctx.default_version which
// we initialize *after* parsing non-positional args, so the parsing
// cannot be done right here.
remaining.push_back("--version-script=" + std::string(arg));
version_scripts.push_back(arg);
} else if (read_arg("dynamic-list")) {
ctx.arg.Bsymbolic = BSYMBOLIC_ALL;
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
} else if (read_arg("dynamic-list-data")) {
ctx.arg.dynamic_list_data = true;
} else if (read_arg("export-dynamic-symbol")) {
ctx.dynamic_list_patterns.push_back({arg, "<command line>"});
} else if (read_arg("export-dynamic-symbol-list")) {
append(ctx.dynamic_list_patterns, parse_dynamic_list(ctx, arg));
} else if (read_flag("as-needed")) {
remaining.push_back("--as-needed");
remaining.emplace_back("--as-needed");
} else if (read_flag("no-as-needed")) {
remaining.push_back("--no-as-needed");
remaining.emplace_back("--no-as-needed");
} else if (read_flag("whole-archive")) {
remaining.push_back("--whole-archive");
remaining.emplace_back("--whole-archive");
} else if (read_flag("no-whole-archive")) {
remaining.push_back("--no-whole-archive");
} else if (read_arg("l")) {
remaining.emplace_back("--no-whole-archive");
} else if (read_arg("l") || read_arg("library")) {
remaining.push_back("-l" + std::string(arg));
} else if (read_arg("script") || read_arg("T")) {
remaining.push_back(std::string(arg));
remaining.emplace_back(arg);
} else if (read_flag("push-state")) {
remaining.push_back("--push-state");
remaining.emplace_back("--push-state");
} else if (read_flag("pop-state")) {
remaining.push_back("--pop-state");
remaining.emplace_back("--pop-state");
} else if (args[0].starts_with("-z") && args[0].size() > 2) {
Warn(ctx) << "unknown command line option: " << args[0];
args = args.subspan(1);
@@ -1283,9 +1388,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "unknown command line option: -dynamic; -dynamic is a "
<< "macOS linker's option. mold does not support macOS.";
} else {
if (args[0][0] == '-')
if (args[0].starts_with('-'))
Fatal(ctx) << "unknown command line option: " << args[0];
remaining.push_back(std::string(args[0]));
remaining.emplace_back(args[0]);
args = args.subspan(1);
}
}
@@ -1328,7 +1433,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
}
if (ctx.arg.relocatable)
ctx.arg.is_static = true;
ctx.arg.static_ = true;
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
if (shuffle_sections_seed)
@@ -1344,6 +1449,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
else if (!ctx.arg.section_order.empty())
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
// `-z dynamic-undefined-weak` is enabled by default for DSOs.
if (z_dynamic_undefined_weak)
ctx.arg.z_dynamic_undefined_weak = *z_dynamic_undefined_weak;
else
ctx.arg.z_dynamic_undefined_weak = ctx.arg.shared;
// --section-order implies `-z norelro`
if (z_relro)
ctx.arg.z_relro = *z_relro;
@@ -1360,10 +1471,12 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "-auxiliary may not be used without -shared";
}
// Even though SH4 is RELA, addends in its relocation records are always
// zero, and actual addends are written to relocated places. So we need
// to handle it as an exception.
if constexpr (!E::is_rela || is_sh4<E>)
if (!ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
<< E::target_name;
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on " << E::name;
if constexpr (is_sparc<E>)
if (ctx.arg.apply_dynamic_relocs)
@@ -1382,15 +1495,43 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.repro = true;
if (ctx.arg.default_symver) {
std::string ver = ctx.arg.soname.empty() ?
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
std::string ver = ctx.arg.soname;
if (ver.empty())
ver = path_filename(ctx.arg.output);
ctx.arg.version_definitions.push_back(ver);
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
}
for (std::string_view path : version_scripts) {
auto open = [&] {
if (MappedFile *mf = open_file(ctx, std::string(path)))
return mf;
for (std::string_view dir : ctx.arg.library_paths)
if (MappedFile *mf =
open_file(ctx, std::string(dir) + "/" + std::string(path)))
return mf;
Fatal(ctx) << "--version-script: file not found: " << path;
};
ReaderContext rctx;
Script(ctx, rctx, open()).parse_version_script();
}
if (separate_debug_file) {
if (separate_debug_file->empty())
ctx.arg.separate_debug_file = ctx.arg.output + ".dbg";
else
ctx.arg.separate_debug_file = *separate_debug_file;
}
if (ctx.arg.shared && warn_shared_textrel)
ctx.arg.warn_textrel = true;
// We don't want the background process to write to stdout
if (ctx.arg.stats || ctx.arg.perf)
ctx.arg.detach = false;
ctx.arg.undefined.push_back(ctx.arg.entry);
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
@@ -1415,8 +1556,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
// However, that mechanism doesn't protect .so files. Therefore, we
// want to disable this optimization if we are creating a shared
// object file.
if (ctx.arg.shared)
ctx.overwrite_output_file = false;
ctx.overwrite_output_file = (!ctx.arg.shared && returns_etxtbsy());
if (!ctx.arg.chroot.empty()) {
if (!ctx.arg.Map.empty())
@@ -1443,4 +1583,4 @@ using E = MOLD_TARGET;
template std::vector<std::string_view> expand_response_files(Context<E> &, char **);
template std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
} // namespace mold::elf
} // namespace mold
+2 -3
View File
@@ -1,7 +1,6 @@
#include "mold.h"
#include "config.h"
namespace mold::elf {
namespace mold {
std::string get_mold_version() {
if (mold_git_hash.empty())
@@ -10,4 +9,4 @@ std::string get_mold_version() {
"; compatible with GNU ld)";
}
} // namespace mold::elf
} // namespace mold
+110 -43
View File
@@ -1,6 +1,6 @@
#include "mold.h"
#include "elf.h"
namespace mold::elf {
namespace mold {
static std::string unknown_type(u32 r_type) {
char buf[50];
@@ -53,6 +53,15 @@ std::string rel_to_string<X86_64>(u32 r_type) {
CASE(R_X86_64_IRELATIVE);
CASE(R_X86_64_GOTPCRELX);
CASE(R_X86_64_REX_GOTPCRELX);
CASE(R_X86_64_CODE_4_GOTPCRELX);
CASE(R_X86_64_CODE_4_GOTTPOFF);
CASE(R_X86_64_CODE_4_GOTPC32_TLSDESC);
CASE(R_X86_64_CODE_5_GOTPCRELX);
CASE(R_X86_64_CODE_5_GOTTPOFF);
CASE(R_X86_64_CODE_5_GOTPC32_TLSDESC);
CASE(R_X86_64_CODE_6_GOTPCRELX);
CASE(R_X86_64_CODE_6_GOTTPOFF);
CASE(R_X86_64_CODE_6_GOTPC32_TLSDESC);
}
return unknown_type(r_type);
}
@@ -107,7 +116,7 @@ std::string rel_to_string<I386>(u32 r_type) {
}
template <>
std::string rel_to_string<ARM64>(u32 r_type) {
std::string rel_to_string<ARM64LE>(u32 r_type) {
switch (r_type) {
CASE(R_AARCH64_NONE);
CASE(R_AARCH64_ABS64);
@@ -217,6 +226,11 @@ std::string rel_to_string<ARM64>(u32 r_type) {
return unknown_type(r_type);
}
template <>
std::string rel_to_string<ARM64BE>(u32 r_type) {
return rel_to_string<ARM64LE>(r_type);
}
template <>
std::string rel_to_string<ARM32>(u32 r_type) {
switch (r_type) {
@@ -402,7 +416,6 @@ std::string rel_to_string<RV64LE>(u32 r_type) {
CASE(R_RISCV_ALIGN);
CASE(R_RISCV_RVC_BRANCH);
CASE(R_RISCV_RVC_JUMP);
CASE(R_RISCV_RVC_LUI);
CASE(R_RISCV_RELAX);
CASE(R_RISCV_SUB6);
CASE(R_RISCV_SET6);
@@ -615,6 +628,7 @@ std::string rel_to_string<PPC64V1>(u32 r_type) {
CASE(R_PPC64_DTPREL16_HIGH);
CASE(R_PPC64_DTPREL16_HIGHA);
CASE(R_PPC64_REL24_NOTOC);
CASE(R_PPC64_ENTRY);
CASE(R_PPC64_PLTSEQ);
CASE(R_PPC64_PLTCALL);
CASE(R_PPC64_PLTSEQ_NOTOC);
@@ -857,7 +871,7 @@ std::string rel_to_string<M68K>(u32 r_type) {
}
template <>
std::string rel_to_string<SH4>(u32 r_type) {
std::string rel_to_string<SH4LE>(u32 r_type) {
switch (r_type) {
CASE(R_SH_NONE);
CASE(R_SH_DIR32);
@@ -891,43 +905,8 @@ std::string rel_to_string<SH4>(u32 r_type) {
}
template <>
std::string rel_to_string<ALPHA>(u32 r_type) {
switch (r_type) {
CASE(R_ALPHA_NONE);
CASE(R_ALPHA_REFLONG);
CASE(R_ALPHA_REFQUAD);
CASE(R_ALPHA_GPREL32);
CASE(R_ALPHA_LITERAL);
CASE(R_ALPHA_LITUSE);
CASE(R_ALPHA_GPDISP);
CASE(R_ALPHA_BRADDR);
CASE(R_ALPHA_HINT);
CASE(R_ALPHA_SREL16);
CASE(R_ALPHA_SREL32);
CASE(R_ALPHA_SREL64);
CASE(R_ALPHA_GPRELHIGH);
CASE(R_ALPHA_GPRELLOW);
CASE(R_ALPHA_GPREL16);
CASE(R_ALPHA_COPY);
CASE(R_ALPHA_GLOB_DAT);
CASE(R_ALPHA_JMP_SLOT);
CASE(R_ALPHA_RELATIVE);
CASE(R_ALPHA_BRSGP);
CASE(R_ALPHA_TLSGD);
CASE(R_ALPHA_TLSLDM);
CASE(R_ALPHA_DTPMOD64);
CASE(R_ALPHA_GOTDTPREL);
CASE(R_ALPHA_DTPREL64);
CASE(R_ALPHA_DTPRELHI);
CASE(R_ALPHA_DTPRELLO);
CASE(R_ALPHA_DTPREL16);
CASE(R_ALPHA_GOTTPREL);
CASE(R_ALPHA_TPREL64);
CASE(R_ALPHA_TPRELHI);
CASE(R_ALPHA_TPRELLO);
CASE(R_ALPHA_TPREL16);
}
return unknown_type(r_type);
std::string rel_to_string<SH4BE>(u32 r_type) {
return rel_to_string<SH4LE>(r_type);
}
template <>
@@ -946,6 +925,8 @@ std::string rel_to_string<LOONGARCH64>(u32 r_type) {
CASE(R_LARCH_TLS_TPREL32);
CASE(R_LARCH_TLS_TPREL64);
CASE(R_LARCH_IRELATIVE);
CASE(R_LARCH_TLS_DESC32);
CASE(R_LARCH_TLS_DESC64);
CASE(R_LARCH_MARK_LA);
CASE(R_LARCH_MARK_PCREL);
CASE(R_LARCH_SOP_PUSH_PCREL);
@@ -1031,6 +1012,23 @@ std::string rel_to_string<LOONGARCH64>(u32 r_type) {
CASE(R_LARCH_ADD_ULEB128);
CASE(R_LARCH_SUB_ULEB128);
CASE(R_LARCH_64_PCREL);
CASE(R_LARCH_CALL36);
CASE(R_LARCH_TLS_DESC_PC_HI20);
CASE(R_LARCH_TLS_DESC_PC_LO12);
CASE(R_LARCH_TLS_DESC64_PC_LO20);
CASE(R_LARCH_TLS_DESC64_PC_HI12);
CASE(R_LARCH_TLS_DESC_HI20);
CASE(R_LARCH_TLS_DESC_LO12);
CASE(R_LARCH_TLS_DESC64_LO20);
CASE(R_LARCH_TLS_DESC64_HI12);
CASE(R_LARCH_TLS_DESC_LD);
CASE(R_LARCH_TLS_DESC_CALL);
CASE(R_LARCH_TLS_LE_HI20_R);
CASE(R_LARCH_TLS_LE_ADD_R);
CASE(R_LARCH_TLS_LE_LO12_R);
CASE(R_LARCH_TLS_LD_PCREL20_S2);
CASE(R_LARCH_TLS_GD_PCREL20_S2);
CASE(R_LARCH_TLS_DESC_PCREL20_S2);
}
return unknown_type(r_type);
}
@@ -1040,4 +1038,73 @@ std::string rel_to_string<LOONGARCH32>(u32 r_type) {
return rel_to_string<LOONGARCH64>(r_type);
}
} // namespace mold::elf
template <>
std::string rel_to_string<ARC>(u32 r_type) {
switch (r_type) {
CASE(R_ARC_8);
CASE(R_ARC_16);
CASE(R_ARC_24);
CASE(R_ARC_32);
CASE(R_ARC_N8);
CASE(R_ARC_N16);
CASE(R_ARC_N24);
CASE(R_ARC_N32);
CASE(R_ARC_SDA);
CASE(R_ARC_SECTOFF);
CASE(R_ARC_S21H_PCREL);
CASE(R_ARC_S21W_PCREL);
CASE(R_ARC_S25H_PCREL);
CASE(R_ARC_S25W_PCREL);
CASE(R_ARC_SDA32);
CASE(R_ARC_SDA_LDST);
CASE(R_ARC_SDA_LDST1);
CASE(R_ARC_SDA_LDST2);
CASE(R_ARC_SDA16_LD);
CASE(R_ARC_SDA16_LD1);
CASE(R_ARC_SDA16_LD2);
CASE(R_ARC_S13_PCREL);
CASE(R_ARC_W);
CASE(R_ARC_32_ME);
CASE(R_ARC_32_ME_S);
CASE(R_ARC_N32_ME);
CASE(R_ARC_SECTOFF_ME);
CASE(R_ARC_SDA32_ME);
CASE(R_ARC_W_ME);
CASE(R_ARC_SECTOFF_ME_1);
CASE(R_ARC_SECTOFF_ME_2);
CASE(R_ARC_SECTOFF_1);
CASE(R_ARC_SECTOFF_2);
CASE(R_ARC_SDA_12);
CASE(R_ARC_SDA16_ST2);
CASE(R_ARC_32_PCREL);
CASE(R_ARC_PC32);
CASE(R_ARC_GOT32);
CASE(R_ARC_GOTPC32);
CASE(R_ARC_PLT32);
CASE(R_ARC_COPY);
CASE(R_ARC_GLOB_DAT);
CASE(R_ARC_JMP_SLOT);
CASE(R_ARC_RELATIVE);
CASE(R_ARC_GOTOFF);
CASE(R_ARC_GOTPC);
CASE(R_ARC_S21W_PCREL_PLT);
CASE(R_ARC_S25H_PCREL_PLT);
CASE(R_ARC_JLI_SECTOFF);
CASE(R_ARC_TLS_DTPMOD);
CASE(R_ARC_TLS_TPOFF);
CASE(R_ARC_TLS_GD_GOT);
CASE(R_ARC_TLS_GD_LD);
CASE(R_ARC_TLS_GD_CALL);
CASE(R_ARC_TLS_IE_GOT);
CASE(R_ARC_TLS_DTPOFF);
CASE(R_ARC_TLS_DTPOFF_S9);
CASE(R_ARC_TLS_LE_S9);
CASE(R_ARC_TLS_LE_32);
CASE(R_ARC_S25W_PCREL_PLT);
CASE(R_ARC_S21H_PCREL_PLT);
CASE(R_ARC_NPS_CMEM16);
}
return unknown_type(r_type);
}
} // namespace mold
+265 -235
View File
@@ -1,17 +1,18 @@
#pragma once
#include "../common/integers.h"
#include "../lib/integers.h"
#include <concepts>
#include <ostream>
#include <string>
#include <type_traits>
namespace mold::elf {
namespace mold {
struct X86_64;
struct I386;
struct ARM64;
struct ARM64LE;
struct ARM64BE;
struct ARM32;
struct RV64LE;
struct RV64BE;
@@ -23,10 +24,11 @@ struct PPC64V2;
struct S390X;
struct SPARC64;
struct M68K;
struct SH4;
struct ALPHA;
struct SH4LE;
struct SH4BE;
struct LOONGARCH64;
struct LOONGARCH32;
struct ARC;
template <typename E> struct ElfSym;
template <typename E> struct ElfShdr;
@@ -50,10 +52,6 @@ std::ostream &operator<<(std::ostream &out, const ElfRel<E> &rel) {
return out;
}
enum : u32 {
R_NONE = 0,
};
enum : u32 {
SHN_UNDEF = 0,
SHN_LORESERVE = 0xff00,
@@ -81,6 +79,7 @@ enum : u32 {
SHT_GROUP = 17,
SHT_SYMTAB_SHNDX = 18,
SHT_RELR = 19,
SHT_CREL = 0x40000014,
SHT_LOOS = 0x60000000,
SHT_LLVM_ADDRSIG = 0x6fff4c03,
SHT_GNU_HASH = 0x6ffffff6,
@@ -90,6 +89,7 @@ enum : u32 {
SHT_HIOS = 0x6fffffff,
SHT_X86_64_UNWIND = 0x70000001,
SHT_ARM_EXIDX = 0x70000001,
SHT_ARC_ATTRIBUTES = 0x70000001,
SHT_ARM_ATTRIBUTES = 0x70000003,
SHT_RISCV_ATTRIBUTES = 0x70000003,
SHT_LOUSER = 0x80000000,
@@ -191,6 +191,7 @@ enum : u32 {
PT_GNU_EH_FRAME = 0x6474e550,
PT_GNU_STACK = 0x6474e551,
PT_GNU_RELRO = 0x6474e552,
PT_GNU_PROPERTY = 0x6474e553,
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
PT_RISCV_ATTRIBUTES = 0x70000003,
@@ -236,9 +237,9 @@ enum : u32 {
EM_SPARC64 = 43,
EM_X86_64 = 62,
EM_AARCH64 = 183,
EM_ARC_COMPACT2 = 195,
EM_RISCV = 243,
EM_LOONGARCH = 258,
EM_ALPHA = 0x9026,
};
enum : u32 {
@@ -287,6 +288,8 @@ enum : u32 {
DT_RELR = 36,
DT_RELRENT = 37,
DT_GNU_HASH = 0x6ffffef5,
DT_DEPAUDIT = 0x6ffffefb,
DT_AUDIT = 0x6ffffefc,
DT_VERSYM = 0x6ffffff0,
DT_RELACOUNT = 0x6ffffff9,
DT_RELCOUNT = 0x6ffffffa,
@@ -297,6 +300,7 @@ enum : u32 {
DT_VERNEEDNUM = 0x6fffffff,
DT_PPC_GOT = 0x70000000,
DT_PPC64_GLINK = 0x70000000,
DT_RISCV_VARIANT_CC = 0x70000001,
DT_AARCH64_VARIANT_PCS = 0x70000005,
DT_AUXILIARY = 0x7ffffffd,
DT_FILTER = 0x7fffffff,
@@ -342,6 +346,12 @@ enum : u32 {
GNU_PROPERTY_X86_FEATURE_1_IBT = 1,
GNU_PROPERTY_X86_FEATURE_1_SHSTK = 2,
GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002,
GNU_PROPERTY_X86_ISA_1_NEEDED = 0xc0008002,
GNU_PROPERTY_X86_ISA_1_BASELINE = 1,
GNU_PROPERTY_X86_ISA_1_V2 = 2,
GNU_PROPERTY_X86_ISA_1_V3 = 4,
GNU_PROPERTY_X86_ISA_1_V4 = 8,
};
enum : u32 {
@@ -379,8 +389,6 @@ enum : u32 {
enum : u32 {
STO_RISCV_VARIANT_CC = 0x80,
STO_ALPHA_NOPV = 0x20,
STO_ALPHA_STD_GPLOAD = 0x22,
};
enum : u32 {
@@ -405,6 +413,10 @@ enum : u32 {
// Relocation types
//
enum : u32 {
R_NONE = 0,
};
enum : u32 {
R_X86_64_NONE = 0,
R_X86_64_64 = 1,
@@ -446,6 +458,15 @@ enum : u32 {
R_X86_64_IRELATIVE = 37,
R_X86_64_GOTPCRELX = 41,
R_X86_64_REX_GOTPCRELX = 42,
R_X86_64_CODE_4_GOTPCRELX = 43,
R_X86_64_CODE_4_GOTTPOFF = 44,
R_X86_64_CODE_4_GOTPC32_TLSDESC = 45,
R_X86_64_CODE_5_GOTPCRELX = 46,
R_X86_64_CODE_5_GOTTPOFF = 47,
R_X86_64_CODE_5_GOTPC32_TLSDESC = 48,
R_X86_64_CODE_6_GOTPCRELX = 49,
R_X86_64_CODE_6_GOTTPOFF = 50,
R_X86_64_CODE_6_GOTPC32_TLSDESC = 51,
};
enum : u32 {
@@ -780,10 +801,6 @@ enum : u32 {
R_RISCV_ALIGN = 43,
R_RISCV_RVC_BRANCH = 44,
R_RISCV_RVC_JUMP = 45,
R_RISCV_RVC_LUI = 46,
R_RISCV_GPREL_LO12_I = 47,
R_RISCV_GPREL_LO12_S = 48,
R_RISCV_GPREL_HI20 = 49,
R_RISCV_RELAX = 51,
R_RISCV_SUB6 = 52,
R_RISCV_SET6 = 53,
@@ -973,6 +990,7 @@ enum : u32 {
R_PPC64_DTPREL16_HIGH = 114,
R_PPC64_DTPREL16_HIGHA = 115,
R_PPC64_REL24_NOTOC = 116,
R_PPC64_ENTRY = 118,
R_PPC64_PLTSEQ = 119,
R_PPC64_PLTCALL = 120,
R_PPC64_PLTSEQ_NOTOC = 121,
@@ -1227,42 +1245,6 @@ enum : u32 {
R_SH_GOTPLT32 = 168,
};
enum : u32 {
R_ALPHA_NONE = 0,
R_ALPHA_REFLONG = 1,
R_ALPHA_REFQUAD = 2,
R_ALPHA_GPREL32 = 3,
R_ALPHA_LITERAL = 4,
R_ALPHA_LITUSE = 5,
R_ALPHA_GPDISP = 6,
R_ALPHA_BRADDR = 7,
R_ALPHA_HINT = 8,
R_ALPHA_SREL16 = 9,
R_ALPHA_SREL32 = 10,
R_ALPHA_SREL64 = 11,
R_ALPHA_GPRELHIGH = 17,
R_ALPHA_GPRELLOW = 18,
R_ALPHA_GPREL16 = 19,
R_ALPHA_COPY = 24,
R_ALPHA_GLOB_DAT = 25,
R_ALPHA_JMP_SLOT = 26,
R_ALPHA_RELATIVE = 27,
R_ALPHA_BRSGP = 28,
R_ALPHA_TLSGD = 29,
R_ALPHA_TLSLDM = 30,
R_ALPHA_DTPMOD64 = 31,
R_ALPHA_GOTDTPREL = 32,
R_ALPHA_DTPREL64 = 33,
R_ALPHA_DTPRELHI = 34,
R_ALPHA_DTPRELLO = 35,
R_ALPHA_DTPREL16 = 36,
R_ALPHA_GOTTPREL = 37,
R_ALPHA_TPREL64 = 38,
R_ALPHA_TPRELHI = 39,
R_ALPHA_TPRELLO = 40,
R_ALPHA_TPREL16 = 41,
};
enum : u32 {
R_LARCH_NONE = 0,
R_LARCH_32 = 1,
@@ -1277,6 +1259,8 @@ enum : u32 {
R_LARCH_TLS_TPREL32 = 10,
R_LARCH_TLS_TPREL64 = 11,
R_LARCH_IRELATIVE = 12,
R_LARCH_TLS_DESC32 = 13,
R_LARCH_TLS_DESC64 = 14,
R_LARCH_MARK_LA = 20,
R_LARCH_MARK_PCREL = 21,
R_LARCH_SOP_PUSH_PCREL = 22,
@@ -1362,8 +1346,100 @@ enum : u32 {
R_LARCH_ADD_ULEB128 = 107,
R_LARCH_SUB_ULEB128 = 108,
R_LARCH_64_PCREL = 109,
R_LARCH_CALL36 = 110,
R_LARCH_TLS_DESC_PC_HI20 = 111,
R_LARCH_TLS_DESC_PC_LO12 = 112,
R_LARCH_TLS_DESC64_PC_LO20 = 113,
R_LARCH_TLS_DESC64_PC_HI12 = 114,
R_LARCH_TLS_DESC_HI20 = 115,
R_LARCH_TLS_DESC_LO12 = 116,
R_LARCH_TLS_DESC64_LO20 = 117,
R_LARCH_TLS_DESC64_HI12 = 118,
R_LARCH_TLS_DESC_LD = 119,
R_LARCH_TLS_DESC_CALL = 120,
R_LARCH_TLS_LE_HI20_R = 121,
R_LARCH_TLS_LE_ADD_R = 122,
R_LARCH_TLS_LE_LO12_R = 123,
R_LARCH_TLS_LD_PCREL20_S2 = 124,
R_LARCH_TLS_GD_PCREL20_S2 = 125,
R_LARCH_TLS_DESC_PCREL20_S2 = 126,
};
enum : u32 {
R_ARC_8 = 1,
R_ARC_16 = 2,
R_ARC_24 = 3,
R_ARC_32 = 4,
R_ARC_N8 = 8,
R_ARC_N16 = 9,
R_ARC_N24 = 10,
R_ARC_N32 = 11,
R_ARC_SDA = 12,
R_ARC_SECTOFF = 13,
R_ARC_S21H_PCREL = 14,
R_ARC_S21W_PCREL = 15,
R_ARC_S25H_PCREL = 16,
R_ARC_S25W_PCREL = 17,
R_ARC_SDA32 = 18,
R_ARC_SDA_LDST = 19,
R_ARC_SDA_LDST1 = 20,
R_ARC_SDA_LDST2 = 21,
R_ARC_SDA16_LD = 22,
R_ARC_SDA16_LD1 = 23,
R_ARC_SDA16_LD2 = 24,
R_ARC_S13_PCREL = 25,
R_ARC_W = 26,
R_ARC_32_ME = 27,
R_ARC_32_ME_S = 105,
R_ARC_N32_ME = 28,
R_ARC_SECTOFF_ME = 29,
R_ARC_SDA32_ME = 30,
R_ARC_W_ME = 31,
R_ARC_SECTOFF_ME_1 = 41,
R_ARC_SECTOFF_ME_2 = 42,
R_ARC_SECTOFF_1 = 43,
R_ARC_SECTOFF_2 = 44,
R_ARC_SDA_12 = 45,
R_ARC_SDA16_ST2 = 48,
R_ARC_32_PCREL = 49,
R_ARC_PC32 = 50,
R_ARC_GOT32 = 59,
R_ARC_GOTPC32 = 51,
R_ARC_PLT32 = 52,
R_ARC_COPY = 53,
R_ARC_GLOB_DAT = 54,
R_ARC_JMP_SLOT = 55,
R_ARC_RELATIVE = 56,
R_ARC_GOTOFF = 57,
R_ARC_GOTPC = 58,
R_ARC_S21W_PCREL_PLT = 60,
R_ARC_S25H_PCREL_PLT = 61,
R_ARC_JLI_SECTOFF = 63,
R_ARC_TLS_DTPMOD = 66,
R_ARC_TLS_TPOFF = 68,
R_ARC_TLS_GD_GOT = 69,
R_ARC_TLS_GD_LD = 70,
R_ARC_TLS_GD_CALL = 71,
R_ARC_TLS_IE_GOT = 72,
R_ARC_TLS_DTPOFF = 67,
R_ARC_TLS_DTPOFF_S9 = 73,
R_ARC_TLS_LE_S9 = 74,
R_ARC_TLS_LE_32 = 75,
R_ARC_S25W_PCREL_PLT = 76,
R_ARC_S21H_PCREL_PLT = 77,
R_ARC_NPS_CMEM16 = 78,
};
// Returns true if a given relocation is of type used for direct
// function call.
template <typename E>
inline bool is_func_call_rel(const ElfRel<E> &r) {
for (u32 r_type : E::R_FUNCALL)
if (r.r_type == r_type)
return true;
return false;
}
//
// DWARF data types
//
@@ -1478,7 +1554,7 @@ template <typename E> using U32 = std::conditional_t<E::is_le, ul32, ub32>;
template <typename E> using U64 = std::conditional_t<E::is_le, ul64, ub64>;
template <typename E> using Word = std::conditional_t<E::is_64, U64<E>, U32<E>>;
template <typename E> using IWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
template <typename E> using SWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
template <typename E> requires E::is_64
struct ElfSym<E> {
@@ -1488,18 +1564,41 @@ struct ElfSym<E> {
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_weak() const { return is_undef() && is_weak(); }
bool ppc64_preserves_r2() const { return ppc64_local_entry != 1; }
bool ppc64_uses_toc() const { return ppc64_local_entry > 1; }
U32<E> st_name;
#ifdef __LITTLE_ENDIAN__
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u8 : 6;
union {
u8 st_visibility : 2;
struct {
u8 : 7;
u8 arm64_variant_pcs : 1;
};
struct {
u8 : 7;
u8 riscv_variant_cc : 1;
};
struct {
u8 : 5;
u8 ppc64_local_entry : 3;
};
};
#else
u8 st_bind : 4;
u8 st_type : 4;
u8 : 6;
u8 st_visibility : 2;
union {
struct {
u8 : 6;
u8 st_visibility : 2;
};
u8 arm64_variant_pcs : 1;
u8 riscv_variant_cc : 1;
u8 ppc64_local_entry : 3;
};
#endif
U16<E> st_shndx;
@@ -1522,13 +1621,23 @@ struct ElfSym<E> {
#ifdef __LITTLE_ENDIAN__
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u8 : 6;
union {
u8 st_visibility : 2;
struct {
u8 : 7;
u8 riscv_variant_cc : 1;
};
};
#else
u8 st_bind : 4;
u8 st_type : 4;
u8 : 6;
u8 st_visibility : 2;
union {
struct {
u8 : 6;
u8 st_visibility : 2;
};
u8 riscv_variant_cc : 1;
};
#endif
U16<E> st_shndx;
@@ -1602,7 +1711,7 @@ struct ElfPhdr<E> {
// We don't want to have too many `if (REL)`s and `if (RELA)`s in our
// codebase, so ElfRel always takes r_addend as a constructor argument.
// If it's REL, the argument will simply be ignored.
template <typename E> requires E::is_le && E::is_rela
template <typename E> requires (E::is_le && E::is_rela)
struct ElfRel<E> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
@@ -1611,10 +1720,10 @@ struct ElfRel<E> {
Word<E> r_offset;
std::conditional_t<E::is_64, U32<E>, u8> r_type;
std::conditional_t<E::is_64, U32<E>, U24<E>> r_sym;
IWord<E> r_addend;
SWord<E> r_addend;
};
template <typename E> requires (!E::is_le) && E::is_rela
template <typename E> requires (!E::is_le && E::is_rela)
struct ElfRel<E> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
@@ -1623,10 +1732,10 @@ struct ElfRel<E> {
Word<E> r_offset;
std::conditional_t<E::is_64, U32<E>, U24<E>> r_sym;
std::conditional_t<E::is_64, U32<E>, u8> r_type;
IWord<E> r_addend;
SWord<E> r_addend;
};
template <typename E> requires E::is_le && (!E::is_rela)
template <typename E> requires (E::is_le && !E::is_rela)
struct ElfRel<E> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend = 0)
@@ -1637,7 +1746,7 @@ struct ElfRel<E> {
std::conditional_t<E::is_64, U32<E>, U24<E>> r_sym;
};
template <typename E> requires (!E::is_le) && (!E::is_rela)
template <typename E> requires (!E::is_le && !E::is_rela)
struct ElfRel<E> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend = 0)
@@ -1648,16 +1757,6 @@ struct ElfRel<E> {
std::conditional_t<E::is_64, U32<E>, u8> r_type;
};
// Returns true if a given relocation is of type used for direct
// function call.
template <typename E>
inline bool is_func_call_rel(const ElfRel<E> &r) {
for (u32 r_type : E::R_FUNCALL)
if (r.r_type == r_type)
return true;
return false;
}
template <typename E>
struct ElfDyn {
Word<E> d_tag;
@@ -1725,94 +1824,6 @@ struct ElfNhdr {
// Target-specific ELF data types
//
template <>
struct ElfSym<ARM64> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
bool is_abs() const { return st_shndx == SHN_ABS; }
bool is_common() const { return st_shndx == SHN_COMMON; }
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_weak() const { return is_undef() && is_weak(); }
ul32 st_name;
#ifdef __LITTLE_ENDIAN__
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u8 : 5;
u8 arm64_variant_pcs : 1; // ARM64-specific
#else
u8 st_bind : 4;
u8 st_type : 4;
u8 arm64_variant_pcs : 1;
u8 : 5;
u8 st_visibility : 2;
#endif
ul16 st_shndx;
ul64 st_value;
ul64 st_size;
};
template <>
struct ElfSym<PPC64V2> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
bool is_abs() const { return st_shndx == SHN_ABS; }
bool is_common() const { return st_shndx == SHN_COMMON; }
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_weak() const { return is_undef() && is_weak(); }
bool preserves_r2() const { return ppc_local_entry != 1; }
bool uses_toc() const { return ppc_local_entry > 1; }
ul32 st_name;
#ifdef __LITTLE_ENDIAN__
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u8 : 3;
u8 ppc_local_entry : 3; // This is PPC64V2-specific
#else
u8 st_bind : 4;
u8 st_type : 4;
u8 ppc_local_entry : 3;
u8 : 3;
u8 st_visibility : 2;
#endif
ul16 st_shndx;
ul64 st_value;
ul64 st_size;
};
template <>
struct ElfSym<ALPHA> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
bool is_abs() const { return st_shndx == SHN_ABS; }
bool is_common() const { return st_shndx == SHN_COMMON; }
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_weak() const { return is_undef() && is_weak(); }
ul32 st_name;
#ifdef __LITTLE_ENDIAN__
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u8 alpha_st_other : 6; // contains STO_ALPHA_NOPV, STO_ALPHA_STD_GPLOAD or 0
#else
u8 st_bind : 4;
u8 st_type : 4;
u8 alpha_st_other : 6;
u8 st_visibility : 2;
#endif
ul16 st_shndx;
ul64 st_value;
ul64 st_size;
};
template <>
struct ElfRel<SPARC64> {
ElfRel() = default;
@@ -1828,10 +1839,10 @@ struct ElfRel<SPARC64> {
};
template <>
struct ElfRel<SH4> {
struct ElfRel<SH4LE> {
ElfRel() = default;
// Addend is ignored except for base relocations because even though
// Addend is ignored except for base relocations because even though
// SH4 is RELA, r_addend is ignored in most cases and works as if it
// were REL.
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
@@ -1843,6 +1854,18 @@ struct ElfRel<SH4> {
il32 r_addend;
};
template <>
struct ElfRel<SH4BE> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
: r_offset(offset), r_sym(sym), r_type(type), r_addend(sym ? 0 : addend) {}
ub32 r_offset;
ub24 r_sym;
u8 r_type;
ib32 r_addend;
};
//
// Machine descriptions
//
@@ -1853,7 +1876,8 @@ template <typename E> concept needs_thunk = requires { E::thunk_size; };
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
template <typename E> concept is_i386 = std::same_as<E, I386>;
template <typename E> concept is_arm64 = std::same_as<E, ARM64>;
template <typename E> concept is_arm64le = std::same_as<E, ARM64LE>;
template <typename E> concept is_arm64be = std::same_as<E, ARM64BE>;
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
@@ -1865,12 +1889,14 @@ template <typename E> concept is_ppc64v2 = std::same_as<E, PPC64V2>;
template <typename E> concept is_s390x = std::same_as<E, S390X>;
template <typename E> concept is_sparc64 = std::same_as<E, SPARC64>;
template <typename E> concept is_m68k = std::same_as<E, M68K>;
template <typename E> concept is_sh4 = std::same_as<E, SH4>;
template <typename E> concept is_alpha = std::same_as<E, ALPHA>;
template <typename E> concept is_sh4le = std::same_as<E, SH4LE>;
template <typename E> concept is_sh4be = std::same_as<E, SH4BE>;
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
template <typename E> concept is_arc = std::same_as<E, ARC>;
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<E>;
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
template <typename E> concept is_rv32 = is_rv32le<E> || is_rv32be<E>;
@@ -1878,10 +1904,11 @@ template <typename E> concept is_riscv = is_rv64<E> || is_rv32<E>;
template <typename E> concept is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
template <typename E> concept is_ppc = is_ppc64<E> || is_ppc32<E>;
template <typename E> concept is_sparc = is_sparc64<E>;
template <typename E> concept is_sh4 = is_sh4le<E> || is_sh4be<E>;
template <typename E> concept is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
struct X86_64 {
static constexpr std::string_view target_name = "x86_64";
static constexpr std::string_view name = "x86_64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
@@ -1906,7 +1933,7 @@ struct X86_64 {
};
struct I386 {
static constexpr std::string_view target_name = "i386";
static constexpr std::string_view name = "i386";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = false;
@@ -1914,7 +1941,7 @@ struct I386 {
static constexpr u32 e_machine = EM_386;
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0xcc }; // int3
static constexpr u32 R_COPY = R_386_COPY;
@@ -1930,8 +1957,8 @@ struct I386 {
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
};
struct ARM64 {
static constexpr std::string_view target_name = "arm64";
struct ARM64LE {
static constexpr std::string_view name = "arm64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
@@ -1957,12 +1984,17 @@ struct ARM64 {
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
};
struct ARM64BE : ARM64LE {
static constexpr std::string_view name = "arm64be";
static constexpr bool is_le = false;
};
struct ARM32 {
static constexpr std::string_view target_name = "arm32";
static constexpr std::string_view name = "arm32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = false;
static constexpr u32 page_size = 4096;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_ARM;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
@@ -1987,8 +2019,10 @@ struct ARM32 {
};
};
struct RV64 {
struct RV64LE {
static constexpr std::string_view name = "riscv64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
@@ -2010,18 +2044,15 @@ struct RV64 {
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
};
struct RV64LE : RV64 {
static constexpr std::string_view target_name = "riscv64";
static constexpr bool is_le = true;
};
struct RV64BE : RV64 {
static constexpr std::string_view target_name = "riscv64be";
struct RV64BE : RV64LE {
static constexpr std::string_view name = "riscv64be";
static constexpr bool is_le = false;
};
struct RV32 {
struct RV32LE {
static constexpr std::string_view name = "riscv32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_RISCV;
@@ -2043,18 +2074,13 @@ struct RV32 {
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
};
struct RV32LE : RV32 {
static constexpr std::string_view target_name = "riscv32";
static constexpr bool is_le = true;
};
struct RV32BE : RV32 {
static constexpr std::string_view target_name = "riscv32be";
struct RV32BE : RV32LE {
static constexpr std::string_view name = "riscv32be";
static constexpr bool is_le = false;
};
struct PPC32 {
static constexpr std::string_view target_name = "ppc32";
static constexpr std::string_view name = "ppc32";
static constexpr bool is_64 = false;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
@@ -2101,7 +2127,7 @@ struct PPC64 {
};
struct PPC64V1 : PPC64 {
static constexpr std::string_view target_name = "ppc64v1";
static constexpr std::string_view name = "ppc64v1";
static constexpr bool is_le = false;
static constexpr u32 plt_hdr_size = 44;
static constexpr u32 pltgot_size = 0;
@@ -2111,7 +2137,7 @@ struct PPC64V1 : PPC64 {
};
struct PPC64V2 : PPC64 {
static constexpr std::string_view target_name = "ppc64v2";
static constexpr std::string_view name = "ppc64v2";
static constexpr bool is_le = true;
static constexpr u32 plt_hdr_size = 52;
static constexpr u32 plt_size = 4;
@@ -2122,7 +2148,7 @@ struct PPC64V2 : PPC64 {
};
struct S390X {
static constexpr std::string_view target_name = "s390x";
static constexpr std::string_view name = "s390x";
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
@@ -2146,7 +2172,7 @@ struct S390X {
};
struct SPARC64 {
static constexpr std::string_view target_name = "sparc64";
static constexpr std::string_view name = "sparc64";
static constexpr bool is_64 = true;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
@@ -2170,7 +2196,7 @@ struct SPARC64 {
};
struct M68K {
static constexpr std::string_view target_name = "m68k";
static constexpr std::string_view name = "m68k";
static constexpr bool is_64 = false;
static constexpr bool is_le = false;
static constexpr bool is_rela = true;
@@ -2192,8 +2218,8 @@ struct M68K {
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
};
struct SH4 {
static constexpr std::string_view target_name = "sh4";
struct SH4LE {
static constexpr std::string_view name = "sh4";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
@@ -2215,41 +2241,22 @@ struct SH4 {
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
};
struct ALPHA {
static constexpr std::string_view target_name = "alpha";
struct SH4BE : SH4LE {
static constexpr std::string_view name = "sh4be";
static constexpr bool is_le = false;
static constexpr u8 filler[] = { 0x90, 0x00 }; // nop
};
struct LOONGARCH64 {
static constexpr std::string_view name = "loongarch64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_ALPHA;
static constexpr u32 plt_hdr_size = 0;
static constexpr u32 plt_size = 0;
static constexpr u32 pltgot_size = 0;
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 }; // bugchk
static constexpr u32 R_COPY = R_ALPHA_COPY;
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
static constexpr u32 R_FUNCALL[] = {};
};
struct LOONGARCH64 {
static constexpr std::string_view target_name = "loongarch64";
static constexpr bool is_64 = true;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 16384;
static constexpr u32 e_machine = EM_LOONGARCH;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 8;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u32 R_COPY = R_LARCH_COPY;
@@ -2261,21 +2268,20 @@ struct LOONGARCH64 {
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
static constexpr u32 R_TLSDESC = R_LARCH_TLS_DESC64;
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26, R_LARCH_CALL36 };
};
struct LOONGARCH32 {
static constexpr std::string_view target_name = "loongarch32";
static constexpr std::string_view name = "loongarch32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 16384;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_LOONGARCH;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 8;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u32 R_COPY = R_LARCH_COPY;
@@ -2287,7 +2293,31 @@ struct LOONGARCH32 {
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
static constexpr u32 R_TLSDESC = R_LARCH_TLS_DESC32;
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26, R_LARCH_CALL36 };
};
} // namespace mold::elf
struct ARC {
static constexpr std::string_view name = "arc";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
static constexpr bool is_rela = true;
static constexpr u32 page_size = 8192;
static constexpr u32 e_machine = EM_ARC_COMPACT2;
static constexpr u32 plt_hdr_size = 24;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0, 0, 0, 0 };
static constexpr u32 R_COPY = R_ARC_COPY;
static constexpr u32 R_GLOB_DAT = R_ARC_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_ARC_JMP_SLOT;
static constexpr u32 R_ABS = R_ARC_32;
static constexpr u32 R_RELATIVE = R_ARC_RELATIVE;
static constexpr u32 R_DTPOFF = R_ARC_TLS_DTPOFF;
static constexpr u32 R_TPOFF = R_ARC_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_ARC_TLS_DTPMOD;
static constexpr u32 R_FUNCALL[] = { R_ARC_S25W_PCREL_PLT };
};
} // namespace mold
+85 -76
View File
@@ -1,30 +1,9 @@
#pragma once
#include "common.h"
#include "../elf/elf.h"
#include "mold.h"
#include "../lib/archive-file.h"
namespace mold {
enum class FileType {
UNKNOWN,
EMPTY,
ELF_OBJ,
ELF_DSO,
MACH_OBJ,
MACH_EXE,
MACH_DYLIB,
MACH_BUNDLE,
MACH_UNIVERSAL,
AR,
THIN_AR,
TAPI,
TEXT,
GCC_LTO_OBJ,
LLVM_BITCODE,
};
template <typename MappedFile>
bool is_text_file(MappedFile *mf) {
static bool is_text_file(MappedFile *mf) {
auto istext = [](char c) {
return isprint(c) || c == '\n' || c == '\t';
};
@@ -34,10 +13,8 @@ bool is_text_file(MappedFile *mf) {
istext(data[2]) && istext(data[3]);
}
template <typename E, typename Context, typename MappedFile>
inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
using namespace mold::elf;
template <typename E>
static bool is_gcc_lto_obj(MappedFile *mf, bool has_plugin) {
const char *data = mf->get_contents().data();
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
@@ -54,7 +31,7 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
// the LTO linker plugin is available and falls back as regular
// objects otherwise. GCC FAT LTO object can be identified by the
// presence of `.gcc.lto_.symtab` section.
if (!ctx.arg.plugin.empty()) {
if (has_plugin) {
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (name.starts_with(".gnu.lto_.symtab."))
return true;
@@ -89,11 +66,10 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
return false;
}
template <typename Context, typename MappedFile>
FileType get_file_type(Context &ctx, MappedFile *mf) {
using namespace elf;
template <typename E>
FileType get_file_type(Context<E> &ctx, MappedFile *mf) {
std::string_view data = mf->get_contents();
bool has_plugin = !ctx.arg.plugin.empty();
if (data.empty())
return FileType::EMPTY;
@@ -106,10 +82,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(ctx, mf))
if (is_gcc_lto_obj<I386>(mf, has_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(ctx, mf))
if (is_gcc_lto_obj<X86_64>(mf, has_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
@@ -122,10 +98,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(ctx, mf))
if (is_gcc_lto_obj<M68K>(mf, has_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(ctx, mf))
if (is_gcc_lto_obj<SPARC64>(mf, has_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
@@ -137,28 +113,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
return FileType::UNKNOWN;
}
if (data.starts_with("\xcf\xfa\xed\xfe")) {
switch (*(ul32 *)(data.data() + 12)) {
case 1: // MH_OBJECT
return FileType::MACH_OBJ;
case 2: // MH_EXECUTE
return FileType::MACH_EXE;
case 6: // MH_DYLIB
return FileType::MACH_DYLIB;
case 8: // MH_BUNDLE
return FileType::MACH_BUNDLE;
}
return FileType::UNKNOWN;
}
if (data.starts_with("!<arch>\n"))
return FileType::AR;
if (data.starts_with("!<thin>\n"))
return FileType::THIN_AR;
if (data.starts_with("--- !tapi-tbd"))
return FileType::TAPI;
if (data.starts_with("\xca\xfe\xba\xbe"))
return FileType::MACH_UNIVERSAL;
if (is_text_file(mf))
return FileType::TEXT;
if (data.starts_with("\xde\xc0\x17\x0b"))
@@ -168,30 +126,81 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
return FileType::UNKNOWN;
}
inline std::string filetype_to_string(FileType type) {
switch (type) {
case FileType::UNKNOWN: return "UNKNOWN";
case FileType::EMPTY: return "EMPTY";
case FileType::ELF_OBJ: return "ELF_OBJ";
case FileType::ELF_DSO: return "ELF_DSO";
case FileType::MACH_EXE: return "MACH_EXE";
case FileType::MACH_OBJ: return "MACH_OBJ";
case FileType::MACH_DYLIB: return "MACH_DYLIB";
case FileType::MACH_BUNDLE: return "MACH_BUNDLE";
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
case FileType::AR: return "AR";
case FileType::THIN_AR: return "THIN_AR";
case FileType::TAPI: return "TAPI";
case FileType::TEXT: return "TEXT";
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
static std::string_view get_elf_type(u8 *buf) {
bool is_le = (buf[EI_DATA] == ELFDATA2LSB);
bool is_64 = (buf[EI_CLASS] == ELFCLASS64);
auto *ehdr_le = (ElfEhdr<I386> *)buf;
auto *ehdr_be = (ElfEhdr<M68K> *)buf;
switch (is_le ? ehdr_le->e_machine : ehdr_be->e_machine) {
case EM_386:
return I386::name;
case EM_X86_64:
return X86_64::name;
case EM_ARM:
return ARM32::name;
case EM_AARCH64:
return is_le ? ARM64LE::name : ARM64BE::name;
case EM_RISCV:
if (is_le)
return is_64 ? RV64LE::name : RV32LE::name;
return is_64 ? RV64BE::name : RV32BE::name;
case EM_PPC:
return PPC32::name;
case EM_PPC64:
return is_le ? PPC64V2::name : PPC64V1::name;
case EM_S390X:
return S390X::name;
case EM_SPARC64:
return SPARC64::name;
case EM_68K:
return M68K::name;
case EM_SH:
return is_le ? SH4LE::name : SH4BE::name;
case EM_LOONGARCH:
return is_64 ? LOONGARCH64::name : LOONGARCH32::name;
case EM_ARC_COMPACT2:
return ARC::name;
default:
return "";
}
return "UNKNOWN";
}
inline std::ostream &operator<<(std::ostream &out, FileType type) {
out << filetype_to_string(type);
return out;
// Read the beginning of a given file and returns its machine type
// (e.g. EM_X86_64 or EM_386).
template <typename E>
std::string_view
get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
case FileType::GCC_LTO_OBJ:
return get_elf_type(mf->data);
case FileType::AR:
for (MappedFile *child : read_fat_archive_members(ctx, mf))
if (FileType ty = get_file_type(ctx, child);
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
return get_elf_type(child->data);
return "";
case FileType::THIN_AR:
for (MappedFile *child : read_thin_archive_members(ctx, mf))
if (FileType ty = get_file_type(ctx, child);
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
return get_elf_type(child->data);
return "";
case FileType::TEXT:
return Script(ctx, rctx, mf).get_script_output_type();
default:
return "";
}
}
using E = MOLD_TARGET;
template FileType get_file_type(Context<E> &, MappedFile *);
template std::string_view
get_machine_type(Context<E> &, ReaderContext &, MappedFile *);
} // namespace mold
+21 -20
View File
@@ -7,7 +7,7 @@
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
namespace mold {
template <typename E>
static bool should_keep(const InputSection<E> &isec) {
@@ -24,7 +24,6 @@ static bool should_keep(const InputSection<E> &isec) {
type == SHT_INIT_ARRAY ||
type == SHT_FINI_ARRAY ||
type == SHT_PREINIT_ARRAY ||
(is_arm32<E> && type == SHT_ARM_EXIDX) ||
name.starts_with(".ctors") ||
name.starts_with(".dtors") ||
name.starts_with(".init") ||
@@ -100,34 +99,36 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
assert(isec->is_visited);
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
auto mark = [&](InputSection<E> *sec) {
if (mark_section(sec)) {
if (depth < 3)
visit(ctx, sec, feeder, depth + 1);
else
feeder.add(sec);
}
};
// If this is a text section, .eh_frame may contain records
// describing how to handle exceptions for that function.
// We want to keep associated .eh_frame records.
for (FdeRecord<E> &fde : isec->get_fdes())
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
if (mark_section(sym->get_input_section()))
feeder.add(sym->get_input_section());
mark(sym->get_input_section());
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
// Symbol can refer to either a section fragment or an input section.
// Mark a fragment as alive.
if (SectionFragment<E> *frag = sym.get_frag()) {
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
if (SectionFragment<E> *frag = sym.get_frag())
frag->is_alive = true;
continue;
}
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
if (mark_section(sym.get_input_section())) {
if (depth < 3)
visit(ctx, sym.get_input_section(), feeder, depth + 1);
else
feeder.add(sym.get_input_section());
}
else
mark(sym.get_input_section());
}
if constexpr (is_arm32<E>)
mark(isec->extra.exidx);
}
// Mark all reachable sections
@@ -172,4 +173,4 @@ using E = MOLD_TARGET;
template void gc_sections(Context<E> &ctx);
} // namespace mold::elf
} // namespace mold
+7 -14
View File
@@ -58,9 +58,8 @@
#include "mold.h"
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
namespace mold::elf {
namespace mold {
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
@@ -127,16 +126,10 @@ struct SectionHeader {
};
struct NameType {
bool operator==(const NameType &) const = default;
bool operator<(const NameType &other) const {
return std::tuple(hash, type, name) <
std::tuple(other.hash, other.type, other.name);
}
std::string_view name;
auto operator<=>(const NameType &) const = default;
u64 hash;
u8 type;
std::string_view name;
};
struct MapValue {
@@ -539,7 +532,7 @@ static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
u8 type = *p++;
std::string_view name = (char *)p;
p += name.size() + 1;
cu->nametypes.push_back({name, hash_string(name), type});
cu->nametypes.push_back(NameType{hash_string(name), type, name});
}
return size;
@@ -626,8 +619,8 @@ static std::vector<Compunit> read_compunits(Context<E> &ctx) {
// Uniquify elements because GCC 11 seems to emit one record for each
// comdat group which results in having a lot of duplicate records.
tbb::parallel_for_each(cus, [&](Compunit &cu) {
sort(cu.nametypes);
tbb::parallel_for_each(cus, [](Compunit &cu) {
ranges::stable_sort(cu.nametypes);
remove_duplicates(cu.nametypes);
});
@@ -791,4 +784,4 @@ using E = MOLD_TARGET;
template void write_gdb_index(Context<E> &);
} // namespace mold::elf
} // namespace mold
+20 -12
View File
@@ -65,7 +65,7 @@
// conditions.
#include "mold.h"
#include "../common/siphash.h"
#include "../lib/siphash.h"
#include <array>
#include <cstdio>
@@ -91,7 +91,7 @@ template <> struct hash<Digest> {
};
}
namespace mold::elf {
namespace mold {
static u8 hmac_key[16];
@@ -133,7 +133,7 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
name != ".init" && name != ".fini";
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
bool is_relro = isec.output_section && isec.output_section->is_relro;
bool is_relro = isec.name().starts_with(".data.rel.ro");
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
(is_readonly || is_relro);
}
@@ -192,7 +192,7 @@ static void merge_leaf_nodes(Context<E> &ctx) {
static Counter non_eligible("icf_non_eligibles");
static Counter leaf("icf_leaf_nodes");
tbb::concurrent_unordered_map<InputSection<E> *, InputSection<E> *,
tbb::concurrent_unordered_map<InputSection<E> *, Atomic<InputSection<E> *>,
LeafHasher<E>, LeafEq<E>> map;
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
@@ -209,8 +209,11 @@ static void merge_leaf_nodes(Context<E> &ctx) {
leaf++;
isec->icf_leaf = true;
auto [it, inserted] = map.insert({isec.get(), isec.get()});
if (!inserted && isec->get_priority() < it->second->get_priority())
it->second = isec.get();
if (!inserted) {
InputSection<E> *isec2 = it->second.load();
while (isec->get_priority() < isec2->get_priority() &&
!it->second.compare_exchange_strong(isec2, isec.get()));
}
} else {
eligible++;
isec->icf_eligible = true;
@@ -465,7 +468,7 @@ static void print_icf_sections(Context<E> &ctx) {
});
tbb::parallel_sort(leaders.begin(), leaders.end(),
[&](InputSection<E> *a, InputSection<E> *b) {
[](InputSection<E> *a, InputSection<E> *b) {
return a->get_priority() < b->get_priority();
});
@@ -568,14 +571,19 @@ void icf_sections(Context<E> &ctx) {
{
Timer t(ctx, "group");
auto *map = new tbb::concurrent_unordered_map<Digest, InputSection<E> *>;
auto *map =
new tbb::concurrent_unordered_map<Digest, Atomic<InputSection<E> *>>;
std::span<Digest> digest = digests[slot];
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> *isec = sections[i];
auto [it, inserted] = map->insert({digest[i], isec});
if (!inserted && isec->get_priority() < it->second->get_priority())
it->second = isec;
if (!inserted) {
InputSection<E> *isec2 = it->second.load();
while (isec->get_priority() < isec2->get_priority() &&
!it->second.compare_exchange_strong(isec2, isec));
}
});
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
@@ -599,7 +607,7 @@ void icf_sections(Context<E> &ctx) {
static Counter eliminated("icf_eliminated");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
if (isec && isec->is_alive && isec->icf_removed()) {
isec->kill();
eliminated++;
}
@@ -612,4 +620,4 @@ using E = MOLD_TARGET;
template void icf_sections(Context<E> &ctx);
} // namespace mold::elf
} // namespace mold
+227 -265
View File
@@ -8,7 +8,7 @@
# include <unistd.h>
#endif
namespace mold::elf {
namespace mold {
// If we haven't seen the same `key` before, create a new instance
// of Symbol and returns it. Otherwise, returns the previously-
@@ -93,12 +93,6 @@ InputFile<E>::InputFile(Context<E> &ctx, MappedFile *mf)
shstrtab = this->get_string(ctx, shstrtab_idx);
}
template <typename E>
std::span<ElfPhdr<E>> InputFile<E>::get_phdrs() {
ElfEhdr<E> &ehdr = get_ehdr();
return {(ElfPhdr<E> *)(mf->data + ehdr.e_phoff), ehdr.e_phnum};
}
template <typename E>
ElfShdr<E> *InputFile<E>::find_section(i64 type) {
for (ElfShdr<E> &sec : elf_sections)
@@ -116,22 +110,6 @@ std::string_view InputFile<E>::get_source_name() const {
return "";
}
template <typename E>
ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile *mf,
std::string archive_name, bool is_in_lib)
: InputFile<E>(ctx, mf), archive_name(archive_name), is_in_lib(is_in_lib) {
this->is_alive = !is_in_lib;
}
template <typename E>
ObjectFile<E> *
ObjectFile<E>::create(Context<E> &ctx, MappedFile *mf,
std::string archive_name, bool is_in_lib) {
ObjectFile<E> *obj = new ObjectFile<E>(ctx, mf, archive_name, is_in_lib);
ctx.obj_pool.emplace_back(obj);
return obj;
}
template <typename E>
static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
return !(shdr.sh_flags & SHF_ALLOC) && name.starts_with(".debug");
@@ -177,7 +155,7 @@ ObjectFile<E>::parse_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr)
// <format-version>
// [ <section-length> "vendor-name" <file-tag> <size> <attribute>*]+ ]*
template <typename E>
template <is_riscv E>
static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
std::string_view data) {
if (data.empty())
@@ -243,7 +221,7 @@ static bool is_known_section_type(const ElfShdr<E> &shdr) {
return true;
if (SHT_LOOS <= ty && ty <= SHT_HIOS && !(flags & SHF_OS_NONCONFORMING))
return true;
if (is_x86<E> && ty == SHT_X86_64_UNWIND)
if (is_x86_64<E> && ty == SHT_X86_64_UNWIND)
return true;
if (is_arm32<E> && (ty == SHT_ARM_EXIDX || ty == SHT_ARM_ATTRIBUTES))
return true;
@@ -252,6 +230,65 @@ static bool is_known_section_type(const ElfShdr<E> &shdr) {
return false;
}
// SHT_CREL is an experimental alternative relocation table format
// designed to reduce the size of the table. Only LLVM supports it
// at the moment.
//
// This function converts a CREL relocation table to a regular one.
template <typename E>
std::vector<ElfRel<E>> decode_crel(Context<E> &ctx, ObjectFile<E> &file,
const ElfShdr<E> &shdr) {
u8 *p = (u8 *)file.get_string(ctx, shdr).data();
u64 hdr = read_uleb(&p);
i64 nrels = hdr >> 3;
bool is_rela = hdr & 0b100;
i64 scale = hdr & 0b11;
if (is_rela && !E::is_rela)
Fatal(ctx) << file << ": CREL with addends is not supported for " << E::name;
u64 offset = 0;
i64 type = 0;
i64 symidx = 0;
i64 addend = 0;
std::vector<ElfRel<E>> vec;
vec.reserve(nrels);
while (vec.size() < nrels) {
u8 flags = *p++;
i64 nflags = is_rela ? 3 : 2;
// The first ULEB-128 encoded value is a concatenation of bit flags and
// an offset delta. The delta may be very large to decrease the
// current offset value by wrapping around. Combined, the encoded value
// can be up to 67 bit long. Thus we can't simply use read_uleb() which
// returns a u64.
u64 delta;
if (flags & 0x80)
delta = (read_uleb(&p) << (7 - nflags)) | ((flags & 0x7f) >> nflags);
else
delta = flags >> nflags;
offset += delta << scale;
if (flags & 1)
symidx += read_sleb(&p);
if (flags & 2)
type += read_sleb(&p);
if (is_rela && (flags & 4))
addend += read_sleb(&p);
vec.emplace_back(offset, type, symidx, addend);
}
return vec;
}
template <typename E>
ComdatGroup *insert_comdat_group(Context<E> &ctx, std::string_view name) {
typename decltype(ctx.comdat_groups)::const_accessor acc;
ctx.comdat_groups.insert(acc, {name, ComdatGroup()});
return const_cast<ComdatGroup *>(&acc->second);
}
template <typename E>
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// Read sections
@@ -280,6 +317,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
}
}
if constexpr (is_arc<E>)
if (shdr.sh_type == SHT_ARC_ATTRIBUTES)
continue;
switch (shdr.sh_type) {
case SHT_GROUP: {
// Get the signature of this section group.
@@ -310,12 +351,14 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
if (entries[0] != GRP_COMDAT)
Fatal(ctx) << *this << ": unsupported SHT_GROUP format";
typename decltype(ctx.comdat_groups)::const_accessor acc;
ctx.comdat_groups.insert(acc, {signature, ComdatGroup()});
ComdatGroup *group = const_cast<ComdatGroup *>(&acc->second);
ComdatGroup *group = insert_comdat_group(ctx, signature);
comdat_groups.push_back({group, (i32)i, entries.subspan(1)});
break;
}
case SHT_CREL:
decoded_crel.resize(i + 1);
decoded_crel[i] = decode_crel(ctx, *this, shdr);
break;
case SHT_REL:
case SHT_RELA:
case SHT_SYMTAB:
@@ -369,6 +412,11 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
is_debug_section(shdr, name))
continue;
// Ignore section is specified by --discard-section.
if (!ctx.arg.discard_section.empty() &&
ctx.arg.discard_section.contains(name))
continue;
if (name == ".comment" &&
this->get_string(ctx, shdr).starts_with("rustc "))
this->is_rust_obj = true;
@@ -448,18 +496,21 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
// Attach relocation sections to their target sections.
for (i64 i = 0; i < this->elf_sections.size(); i++) {
const ElfShdr<E> &shdr = this->elf_sections[i];
if (shdr.sh_type != (E::is_rela ? SHT_RELA : SHT_REL))
continue;
if (shdr.sh_info >= sections.size())
Fatal(ctx) << *this << ": invalid relocated section index: "
<< (u32)shdr.sh_info;
if (std::unique_ptr<InputSection<E>> &target = sections[shdr.sh_info]) {
assert(target->relsec_idx == -1);
target->relsec_idx = i;
if (shdr.sh_type == (E::is_rela ? SHT_RELA : SHT_REL) ||
shdr.sh_type == SHT_CREL) {
if (std::unique_ptr<InputSection<E>> &target = sections[shdr.sh_info]) {
assert(target->relsec_idx == -1);
target->relsec_idx = i;
}
}
}
// Attach .arm.exidx sections to their corresponding sections
if constexpr (is_arm32<E>)
for (std::unique_ptr<InputSection<E>> &isec : this->sections)
if (isec && isec->shdr().sh_type == SHT_ARM_EXIDX)
if (InputSection<E> *target = sections[isec->shdr().sh_link].get())
target->extra.exidx = isec.get();
}
// .eh_frame contains data records explaining how to handle exceptions.
@@ -548,6 +599,8 @@ void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
i64 cie_offset = *(I32<E> *)(contents.data() + fdes[i].input_offset + 4);
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
}
isec->is_alive = false;
}
auto get_isec = [&](const FdeRecord<E> &fde) {
@@ -556,19 +609,23 @@ void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
// We assume that FDEs for the same input sections are contiguous
// in `fdes` vector.
sort(fdes, [&](const FdeRecord<E> &a, const FdeRecord<E> &b) {
return get_isec(a)->get_priority() < get_isec(b)->get_priority();
ranges::stable_sort(fdes, {}, [&](const FdeRecord<E> &x) {
return get_isec(x)->get_priority();
});
// Associate FDEs to input sections.
for (i64 i = 0; i < fdes.size();) {
InputSection<E> *isec = get_isec(fdes[i]);
assert(isec->fde_begin == -1);
isec->fde_begin = i++;
while (i < fdes.size() && isec == get_isec(fdes[i]))
i++;
isec->fde_end = i;
if (isec->is_alive) {
isec->fde_begin = i++;
while (i < fdes.size() && isec == get_isec(fdes[i]))
i++;
isec->fde_end = i;
} else {
fdes[i++].is_alive = false;
}
}
}
@@ -633,7 +690,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
if (ver != "@" && ver != "@@") {
if (ver.starts_with("@@"))
key = name;
has_symver.set(i - this->first_global);
has_symver[i - this->first_global] = true;
}
}
@@ -661,118 +718,39 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
template <typename E>
void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
if constexpr (is_riscv<E> || is_loongarch<E>) {
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
return a.r_offset < b.r_offset;
};
for (i64 i = 1; i < sections.size(); i++) {
std::unique_ptr<InputSection<E>> &isec = sections[i];
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
continue;
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
if (!std::is_sorted(rels.begin(), rels.end(), less))
sort(rels, less);
if (!ranges::is_sorted(rels, {}, &ElfRel<E>::r_offset))
ranges::stable_sort(rels, {}, &ElfRel<E>::r_offset);
}
}
}
static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
if (entsize == 1)
return data.find('\0', pos);
for (; pos <= data.size() - entsize; pos += entsize)
if (data.substr(pos, entsize).find_first_not_of('\0') == data.npos)
return pos;
return data.npos;
}
// Mergeable sections (sections with SHF_MERGE bit) typically contain
// string literals. Linker is expected to split the section contents
// into null-terminated strings, merge them with mergeable strings
// from other object files, and emit uniquified strings to an output
// file.
//
// This mechanism reduces the size of an output file. If two source
// files happen to contain the same string literal, the output will
// contain only a single copy of it.
//
// It is less common than string literals, but mergeable sections can
// contain fixed-sized read-only records too.
//
// This function splits the section contents into small pieces that we
// call "section fragments". Section fragment is a unit of merging.
//
// We do not support mergeable sections that have relocations.
template <typename E>
static std::unique_ptr<MergeableSection<E>>
split_section(Context<E> &ctx, InputSection<E> &sec) {
if (!sec.is_alive || sec.relsec_idx != -1 || sec.sh_size == 0)
return nullptr;
void ObjectFile<E>::convert_mergeable_sections(Context<E> &ctx) {
// Convert InputSections to MergeableSections
for (i64 i = 0; i < this->sections.size(); i++) {
InputSection<E> *isec = this->sections[i].get();
if (!isec || isec->sh_size == 0 || isec->relsec_idx != -1)
continue;
const ElfShdr<E> &shdr = sec.shdr();
if (!(shdr.sh_flags & SHF_MERGE))
return nullptr;
const ElfShdr<E> &shdr = isec->shdr();
if (!(shdr.sh_flags & SHF_MERGE))
continue;
i64 entsize = shdr.sh_entsize;
if (entsize == 0)
entsize = (shdr.sh_flags & SHF_STRINGS) ? 1 : (int)shdr.sh_addralign;
MergedSection<E> *parent =
MergedSection<E>::get_instance(ctx, isec->name(), shdr);
if (entsize == 0)
return nullptr;
i64 addralign = shdr.sh_addralign;
if (addralign == 0)
addralign = 1;
std::unique_ptr<MergeableSection<E>> m(new MergeableSection<E>);
m->parent = MergedSection<E>::get_instance(ctx, sec.name(), shdr.sh_type,
shdr.sh_flags, entsize, addralign);
m->p2align = sec.p2align;
// If thes section contents are compressed, uncompress them.
sec.uncompress(ctx);
std::string_view data = sec.contents;
m->contents = sec.contents;
if (data.size() > UINT32_MAX)
Fatal(ctx) << sec << ": mergeable section too large";
// Split sections
if (shdr.sh_flags & SHF_STRINGS) {
for (i64 pos = 0; pos < data.size();) {
m->frag_offsets.push_back(pos);
size_t end = find_null(data, pos, entsize);
if (end == data.npos)
Fatal(ctx) << sec << ": string is not null terminated";
pos = end + entsize;
if (parent) {
this->mergeable_sections[i] =
std::make_unique<MergeableSection<E>>(ctx, *parent, this->sections[i]);
this->sections[i] = nullptr;
}
} else {
if (data.size() % entsize)
Fatal(ctx) << sec << ": section size is not multiple of sh_entsize";
m->frag_offsets.reserve(data.size() / entsize);
for (i64 pos = 0; pos < data.size(); pos += entsize)
m->frag_offsets.push_back(pos);
}
// Compute hashes for section pieces
HyperLogLog estimator;
m->hashes.reserve(m->frag_offsets.size());
for (i64 i = 0; i < m->frag_offsets.size(); i++) {
u64 hash = hash_string(m->get_contents(i));
m->hashes.push_back(hash);
estimator.insert(hash);
}
m->parent->estimator.merge(estimator);
static Counter counter("string_fragments");
counter += m->frag_offsets.size();
return m;
}
// Usually a section is an atomic unit of inclusion or exclusion.
@@ -811,43 +789,17 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
// section piece in a section, but it doesn't do for any other types
// of symbols.
//
// In mold, we attach symbols to section pieces. If a relocation refers
// to a section symbol, and that symbol's section is a mergeable one,
// we create a new dummy symbol for a section piece and redirect the
// relocation to this new symbol. If a non-section symbol refers to a
// section piece, the section piece is attached to the symbol.
// Section garbage collection and Identical Code Folding work on graphs
// where sections or section pieces are vertices and relocations are
// edges. To make it easy to handle them, we rewrite symbols and
// relocations so that each non-absolute symbol always refers to either
// a non-mergeable section or a section piece.
//
// We do that only for SHF_ALLOC sections because GC and ICF work only
// on memory-allocated sections. Non-memory-allocated mergeable sections
// are not handled here for performance reasons.
template <typename E>
void ObjectFile<E>::initialize_mergeable_sections(Context<E> &ctx) {
mergeable_sections.resize(sections.size());
for (i64 i = 0; i < sections.size(); i++) {
if (std::unique_ptr<InputSection<E>> &isec = sections[i]) {
if (std::unique_ptr<MergeableSection<E>> m = split_section(ctx, *isec)) {
mergeable_sections[i] = std::move(m);
isec->is_alive = false;
}
}
}
}
template <typename E>
void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
for (std::unique_ptr<MergeableSection<E>> &m : mergeable_sections) {
if (m) {
m->fragments.reserve(m->frag_offsets.size());
for (i64 i = 0; i < m->frag_offsets.size(); i++) {
SectionFragment<E> *frag =
m->parent->insert(ctx, m->get_contents(i), m->hashes[i], m->p2align);
m->fragments.push_back(frag);
}
// Reclaim memory as we'll never use this vector again
m->hashes.clear();
m->hashes.shrink_to_fit();
}
}
void ObjectFile<E>::reattach_section_pieces(Context<E> &ctx) {
// Attach section pieces to symbols.
for (i64 i = 1; i < this->elf_syms.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
@@ -856,8 +808,9 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
if (esym.is_abs() || esym.is_common() || esym.is_undef())
continue;
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[get_shndx(esym)];
if (!m || m->fragments.empty())
i64 shndx = get_shndx(esym);
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[shndx];
if (!m || !m->parent.resolved)
continue;
SectionFragment<E> *frag;
@@ -874,49 +827,51 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
// Compute the size of frag_syms.
i64 nfrag_syms = 0;
for (std::unique_ptr<InputSection<E>> &isec : sections)
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC))
if (isec && (isec->shdr().sh_flags & SHF_ALLOC))
for (ElfRel<E> &r : isec->get_rels(ctx))
if (const ElfSym<E> &esym = this->elf_syms[r.r_sym];
esym.st_type == STT_SECTION && mergeable_sections[get_shndx(esym)])
nfrag_syms++;
esym.st_type == STT_SECTION)
if (mergeable_sections[get_shndx(esym)])
nfrag_syms++;
this->frag_syms.resize(nfrag_syms);
// For each relocation referring a mergeable section symbol, we create
// a new dummy non-section symbol and redirect the relocation to the
// newly-created symbol.
// For each relocation referring to a mergeable section symbol, we
// create a new dummy non-section symbol and redirect the relocation
// to the newly created symbol.
i64 idx = 0;
for (std::unique_ptr<InputSection<E>> &isec : sections) {
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
continue;
if (isec && (isec->shdr().sh_flags & SHF_ALLOC)) {
for (ElfRel<E> &r : isec->get_rels(ctx)) {
const ElfSym<E> &esym = this->elf_syms[r.r_sym];
if (esym.st_type != STT_SECTION)
continue;
for (ElfRel<E> &r : isec->get_rels(ctx)) {
const ElfSym<E> &esym = this->elf_syms[r.r_sym];
if (esym.st_type != STT_SECTION)
continue;
i64 shndx = get_shndx(esym);
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[shndx];
if (!m)
continue;
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[get_shndx(esym)];
if (!m)
continue;
assert(m->parent.resolved);
i64 r_addend = get_addend(*isec, r);
i64 r_addend = get_addend(*isec, r);
SectionFragment<E> *frag;
i64 in_frag_offset;
std::tie(frag, in_frag_offset) = m->get_fragment(esym.st_value + r_addend);
SectionFragment<E> *frag;
i64 in_frag_offset;
std::tie(frag, in_frag_offset) = m->get_fragment(esym.st_value + r_addend);
if (!frag)
Fatal(ctx) << *this << ": bad relocation at " << r.r_sym;
if (!frag)
Fatal(ctx) << *this << ": bad relocation at " << r.r_sym;
Symbol<E> &sym = this->frag_syms[idx];
sym.file = this;
sym.set_name("<fragment>");
sym.sym_idx = r.r_sym;
sym.visibility = STV_HIDDEN;
sym.set_frag(frag);
sym.value = in_frag_offset - r_addend;
r.r_sym = this->elf_syms.size() + idx;
idx++;
Symbol<E> &sym = this->frag_syms[idx];
sym.file = this;
sym.set_name("<fragment>");
sym.sym_idx = r.r_sym;
sym.visibility = STV_HIDDEN;
sym.set_frag(frag);
sym.value = in_frag_offset - r_addend;
r.r_sym = this->elf_syms.size() + idx;
idx++;
}
}
}
@@ -929,6 +884,8 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
template <typename E>
void ObjectFile<E>::parse(Context<E> &ctx) {
sections.resize(this->elf_sections.size());
mergeable_sections.resize(sections.size());
symtab_sec = this->find_section(SHT_SYMTAB);
if (symtab_sec) {
@@ -945,7 +902,6 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
initialize_sections(ctx);
initialize_symbols(ctx);
sort_relocations(ctx);
parse_ehframe(ctx);
}
// Symbols with higher priorities overwrites symbols with lower priorities.
@@ -960,6 +916,10 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
// 7. Unclaimed (nonexistent) symbol
//
// Ties are broken by file priority.
//
// Note that the above priorities are based on heuristics and not on exact
// science. We tried several different orders and settled on the current
// one just because it avoids link errors in all programs we've tested.
template <typename E>
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_in_archive) {
auto get_sym_rank = [&] {
@@ -983,7 +943,7 @@ template <typename E>
static u64 get_rank(const Symbol<E> &sym) {
if (!sym.file)
return 7 << 24;
return get_rank(sym.file, sym.esym(), !sym.file->is_alive);
return get_rank(sym.file, sym.esym(), !sym.file->is_reachable);
}
// Symbol's visibility is set to the most restrictive one. For example,
@@ -1043,7 +1003,7 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
}
std::scoped_lock lock(sym.mu);
if (get_rank(this, esym, !this->is_alive) < get_rank(sym)) {
if (get_rank(this, esym, !this->is_reachable) < get_rank(sym)) {
sym.file = this;
sym.set_input_section(isec);
sym.value = esym.st_value;
@@ -1058,7 +1018,7 @@ template <typename E>
void
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) {
assert(this->is_alive);
assert(this->is_reachable);
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
const ElfSym<E> &esym = this->elf_syms[i];
@@ -1072,14 +1032,16 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
if (sym.is_traced)
print_trace_symbol(ctx, *this, esym, sym);
if (sym.file && !esym.is_weak() &&
(esym.is_undef() || (esym.is_common() && !sym.esym().is_common())) &&
!sym.file->is_alive.test_and_set()) {
feeder(sym.file);
if (sym.file) {
bool undef_ref = esym.is_undef() && (!esym.is_weak() || sym.file->is_dso);
bool common_ref = esym.is_common() && !sym.esym().is_common();
if (sym.is_traced)
Out(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
<< " for " << sym;
if ((undef_ref || common_ref) && !sym.file->is_reachable.test_and_set()) {
feeder(sym.file);
if (sym.is_traced)
Out(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
<< " for " << sym;
}
}
}
}
@@ -1140,8 +1102,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
continue;
Symbol<E> &sym = *this->symbols[i];
std::scoped_lock lock(sym.mu);
if (sym.file != this) {
if (ctx.arg.warn_common)
Warn(ctx) << *this << ": multiple common symbols: " << sym;
@@ -1162,7 +1122,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
auto isec = std::make_unique<InputSection<E>>(ctx, *this, idx);
sym.file = this;
sym.set_input_section(isec.get());
sym.value = 0;
sym.sym_idx = i;
@@ -1183,7 +1142,7 @@ static bool should_write_to_local_symtab(Context<E> &ctx, Symbol<E> &sym) {
// merged, so their origins shouldn't matter, but I don't really
// know the rationale. Anyway, this is the behavior of the
// traditional linkers.
if (sym.name().starts_with(".L")) {
if (sym.name().starts_with(".L") || sym.name() == "L0\001") {
if (ctx.arg.discard_locals)
return false;
@@ -1197,12 +1156,9 @@ static bool should_write_to_local_symtab(Context<E> &ctx, Symbol<E> &sym) {
template <typename E>
void ObjectFile<E>::compute_symtab_size(Context<E> &ctx) {
if (ctx.arg.strip_all)
return;
this->output_sym_indices.resize(this->elf_syms.size(), -1);
auto is_alive = [&](Symbol<E> &sym) -> bool {
auto is_alive = [](Symbol<E> &sym) -> bool {
if (SectionFragment<E> *frag = sym.get_frag())
return frag->is_alive;
if (InputSection<E> *isec = sym.get_input_section())
@@ -1257,21 +1213,17 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
strtab_off += write_string(strtab_base + strtab_off, sym.name());
};
i64 local_symtab_idx = this->local_symtab_idx;
i64 global_symtab_idx = this->global_symtab_idx;
i64 local_idx = this->local_symtab_idx;
i64 global_idx = this->global_symtab_idx;
for (i64 i = 1; i < this->first_global; i++)
if (Symbol<E> &sym = *this->symbols[i]; sym.write_to_symtab)
write_sym(sym, local_symtab_idx++);
write_sym(sym, local_idx++);
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
if (sym.file == this && sym.write_to_symtab) {
if (sym.is_local(ctx))
write_sym(sym, local_symtab_idx++);
else
write_sym(sym, global_symtab_idx++);
}
if (sym.file == this && sym.write_to_symtab)
write_sym(sym, sym.is_local(ctx) ? local_idx++ : global_idx++);
}
}
@@ -1290,30 +1242,17 @@ std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
return out;
}
template <typename E>
SharedFile<E> *SharedFile<E>::create(Context<E> &ctx, MappedFile *mf) {
SharedFile<E> *obj = new SharedFile(ctx, mf);
ctx.dso_pool.emplace_back(obj);
return obj;
}
template <typename E>
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile *mf)
: InputFile<E>(ctx, mf) {
this->is_alive = !ctx.as_needed;
}
template <typename E>
std::string SharedFile<E>::get_soname(Context<E> &ctx) {
if (ElfShdr<E> *sec = this->find_section(SHT_DYNAMIC))
for (ElfDyn<E> &dyn : this->template get_data<ElfDyn<E>>(ctx, *sec))
if (dyn.d_tag == DT_SONAME)
return this->symbol_strtab.data() + dyn.d_val;
return this->get_string(ctx, sec->sh_link).data() + dyn.d_val;
if (this->mf->given_fullpath)
return this->filename;
return filepath(this->filename).filename().string();
return path_filename(this->filename);
}
template <typename E>
@@ -1365,6 +1304,25 @@ void SharedFile<E>::parse(Context<E> &ctx) {
counter += this->elf_syms.size();
}
template <typename E>
std::vector<std::string_view> SharedFile<E>::get_dt_needed(Context<E> &ctx) {
std::vector<std::string_view> vec;
if (ElfShdr<E> *sec = this->find_section(SHT_DYNAMIC))
for (ElfDyn<E> &dyn : this->template get_data<ElfDyn<E>>(ctx, *sec))
if (dyn.d_tag == DT_NEEDED)
vec.push_back(this->get_string(ctx, sec->sh_link).data() + dyn.d_val);
return vec;
}
template <typename E>
std::string_view SharedFile<E>::get_dt_audit(Context<E> &ctx) {
if (ElfShdr<E> *sec = this->find_section(SHT_DYNAMIC))
for (ElfDyn<E> &dyn : this->template get_data<ElfDyn<E>>(ctx, *sec))
if (dyn.d_tag == DT_AUDIT)
return this->get_string(ctx, sec->sh_link).data() + dyn.d_val;
return "";
}
// Symbol versioning is a GNU extension to the ELF file format. I don't
// particularly like the feature as it complicates the semantics of
// dynamic linking, but we need to support it anyway because it is
@@ -1429,7 +1387,8 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
for (i64 i = 0; i < this->symbols.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
const ElfSym<E> &esym = this->elf_syms[i];
if (esym.is_undef())
if (esym.is_undef() || sym.skip_dso)
continue;
std::scoped_lock lock(sym.mu);
@@ -1456,8 +1415,11 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
if (sym.is_traced)
print_trace_symbol(ctx, *this, esym, sym);
if (esym.is_undef() && !esym.is_weak() && sym.file && !sym.file->is_dso &&
!sym.file->is_alive.test_and_set()) {
// We follow undefined symbols in a DSO only to handle
// --no-allow-shlib-undefined.
if (esym.is_undef() && !esym.is_weak() && sym.file &&
(!sym.file->is_dso || !ctx.arg.allow_shlib_undefined) &&
!sym.file->is_reachable.test_and_set()) {
feeder(sym.file);
if (sym.is_traced)
@@ -1484,11 +1446,10 @@ std::span<Symbol<E> *> SharedFile<E>::get_symbols_at(Symbol<E> *sym) {
});
});
auto [begin, end] = std::equal_range(sorted_syms.begin(), sorted_syms.end(),
sym, [&](Symbol<E> *a, Symbol<E> *b) {
return a->esym().st_value < b->esym().st_value;
auto [begin, end] = ranges::equal_range(sorted_syms, sym->esym().st_value, {},
[](Symbol<E> *x) {
return x->esym().st_value;
});
return {&*begin, (size_t)(end - begin)};
}
@@ -1510,9 +1471,12 @@ i64 SharedFile<E>::get_alignment(Symbol<E> *sym) {
template <typename E>
bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)this->mf->data;
std::span<ElfPhdr<E>> phdrs((ElfPhdr<E> *)(this->mf->data + ehdr.e_phoff),
ehdr.e_phnum);
u64 val = sym->esym().st_value;
for (ElfPhdr<E> &phdr : this->get_phdrs())
for (ElfPhdr<E> &phdr : phdrs)
if ((phdr.p_type == PT_LOAD || phdr.p_type == PT_GNU_RELRO) &&
!(phdr.p_flags & PF_W) &&
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
@@ -1522,9 +1486,6 @@ bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
template <typename E>
void SharedFile<E>::compute_symtab_size(Context<E> &ctx) {
if (ctx.arg.strip_all)
return;
this->output_sym_indices.resize(this->elf_syms.size(), -1);
// Compute the size of global symbols.
@@ -1571,7 +1532,8 @@ template class SharedFile<E>;
template Symbol<E> *get_symbol(Context<E> &, std::string_view, std::string_view);
template Symbol<E> *get_symbol(Context<E> &, std::string_view);
template std::string_view demangle(const Symbol<E> &);
template ComdatGroup *insert_comdat_group(Context<E> &, std::string_view);
template std::ostream &operator<<(std::ostream &, const Symbol<E> &);
template std::ostream &operator<<(std::ostream &, const InputFile<E> &);
} // namespace mold::elf
} // namespace mold
+155 -275
View File
@@ -1,15 +1,9 @@
#include "mold.h"
#include <limits>
#include <zlib.h>
#include <zstd.h>
namespace mold::elf {
typedef enum {
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL,
BASEREL, IFUNC_DYNREL,
} Action;
namespace mold {
static i64 to_p2align(u64 alignment) {
if (alignment == 0)
@@ -106,125 +100,36 @@ void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
}
}
template <typename E>
static bool
is_relr_reloc(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel) {
ElfShdr<E> shdr = isec.shdr();
return ctx.arg.pack_dyn_relocs_relr &&
!(shdr.sh_flags & SHF_EXECINSTR) &&
shdr.sh_addralign % sizeof(Word<E>) == 0 &&
rel.r_offset % sizeof(Word<E>) == 0;
}
typedef enum : u8 { NONE, ERROR, COPYREL, PLT, CPLT } Action;
template <typename E>
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
const ElfRel<E> &rel, Action action) {
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
auto error = [&] {
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
<< std::hex << rel.r_offset << " against symbol `"
<< sym << "' can not be used; recompile with " << msg;
};
auto check_textrel = [&] {
if (!writable) {
if (ctx.arg.z_text) {
error();
} else if (ctx.arg.warn_textrel) {
Warn(ctx) << isec << ": relocation against symbol `" << sym
<< "' in read-only section";
}
ctx.has_textrel = true;
}
};
auto copyrel = [&] {
assert(sym.is_imported);
if (sym.esym().st_visibility == STV_PROTECTED) {
Error(ctx) << isec
<< ": cannot make copy relocation for protected symbol '" << sym
<< "', defined in " << *sym.file << "; recompile with -fPIC";
}
sym.flags |= NEEDS_COPYREL;
};
auto dynrel = [&] {
check_textrel();
isec.file.num_dynrel++;
};
static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
switch (action) {
case NONE:
break;
case ERROR:
// Print out the "recompile with -fPIC" error message.
error();
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
<< std::hex << rel.r_offset << " against symbol `"
<< sym << "' can not be used; recompile with -fPIC";
break;
case COPYREL:
// Create a copy relocation.
if (!ctx.arg.z_copyreloc)
error();
copyrel();
break;
case DYN_COPYREL:
// Same as COPYREL but try to avoid creating a copy relocation by
// creating a dynamic relocation instead if the relocation is in
// a writable section.
//
// GHC (Glasgow Haskell Compiler) places a small amount of data in
// .text before each function and access that data with a fixed
// offset. The function breaks if we copy-relocate the data. For such
// programs, we should avoid copy relocations if possible.
//
// Besides GHC, copy relocation is a hacky solution, so if we can
// represent a relocation either with copyrel or dynrel, we prefer
// dynamic relocation.
if (writable || !ctx.arg.z_copyreloc)
dynrel();
else
copyrel();
// Create a copy relocation
if (!sym.file->is_dso) {
assert(sym.esym().is_undef_weak());
Error(ctx) << isec << ": cannot create a copy relocation for "
<< sym <<"; recompile with -fPIE or -fPIC";
}
sym.flags |= NEEDS_COPYREL;
break;
case PLT:
// Create a PLT entry.
// Create a PLT entry
sym.flags |= NEEDS_PLT;
break;
case CPLT:
// Create a canonical PLT entry.
// Create a canonical PLT entry
sym.flags |= NEEDS_CPLT;
break;
case DYN_CPLT:
// Same as CPLT but try to avoid creating a canonical PLT creating by
// creating a dynamic relocation instead if the relocation is in a
// writable section. The motivation behind it is hte same as DYN_COPYREL.
if (writable)
dynrel();
else
sym.flags |= NEEDS_CPLT;
break;
case DYNREL:
// Create a dynamic relocation.
dynrel();
break;
case BASEREL:
// Create a base relocation.
check_textrel();
if (!is_relr_reloc(ctx, isec, rel))
isec.file.num_dynrel++;
break;
case IFUNC_DYNREL:
// Create an IRELATIVE relocation for a GNU ifunc symbol.
//
// We usually create an IRELATIVE relocation in .got for each ifunc.
// However, if a statically-initialized pointer is initialized to an
// ifunc's address, we have no choice other than emitting an IRELATIVE
// relocation for each such pointer.
dynrel();
ctx.num_ifunc_dynrels++;
break;
default:
unreachable();
}
}
@@ -249,102 +154,44 @@ static inline i64 get_sym_type(Symbol<E> &sym) {
}
template <typename E>
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
// We cannot promote them to dynamic relocations because the dynamic
// linker generally does not support PC-relative relocations.
static Action table[3][4] = {
static Action table[][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
{ ERROR, NONE, COPYREL, CPLT }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
return table[get_output_type(ctx)][get_sym_type(sym)];
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
do_action(ctx, action, *this, sym, rel);
}
template <typename E>
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
// This is a decision table for absolute relocations that is smaller
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
// generally does not support dynamic relocations smaller than the
// pointer size, we need to report an error if a relocation cannot be
// resolved at link-time.
static Action table[3][4] = {
static Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // Shared object
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return sym.is_pde_ifunc(ctx) ? NONE : IFUNC_DYNREL;
// This is a decision table for absolute relocations for the pointer
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
// a dynamic relocation if we cannot resolve an address at link-time.
static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
};
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC_DYNREL;
// As a special case, we do not create copy relocations nor canonical
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
// GOT-like section, and no user-generated code directly uses values
// in it.
static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
};
return table[get_output_type(ctx)][get_sym_type(sym)];
}
template <typename E>
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_pcrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_absrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_dyn_absrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
do_action(ctx, action, *this, sym, rel);
}
template <typename E>
void InputSection<E>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
if (ctx.arg.is_static ||
(ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
// Relax TLSDESC to Local Exec. In this case, we directly materialize
// a TP-relative offset, so no dynamic relocation is needed.
//
@@ -373,92 +220,38 @@ void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
<< " recompile with -fPIC";
}
template <typename E>
static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel,
Action action) {
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
auto emit_abs_dynrel = [&] {
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = A;
};
switch (action) {
case COPYREL:
case CPLT:
case NONE:
*(Word<E> *)loc = S + A;
break;
case BASEREL:
if (is_relr_reloc(ctx, isec, rel)) {
*(Word<E> *)loc = S + A;
} else {
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = S + A;
}
break;
case DYN_COPYREL:
if (writable || !ctx.arg.z_copyreloc)
emit_abs_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYN_CPLT:
if (writable)
emit_abs_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYNREL:
emit_abs_dynrel();
break;
case IFUNC_DYNREL:
if constexpr (supports_ifunc<E>) {
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = addr;
} else {
unreachable();
}
break;
default:
unreachable();
}
}
template <typename E>
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P,
ElfRel<E> **dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
get_dyn_absrel_action(ctx, sym));
}
template <typename E>
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P,
ElfRel<E> **dynrel) {
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
get_ppc64_toc_action(ctx, sym));
}
template <typename E>
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
return;
// Copy data
if constexpr (is_riscv<E>)
copy_contents_riscv(ctx, buf);
else
// Copy data. In RISC-V and LoongArch object files, sections are not
// atomic unit of copying because of relaxation. That is, some
// relocations are allowed to remove bytes from the middle of a
// section and shrink the overall size of it.
if constexpr (is_riscv<E> || is_loongarch<E>) {
std::span<RelocDelta> deltas = extra.r_deltas;
if (deltas.empty()) {
// If a section is not relaxed, we can copy it as a one big chunk.
copy_contents(ctx, buf);
} else {
// A relaxed section is copied piece-wise.
memcpy(buf, contents.data(), deltas[0].offset);
for (i64 i = 0; i < deltas.size(); i++) {
i64 offset = deltas[i].offset;
i64 delta = deltas[i].delta;
i64 end = (i + 1 == deltas.size()) ? contents.size() : deltas[i + 1].offset;
i64 removed_bytes = get_removed_bytes(deltas, i);
memcpy(buf + offset + removed_bytes - delta,
contents.data() + offset + removed_bytes,
end - offset - removed_bytes);
}
}
} else {
copy_contents(ctx, buf);
}
// Apply relocations
if (!ctx.arg.relocatable) {
@@ -474,12 +267,14 @@ template <typename E>
std::string_view
InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
for (Symbol<E> *sym : file.symbols) {
const ElfSym<E> &esym = sym->esym();
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
if (ctx.arg.demangle)
return demangle(*sym);
return sym->name();
if (sym->file == &file) {
const ElfSym<E> &esym = sym->esym();
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
if (ctx.arg.demangle)
return demangle(*sym);
return sym->name();
}
}
}
return "";
@@ -530,6 +325,7 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
// Every ELF file has an absolute local symbol as its first symbol.
// Referring to that symbol is always valid.
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
if (is_undef && sym.esym().is_undef()) {
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR && !sym.is_imported) {
record();
@@ -541,20 +337,104 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
}
}
// If a protected/hidden undefined symbol is resolved to other .so,
// it's handled as if no symbols were found.
if (sym.file->is_dso &&
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
record();
return true;
return false;
}
template <typename E>
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
std::unique_ptr<InputSection<E>> &isec)
: parent(parent), p2align(isec->p2align), section(std::move(isec)) {
section->uncompress(ctx);
std::scoped_lock lock(parent.mu);
parent.members.push_back(this);
}
static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
if (entsize == 1)
return data.find('\0', pos);
for (; pos <= data.size() - entsize; pos += entsize)
if (data.substr(pos, entsize).find_first_not_of('\0') == data.npos)
return pos;
return data.npos;
}
// Mergeable sections (sections with SHF_MERGE bit) typically contain
// string literals. Linker is expected to split the section contents
// into null-terminated strings, merge them with mergeable strings
// from other object files, and emit uniquified strings to an output
// file.
//
// This mechanism reduces the size of an output file. If two source
// files happen to contain the same string literal, the output will
// contain only a single copy of it.
//
// It is less common than string literals, but mergeable sections can
// contain fixed-sized read-only records too.
//
// This function splits the section contents into small pieces that we
// call "section fragments". Section fragment is a unit of merging.
//
// We do not support mergeable sections that have relocations.
template <typename E>
void MergeableSection<E>::split_contents(Context<E> &ctx) {
std::string_view data = section->contents;
if (data.size() > UINT32_MAX)
Fatal(ctx) << *section << ": mergeable section too large";
i64 entsize = parent.shdr.sh_entsize;
// Split sections
if (parent.shdr.sh_flags & SHF_STRINGS) {
for (i64 pos = 0; pos < data.size();) {
frag_offsets.push_back(pos);
size_t end = find_null(data, pos, entsize);
if (end == data.npos)
Fatal(ctx) << *section << ": string is not null terminated";
pos = end + entsize;
}
} else {
if (data.size() % entsize)
Fatal(ctx) << *section << ": section size is not multiple of sh_entsize";
frag_offsets.reserve(data.size() / entsize);
for (i64 pos = 0; pos < data.size(); pos += entsize)
frag_offsets.push_back(pos);
}
return false;
// Compute hashes for section pieces
HyperLogLog estimator;
hashes.reserve(frag_offsets.size());
for (i64 i = 0; i < frag_offsets.size(); i++) {
u64 hash = hash_string(get_contents(i));
hashes.push_back(hash);
estimator.insert(hash);
}
parent.estimator.merge(estimator);
static Counter counter("string_fragments");
counter += frag_offsets.size();
}
template <typename E>
void MergeableSection<E>::resolve_contents(Context<E> &ctx) {
fragments.reserve(frag_offsets.size());
for (i64 i = 0; i < frag_offsets.size(); i++)
fragments.push_back(parent.insert(ctx, get_contents(i), hashes[i], p2align));
// Reclaim memory as we'll never use this vector again
hashes.clear();
hashes.shrink_to_fit();
}
using E = MOLD_TARGET;
template bool cie_equals(const CieRecord<E> &, const CieRecord<E> &);
template class InputSection<E>;
template class MergeableSection<E>;
} // namespace mold::elf
} // namespace mold
+422
View File
@@ -0,0 +1,422 @@
// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually
// a shared object file but an ASCII text file containing a linker
// script to include a "real" libc.so file. Therefore, we need to
// support a (very limited) subset of the linker script language.
#include "mold.h"
#include <cctype>
namespace mold {
static std::string_view get_line(std::string_view input, const char *pos) {
assert(input.data() <= pos);
assert(pos < input.data() + input.size());
i64 start = input.rfind('\n', pos - input.data());
if (start == input.npos)
start = 0;
else
start++;
i64 end = input.find('\n', pos - input.data());
if (end == input.npos)
end = input.size();
return input.substr(start, end - start);
}
template <typename E>
void Script<E>::error(std::string_view pos, std::string msg) {
std::string_view input = mf->get_contents();
std::string_view line = get_line(input, pos.data());
i64 lineno = 1;
for (i64 i = 0; input.data() + i < line.data(); i++)
if (input[i] == '\n')
lineno++;
std::string label = mf->name + ":" + std::to_string(lineno) + ": ";
i64 indent = strlen("mold: fatal: ") + label.size();
i64 column = pos.data() - line.data();
Fatal(ctx) << label << line << "\n"
<< std::string(indent + column, ' ') << "^ " << msg;
}
template <typename E>
void Script<E>::tokenize() {
std::string_view input = mf->get_contents();
while (!input.empty()) {
if (isspace(input[0])) {
input = input.substr(1);
continue;
}
if (input.starts_with("/*")) {
i64 pos = input.find("*/", 2);
if (pos == std::string_view::npos)
error(input, "unclosed comment");
input = input.substr(pos + 2);
continue;
}
if (input[0] == '#') {
i64 pos = input.find("\n", 1);
if (pos == std::string_view::npos)
break;
input = input.substr(pos + 1);
continue;
}
if (input[0] == '"') {
i64 pos = input.find('"', 1);
if (pos == std::string_view::npos)
error(input, "unclosed string literal");
tokens.push_back(input.substr(0, pos + 1));
input = input.substr(pos + 1);
continue;
}
i64 pos = input.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$/\\~=+[]*?-!^:");
if (pos == 0)
pos = 1;
else if (pos == input.npos)
pos = input.size();
tokens.push_back(input.substr(0, pos));
input = input.substr(pos);
}
}
template <typename E>
std::span<std::string_view>
Script<E>::skip(std::span<std::string_view> tok, std::string_view str) {
if (tok.empty())
Fatal(ctx) << mf->name << ": expected '" << str << "', but got EOF";
if (tok[0] != str)
error(tok[0], "expected '" + std::string(str) + "'");
return tok.subspan(1);
}
static std::string_view unquote(std::string_view s) {
if (s.size() > 0 && s[0] == '"') {
assert(s[s.size() - 1] == '"');
return s.substr(1, s.size() - 2);
}
return s;
}
template <typename E>
std::span<std::string_view>
Script<E>::read_output_format(std::span<std::string_view> tok) {
tok = skip(tok, "(");
while (!tok.empty() && tok[0] != ")")
tok = tok.subspan(1);
if (tok.empty())
Fatal(ctx) << mf->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
std::string rel = std::filesystem::relative(path, ctx.arg.sysroot).string();
return rel != "." && !rel.starts_with("../");
}
template <typename E>
MappedFile *Script<E>::resolve_path(std::string_view tok, bool check_target) {
std::string str(unquote(tok));
auto open = [&](const std::string &path) -> MappedFile * {
MappedFile *mf = open_file(ctx, path);
if (!mf)
return nullptr;
if (check_target) {
std::string_view target = get_machine_type(ctx, rctx, mf);
if (!target.empty() && target != E::name) {
Warn(ctx) << path << ": skipping incompatible file: " << target
<< " (e_machine " << (int)E::e_machine << ")";
return nullptr;
}
}
return mf;
};
// GNU ld prepends the sysroot if a pathname starts with '/' and the
// script being processed is in the sysroot. We do the same.
if (str.starts_with('/') && is_in_sysroot(ctx, mf->name))
return must_open_file(ctx, ctx.arg.sysroot + str);
if (str.starts_with('=')) {
std::string path;
if (ctx.arg.sysroot.empty())
path = str.substr(1);
else
path = ctx.arg.sysroot + str.substr(1);
return must_open_file(ctx, path);
}
if (str.starts_with("-l"))
return find_library(ctx, rctx, str.substr(2));
if (!str.starts_with('/'))
if (MappedFile *mf2 = open(path_clean(mf->name + "/../" + str)))
return mf2;
if (MappedFile *mf = open(str))
return mf;
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + str;
if (MappedFile *mf = open(path))
return mf;
}
error(tok, "library not found: " + str);
}
template <typename E>
std::span<std::string_view>
Script<E>::read_group(std::span<std::string_view> tok) {
tok = skip(tok, "(");
while (!tok.empty() && tok[0] != ")") {
if (tok[0] == "AS_NEEDED") {
bool orig = rctx.as_needed;
rctx.as_needed = true;
tok = read_group(tok.subspan(1));
rctx.as_needed = orig;
continue;
}
MappedFile *mf = resolve_path(tok[0], true);
read_file(ctx, rctx, mf);
tok = tok.subspan(1);
}
if (tok.empty())
Fatal(ctx) << mf->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
void Script<E>::parse_linker_script() {
std::call_once(once, [&] { tokenize(); });
std::span<std::string_view> tok = tokens;
while (!tok.empty()) {
if (tok[0] == "OUTPUT_FORMAT") {
tok = read_output_format(tok.subspan(1));
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
tok = read_group(tok.subspan(1));
} else if (tok[0] == "VERSION") {
tok = tok.subspan(1);
tok = skip(tok, "{");
tok = read_version_script(tok);
tok = skip(tok, "}");
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
get_symbol(ctx, unquote(tok[2])));
tok = tok.subspan(4);
} else if (tok[0] == ";") {
tok = tok.subspan(1);
} else {
error(tok[0], "unknown linker script token");
}
}
}
template <typename E>
std::string_view Script<E>::get_script_output_type() {
std::call_once(once, [&] { tokenize(); });
std::span<std::string_view> tok = tokens;
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
if (tok[2] == "elf64-x86-64")
return X86_64::name;
if (tok[2] == "elf32-i386")
return I386::name;
}
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
tok[1] == "(")
if (MappedFile *mf = resolve_path(tok[2], false))
return get_machine_type(ctx, rctx, mf);
return "";
}
static bool read_label(std::span<std::string_view> &tok, std::string label) {
if (tok.size() >= 1 && tok[0] == label + ":") {
tok = tok.subspan(1);
return true;
}
if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") {
tok = tok.subspan(2);
return true;
}
return false;
}
template <typename E>
std::span<std::string_view>
Script<E>::read_version_script_commands(std::span<std::string_view> tok,
std::string_view ver_str, u16 ver_idx,
bool is_global, bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (read_label(tok, "global")) {
is_global = true;
continue;
}
if (read_label(tok, "local")) {
is_global = false;
continue;
}
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(tok, "{");
tok = read_version_script_commands(tok, ver_str, ver_idx, is_global, false);
} else {
tok = skip(tok, "\"C++\"");
tok = skip(tok, "{");
tok = read_version_script_commands(tok, ver_str, ver_idx, is_global, true);
}
tok = skip(tok, "}");
tok = skip(tok, ";");
continue;
}
if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), mf->name, ver_str,
ver_idx, is_cpp});
} else {
ctx.version_patterns.push_back({unquote(tok[0]), mf->name, ver_str,
VER_NDX_LOCAL, is_cpp});
}
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "}")
break;
tok = skip(tok, ";");
}
return tok;
}
template <typename E>
std::span<std::string_view>
Script<E>::read_version_script(std::span<std::string_view> tok) {
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
while (!tok.empty() && tok[0] != "}") {
std::string_view ver_str;
u16 ver_idx;
if (tok[0] == "{") {
ver_str = "global";
ver_idx = VER_NDX_GLOBAL;
} else {
ver_str = tok[0];
ver_idx = next_ver++;
ctx.arg.version_definitions.emplace_back(tok[0]);
tok = tok.subspan(1);
}
tok = skip(tok, "{");
tok = read_version_script_commands(tok, ver_str, ver_idx, true, false);
tok = skip(tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
tok = skip(tok, ";");
}
return tok;
}
template <typename E>
void Script<E>::parse_version_script() {
std::call_once(once, [&] { tokenize(); });
std::span<std::string_view> tok = tokens;
tok = read_version_script(tok);
if (!tok.empty())
error(tok[0], "trailing garbage token");
}
template <typename E>
std::span<std::string_view>
Script<E>::read_dynamic_list_commands(std::span<std::string_view> tok,
std::vector<DynamicPattern> &result,
bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(tok, "{");
tok = read_dynamic_list_commands(tok, result, false);
} else {
tok = skip(tok, "\"C++\"");
tok = skip(tok, "{");
tok = read_dynamic_list_commands(tok, result, true);
}
tok = skip(tok, "}");
tok = skip(tok, ";");
continue;
}
result.push_back({unquote(tok[0]), "", is_cpp});
tok = skip(tok.subspan(1), ";");
}
return tok;
}
template <typename E>
std::vector<DynamicPattern> Script<E>::parse_dynamic_list() {
std::call_once(once, [&] { tokenize(); });
std::span<std::string_view> tok = tokens;
std::vector<DynamicPattern> result;
tok = skip(tok, "{");
tok = read_dynamic_list_commands(tok, result, false);
tok = skip(tok, "}");
tok = skip(tok, ";");
if (!tok.empty())
error(tok[0], "trailing garbage token");
for (DynamicPattern &p : result)
p.source = mf->name;
return result;
}
template <typename E>
std::vector<DynamicPattern>
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
ReaderContext rctx;
MappedFile *mf = must_open_file(ctx, std::string(path));
return Script(ctx, rctx, mf).parse_dynamic_list();
}
using E = MOLD_TARGET;
template class Script<E>;
template
std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);
} // namespace mold
+33 -28
View File
@@ -95,7 +95,7 @@
# define LOG std::ostringstream()
#endif
namespace mold::elf {
namespace mold {
// Global variables
// We store LTO-related information to global variables,
@@ -177,13 +177,14 @@ static PluginStatus add_input_file(const char *path) {
static i64 file_priority = 100;
MappedFile *mf = must_open_file(ctx, path);
mf->is_dependency = false;
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, "");
ctx.obj_pool.emplace_back(file);
lto_objects<E>.push_back(file);
file->priority = file_priority++;
file->is_alive = true;
file->is_reachable = true;
file->parse(ctx);
file->resolve_symbols(ctx);
return LDPS_OK;
@@ -200,10 +201,7 @@ static PluginStatus release_input_file(const void *handle) {
LOG << "release_input_file\n";
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
if (file.mf->fd != -1) {
close(file.mf->fd);
file.mf->fd = -1;
}
file.mf->close_fd();
return LDPS_OK;
}
@@ -287,7 +285,7 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
// If file is an archive member which was not chose to be included in
// to the final result, we need to make the plugin to ignore all
// symbols.
if (!file.is_alive) {
if (!file.is_reachable) {
assert(!is_v2);
for (int i = 0; i < nsyms; i++)
psyms[i].resolution = LDPR_PREEMPTED_REG;
@@ -345,7 +343,7 @@ static void restart_process(Context<E> &ctx) {
args.push_back(strdup(std::string(arg).c_str()));
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
if (file->is_lto_obj && !file->is_alive)
if (file->is_lto_obj && !file->is_reachable)
args.push_back(strdup(("--:ignore-ir-file=" +
file->mf->get_identifier()).c_str()));
@@ -449,7 +447,7 @@ static void load_lto_plugin(Context<E> &ctx) {
phase = 1;
gctx<E> = &ctx;
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!handle)
Fatal(ctx) << "could not open plugin file: " << dlerror();
@@ -570,7 +568,7 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
// Returns false if it's GCC.
template <typename E>
static bool is_llvm(Context<E> &ctx) {
return ctx.arg.plugin.ends_with("LLVMgold.so");
return ctx.arg.plugin.find("LLVMgold.") != ctx.arg.plugin.npos;
}
// Returns true if a given linker plugin supports the get_symbols_v3 API.
@@ -590,17 +588,23 @@ create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
file.offset = mf->get_offset();
file.filesize = mf->size;
if (mf2->fd == -1)
mf2->fd = open(file.name, O_RDONLY);
mf2->reopen_fd(file.name);
file.fd = mf2->fd;
if (file.fd == -1)
if (!file.fd)
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
return file;
}
template <typename E>
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
if (ctx.arg.plugin.empty())
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
<< "because no -plugin option was given. Please make sure you "
<< "added -flto not only for creating object files but also for "
<< "creating the final executable.";
load_lto_plugin(ctx);
// V0 API's claim_file is not thread-safe.
@@ -609,12 +613,6 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
if (!is_gcc_linker_api_v1)
lock.lock();
if (ctx.arg.plugin.empty())
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
<< "because no -plugin option was given. Please make sure you "
<< "added -flto not only for creating object files but also for "
<< "creating the final executable.";
// Create mold's object instance
ObjectFile<E> *obj = new ObjectFile<E>;
ctx.obj_pool.emplace_back(obj);
@@ -645,8 +643,7 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// open files" issue, we close fd only for GCC. This is ugly, though.
if (!is_llvm(ctx)) {
MappedFile *mf2 = mf->parent ? mf->parent : mf;
close(mf2->fd);
mf2->fd = -1;
mf2->close_fd();
}
// Create a symbol strtab
@@ -657,6 +654,7 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// Initialize esyms
obj->lto_elf_syms.resize(plugin_symbols.size() + 1);
obj->lto_comdat_groups.resize(plugin_symbols.size() + 1);
i64 strtab_offset = 1;
for (i64 i = 0; i < plugin_symbols.size(); i++) {
@@ -667,6 +665,13 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
i64 len = strlen(psym.name);
memcpy(strtab.data() + strtab_offset, psym.name, len);
strtab_offset += len + 1;
// comdat_key is non-null if the symbol is defined in a comdat member
// section. We handle such symbols differently than comdat symbols in
// a regular file because, unlike regular object files, IR files don't
// have input sections.
if (psym.comdat_key)
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, psym.comdat_key);
}
obj->symbol_strtab = save_string(ctx, strtab);
@@ -678,8 +683,8 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// Entry point
template <typename E>
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
Timer t(ctx, "do_lto");
std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
Timer t(ctx, "run_lto_plugin");
load_lto_plugin(ctx);
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
@@ -689,7 +694,7 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
phase = 2;
// Set `referenced_by_regular_obj` bit.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (!file->is_lto_obj) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file && !sym->file->is_dso &&
@@ -721,7 +726,7 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
// given to the LTO backend. Such sections contains code and data for
// peripherails (typically GPUs).
for (ObjectFile<E> *file : ctx.objs) {
if (file->is_alive && !file->is_lto_obj && file->is_gcc_offload_obj) {
if (file->is_reachable && !file->is_lto_obj && file->is_gcc_offload_obj) {
PluginInputFile pfile = create_plugin_input_file(ctx, file->mf);
int claimed = false;
claim_file_hook(&pfile, &claimed);
@@ -747,7 +752,7 @@ void lto_cleanup(Context<E> &ctx) {
using E = MOLD_TARGET;
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
template std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &);
template void lto_cleanup(Context<E> &);
} // namespace mold::elf
} // namespace mold
+4 -4
View File
@@ -1,7 +1,7 @@
#include "mold.h"
#include "lto.h"
namespace mold::elf {
namespace mold {
template <typename E>
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
@@ -9,7 +9,7 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
}
template <typename E>
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
return {};
}
@@ -19,7 +19,7 @@ void lto_cleanup(Context<E> &ctx) {}
using E = MOLD_TARGET;
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
template std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &);
template void lto_cleanup(Context<E> &);
} // namespace mold::elf
} // namespace mold
+6 -2
View File
@@ -1,6 +1,6 @@
#pragma once
#include "../common/integers.h"
#include "../lib/integers.h"
namespace mold {
@@ -73,7 +73,11 @@ enum PluginOutputFileType {
struct PluginInputFile {
const char *name;
i32 fd;
#if __MINGW32__
HANDLE fd;
#else
int fd;
#endif
u64 offset;
u64 filesize;
void *handle;
+202 -256
View File
@@ -1,17 +1,11 @@
#include "mold.h"
#include "../common/archive-file.h"
#include "../common/output-file.h"
#include "../lib/archive-file.h"
#include <cstring>
#include <functional>
#include <iomanip>
#include <map>
#include <regex>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/global_control.h>
#include <tbb/parallel_for_each.h>
#include <unordered_set>
#ifdef _WIN32
@@ -23,118 +17,45 @@
#ifdef MOLD_X86_64
int main(int argc, char **argv) {
return mold::elf::elf_main<mold::elf::X86_64>(argc, argv);
mold::set_mimalloc_options();
return mold::mold_main<mold::X86_64>(argc, argv);
}
#endif
namespace mold::elf {
// Read the beginning of a given file and returns its machine type
// (e.g. EM_X86_64 or EM_386).
template <typename E>
std::string_view get_machine_type(Context<E> &ctx, MappedFile *mf) {
auto get_elf_type = [&](u8 *buf) -> std::string_view {
bool is_le = (((ElfEhdr<I386> *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
bool is_64;
u32 e_machine;
if (is_le) {
auto &ehdr = *(ElfEhdr<I386> *)buf;
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
e_machine = ehdr.e_machine;
} else {
auto &ehdr = *(ElfEhdr<M68K> *)buf;
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
e_machine = ehdr.e_machine;
}
switch (e_machine) {
case EM_386:
return I386::target_name;
case EM_X86_64:
return X86_64::target_name;
case EM_ARM:
return ARM32::target_name;
case EM_AARCH64:
return ARM64::target_name;
case EM_RISCV:
if (is_le)
return is_64 ? RV64LE::target_name : RV32LE::target_name;
return is_64 ? RV64BE::target_name : RV32BE::target_name;
case EM_PPC:
return PPC32::target_name;
case EM_PPC64:
return is_le ? PPC64V2::target_name : PPC64V1::target_name;
case EM_S390X:
return S390X::target_name;
case EM_SPARC64:
return SPARC64::target_name;
case EM_68K:
return M68K::target_name;
case EM_SH:
return SH4::target_name;
case EM_ALPHA:
return ALPHA::target_name;
case EM_LOONGARCH:
return is_64 ? LOONGARCH64::target_name : LOONGARCH32::target_name;
default:
return "";
}
};
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
case FileType::GCC_LTO_OBJ:
return get_elf_type(mf->data);
case FileType::AR:
for (MappedFile *child : read_fat_archive_members(ctx, mf))
if (FileType ty = get_file_type(ctx, child);
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
return get_elf_type(child->data);
return "";
case FileType::THIN_AR:
for (MappedFile *child : read_thin_archive_members(ctx, mf))
if (FileType ty = get_file_type(ctx, child);
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
return get_elf_type(child->data);
return "";
case FileType::TEXT:
return get_script_output_type(ctx, mf);
default:
return "";
}
}
namespace mold {
template <typename E>
static void
check_file_compatibility(Context<E> &ctx, MappedFile *mf) {
std::string_view target = get_machine_type(ctx, mf);
check_file_compatibility(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
std::string_view target = get_machine_type(ctx, rctx, mf);
if (target != ctx.arg.emulation)
Fatal(ctx) << mf->name << ": incompatible file type: "
<< ctx.arg.emulation << " is expected but got " << target;
}
template <typename E>
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile *mf,
std::string archive_name) {
static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
static Counter count("parsed_objs");
count++;
check_file_compatibility(ctx, mf);
check_file_compatibility(ctx, rctx, mf);
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
ctx.obj_pool.emplace_back(file);
file->priority = ctx.file_priority++;
ctx.tg.run([file, &ctx] { file->parse(ctx); });
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
return file;
}
template <typename E>
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile *mf,
std::string archive_name) {
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
static Counter count("parsed_lto_objs");
count++;
@@ -144,8 +65,9 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile *mf,
ObjectFile<E> *file = read_lto_object(ctx, mf);
file->priority = ctx.file_priority++;
file->archive_name = archive_name;
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
file->is_alive = !file->is_in_lib;
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
return file;
@@ -153,40 +75,39 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile *mf,
template <typename E>
static SharedFile<E> *
new_shared_file(Context<E> &ctx, MappedFile *mf) {
check_file_compatibility(ctx, mf);
new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
check_file_compatibility(ctx, rctx, mf);
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
ctx.dso_pool.emplace_back(file);
file->priority = ctx.file_priority++;
ctx.tg.run([file, &ctx] { file->parse(ctx); });
file->is_reachable = !rctx.as_needed;
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
return file;
}
template <typename E>
void read_file(Context<E> &ctx, MappedFile *mf) {
if (ctx.visited.contains(mf->name))
return;
void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, mf, ""));
ctx.objs.push_back(new_object_file(ctx, rctx, mf, ""));
return;
case FileType::ELF_DSO:
ctx.dsos.push_back(new_shared_file(ctx, mf));
ctx.visited.insert(mf->name);
ctx.dsos.push_back(new_shared_file(ctx, rctx, mf));
return;
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile *child : read_archive_members(ctx, mf)) {
switch (get_file_type(ctx, child)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
ctx.objs.push_back(new_object_file(ctx, rctx, child, mf->name));
break;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
if (ObjectFile<E> *file = new_lto_obj(ctx, rctx, child, mf->name))
ctx.objs.push_back(file);
break;
case FileType::ELF_DSO:
@@ -197,15 +118,13 @@ void read_file(Context<E> &ctx, MappedFile *mf) {
break;
}
}
if (!ctx.whole_archive)
ctx.visited.insert(mf->name);
return;
case FileType::TEXT:
parse_linker_script(ctx, mf);
Script(ctx, rctx, mf).parse_linker_script();
return;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
if (ObjectFile<E> *file = new_lto_obj(ctx, rctx, mf, ""))
ctx.objs.push_back(file);
return;
default:
@@ -215,34 +134,47 @@ void read_file(Context<E> &ctx, MappedFile *mf) {
template <typename E>
static std::string_view
detect_machine_type(Context<E> &ctx, std::vector<std::string> paths) {
std::erase(paths, "-");
detect_machine_type(Context<E> &ctx, std::vector<std::string> args) {
for (ReaderContext rctx; const std::string &arg : args) {
if (arg == "--Bstatic") {
rctx.static_ = true;
} else if (arg == "--Bdynamic") {
rctx.static_ = false;
} else if (!arg.starts_with('-')) {
if (MappedFile *mf = open_file(ctx, arg))
if (get_file_type(ctx, mf) != FileType::TEXT)
if (std::string_view target = get_machine_type(ctx, rctx, mf);
!target.empty())
return target;
}
}
for (const std::string &path : paths)
if (auto *mf = open_file(ctx, path))
if (get_file_type(ctx, mf) != FileType::TEXT)
if (std::string_view target = get_machine_type(ctx, mf);
!target.empty())
return target;
for (const std::string &path : paths)
if (auto *mf = open_file(ctx, path))
if (get_file_type(ctx, mf) == FileType::TEXT)
if (std::string_view target = get_script_output_type(ctx, mf);
!target.empty())
return target;
for (ReaderContext rctx; const std::string &arg : args) {
if (arg == "--Bstatic") {
rctx.static_ = true;
} else if (arg == "--Bdynamic") {
rctx.static_ = false;
} else if (!arg.starts_with('-')) {
if (MappedFile *mf = open_file(ctx, arg))
if (get_file_type(ctx, mf) == FileType::TEXT)
if (std::string_view target =
Script(ctx, rctx, mf).get_script_output_type();
!target.empty())
return target;
}
}
Fatal(ctx) << "-m option is missing";
}
template <typename E>
MappedFile *open_library(Context<E> &ctx, std::string path) {
MappedFile *open_library(Context<E> &ctx, ReaderContext &rctx, std::string path) {
MappedFile *mf = open_file(ctx, path);
if (!mf)
return nullptr;
std::string_view target = get_machine_type(ctx, mf);
if (!target.empty() && target != E::target_name) {
std::string_view target = get_machine_type(ctx, rctx, mf);
if (!target.empty() && target != E::name) {
Warn(ctx) << path << ": skipping incompatible file: " << target
<< " (e_machine " << (int)E::e_machine << ")";
return nullptr;
@@ -251,11 +183,11 @@ MappedFile *open_library(Context<E> &ctx, std::string path) {
}
template <typename E>
MappedFile *find_library(Context<E> &ctx, std::string name) {
MappedFile *find_library(Context<E> &ctx, ReaderContext &rctx, std::string name) {
if (name.starts_with(':')) {
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + name.substr(1);
if (MappedFile *mf = open_library(ctx, path))
if (MappedFile *mf = open_library(ctx, rctx, path))
return mf;
}
Fatal(ctx) << "library not found: " << name;
@@ -263,94 +195,88 @@ MappedFile *find_library(Context<E> &ctx, std::string name) {
for (std::string_view dir : ctx.arg.library_paths) {
std::string stem = std::string(dir) + "/lib" + name;
if (!ctx.is_static)
if (MappedFile *mf = open_library(ctx, stem + ".so"))
if (!rctx.static_)
if (MappedFile *mf = open_library(ctx, rctx, stem + ".so"))
return mf;
if (MappedFile *mf = open_library(ctx, stem + ".a"))
if (MappedFile *mf = open_library(ctx, rctx, stem + ".a"))
return mf;
}
Fatal(ctx) << "library not found: " << name;
}
template <typename E>
MappedFile *find_from_search_paths(Context<E> &ctx, std::string name) {
if (MappedFile *mf = open_file(ctx, name))
return mf;
for (std::string_view dir : ctx.arg.library_paths)
if (MappedFile *mf =
open_file(ctx, std::string(dir) + "/" + name))
return mf;
return nullptr;
}
template <typename E>
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
Timer t(ctx, "read_input_files");
std::vector<std::tuple<bool, bool, bool, bool>> state;
ctx.is_static = ctx.arg.is_static;
ReaderContext rctx;
std::vector<ReaderContext> stack;
std::unordered_set<std::string_view> visited;
tbb::task_group tg;
rctx.tg = &tg;
while (!args.empty()) {
std::string_view arg = args[0];
args = args.subspan(1);
if (arg == "--as-needed") {
ctx.as_needed = true;
rctx.as_needed = true;
} else if (arg == "--no-as-needed") {
ctx.as_needed = false;
rctx.as_needed = false;
} else if (arg == "--whole-archive") {
ctx.whole_archive = true;
rctx.whole_archive = true;
} else if (arg == "--no-whole-archive") {
ctx.whole_archive = false;
rctx.whole_archive = false;
} else if (arg == "--Bstatic") {
ctx.is_static = true;
rctx.static_ = true;
} else if (arg == "--Bdynamic") {
ctx.is_static = false;
rctx.static_ = false;
} else if (arg == "--start-lib") {
ctx.in_lib = true;
rctx.in_lib = true;
} else if (arg == "--end-lib") {
ctx.in_lib = false;
} else if (remove_prefix(arg, "--version-script=")) {
MappedFile *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--version-script: file not found: " << arg;
parse_version_script(ctx, mf);
rctx.in_lib = false;
} else if (arg == "--push-state") {
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
ctx.in_lib});
stack.push_back(rctx);
} else if (arg == "--pop-state") {
if (state.empty())
if (stack.empty())
Fatal(ctx) << "no state pushed before popping";
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
state.back();
state.pop_back();
} else if (remove_prefix(arg, "-l")) {
MappedFile *mf = find_library(ctx, std::string(arg));
rctx = stack.back();
stack.pop_back();
} else if (arg.starts_with("-l")) {
arg = arg.substr(2);
if (visited.contains(arg))
continue;
visited.insert(arg);
MappedFile *mf = find_library(ctx, rctx, std::string(arg));
mf->given_fullpath = false;
read_file(ctx, mf);
read_file(ctx, rctx, mf);
} else {
read_file(ctx, must_open_file(ctx, std::string(arg)));
read_file(ctx, rctx, must_open_file(ctx, std::string(arg)));
}
}
if (ctx.objs.empty())
Fatal(ctx) << "no input files";
ctx.tg.wait();
tg.wait();
}
template <typename E>
int elf_main(int argc, char **argv) {
static bool has_lto_obj(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs)
if (file->is_reachable && (file->is_lto_obj || file->is_gcc_offload_obj))
return true;
return false;
}
template <typename E>
int mold_main(int argc, char **argv) {
Context<E> ctx;
// Process -run option first. process_run_subcommand() does not return.
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv)) {
#if defined(_WIN32) || defined(__APPLE__)
Fatal(ctx) << "-run is supported only on Unix";
#endif
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv))
process_run_subcommand(ctx, argc, argv);
}
// Parse non-positional command line options
ctx.cmdline_args = expand_response_files(ctx, argv);
@@ -362,7 +288,7 @@ int elf_main(int argc, char **argv) {
// Redo if -m is not x86-64.
if constexpr (is_x86_64<E>)
if (ctx.arg.emulation != X86_64::target_name)
if (ctx.arg.emulation != X86_64::name)
return redo_main(ctx, argc, argv);
Timer t_all(ctx, "all");
@@ -375,12 +301,8 @@ int elf_main(int argc, char **argv) {
<< ": " << errno_string();
// Fork a subprocess unless --no-fork is given.
std::function<void()> on_complete;
#if !defined(_WIN32) && !defined(__APPLE__)
if (ctx.arg.fork)
on_complete = fork_child();
#endif
fork_child();
acquire_global_lock();
@@ -393,8 +315,8 @@ int elf_main(int argc, char **argv) {
// Handle --retain-symbols-file options if any.
if (ctx.arg.retain_symbols_file)
for (std::string_view name : *ctx.arg.retain_symbols_file)
get_symbol(ctx, name)->write_to_symtab = true;
for (Symbol<E> *sym : *ctx.arg.retain_symbols_file)
sym->write_to_symtab = true;
for (std::string_view arg : ctx.arg.trace_symbol)
get_symbol(ctx, arg)->is_traced = true;
@@ -410,6 +332,10 @@ int elf_main(int argc, char **argv) {
});
}
// Handle -repro
if (ctx.arg.repro)
write_repro_file(ctx);
Timer t_before_copy(ctx, "before_copy");
// Apply -exclude-libs
@@ -419,26 +345,26 @@ int elf_main(int argc, char **argv) {
if (!ctx.arg.relocatable)
create_internal_file(ctx);
// resolve_symbols is 4 things in 1 phase:
//
// - Determine the set of object files to extract from archives.
// - Remove redundant COMDAT sections (e.g. duplicate inline functions).
// - Finally, the actual symbol resolution.
// - LTO, which requires preliminary symbol resolution before running
// and a follow-up re-resolution after the LTO objects are emitted.
//
// These passes have complex interactions, and unfortunately has to be
// put together in a single phase.
// Resolve symbols by choosing the most appropriate file for each
// symbol. This pass also removes redundant comdat sections (e.g.
// duplicate inline functions).
resolve_symbols(ctx);
// "Kill" .eh_frame input sections after symbol resolution.
kill_eh_frame_sections(ctx);
// If there's an object file compiled with -flto, do link-time
// optimization.
if (has_lto_obj(ctx))
do_lto(ctx);
// Now that we know which object files are to be included to the
// final output, we can remove unnecessary files.
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_reachable; });
std::erase_if(ctx.dsos, [](InputFile<E> *file) { return !file->is_reachable; });
// Parse .eh_frame section contents.
parse_eh_frame_sections(ctx);
// Split mergeable section contents into section pieces.
split_section_pieces(ctx);
// Resolve mergeable section pieces to merge them.
resolve_section_pieces(ctx);
create_merged_sections(ctx);
// Handle --relocatable. Since the linker's behavior is quite different
// from the normal one when the option is given, the logic is implemented
@@ -460,10 +386,18 @@ int elf_main(int argc, char **argv) {
// Set is_imported and is_exported bits for each symbol.
compute_import_export(ctx);
// Make sure that there's no duplicate symbol
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Set "address-taken" bits for input sections.
if (ctx.arg.icf)
compute_address_significance(ctx);
// Handle PPC64-specific .opd sections.
if constexpr (is_ppc64v1<E>)
ppc64v1_rewrite_opd(ctx);
// Garbage-collect unreachable sections.
if (ctx.arg.gc_sections)
gc_sections(ctx);
@@ -472,25 +406,23 @@ int elf_main(int argc, char **argv) {
if (ctx.arg.icf)
icf_sections(ctx);
// Compute sizes of sections containing mergeable strings.
compute_merged_section_sizes(ctx);
// Create linker-synthesized sections such as .got or .plt.
create_synthetic_sections(ctx);
// Make sure that there's no duplicate symbol
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Handle --no-allow-shlib-undefined
if (!ctx.arg.allow_shlib_undefined)
check_shlib_undefined(ctx);
// Warn if symbols with different types are defined under the same name.
check_symbol_types(ctx);
if constexpr (is_ppc64v1<E>)
ppc64v1_rewrite_opd(ctx);
// Bin input sections into output sections.
create_output_sections(ctx);
// Convert an .ARM.exidx to a synthetic section.
if constexpr (is_arm32<E>)
create_arm_exidx_section(ctx);
// Handle --section-align options.
if (!ctx.arg.section_align.empty())
apply_section_align(ctx);
@@ -526,10 +458,6 @@ int elf_main(int argc, char **argv) {
if (ctx.arg.print_dependencies)
print_dependencies(ctx);
// Handle -repro
if (ctx.arg.repro)
write_repro_file(ctx);
// Handle --require-defined
for (Symbol<E> *sym : ctx.arg.require_defined)
if (!sym->file)
@@ -552,16 +480,7 @@ int elf_main(int argc, char **argv) {
shuffle_sections(ctx);
// Copy string referred by .dynamic to .dynstr.
for (SharedFile<E> *file : ctx.dsos)
ctx.dynstr->add_string(file->soname);
for (std::string_view str : ctx.arg.auxiliary)
ctx.dynstr->add_string(str);
for (std::string_view str : ctx.arg.filter)
ctx.dynstr->add_string(str);
if (!ctx.arg.rpaths.empty())
ctx.dynstr->add_string(ctx.arg.rpaths);
if (!ctx.arg.soname.empty())
ctx.dynstr->add_string(ctx.arg.soname);
add_dynamic_strings(ctx);
if constexpr (is_ppc64v1<E>)
ppc64v1_scan_symbols(ctx);
@@ -573,14 +492,17 @@ int elf_main(int argc, char **argv) {
// Compute the is_weak bit for each imported symbol.
compute_imported_symbol_weakness(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// Sort sections by section attributes so that we'll have to
// create as few segments as possible.
sort_output_sections(ctx);
if (!ctx.arg.separate_debug_file.empty())
separate_debug_sections(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed
// relocations now so that we can fix section sizes and file layout.
@@ -590,7 +512,7 @@ int elf_main(int argc, char **argv) {
// Reserve a space for dynamic symbol strings in .dynstr and sort
// .dynsym contents if necessary. Beyond this point, no symbol will
// be added to .dynsym.
ctx.dynsym->finalize(ctx);
sort_dynsyms(ctx);
// Print reports about undefined symbols, if needed.
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
@@ -603,9 +525,6 @@ int elf_main(int argc, char **argv) {
// Fill .gnu.version_r section contents.
ctx.verneed->construct(ctx);
// Compute .symtab and .strtab sizes for each file.
create_output_symtab(ctx);
// .eh_frame is a special section from the linker's point of view,
// as its contents are parsed and reconstructed by the linker,
// unlike other sections that are regarded as opaque bytes.
@@ -617,6 +536,17 @@ int elf_main(int argc, char **argv) {
if (ctx.arg.emit_relocs)
create_reloc_sections(ctx);
// We've created range extension thunks with a pessimistive assumption
// that all out-of-section references are out of range. Now that we are
// able to assign addresses to all SHF_ALLOC output sections, we can
// eliminate excessive thunks.
if constexpr (needs_thunk<E>)
remove_redundant_thunks(ctx);
// Compute .symtab and .strtab sizes for each file.
if (!ctx.arg.strip_all)
create_output_symtab(ctx);
// Compute the section header values for all sections.
compute_section_headers(ctx);
@@ -627,8 +557,17 @@ int elf_main(int argc, char **argv) {
// that they can jump to anywhere in ±2 GiB by default. They may
// be replaced with shorter instruction sequences if destinations
// are close enough. Do this optimization.
if constexpr (is_riscv<E>)
filesize = riscv_resize_sections(ctx);
if constexpr (is_riscv<E> || is_loongarch<E>) {
shrink_sections(ctx);
filesize = set_osec_offsets(ctx);
}
if constexpr (is_arm32<E>) {
if (ctx.extra.exidx) {
ctx.extra.exidx->remove_duplicate_entries(ctx);
filesize = set_osec_offsets(ctx);
}
}
// At this point, memory layout is fixed.
@@ -640,16 +579,21 @@ int elf_main(int argc, char **argv) {
// If --compress-debug-sections is given, compress .debug_* sections
// using zlib.
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
filesize = compress_debug_sections(ctx);
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
compress_debug_sections(ctx);
filesize = set_osec_offsets(ctx);
}
// At this point, both memory and file layouts are fixed.
// Gather thunk symbols and attach them to themselves.
if constexpr (needs_thunk<E>)
gather_thunk_addresses(ctx);
t_before_copy.stop();
// Create an output file
ctx.output_file =
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0777);
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
ctx.buf = ctx.output_file->buf;
Timer t_copy(ctx, "copy");
@@ -657,27 +601,28 @@ int elf_main(int argc, char **argv) {
// Copy input sections to the output file and apply relocations.
copy_chunks(ctx);
if (ctx.arg.z_rewrite_endbr)
rewrite_endbr(ctx);
if constexpr (is_x86_64<E>)
if (ctx.arg.z_rewrite_endbr)
rewrite_endbr(ctx);
// Dynamic linker works better with sorted .rela.dyn section,
// so we sort them.
ctx.reldyn->sort(ctx);
// Zero-clear paddings between sections
clear_padding(ctx);
sort_reldyn(ctx);
// .gdb_index's contents cannot be constructed before applying
// relocations to other debug sections. We have relocated debug
// sections now, so write the .gdb_index section.
if (ctx.gdb_index)
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
write_gdb_index(ctx);
// .note.gnu.build-id section contains a cryptographic hash of the
// entire output file. Now that we wrote everything except build-id,
// we can compute it.
if (ctx.buildid)
ctx.buildid->write_buildid(ctx);
write_build_id(ctx);
if (!ctx.arg.separate_debug_file.empty())
write_gnu_debuglink(ctx);
t_copy.stop();
ctx.checkpoint();
@@ -697,6 +642,9 @@ int elf_main(int argc, char **argv) {
if (ctx.arg.print_map)
print_map(ctx);
if (!ctx.arg.separate_debug_file.empty())
write_separate_debug_file(ctx);
// Show stats numbers
if (ctx.arg.stats)
show_stats(ctx);
@@ -707,9 +655,7 @@ int elf_main(int argc, char **argv) {
std::cout << std::flush;
std::cerr << std::flush;
if (on_complete)
on_complete();
notify_parent();
release_global_lock();
if (ctx.arg.quick_exit)
@@ -723,6 +669,6 @@ int elf_main(int argc, char **argv) {
using E = MOLD_TARGET;
template int elf_main<E>(int, char **);
template int mold_main<E>(int, char **);
} // namespace mold::elf
} // namespace mold
+23 -28
View File
@@ -4,24 +4,15 @@
#include <iomanip>
#include <ios>
#include <sstream>
#include <tbb/concurrent_hash_map.h>
#include <tbb/parallel_for_each.h>
#include <unordered_map>
namespace mold::elf {
namespace mold {
template <typename E>
using Map =
tbb::concurrent_hash_map<InputSection<E> *, std::vector<Symbol<E> *>>;
template <typename E>
static std::unique_ptr<std::ofstream> open_output_file(Context<E> &ctx) {
std::unique_ptr<std::ofstream> file(new std::ofstream);
file->open(ctx.arg.Map.c_str());
if (!file->is_open())
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
return file;
}
template <typename E>
static Map<E> get_map(Context<E> &ctx) {
Map<E> map;
@@ -44,10 +35,8 @@ static Map<E> get_map(Context<E> &ctx) {
return map;
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
for (auto it = range.begin(); it != range.end(); it++) {
std::vector<Symbol<E> *> &vec = it->second;
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
}
for (auto &[k, v] : range)
ranges::stable_sort(v, {}, &Symbol<E>::value);
});
return map;
}
@@ -57,11 +46,13 @@ void print_map(Context<E> &ctx) {
Timer t(ctx, "print_map");
std::ostream *out = &std::cout;
std::unique_ptr<std::ofstream> file;
std::ofstream file;
if (!ctx.arg.Map.empty()) {
file = open_output_file(ctx);
out = file.get();
file.open(ctx.arg.Map);
if (!file.is_open())
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
out = &file;
}
// Construct a section-to-symbol map.
@@ -70,28 +61,32 @@ void print_map(Context<E> &ctx) {
// Print a mapfile.
*out << " VMA Size Align Out In Symbol\n";
for (Chunk<E> *osec : ctx.chunks) {
for (Chunk<E> *chunk : ctx.chunks) {
*out << std::showbase
<< std::setw(18) << std::hex << (u64)osec->shdr.sh_addr << std::dec
<< std::setw(11) << (u64)osec->shdr.sh_size
<< std::setw(6) << (u64)osec->shdr.sh_addralign
<< " " << osec->name << "\n";
<< std::setw(18) << std::hex << (u64)chunk->shdr.sh_addr << std::dec
<< std::setw(11) << (u64)chunk->shdr.sh_size
<< std::setw(6) << (u64)chunk->shdr.sh_addralign
<< " " << chunk->name << "\n";
if (!osec->to_osec())
OutputSection<E> *osec = chunk->to_osec();
if (!osec)
continue;
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
std::span<InputSection<E> *> members = osec->members;
std::vector<std::string> bufs(members.size());
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
InputSection<E> *mem = members[i];
std::ostringstream ss;
u64 addr = osec->shdr.sh_addr + mem->offset;
u64 addr = 0;
if (osec->shdr.sh_flags & SHF_ALLOC)
addr = osec->shdr.sh_addr + mem->offset;
ss << std::showbase
<< std::setw(18) << std::hex << addr << std::dec
<< std::setw(11) << (u64)mem->sh_size
<< std::setw(6) << (1 << (u64)mem->p2align)
<< std::setw(6) << (1 << mem->p2align)
<< " " << *mem << "\n";
typename Map<E>::const_accessor acc;
@@ -114,4 +109,4 @@ using E = MOLD_TARGET;
template void print_map(Context<E> &ctx);
} // namespace mold::elf
} // namespace mold
+1 -1
View File
@@ -9,7 +9,7 @@
#include <string.h>
#include <unistd.h>
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
#if __has_include(<alloca.h>)
# include <alloca.h>
#endif
+552 -245
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+194
View File
@@ -0,0 +1,194 @@
#include "mold.h"
#include <fcntl.h>
#include <filesystem>
#include <sys/file.h>
#include <sys/mman.h>
#include <system_error>
namespace mold {
static u32 get_umask() {
u32 orig_umask = umask(0);
umask(orig_umask);
return orig_umask;
}
template <typename E>
static int
open_or_create_file(Context<E> &ctx, std::string path, std::string tmpfile,
int perm) {
// Reuse an existing file if exists and writable because on Linux,
// writing to an existing file is much faster than creating a fresh
// file and writing to it.
if (ctx.overwrite_output_file && rename(path.c_str(), tmpfile.c_str()) == 0) {
i64 fd = ::open(tmpfile.c_str(), O_RDWR | O_CREAT, perm);
if (fd != -1)
return fd;
unlink(tmpfile.c_str());
}
i64 fd = ::open(tmpfile.c_str(), O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << tmpfile << ": " << errno_string();
return fd;
}
template <typename E>
class MemoryMappedOutputFile : public OutputFile<E> {
public:
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, int perm)
: OutputFile<E>(path, filesize, true) {
std::string pid = std::to_string(getpid());
std::string tmpfile =
path_dirname(path) / ("." + path_filename(path) + "." + pid);
this->fd = open_or_create_file(ctx, path, tmpfile, perm);
if (fchmod(this->fd, perm & ~get_umask()) == -1)
Fatal(ctx) << "fchmod failed: " << errno_string();
if (ftruncate(this->fd, filesize) == -1)
Fatal(ctx) << "ftruncate failed: " << errno_string();
output_tmpfile = (char *)save_string(ctx, tmpfile).data();
#ifdef __linux__
fallocate(this->fd, 0, 0, filesize);
#endif
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
}
~MemoryMappedOutputFile() {
if (fd2 != -1)
::close(fd2);
}
void close(Context<E> &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
if (this->buf2.empty()) {
::close(this->fd);
} else {
FILE *out = fdopen(this->fd, "w");
fseek(out, 0, SEEK_END);
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
fclose(out);
}
// If an output file already exists, open a file and then remove it.
// This is the fastest way to unlink a file, as it does not make the
// system to immediately release disk blocks occupied by the file.
fd2 = ::open(this->path.c_str(), O_RDONLY);
if (fd2 != -1)
unlink(this->path.c_str());
if (rename(output_tmpfile, this->path.c_str()) == -1)
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
output_tmpfile = nullptr;
}
private:
int fd2 = -1;
};
template <typename E>
std::unique_ptr<OutputFile<E>>
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, int perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
std::error_code error;
bool is_special = path == "-" ||
(!std::filesystem::is_regular_file(path, error) && !error);
OutputFile<E> *file;
if (is_special)
file = new MallocOutputFile(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
#ifdef MADV_HUGEPAGE
// Enable transparent huge page for an output memory-mapped file.
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
// but it can make the linker ~10% faster. You can try it by creating
// a tmpfs with the following commands
//
// $ mkdir tmp
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
//
// and then specifying a path under the directory as an output file.
madvise(file->buf, filesize, MADV_HUGEPAGE);
#endif
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile>(file);
}
// LockingOutputFile is similar to MemoryMappedOutputFile, but it doesn't
// rename output files and instead acquires file lock using flock().
template <typename E>
LockingOutputFile<E>::LockingOutputFile(Context<E> &ctx, std::string path,
int perm)
: OutputFile<E>(path, 0, true) {
this->fd = ::open(path.c_str(), O_RDWR | O_CREAT, perm);
if (this->fd == -1)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
flock(this->fd, LOCK_EX);
// We may be overwriting to an existing debug info file. We want to
// make the file unusable so that gdb won't use it by accident until
// it's ready.
u8 buf[256] = {};
(void)!!write(this->fd, buf, sizeof(buf));
}
template <typename E>
void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {
if (ftruncate(this->fd, filesize) == -1)
Fatal(ctx) << "ftruncate failed: " << errno_string();
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << this->path << ": mmap failed: " << errno_string();
this->filesize = filesize;
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
}
template <typename E>
void LockingOutputFile<E>::close(Context<E> &ctx) {
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
if (!this->buf2.empty()) {
FILE *out = fdopen(this->fd, "w");
fseek(out, 0, SEEK_END);
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
fclose(out);
}
::close(this->fd);
}
using E = MOLD_TARGET;
template class OutputFile<E>;
template class LockingOutputFile<E>;
} // namespace mold
+117
View File
@@ -0,0 +1,117 @@
#include "mold.h"
#include <fcntl.h>
#include <windows.h>
namespace mold {
template <typename E>
class MemoryMappedOutputFile : public OutputFile<E> {
public:
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, int perm)
: OutputFile<E>(path, filesize, true) {
// TODO: use intermediate temporary file for output.
DWORD attrs = (perm & 0200) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY;
handle = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, CREATE_ALWAYS, attrs, nullptr);
if (handle == INVALID_HANDLE_VALUE)
Fatal(ctx) << "cannot open " << path << ": " << GetLastError();
HANDLE map = CreateFileMapping(handle, nullptr, PAGE_READWRITE, 0,
filesize, nullptr);
if (!map)
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
this->buf = (u8 *)MapViewOfFile(map, FILE_MAP_WRITE, 0, 0, filesize);
if (!this->buf)
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
CloseHandle(map);
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
}
~MemoryMappedOutputFile() {
if (handle != INVALID_HANDLE_VALUE)
CloseHandle(handle);
}
void close(Context<E> &ctx) override {
Timer t(ctx, "close_file");
UnmapViewOfFile(this->buf);
if (!this->buf2.empty()) {
if (SetFilePointer(handle, 0, nullptr, FILE_END) == INVALID_SET_FILE_POINTER)
Fatal(ctx) << this->path << ": SetFilePointer failed: "
<< GetLastError();
DWORD written;
if (!WriteFile(handle, this->buf2.data(), this->buf2.size(), &written,
nullptr))
Fatal(ctx) << this->path << ": WriteFile failed: " << GetLastError();
}
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
}
private:
HANDLE handle;
};
template <typename E>
std::unique_ptr<OutputFile<E>>
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, int perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
bool is_special = false;
if (path == "-") {
is_special = true;
} else {
HANDLE h = CreateFileA(path.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h != INVALID_HANDLE_VALUE) {
if (GetFileType(h) != FILE_TYPE_DISK)
is_special = true;
CloseHandle(h);
}
}
OutputFile<E> *file;
if (is_special)
file = new MallocOutputFile(ctx, path, filesize, perm);
else
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return std::unique_ptr<OutputFile<E>>(file);
}
template <typename E>
LockingOutputFile<E>::LockingOutputFile(Context<E> &ctx, std::string path,
int perm)
: OutputFile<E>(path, 0, true) {
Fatal(ctx) << "LockingOutputFile is not supported on Windows";
}
template <typename E>
void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {}
template <typename E>
void LockingOutputFile<E>::close(Context<E> &ctx) {}
using E = MOLD_TARGET;
template class OutputFile<E>;
template class LockingOutputFile<E>;
} // namespace mold
File diff suppressed because it is too large. Load diff
+7 -12
View File
@@ -35,7 +35,7 @@
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
namespace mold {
// Create linker-synthesized sections
template <typename E>
@@ -53,7 +53,9 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
ctx.strtab = push(new StrtabSection<E>);
ctx.symtab = push(new SymtabSection<E>);
ctx.shstrtab = push(new ShstrtabSection<E>);
ctx.note_property = push(new NotePropertySection<E>);
if constexpr (is_x86<E>)
ctx.extra.note_property = push(new NotePropertySection<E>);
if constexpr (is_riscv<E>)
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
@@ -109,10 +111,7 @@ template <typename E>
static void r_claim_unresolved_symbols(Context<E> &ctx) {
Timer t(ctx, "r_claim_unresolved_symbols");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
if (!file->is_alive)
return;
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
const ElfSym<E> &esym = file->elf_syms[i];
Symbol<E> &sym = *file->symbols[i];
@@ -148,8 +147,6 @@ static u64 r_set_osec_offsets(Context<E> &ctx) {
template <typename E>
void combine_objects(Context<E> &ctx) {
compute_merged_section_sizes(ctx);
create_output_sections(ctx);
r_create_synthetic_sections(ctx);
@@ -171,12 +168,10 @@ void combine_objects(Context<E> &ctx) {
compute_section_headers(ctx);
i64 filesize = r_set_osec_offsets(ctx);
ctx.output_file =
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0666);
ctx.buf = ctx.output_file->buf;
copy_chunks(ctx);
clear_padding(ctx);
ctx.output_file->close(ctx);
ctx.checkpoint();
@@ -197,4 +192,4 @@ using E = MOLD_TARGET;
template void combine_objects(Context<E> &);
} // namespace mold::elf
} // namespace mold
+138
View File
@@ -0,0 +1,138 @@
// Since RISC instructions are generally up to 32 bits long, there's no
// way to embed very large immediates into their branch instructions. For
// example, RISC-V's JAL (jump and link) instruction can jump to only
// within PC ± 1 MiB because its immediate is 21 bits long. If the
// destination is further than that, we need to use two instructions
// instead; the first instruction being AUIPC, which sets the upper 20
// bits of a displacement to a register, and the second being JALR, which
// specifies the lower 12 bits and the register. Combined, they specify a
// 32-bit displacement, which is sufficient to support the medium code
// model.
//
// However, always using two or more instructions for function calls is a
// waste of time and space if the branch target is within a single
// instruction's reach. There are two approaches to address this problem
// as follows:
//
// 1. The compiler optimistically emits a single branch instruction for
// all function calls. The linker then checks if the branch target is
// reachable, and if not, redirects the branch to a linker-synthesized
// code sequence that uses two or more instructions to branch further.
// That linker-synthesized code is called a "thunk". All RISC psABIs
// except RISC-V and LoongArch take this approach.
//
// 2. The compiler pessimistically emits two instructions to branch
// anywhere in PC ± 2 GiB, and the linker rewrites them with a single
// instruction if the branch target is close enough. RISC-V and
// LoongArch take this approach.
//
// This file contains functions to support (2). For (1), see thunks.cc.
//
// With the presence of this code-shrinking relaxation, sections can no
// longer be considered as atomic units. If we delete an instruction from
// the middle of a section, the section contents after that point need to
// be shifted by the size of the instruction. Symbol values and relocation
// offsets have to be shifted too if they refer to bytes past the deleted
// ones.
//
// In mold, we use `r_deltas` to memorize how many bytes have been shifted
// for relocations. For symbols, we directly mutate their `value` member.
//
// RISC-V and LoongArch object files tend to have way more relocations
// than those for other targets. This is because all branches, including
// those that jump within the same section, are explicitly expressed with
// relocations. Here is why we need them: all control-flow statements,
// such as `if` or `for`, are implemented using branch instructions. For
// other targets, the compiler doesn't emit relocations for such branches
// because it knows at compile-time exactly how many bytes have to be
// skipped. That's not true in RISC-V and LoongArch because the linker may
// delete bytes between a branch and its target. Therefore, all branches,
// including in-section ones, have to be explicitly expressed with
// relocations.
//
// Note that this mechanism only shrinks sections and never enlarges them,
// as the compiler always emits the longest instruction sequence. This
// makes the linker implementation a bit simpler because we don't need to
// worry about oscillation.
#if MOLD_RV64LE || MOLD_RV64BE || MOLD_RV32LE || MOLD_RV32BE || \
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
#include "mold.h"
#include <tbb/parallel_for_each.h>
namespace mold {
using E = MOLD_TARGET;
template <>
i64 get_r_delta(InputSection<E> &isec, u64 offset) {
std::span<RelocDelta> deltas = isec.extra.r_deltas;
auto it = ranges::upper_bound(deltas, offset, std::less_equal<>{},
&RelocDelta::offset);
return (it == deltas.begin()) ? 0 : (it - 1)->delta;
}
template <>
void shrink_sections(Context<E> &ctx) {
Timer t(ctx, "shrink_sections");
// Find all relaxable relocations and record how many bytes we can save
// into r_deltas.
//
// Technically speaking, relaxing relocations may allow more relocations
// to be relaxed because the distance between a branch instruction and
// its target may decrease as a result of relaxation. That said, the
// number of such relocations is negligible (I tried to self-host mold
// on RISC-V as an experiment and found that the mold-built .text is
// only ~0.04% larger than that of GNU ld), so we don't bother to handle
// them. We scan relocations only once here.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
shrink_section(ctx, *isec);
});
// Fix symbol values.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file)
if (InputSection<E> *isec = sym->get_input_section())
if (isec->shdr().sh_flags & SHF_EXECINSTR)
if (i64 delta = get_r_delta(*isec, sym->value))
sym->value -= delta;
});
// Recompute sizes of executable sections
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
if (chunk->to_osec() && (chunk->shdr.sh_flags & SHF_EXECINSTR))
chunk->compute_section_size(ctx);
});
}
// Returns the distance between a relocated place and a symbol.
template <>
i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel) {
// We handle absolute symbols as if they were infinitely far away
// because `shrink_section` may increase a distance between a branch
// instruction and an absolute symbol. Branching to an absolute
// location is extremely rare in real code, though.
if (sym.is_absolute())
return INT64_MAX;
// Likewise, relocations against weak undefined symbols won't be relaxed.
if (sym.esym().is_undef_weak())
return INT64_MAX;
// Compute a distance between the relocated place and the symbol.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
i64 P = isec.get_addr() + rel.r_offset;
return S + A - P;
}
} // namespace mold
#endif
+23 -17
View File
@@ -1,23 +1,22 @@
#if !defined(_WIN32) && !defined(__APPLE__)
#include "mold.h"
#include "config.h"
#include <csignal>
#include <filesystem>
#include <signal.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
namespace mold::elf {
namespace mold {
#ifdef MOLD_X86_64
static int pipe_write_fd = -1;
// Exiting from a program with large memory usage is slow --
// it may take a few hundred milliseconds. To hide the latency,
// we fork a child and let it do the actual linking work.
std::function<void()> fork_child() {
void fork_child() {
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
@@ -50,12 +49,17 @@ std::function<void()> fork_child() {
// Child
close(pipefd[0]);
pipe_write_fd = pipefd[1];
}
return [=] {
char buf[] = {1};
[[maybe_unused]] int n = write(pipefd[1], buf, 1);
assert(n == 1);
};
void notify_parent() {
if (pipe_write_fd == -1)
return;
char buf[] = {1};
[[maybe_unused]] int n = write(pipe_write_fd, buf, 1);
assert(n == 1);
pipe_write_fd = -1;
}
#endif
@@ -84,6 +88,9 @@ static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
template <typename E>
[[noreturn]]
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
#ifdef __APPLE__
Fatal(ctx) << "-run is not supported on macOS";
#else
assert(argv[1] == "-run"s || argv[1] == "--run"s);
if (!argv[2])
@@ -94,11 +101,11 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
std::string dso_path = find_dso(ctx, self);
// Set environment variables
putenv(strdup(("LD_PRELOAD=" + dso_path).c_str()));
putenv(strdup(("MOLD_PATH=" + self).c_str()));
setenv("LD_PRELOAD", dso_path.c_str(), 1);
setenv("MOLD_PATH", self.c_str(), 1);
// If ld, ld.lld or ld.gold is specified, run mold itself
if (std::string cmd = filepath(argv[2]).filename();
if (std::string cmd = path_filename(argv[2]);
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
std::vector<char *> args;
args.push_back(argv[0]);
@@ -111,12 +118,11 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
// Execute a given command
execvp(argv[2], argv + 2);
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
#endif
}
using E = MOLD_TARGET;
template void process_run_subcommand(Context<E> &, int, char **);
} // namespace mold::elf
#endif
} // namespace mold
+20
View File
@@ -0,0 +1,20 @@
#include "mold.h"
namespace mold {
#ifdef MOLD_X86_64
void fork_child() {}
void notify_parent() {}
#endif
template <typename E>
[[noreturn]]
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
Fatal(ctx) << "-run is supported only on Unix";
}
using E = MOLD_TARGET;
template void process_run_subcommand(Context<E> &, int, char **);
} // namespace mold
+281
View File
@@ -0,0 +1,281 @@
// RISC instructions are usually up to 4 bytes long, so the immediates of
// their branch instructions are naturally smaller than 32 bits. This is
// contrary to x86-64 on which branch instructions take 4 bytes immediates
// and can jump to anywhere within PC ± 2 GiB.
//
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
// ARM64's ±128 MiB, for example. If a branch target is further than that,
// we need to let it branch to a linker-synthesized code sequence that
// construct a full 32 bit address in a register and jump there. That
// linker-synthesized code is called "thunk".
//
// The function in this file creates thunks.
//
// Note that although thunks play an important role in an executable, they
// don't take up too much space in it. For example, among the clang-16's
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
// need more thunks on ARM32 whose branch range is shorter than ARM64.
// That said, the total size of thunks still isn't that much. Therefore,
// we don't need to try too hard to reduce thunk size to the absolute
// minimum.
#if MOLD_ARM32 || MOLD_ARM64LE || MOLD_ARM64BE || MOLD_PPC32 || \
MOLD_PPC64V1 || MOLD_PPC64V2
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold {
using E = MOLD_TARGET;
// We create thunks for each 25.6/3.2/6.4 MiB code block for
// ARM64/ARM32/PPC, respectively.
static constexpr i64 batch_size = branch_distance<E> / 5;
// We assume that a single thunk group is smaller than 1 MiB.
static constexpr i64 max_thunk_size = 1024 * 1024;
// We align thunks to 16 byte boundaries because many processor vendors
// recommend we align branch targets to 16 byte boundaries for performance
// reasons.
static constexpr i64 thunk_align = 16;
template <typename E>
static bool is_reachable(Context<E> &ctx, bool first_pass, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
InputSection<E> *isec2 = sym.get_input_section();
if (isec2 && isec.output_section == isec2->output_section &&
isec2->offset == -1)
return false;
// We don't know about the final file layout on the first pass, so
// we assume pessimistically that all out-of-section relocations are
// out-of-range. Excessive thunks will be removed on the second pass.
if (first_pass) {
if (!isec2 || isec.output_section != isec2->output_section)
return false;
// Even if the target is the same section, we branch to its PLT
// if it has one. So a symbol with a PLT is also considered an
// out-of-section reference.
if (sym.has_plt(ctx))
return false;
}
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx, NO_OPD);
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -branch_distance<E> <= val && val < branch_distance<E>;
}
template <typename E>
static bool needs_shim(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel) {
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are within their ranges.
if constexpr (is_arm32<E>) {
bool is_thumb = sym.get_addr(ctx) & 1;
return (rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb);
}
// On PowerPC, all PLT calls go through range extension thunks.
if constexpr (is_ppc32<E> || is_ppc64v1<E>)
return sym.has_plt(ctx);
// PowerPC before Power9 lacks PC-relative load/store instructions.
// Functions compiled for Power9 or earlier assume that r2 points to
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
// We need to a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>)
return sym.has_plt(ctx) ||
(rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc());
return false;
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
bool first_pass) {
std::span<InputSection<E> *> m = members;
if (m.empty())
return;
// Initialize input sections with a dummy offset so that we can
// distinguish sections that have got an address with the one who
// haven't.
for (InputSection<E> *isec : m)
isec->offset = -1;
thunks.clear();
// We create thunks from the beginning of the section to the end.
// We manage progress using four offsets which increase monotonically.
// The locations they point to are always A <= B <= C <= D.
//
// Input sections between B and C are in the current batch.
//
// A is the input section with the smallest address than can reach
// from the current batch.
//
// D is the input section with the largest address such that the thunk
// is reachable from the current batch if it's inserted at D.
//
// ................................ <input sections> ............
// A B C D
// ^ We insert a thunk for the current batch just before D
// <---> The current batch, which is smaller than BATCH_SIZE
// <--------> Smaller than BRANCH_DISTANCE
// <--------> Smaller than BRANCH_DISTANCE
// <-------------> Reachable from the current batch
i64 a = 0;
i64 b = 0;
i64 c = 0;
i64 d = 0;
i64 offset = 0;
// The smallest thunk index that is reachable from the current batch.
i64 t = 0;
while (b < m.size()) {
// Move D foward as far as we can jump from B to a thunk at D.
auto d_thunk_end = [&] {
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
return align_to(d_end, thunk_align) + max_thunk_size;
};
while (d < m.size() &&
(b == d || d_thunk_end() <= m[b]->offset + branch_distance<E>)) {
offset = align_to(offset, 1 << m[d]->p2align);
m[d]->offset = offset;
offset += m[d]->sh_size;
d++;
}
// Move C forward so that C is apart from B by BATCH_SIZE. We want
// to make sure that there's at least one section between B and C
// to ensure progress.
c = b + 1;
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
c++;
// Move A forward so that A is reachable from C.
i64 c_offset = (c == d) ? offset : m[c]->offset;
while (a < b && m[a]->offset + branch_distance<E> < c_offset)
a++;
// Erase references to out-of-range thunks.
for (; t < thunks.size() && thunks[t]->offset < m[a]->offset; t++)
for (Symbol<E> *sym : thunks[t]->symbols)
sym->flags = 0;
// Create a new thunk and place it at D.
offset = align_to(offset, thunk_align);
thunks.emplace_back(std::make_unique<Thunk<E>>(*this, offset));
Thunk<E> &thunk = *thunks.back();
// Scan relocations between B and C to collect symbols that need
// entries in the new thunk.
std::mutex mu;
tbb::parallel_for(b, c, [&](i64 i) {
InputSection<E> &isec = *m[i];
for (const ElfRel<E> &rel : isec.get_rels(ctx)) {
if (!is_func_call_rel(rel))
continue;
// Skip if the symbol is undefined. apply_reloc() will report an error.
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.file)
continue;
// Skip if we can directly branch to the destination.
if (is_reachable(ctx, first_pass, isec, sym, rel) &&
!needs_shim(ctx, sym, rel))
continue;
// Add the symbol to the current thunk if it's not added already.
if (!sym.flags.test_and_set()) {
std::scoped_lock lock(mu);
thunk.symbols.push_back(&sym);
}
}
});
// Sort symbols added to the thunk to make the output deterministic.
ranges::sort(thunk.symbols, {}, [](Symbol<E> *x) {
return std::tuple{x->file->priority, x->sym_idx};
});
// Now that we know the number of symbols in the thunk, we can compute
// the thunk's size.
assert(thunk.size() < max_thunk_size);
offset += thunk.size();
// Move B forward to point to the begining of the next batch.
b = c;
}
for (; t < thunks.size(); t++)
for (Symbol<E> *sym : thunks[t]->symbols)
sym->flags = 0;
this->shdr.sh_size = offset;
}
template <>
void remove_redundant_thunks(Context<E> &ctx) {
Timer t(ctx, "remove_redundant_thunks");
set_osec_offsets(ctx);
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_flags & SHF_EXECINSTR)
osec->create_range_extension_thunks(ctx, false);
}
// When applying relocations, we want to know the address in a reachable
// range extension thunk for a given symbol. Doing it by scanning all
// reachable range extension thunks is too expensive.
//
// In this function, we create a list of all addresses in range extension
// thunks for each symbol, so that it is easy to find one.
//
// Note that thunk_addrs must be sorted for binary search.
template <>
void gather_thunk_addresses(Context<E> &ctx) {
Timer t(ctx, "gather_thunk_addresses");
std::vector<OutputSection<E> *> sections;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
sections.push_back(osec);
ranges::stable_sort(sections, {}, [](OutputSection<E> *x) {
return x->shdr.sh_addr;
});
for (OutputSection<E> *osec : sections) {
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks) {
for (i64 i = 0; i < thunk->symbols.size(); i++) {
Symbol<E> &sym = *thunk->symbols[i];
sym.add_aux(ctx);
ctx.symbol_aux[sym.aux_idx].thunk_addrs.push_back(thunk->get_addr(i));
}
}
}
}
} // namespace mold
#endif
+52 -48
View File
@@ -72,21 +72,46 @@
// TLV is sometimes compiled to a function call! The module ID and the
// offset are usually stored to GOT as two consecutive words.
//
// The last access method is the most generic, so the compiler emits such
// code by default. But that's the most expensive one, so the linker
// rewrites instructions if possible so that 3) is relaxed to 2) or even
// to 1).
//
// 1) is called the Local Exec access model. 2) is Initial Exec, and 3) is
// General Dynamic.
//
// There's another little trick that the compiler can use if it knows two
// TLVs are in the same ELF file (usually in the same file as the code is).
// In this case, we can call __tls_get_addr() only once with a module ID and
// the offset 0 to obtain the base address of the ELF file's TLS block. The
// base address obtained this way is sometimes called Dynamic Thread Pointer
// or DTP. We can then compute TLVs' addresses by adding their DTP-relative
// addresses to DTP. This access model is called the Local Dynamic.
// In this case, we can call __tls_get_addr() only once with the module ID
// and the offset 0 to obtain the base address of the ELF file's TLS block.
// The base address obtained this way is sometimes called Dynamic Thread
// Pointer or DTP. We can then compute TLVs' addresses by adding their
// DTP-relative addresses to DTP. This access model is called the Local
// Dynamic.
//
// The compiler tries to emit the most efficient code for a TLV access based
// on compiler command line options and TLV properties as follows:
//
// 1. If -fno-PIC or -fPIE is given, and a TLV is defined as a file-local
// variable, the compiler knows that it is compiling code for an
// executable (i.e. not for a shared library) and the TLV is defined
// within the executable. In this case, Local Exec is used to access the
// variable.
//
// 2. If -fno-PIC or -fPIE is given (i.e. the compiler is compiling code for
// a main executable), but a TLV is defined in another translation unit,
// then the variable may be defined not in the main executable but in a
// shared library. In this case, Initial Exec is used to access the
// variable.
//
// 3. If -fPIC is given, it may be compiling code for dlopen'able shared
// library. In this case, Local Dynamic or General Dynamic is used to
// access TLVs.
//
// You can also manually control how the compiler emits TLV access code
// globally with `-ftls-model=<model-name>` or per-variable basis with
// `__attribute__((tls_model(<model-name>)))`. For example, if you are
// building a shared library that you have no plan to use with dlopen(), you
// may want to compile the source files with `-ftls-model=initial-exec` to
// avoid the cost associated with the General Dynamic access model.
//
// The linker may rewrite instructions with a code sequence for a cheaper
// access model at link-time.
//
// === TLS Descriptor access model ===
//
@@ -122,44 +147,26 @@
#include "mold.h"
namespace mold::elf {
template <typename E>
static ElfPhdr<E> *get_tls_segment(Context<E> &ctx) {
if (ctx.phdr)
for (ElfPhdr<E> &phdr : ctx.phdr->phdrs)
if (phdr.p_type == PT_TLS)
return &phdr;
return nullptr;
}
template <typename E>
u64 get_tls_begin(Context<E> &ctx) {
if (ElfPhdr<E> *phdr = get_tls_segment(ctx))
return phdr->p_vaddr;
return 0;
}
namespace mold {
// Returns the TP address which can be used for efficient TLV accesses in
// the main executable. TP at runtime refers to a per-process TLS block
// whose address is not known at link-time. So the address returned from
// this function is the TP if the TLS template image were a TLS block.
template <typename E>
u64 get_tp_addr(Context<E> &ctx) {
ElfPhdr<E> *phdr = get_tls_segment(ctx);
if (!phdr)
return 0;
u64 get_tp_addr(const ElfPhdr<E> &phdr) {
assert(phdr.p_type == PT_TLS);
if constexpr (is_x86<E> || is_sparc<E> || is_s390x<E>) {
// On x86, SPARC and s390x, TP (%gs on i386, %fs on x86-64, %g7 on SPARC
// and %a0/%a1 on s390x) refers to past the end of the TLS block for
// historical reasons. TLVs are accessed with negative offsets from TP.
return align_to(phdr->p_vaddr + phdr->p_memsz, phdr->p_align);
} else if constexpr (is_arm<E> || is_sh4<E> || is_alpha<E>) {
// On ARM, SH4 and Alpha, the runtime appends two words at the beginning
return align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
} else if constexpr (is_arm<E> || is_sh4<E> || is_arc<E>) {
// On ARM and SH4, the runtime appends two words at the beginning
// of TLV template image when copying TLVs to the TLS block, so we need
// to offset it.
return align_down(phdr->p_vaddr - sizeof(Word<E>) * 2, phdr->p_align);
return align_down(phdr.p_vaddr - sizeof(Word<E>) * 2, phdr.p_align);
} else if constexpr (is_ppc<E> || is_m68k<E>) {
// On PowerPC and m68k, TP is 0x7000 (28 KiB) past the beginning
// of the TLV block to maximize the addressable range of load/store
@@ -167,24 +174,22 @@ u64 get_tp_addr(Context<E> &ctx) {
// (32 KiB) off because there's a small implementation-defined piece of
// data before the initial TLV block, and the runtime wants to access
// them efficiently too.
return phdr->p_vaddr + 0x7000;
return phdr.p_vaddr + 0x7000;
} else {
// RISC-V and LoongArch just uses the beginning of the main executable's
// TLV block as TP. Their load/store instructions usually take 12-bits
// signed immediates, so the beginning of the TLS block ± 2 KiB is
// accessible with a single load/store instruction.
static_assert(is_riscv<E> || is_loongarch<E>);
return phdr->p_vaddr;
return phdr.p_vaddr;
}
}
// Returns the address __tls_get_addr() would return if it's called
// with offset 0.
template <typename E>
u64 get_dtp_addr(Context<E> &ctx) {
ElfPhdr<E> *phdr = get_tls_segment(ctx);
if (!phdr)
return 0;
u64 get_dtp_addr(const ElfPhdr<E> &phdr) {
assert(phdr.p_type == PT_TLS);
if constexpr (is_ppc<E> || is_m68k<E>) {
// On PowerPC and m68k, R_DTPOFF is resolved to the address 0x8000
@@ -193,21 +198,20 @@ u64 get_dtp_addr(Context<E> &ctx) {
// immediates. That is, if the offset were right at the beginning of the
// start of the TLS block, the half of addressible space (negative
// immediates) would have been wasted.
return phdr->p_vaddr + 0x8000;
return phdr.p_vaddr + 0x8000;
} else if constexpr (is_riscv<E>) {
// On RISC-V, the bias is 0x800 as the load/store instructions in the
// ISA usually have a 12-bit immediate.
return phdr->p_vaddr + 0x800;
return phdr.p_vaddr + 0x800;
} else {
// On other targets, DTP simply refers to the beginning of the TLS block.
return phdr->p_vaddr;
return phdr.p_vaddr;
}
}
using E = MOLD_TARGET;
template u64 get_tls_begin<E>(Context<E> &);
template u64 get_tp_addr<E>(Context<E> &);
template u64 get_dtp_addr<E>(Context<E> &);
template u64 get_tp_addr(const ElfPhdr<E> &);
template u64 get_dtp_addr(const ElfPhdr<E> &);
} // namespace mold::elf
} // namespace mold
@@ -18,7 +18,7 @@ endif()
if(${MACHINE} MATCHES "amd64")
set(MACHINE x86_64)
elseif(${MACHINE} MATCHES "i386")
elseif(${MACHINE} MATCHES "i.86")
set(MACHINE i686)
elseif(${MACHINE} MATCHES "arm.*")
set(MACHINE arm)
@@ -30,13 +30,14 @@ endif()
if(MOLD_ENABLE_QEMU_TESTS)
list(APPEND QEMU_ARCHS
x86_64 i386 arm aarch64 ppc ppc64 ppc64le sparc64 sh4 s390x
alpha riscv64 riscv32 m68k loongarch64)
x86_64 i386 arm aarch64 aarch64_be ppc ppc64 ppc64le sparc64 sh4 sh4eb
s390x riscv64 riscv32 m68k loongarch64 arc)
LIST(APPEND TRIPLES
x86_64-linux-gnu
i686-linux-gnu
aarch64-linux-gnu
aarch64_be-linux-gnu
arm-linux-gnueabihf
riscv64-linux-gnu
powerpc-linux-gnu
@@ -45,10 +46,11 @@ if(MOLD_ENABLE_QEMU_TESTS)
sparc64-linux-gnu
s390x-linux-gnu
sh4-linux-gnu
alpha-linux-gnu
sh4aeb-linux-gnu
riscv32-linux-gnu
m68k-linux-gnu
loongarch64-linux-gnu)
loongarch64-linux-gnu
arc-linux-gnu)
foreach(ARCH IN LISTS QEMU_ARCHS)
find_program(HAS_qemu-${ARCH} qemu-${ARCH})
@@ -69,10 +71,10 @@ function(add_target ARCH TRIPLE)
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
"*.sh")
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
list(FILTER ALL_TESTS EXCLUDE REGEX "^arch-")
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
"${ARCH}_*.sh")
"arch-${ARCH}-*.sh")
list(APPEND TESTS ${ALL_TESTS})
@@ -112,6 +114,10 @@ if(${MACHINE} STREQUAL "aarch64" OR (HAS_qemu-aarch64 AND HAS_aarch64-linux-gnu-
add_target(aarch64 aarch64-linux-gnu)
endif()
if(${MACHINE} STREQUAL "aarch64_be" OR (HAS_qemu-aarch64_be AND HAS_aarch64_be-linux-gnu-gcc))
add_target(aarch64_be aarch64_be-linux-gnu)
endif()
if(${MACHINE} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
add_target(arm arm-linux-gnueabihf)
endif()
@@ -168,8 +174,8 @@ if(${MACHINE} STREQUAL "sh4" OR (HAS_qemu-sh4 AND HAS_sh4-linux-gnu-gcc))
add_target(sh4 sh4-linux-gnu)
endif()
if(${MACHINE} STREQUAL "alpha" OR (HAS_qemu-alpha AND HAS_alpha-linux-gnu-gcc))
add_target(alpha alpha-linux-gnu)
if(${MACHINE} STREQUAL "sh4aeb" OR (HAS_qemu-sh4eb AND HAS_sh4aeb-linux-gnu-gcc))
add_target(sh4aeb sh4aeb-linux-gnu)
endif()
if(${MACHINE} STREQUAL "m68k" OR (HAS_qemu-m68k AND HAS_m68k-linux-gnu-gcc))
@@ -179,3 +185,7 @@ endif()
if(${MACHINE} STREQUAL "loongarch64" OR (HAS_qemu-loongarch64 AND HAS_loongarch64-linux-gnu-gcc))
add_target(loongarch64 loongarch64-linux-gnu)
endif()
if(${MACHINE} STREQUAL "arc" OR (HAS_qemu-loongarch64 AND HAS_arc-linux-gnu-gcc))
add_target(arc arc-linux-gnu)
endif()
+2 -3
View File
@@ -5,7 +5,6 @@
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = s390x ] && skip
[ $MACHINE = alpha ] && skip
[[ $MACHINE = loongarch* ]] && skip
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
@@ -19,5 +18,5 @@ extern char foo;
int main() { printf("foo=%p\n", &foo); }
EOF
! $CC -B. -o $t/exe -pie $t/a.o $t/b.o -Wl,-z,text >& $t/log
grep -q 'recompile with -fPIC' $t/log
not $CC -B. -o $t/exe -pie $t/a.o $t/b.o -Wl,-z,text |&
grep 'recompile with -fPIC'
@@ -8,7 +8,7 @@
cat <<EOF | $CC -o $t/a.o -c -x assembler -
.globl foo
foo = 0x800008
foo = 0xa00008
EOF
cat <<EOF | $CC -o $t/b.o -c -fno-PIC -xc -
@@ -36,4 +36,4 @@ int main() {
EOF
$CC -B. -o $t/exe -no-pie $t/a.o $t/b.o
$QEMU $t/exe | grep -q '^ip=0x80000.$'
$QEMU $t/exe | grep '^ip=0xa0000.$'
@@ -4,6 +4,6 @@
echo 'int main() { return 0; }' | $CC -c -o $t/a.o -xc -
echo 'int main() { return 1; }' | $CC -c -o $t/b.o -xc -
! $CC -B. -o $t/exe $t/a.o $t/b.o 2> /dev/null || false
not $CC -B. -o $t/exe $t/a.o $t/b.o 2> /dev/null
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-allow-multiple-definition
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-z,muldefs
File renamed without changes.
@@ -1,8 +1,6 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] || skip
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
#include <stdio.h>
@@ -20,4 +18,4 @@ EOF
$CC -B. -o $t/exe $t/a.o \
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
$OBJDUMP -dr $t/exe | grep -Fq '<fn1$thunk>:'
$OBJDUMP -dr $t/exe | grep -E '<fn1\$thunk[0-9]+>:'
@@ -10,7 +10,7 @@ foo:
EOF
$CC -B. -shared -o $t/b.so $t/a.o
readelf -W --dyn-syms $t/b.so | grep foo | grep -q '[VARIANT_PCS]'
readelf -W --dyn-syms $t/b.so | grep foo | grep -F '[VARIANT_PCS]'
cat <<EOF | $CC -c -o $t/c.o -xc -
void foo();
@@ -18,4 +18,4 @@ int main() { foo(); }
EOF
$CC -B. -o $t/exe $t/c.o $t/b.so
readelf -W --dynamic $t/exe | grep -q AARCH64_VARIANT_PCS
readelf -W --dynamic $t/exe | grep AARCH64_VARIANT_PCS
@@ -12,5 +12,7 @@ extern char foo;
int main() { printf("foo=%p\n", &foo); }
EOF
! $CC -B. -o $t/exe -pie $t/a.o $t/b.o >& $t/log
grep -q 'recompile with -fPIC' $t/log
$CC -o $t/exe -pie $t/a.o $t/b.o >& /dev/null && skip
not $CC -B. -o $t/exe -pie $t/a.o $t/b.o |&
grep 'recompile with -fPIC'
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
. $(dirname $0)/common.inc
cat <<EOF | $CXX -o $t/a.o -c -xc++ - -fno-PIC
#include <stdio.h>
extern char _etext[];
int main() {
printf("%p\n", _etext);
}
EOF
$CXX -B. -o $t/exe $t/a.o -no-pie
readelf --unwind $t/exe | grep "$($QEMU $t/exe) .*cantunwind"
Loaded 100 of 1160 files, more files were not shown because too many files have changed in this diff. Show more