Compare commits

...
830 Commits
Author SHA1 Message Date
Rui Ueyama 0c57eca7b0 Support 32-bit PA-RISC 2023-01-05 14:50:32 +08:00
Rui Ueyama d3e0ffe0b9 Refactor 2023-01-05 09:49:28 +08:00
Rui Ueyama 17a24cdd7e Fix "error: enumerated and non-enumerated type in conditional expression" warning
Fixes https://github.com/rui314/mold/issues/945
2023-01-04 09:02:16 +08:00
Rui Ueyama 4a66d7a573 Revert "Bit permutation"
This reverts commit cbd88ed3f3.
It was a nice abstraction but wasn't worth its complexity unless we
use it in various places. The original simple code is more in line
with other parts of mold.

Fixes https://github.com/rui314/mold/issues/946
2023-01-04 08:51:20 +08:00
Rui Ueyama cbd88ed3f3 Bit permutation 2023-01-03 18:05:15 +08:00
Rui Ueyama 9157414cae Make ElfRel trivial type
Fixes https://github.com/rui314/mold/issues/944
2023-01-03 16:47:24 +08:00
Rui Ueyama 6663adb700 Refactor 2023-01-03 16:47:16 +08:00
Rui Ueyama 7c64f67499 Merge pull request #943 from marxin/fix-Wunused-variable 2023-01-03 17:35:24 +09:00
Martin Liska 7539c3fd15 Fix warning: unused variable 'G' [-Wunused-variable]
elf/arch-i386.cc:425:9: warning: unused variable 'G' [-Wunused-variable]

Signed-off-by: Martin Liska <[email protected]>
2023-01-03 09:31:28 +01:00
Rui Ueyama 83f6bc18e1 Refactor 2023-01-02 20:52:43 +08:00
Rui Ueyama ea4cb40131 Fix CI 2023-01-02 20:44:45 +08:00
Rui Ueyama bdc3d71a23 Refactor 2023-01-02 19:53:05 +08:00
Rui Ueyama b15ce5203d Refactor 2023-01-02 16:31:14 +08:00
Rui Ueyama 4507e6e51d Do not create thunks for all symbols 2023-01-02 16:13:16 +08:00
Rui Ueyama c86689b227 Do not use C macros
We used to use C macros as a poor-man's way to inline Symbol::get_addr(),
but it looks like inlining that member function isn't an overall win.
This change removes `inline` from that member function and depends on the
compiler's optimizations to optimize function calls.
2023-01-02 15:44:11 +08:00
Rui Ueyama 89156f3fcc Simplify 2023-01-02 09:53:15 +08:00
Rui Ueyama d0abdf3bbe Refactor 2023-01-02 05:43:16 +08:00
Rui Ueyama 9615b8b962 Do not bother to clear existing bits when applying relocations
We were inconsistent as to when to clear existing values when applying
relocations. mold was broken from the point of view that bits must be
cleared before applying relcoations. But at the same time, we had code
to clear bits for some types of relocations.

This patch removes all bit clear code for the sake of simplicity.
2023-01-02 05:17:15 +08:00
Rui Ueyama 608b328cbf Reduce symbol size 2023-01-01 12:12:34 +08:00
Rui Ueyama cbc71ac363 Clear existing values when applying relocations 2023-01-01 12:12:22 +08:00
Rui Ueyama df9e997cc3 Refactor 2023-01-01 07:40:55 +08:00
Rui Ueyama abe7943c06 Refactor 2023-01-01 07:11:23 +08:00
Rui Ueyama 764f802180 Fix CI 2022-12-31 20:28:01 +08:00
Rui Ueyama 651adad5f2 Support 32-bit PowerPC ABI 2022-12-31 17:56:37 +08:00
Rui Ueyama 34bfbc0983 Rename inttypes.h integers.h
`inttypes.h` unintentionally collided with the system's `inttypes.h`.

Fixes https://github.com/rui314/mold/issues/936
2022-12-30 21:46:05 +08:00
Rui Ueyama 9c355b7765 Treat R_ARM_PLT32 as a synonym for R_ARM_CALL
According to the ARM psABI document, R_ARM_PLT32 is deprecated.
It looks like old compilers emit it for function calls.

Fixes https://github.com/rui314/mold/issues/934
2022-12-30 18:14:49 +08:00
Rui Ueyama ccd47db916 Set a correct value to __iplt_start for statically-linked binaries
Fixes https://github.com/rui314/mold/issues/930
2022-12-30 12:26:19 +08:00
Rui Ueyama ad30db1102 Refactor 2022-12-30 12:05:07 +08:00
Rui Ueyama b3db6c9976 Revert "Eliminate uses of <bit>"
This reverts commit 33af22848f.

https://github.com/rui314/mold/issues/936
2022-12-30 11:09:50 +08:00
Rui Ueyama 02ca6cc643 Do not mention --ld-path because -fuse-ld works both for clang and gcc 2022-12-30 10:53:10 +08:00
Rui Ueyama d5a1c7a1b3 Merge pull request #938 from arbipher/patch-2
Add explanation for `-fuse-ld-mold` in README
2022-12-30 11:44:44 +09:00
Weng Shiwei 翁士伟 13a4cf9994 Update README.md 2022-12-29 21:40:07 -05:00
Weng Shiwei 翁士伟 e63bf01704 Update README.md 2022-12-29 21:33:30 -05:00
Rui Ueyama f23c1c9b69 Fix CI 2022-12-30 10:14:24 +08:00
Weng Shiwei 翁士伟 e454d54a8a Update README.md 2022-12-29 20:53:44 -05:00
Rui Ueyama 33af22848f Eliminate uses of <bit>
Some old C++ compilers don't provide that header.

Fixes https://github.com/rui314/mold/issues/936
2022-12-30 09:16:38 +08:00
Rui Ueyama 610d90fa69 Simplify 2022-12-29 17:52:47 +08:00
Rui Ueyama 9c62c55a2c Rename a file 2022-12-29 17:39:39 +08:00
Rui Ueyama 34ca952a9b Refactor 2022-12-29 16:59:08 +08:00
Rui Ueyama 487ef64336 Remove dead code 2022-12-29 16:46:36 +08:00
Rui Ueyama f33968c782 Refactor 2022-12-29 16:20:40 +08:00
Rui Ueyama 7c03f5e4c0 Refactor 2022-12-29 15:49:12 +08:00
Rui Ueyama 6ff6084ab8 Refactor 2022-12-29 11:35:53 +08:00
Rui Ueyama 326e2c6ba1 Copy st_other from input symbols to output symbols 2022-12-29 11:35:53 +08:00
Rui Ueyama efe117d946 Merge pull request #933 from marxin/support-fno-use-linker-plugin
support correctly -fno-use-linker-plugin
2022-12-29 12:35:38 +09:00
Rui Ueyama b39ee6a985 Refactor 2022-12-29 09:30:59 +08:00
Martin Liska 5f99006de3 support correctly -fno-use-linker-plugin
Right now, mold linker does not work with -fno-use-linker-plugin
and thus use assembly for GCC FAT LTO objects. I noticed that
when running GCC test-suite with mold.

Fixes: 804b843983

Signed-off-by: Martin Liska <[email protected]>
2022-12-28 19:46:08 +01:00
Rui Ueyama 033d5f3406 Refactor 2022-12-28 22:35:26 +08:00
Rui Ueyama d96c9b8236 Do not set an undefined value to dynrel 2022-12-28 22:23:50 +08:00
Rui Ueyama a45f97b474 Refactor 2022-12-28 17:53:58 +08:00
Rui Ueyama 10a1ca0d8e Refactor 2022-12-28 17:44:57 +08:00
Rui Ueyama 4362727cc8 Refactor 2022-12-28 17:25:25 +08:00
Rui Ueyama 40ddfa533b Refactor 2022-12-28 15:11:05 +08:00
Rui Ueyama 447bb8b036 Update a comment 2022-12-28 14:05:39 +08:00
Rui Ueyama 6231510794 Support DEC Alpha 2022-12-28 13:57:00 +08:00
Rui Ueyama 352c086047 Refactor 2022-12-28 12:52:41 +08:00
Rui Ueyama fb1ca49d9b Update comments 2022-12-27 22:36:45 +08:00
Rui Ueyama 1b0979c795 Refactor 2022-12-27 21:26:57 +08:00
Rui Ueyama 5a38f70a37 Refactor 2022-12-27 18:16:31 +08:00
Rui Ueyama 4da942723e Refactor 2022-12-27 14:09:30 +08:00
Rui Ueyama e26ea4ba05 Refactor 2022-12-27 12:08:52 +08:00
Rui Ueyama 8cfa64944e Update comments 2022-12-27 11:25:06 +08:00
Rui Ueyama 21855a194c Attempt to fix CI 2022-12-27 10:46:29 +08:00
Rui Ueyama 3411e17d8b [ELF] Support SH-4 ISA 2022-12-27 10:34:20 +08:00
Rui Ueyama e1f587eafb Merge pull request #926 from ZhongRuoyu/test-permission
`warn-symbol-type.sh`: fix file permission
2022-12-26 19:16:55 +09:00
Ruoyu Zhong 215f351dcd warn-symbol-type.sh: fix file permission
Also, end this file with a newline.

Signed-off-by: Ruoyu Zhong <[email protected]>
2022-12-26 16:21:41 +08:00
Rui Ueyama a49a201695 Bump mold version to 1.8.0 2022-12-25 19:55:07 +08:00
Rui Ueyama e7d80bcf33 Remove a redundant if condition 2022-12-25 18:12:14 +08:00
Rui Ueyama feaf2632c5 Fix typo in command line parameter
There's no such option as `-z nodefs` in any linker. The correct option
ame is `-z undefs`.
2022-12-25 16:43:23 +08:00
Rui Ueyama b510588677 Support -z nopack-relative-relocs 2022-12-25 16:39:16 +08:00
Rui Ueyama aa51aea245 Merge pull request #925 from ishitatsuyuki/type-warn
[ELF] Warn on symbol type mismatches.
2022-12-25 16:22:32 +09:00
Tatsuyuki Ishi b70211ec08 [ELF] Warn on symbol type mismatches.
Linking symbols with differing types will almost always lead to a crash; warn to
make it easier for the user to figure out the cause.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-12-25 14:18:41 +08:00
Tatsuyuki Ishi 30f79a0d62 [ELF] Add formatter for st_type.
Will be used to print symbol type mismatch warnings.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-12-25 14:18:41 +08:00
Rui Ueyama a3a98ac5f5 Fix build failure on some targets
Fixes https://github.com/rui314/mold/issues/924
2022-12-25 11:47:31 +08:00
Rui Ueyama ed235f3db6 Add -z [no]dynamic-undefined-weak 2022-12-25 11:46:13 +08:00
Rui Ueyama ad79674ec5 Refactor 2022-12-25 08:31:33 +08:00
Rui Ueyama 2ab4175ac1 Delete an unused variable 2022-12-25 08:31:33 +08:00
Rui Ueyama 408bdc1e87 Refactor 2022-12-24 14:40:10 +08:00
Rui Ueyama 86e6fa17a5 Refactor 2022-12-24 12:30:05 +08:00
Rui Ueyama 57c5b9ea27 Refactor 2022-12-23 20:40:37 +08:00
Rui Ueyama 23fff53e94 Do not crash if there's a SHF_MERGE section with entsize == 0
It looks like the OCaml compiler creates such broken sections.
2022-12-23 20:40:37 +08:00
Rui Ueyama c829aed2df Merge pull request #923 from marxin/badges
Add badges to README.md
2022-12-23 17:27:44 +09:00
Martin Liska e53df1deda Add badges to README.md
Include GitHub CI and openSUSE OBS badges.

Fixes: #922

Signed-off-by: Martin Liska <[email protected]>
2022-12-23 09:23:56 +01:00
Rui Ueyama 1283061d2b Remove CI logging 2022-12-23 14:38:42 +08:00
Rui Ueyama 5e976dbf97 Add logging to CI 2022-12-23 11:36:45 +08:00
Rui Ueyama a86d94cee7 [ELF] Relax sequencial consistency memory stores 2022-12-23 11:22:48 +08:00
Rui Ueyama 365779fbe4 Refactor 2022-12-23 10:54:47 +08:00
Rui Ueyama 0c456efebb [ELF] Fix the --help message
Pointed out by justanotheranonymoususer

https://github.com/rui314/mold/commit/e2d7353213004f5eac0eeda372dac7cbbdb1cb28
2022-12-21 16:12:14 +08:00
Rui Ueyama e2d7353213 [ELF] Support --[no-]undefined-version
Fixes https://github.com/rui314/mold/issues/921
2022-12-21 12:50:45 +08:00
Rui Ueyama 864f9a0490 Deduplicate the help message
Fixes https://github.com/rui314/mold/issues/920
2022-12-20 16:12:27 +08:00
Rui Ueyama 262bf67b20 Make OutputFile::close faster 2022-12-17 12:13:00 +08:00
Rui Ueyama bffc887dae Merge pull request #918 from marxin/fix-typo 2022-12-16 05:59:28 -08:00
Martin Liska 262ef21f32 fix copy&pasted typo in elf/lto-unix.cc
Signed-off-by: Martin Liska <[email protected]>
2022-12-16 13:57:00 +01:00
Rui Ueyama 07d89116a8 [ELF] Make --wrap work with LTO
Fixes https://github.com/rui314/mold/issues/916
2022-12-16 13:06:00 +08:00
Rui Ueyama cc3e5fd1a8 Refactor 2022-12-16 10:38:01 +08:00
Rui Ueyama b2858d2680 [ELF] Allow to initialize a global variable with an ifunc function pointer
This is a tricky case, but if you initialize a global variable with a
pointer to an ifunc function like this:

  int bar() __attribute__((ifunc("resolve_bar")));
  void *bar2 = bar;

then we need to emit two dynamic relocations, one for `bar()` and the other
for `bar2`. This results in calling `resolve_bar` twice at runtime, but
there's no other way to initialize the function and the variable correctly.

Fixes https://github.com/rui314/mold/issues/915
2022-12-15 16:46:44 +08:00
Rui Ueyama 293a4ca425 Refactor 2022-12-15 16:46:24 +08:00
Rui Ueyama 22c9ec8cb6 [ELF] Ignore -z start-stop-visibility=hidden
https://github.com/rui314/mold/issues/826
2022-12-14 17:20:18 +08:00
Rui Ueyama a260630f48 Merge pull request #913 from ZhongRuoyu/openssl-libdir 2022-12-12 20:12:19 -08:00
Rui Ueyama 86d37b4720 Merge pull request #911 from ZhongRuoyu/ld-symlink 2022-12-12 18:19:24 -08:00
Rui Ueyama 099663aaae Merge pull request #912 from marxin/ignore-max-cache-size 2022-12-12 18:18:20 -08:00
Ruoyu Zhong 678ef917cc Dockerfile: fix OpenSSL libdir
In the current version of Docker image, OpenSSL libraries are installed
to `/usr/local/lib64`. It is not present in the default dynamic linker
search path, and thus results in the following error when libcrypto is
not statically-linked:

  mold: error while loading shared libraries: libcrypto.so.3: cannot
  open shared object file: No such file or directory

Fix that by overriding `libdir`, as suggested in
https://github.com/openssl/openssl/blob/master/INSTALL.md#libdir. Also,
run `ldconfig` to update dynamic linker cache.

Signed-off-by: Ruoyu Zhong <[email protected]>
2022-12-12 23:40:02 +08:00
Martin Liska d05e83e40b ignore BFD linker argument --max-cache-size=size
Signed-off-by: Martin Liska <[email protected]>
2022-12-12 15:10:14 +01:00
Ruoyu Zhong a18fe8bccf Install ld64.sold symlink
Signed-off-by: Ruoyu Zhong <[email protected]>
2022-12-12 21:27:25 +08:00
Ruoyu Zhong 56c56cc450 Fix ld.sold symlink installation
Fixes #910.

Signed-off-by: Ruoyu Zhong <[email protected]>
2022-12-12 21:25:32 +08:00
Rui Ueyama 998c7afb67 Do not run tests on macOS 2022-12-12 17:33:23 +08:00
Rui Ueyama a90e7ac782 Remove mold/macOS
mold/macOS will be available at https://github.com/bluewhalesystems/sold
as a source-available commercial product.
2022-12-12 17:05:09 +08:00
Rui Ueyama bd584ac05e Automatically detect if mold is sold 2022-12-12 16:00:59 +08:00
Rui Ueyama 30aecda799 Change the version string for commercial version of mold
Since many programs recognizes `^mold` as a check if a linker is mold,
we want to always print out "mold" as a product name.
2022-12-12 15:19:59 +08:00
Rui Ueyama 05ff76f361 [ELF][ARM32] Support R_ARM_GOTOFF32
Fixes https://github.com/rui314/mold/issues/909
2022-12-12 11:51:08 +08:00
Rui Ueyama 83d8189c51 Refactor 2022-12-11 18:04:16 +08:00
Rui Ueyama 7c56dddc8c [ELF] Cache DJB hash
We used to compute DJB hash twice.
2022-12-11 16:14:16 +08:00
Rui Ueyama ba8a4d9b98 Refactor 2022-12-11 13:44:29 +08:00
Rui Ueyama afe01a1367 Improve a comment 2022-12-10 18:13:47 +08:00
Rui Ueyama bcc74d68ee Simplify 2022-12-10 17:27:00 +08:00
Rui Ueyama a6c4530e43 Fix -Wunused-function 2022-12-10 16:03:25 +08:00
Rui Ueyama f83581880d Upgrade to mimalloc 2.0.7 2022-12-10 15:41:48 +08:00
Rui Ueyama 0d4193f564 Optimize our concurrent map 2022-12-10 14:24:21 +08:00
Rui Ueyama 88b4c1eb81 [ELF] Optimize scan_relocations 2022-12-10 14:06:08 +08:00
Rui Ueyama cd4fb9f26e Update man page
`-preload` has been removed.

https://github.com/rui314/mold/issues/414
2022-12-10 10:31:36 +08:00
Rui Ueyama 3e23192256 [Mach-O] Fix crash bug 2022-12-09 15:47:29 +08:00
Rui Ueyama 589cb55516 Do not create __objc_stubs etc. sections if they already exist 2022-12-09 15:30:24 +08:00
Rui Ueyama b14727149b [ELF] Allow a section symbol as a comdat group name
A symbol with `STT_SECTION` doesn't have name (i.e. its `st_name` is zero.)
If such symbol is used as a comdat group name, we need to use a section name
instead of a symbol name as a group name.
2022-12-09 13:53:26 +08:00
Rui Ueyama df8e2d859c Refactor 2022-12-09 13:04:21 +08:00
Rui Ueyama faa71c6f1c Merge pull request #908 from marxin/i386-plt-use-int3
[ELF][i386] Use int3 as padding in .plt and .plt.got entires
2022-12-08 19:38:47 -08:00
Martin Liska 3486f69fcf [ELF][i386] Use int3 as padding in .plt and .plt.got entires
Similarly to x86_64, use int3 (0xcc) as padding at the end of plt
and pltgot entries in order to increase security.

Signed-off-by: Martin Liska <[email protected]>
2022-12-08 15:01:47 +01:00
Rui Ueyama 0f36a40a44 [Mach-O] Create __objc_{methname,selrefs,stubs} sections
Recent versions of Xcode started creating `_objc_msgSend$foo` symbols,
and we need to synthesize the symbols and place them into `__objc_stubs`
section.
2022-12-08 17:33:37 +08:00
Rui Ueyama 73f32940ab [ELF] Parallelize HashSection::copy_buf and GnuHashSection::copy_buf
If there are lots of exported symbols, their performances matter.
2022-12-08 15:13:44 +08:00
Rui Ueyama 3d255b2cdf Merge pull request #907 from ishitatsuyuki/defsym-lto
[ELF] Keep a reference to the defsym target.
2022-12-07 22:02:55 -08:00
Tatsuyuki Ishi 27b543a725 [ELF] Keep a reference to the defsym target.
Add an undefined symbol to keep a reference to the defsym target. This prevents
elimination by e.g. LTO or gc-sections. A test is added for the case of LTO
dead-code elimination.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-12-08 12:05:09 +08:00
Tatsuyuki Ishi 79483332b7 [ELF] Only assign "owned" synthetic symbols to output section.
In preparation for introducing undefined symbols that references external object
files (and should not get the output section overridden).

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-12-08 11:57:42 +08:00
Rui Ueyama b09be697b5 Revert "Link against libc++ instead of libstdc++ if -DMOLD_MOSTLY_STATIC"
This reverts commit 94122f4ffd.

Fixes https://github.com/rui314/mold/issues/905
2022-12-07 14:39:20 +08:00
Rui Ueyama b7439b5efc [ELF] Do not remove SHF_LINK_ORDER bit
SHF_LINK_ORDER bit doesn't matter in an executable, but it looks like
the `strip` command in binutils 2.35 or earlier on ARM32 complains about
`.ARM.exidx` if we do not preserve that bit.

Fixes https://github.com/rui314/mold/issues/889
2022-12-06 13:52:38 +08:00
Rui Ueyama 8c87f16592 [ELF] Make --dynamic-list to search in -L locations
Fixes https://github.com/rui314/mold/issues/904
2022-12-06 13:35:16 +08:00
Rui Ueyama e16328e790 Refactor 2022-12-03 16:03:00 +08:00
Rui Ueyama bb5d96be6e Allow negative values for R_AARCH64_TLSLE_MOVW_TPREL_G[012] relocs 2022-12-03 11:44:52 +08:00
Rui Ueyama 3c1a0550da Search version script from library search paths
Fixes https://github.com/rui314/mold/issues/822
2022-12-03 10:34:37 +08:00
Rui Ueyama dd90ade29d Fix CI 2022-12-02 16:25:19 +08:00
Rui Ueyama 5866c9e95a [ELF] Do not create copy relocations if dynamic relocations suffice
We used to create copy relocations if we are creating a non-PIC executable
and there's a direct reference to imported data. That works for most
programs but not for GHC (Glasgow Haskell Compiler). GHC places function
machine code and its supplimental data next to each other in the .text
section. If we create a copy relocation for such function, the resulting
executable won't work because (1) copy-relocated data is not executable
and (2) it may separate the function code from its supplemental data.

In this patch, I made a change so that mold creates dynamic relocations
instead of copy relocations if possible.
2022-12-02 16:08:34 +08:00
Rui Ueyama 372f34300e Fix CI 2022-12-02 16:08:23 +08:00
Rui Ueyama 6cb5b53d5d [ELF] Handle escape character in response file
GHC uses backslash escapes instead of double-quoted strings in response files
2022-12-02 15:46:50 +08:00
Rui Ueyama 1d4b2071ca [ELF] Implement R_AARCH64_TLSLE_MOVW_TPREL relocations
Fixes https://github.com/rui314/mold/issues/901
2022-12-02 12:23:31 +08:00
Rui Ueyama f0774d4e24 Merge pull request #893 from ishitatsuyuki/tls
[ELF] TLS alignment fix
2022-11-30 01:16:06 -08:00
Rui Ueyama f007127ad3 Merge pull request #899 from marxin/fix-typos-v2
fix typos in comments and help string
2022-11-30 01:12:27 -08:00
Rui Ueyama 38c1211dba [ELF] Optimize scan_relocations 2022-11-30 17:02:55 +08:00
Tatsuyuki Ishi bd46edf3f0 [ELF] Align the first TLS section with largest required TLS alignment.
TLS chunks alignments are special: in addition to having their virtual
addresses aligned, they also have to be aligned when the region of
tls_begin is copied to a new thread's storage area. In other words, their
offset against tls_begin also has to be aligned.

A good way to achieve this is to take the largest alignment requirement of
all TLS sections and make tls_begin also aligned to that.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-30 16:12:49 +08:00
Rui Ueyama 48b2c0f8a3 Make test less verbose 2022-11-29 14:36:57 +08:00
Rui Ueyama 8975fc2ed2 Allow a double-quoted filename 2022-11-29 14:34:31 +08:00
Rui Ueyama 594beb67a2 Merge pull request #900 from marxin/gcc-fat-lto-objects
support -ffat-lto-objects for GCC compiler
2022-11-28 17:53:01 -08:00
Martin Liska 804b843983 support -ffat-lto-objects for GCC compiler
FAT LTO objects are objects that contain both native assembly
and LTO bytecode. Identify such files as GCC_LTO_OBJ.

Signed-off-by: Martin Liska <[email protected]>
2022-11-28 17:00:13 +01:00
Martin Liska 032a44626f fix typos in comments and help string
Signed-off-by: Martin Liska <[email protected]>
2022-11-28 15:24:27 +01:00
Rui Ueyama d2cfffc24d Add a test 2022-11-28 17:39:13 +08:00
Rui Ueyama c2a0ae1c96 Add --relocatable-merge-sections 2022-11-28 17:35:06 +08:00
Rui Ueyama cf8cce6d17 -r: Infer machine type even if all input files are given via linker script 2022-11-28 17:35:06 +08:00
Rui Ueyama 0420a527c0 Add a workaround for a bad GHC output 2022-11-28 14:22:07 +08:00
Rui Ueyama 54e45746cc Merge pull request #896 from hack3ric/main 2022-11-27 04:42:09 -08:00
Eric Long c0c863ff8b Fix atomic checks
Signed-off-by: Eric Long <[email protected]>
2022-11-27 18:13:22 +08:00
Rui Ueyama e08efab1e7 Refactor 2022-11-27 12:39:47 +08:00
Rui Ueyama 41bc68e26d -r: Set SHF_INFO_LINK to relocation sections 2022-11-27 12:31:46 +08:00
Rui Ueyama 8f682da608 Refactor 2022-11-27 12:07:56 +08:00
Rui Ueyama 72ac3f200d Refactor 2022-11-27 12:04:48 +08:00
Rui Ueyama 32dcda5aa9 -r: fix relocation addends for section fragments 2022-11-27 12:04:48 +08:00
Rui Ueyama 26e3203e9a Merge pull request #895 from justanotheranonymoususer/patch-1
Fix typos in execstack.md
2022-11-26 04:26:16 -08:00
Rui Ueyama 1969b52569 Refactor 2022-11-26 20:10:26 +08:00
justanotheranonymoususer 53a84a182c Fix typos in execstack.md 2022-11-26 13:48:17 +02:00
Rui Ueyama c1b57d9977 -r: Do not strip .note.GNU-stack 2022-11-26 16:40:16 +08:00
Rui Ueyama 556a5cabf9 -r: Do not emit .eh_frame if there's no input .eh_frame 2022-11-26 16:33:32 +08:00
Rui Ueyama 3a4166a066 Avoid creating two sections with the same name 2022-11-26 13:43:23 +08:00
Rui Ueyama 85c3467147 Optimize 2022-11-26 12:54:22 +08:00
Rui Ueyama 7f124d4271 Optimize 2022-11-26 12:23:52 +08:00
Rui Ueyama 8d2ddf7038 Optimize 2022-11-26 12:15:34 +08:00
Rui Ueyama 3d0bd4dacf Silence TSAN error 2022-11-26 12:03:12 +08:00
Rui Ueyama d5bbe347a1 Accept -perf with -r 2022-11-26 11:29:39 +08:00
Rui Ueyama f9c721f9a9 Handle an output file with more than 65535 sections
Executables and shared libraries usually don't have too many sections,
but relocatable object files may. So we need this for --relocatable.
2022-11-25 19:38:09 +08:00
Rui Ueyama fc16eb4e34 Remove dead code 2022-11-25 16:45:07 +08:00
Rui Ueyama 683583a8c4 Fix CI 2022-11-25 15:47:39 +08:00
Rui Ueyama 6524389621 Generalize a test 2022-11-25 15:30:19 +08:00
Rui Ueyama 75e059af5b Optimize create_output_sections
It turned out that tbb::concurrent_hash_map is much slower than
std::unordered_map with a reader-writer lock for this function.
2022-11-25 15:17:47 +08:00
Rui Ueyama ecd61a4fed [ELF] Create output sections lazily
Previously, we create output sections when input sections are instantiated.
That is not ideal because input sections may be removed, and if that's the
case, any work that has done for the removed section is wasted.
2022-11-25 13:46:38 +08:00
Rui Ueyama 8614fbbaff Refactor 2022-11-25 11:06:18 +08:00
Rui Ueyama e1445e7a84 Merge pull request #892 from marxin/fix-typos-in-man 2022-11-24 17:22:04 -08:00
Martin Liska be01de4b45 man: fixes a couple of typos
Signed-off-by: Martin Liska <[email protected]>
2022-11-24 15:01:40 +01:00
Rui Ueyama 968a8cf0be Merge pull request #891 from marxin/comment-note.gnu.property
Document unsupported .note.gnu.property properties
2022-11-24 05:24:25 -08:00
Martin Liska e5e3925d5b Document unsupported .note.gnu.property properties
Mention 2 properties that are not suported right now.

Signed-off-by: Martin Liska <[email protected]>
2022-11-24 13:54:13 +01:00
Rui Ueyama 57b09ccfb7 [ELF] Merge .note.gnu.property if --relocatable is given 2022-11-24 17:24:38 +08:00
Rui Ueyama 50d0cc6959 Refactor 2022-11-24 17:16:33 +08:00
Rui Ueyama f43c0fde26 Fix CI 2022-11-24 17:08:45 +08:00
Rui Ueyama 3c4dd61710 Make NotePropertySection::update_shdr idempotent 2022-11-24 16:58:47 +08:00
Rui Ueyama ed8580f86a Refactor 2022-11-24 16:49:39 +08:00
Rui Ueyama b2f44b07fc Refactor 2022-11-24 15:12:18 +08:00
Rui Ueyama 1ccf1f8ffb Relocatable output file shouldn't have an executable bit 2022-11-24 15:06:52 +08:00
Rui Ueyama 2e3e177a6d Refactor 2022-11-24 15:02:02 +08:00
Rui Ueyama 5e345b0f82 Fix CI 2022-11-24 14:36:37 +08:00
Rui Ueyama 6fd2c69926 Add comments 2022-11-24 14:24:50 +08:00
Rui Ueyama f8e7bace50 Fix CI 2022-11-24 14:00:49 +08:00
Rui Ueyama 46def3b49f 80 columns 2022-11-24 13:35:10 +08:00
Rui Ueyama c9a7ae733c [ELF] Re-implement --relocatable
This is an initial patch to reimplement --relocatable. Unlike the previous
implementation, the new one aims to be on par with GNU ld in terms of the
behavior. Previously, we didn't for example merge input sections at all.
Now we do it in the new implementation.

The previous implementation was slow. We also want to fix it in the new one.

The new --relocation feature is not complete yet; there are many rough edges.
But I think this patch is good enough to be submitted.

This work is funded by Mercury (https://mercury.com).
2022-11-24 12:24:57 +08:00
Rui Ueyama 9d437dfd6f Merge pull request #885 from marxin/fix-psabi-link
Add i386-psabi.pdf to arch-x86-64
2022-11-21 22:09:53 -08:00
Rui Ueyama 5d277c1b90 Merge pull request #887 from ZhongRuoyu/cmake-CMP0077
Work around CMake CMP0077 warning
2022-11-21 22:02:31 -08:00
Rui Ueyama a8b070bc56 Update README 2022-11-22 13:06:35 +08:00
Rui Ueyama 6eb6a93ced Merge pull request #886 from CraneDancingShape/pr_nim.md
Add README.md "If you are using Nim" & Fix "If you are using Rust" mi…
2022-11-21 21:05:28 -08:00
Zhong Ruoyu 89ebf2179c Work around CMake CMP0077 warning
As documented by CMake [^1], prior to CMake 3.13, entries set by
`option()` can override a non-cached old value. The `CMakeLists.txt` in
the bundled mimalloc specifies a minimum CMake version of 3.0, so that
old behaviour is expected. That means no matter whatever value of
`MI_BUILD_STATIC` we set in `mold_add_mimalloc` does not really take
effect (this can be seen by adding some debug messages). That didn't
trouble us because it's on by default anyway.

While the documented solution is to set `CMAKE_POLICY_DEFAULT_CMP0077`
to `NEW`, we could actually also work around this by making them cache
variables. This is fine and valid because their values are not meant to
be changed anyway. (And this workaround is already being used in
`mold_add_tbb`, so by doing this, we are also eliminating some
inconcistency.)

Fixes #874.

[^1]: See `cmake --help-policy CMP0077` or
https://cmake.org/cmake/help/latest/policy/CMP0077.html

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-22 01:03:08 +08:00
CraneDancingShape 22635e3716 Add README.md "If you are using Nim" & Fix "If you are using Rust" miss-spell. 2022-11-21 23:30:28 +09:00
Martin Liska 2ae49688e8 Add i386-psabi.pdf to arch-x86-64
Some parts (like .note.gnu.property section format)
of the ABI are defined in i386 manual.
2022-11-21 15:18:52 +01:00
Rui Ueyama 988241975f Merge pull request #877 from sicherha/fix_span_out_of_bounds
Fix out-of-bounds error on aarch64 with `_GLIBCXX_ASSERTIONS` enabled…
2022-11-19 20:31:19 -08:00
Rui Ueyama f5fb6b4abb Merge pull request #880 from ZhongRuoyu/dockerfile-cleanup
Dockerfile: perform cleanup at the end
2022-11-19 18:57:01 -08:00
Rui Ueyama ada8dec8da Fix -Wdangling-reference
Fixes https://github.com/rui314/mold/issues/882
2022-11-20 10:55:29 +08:00
Rui Ueyama a18316a83a Merge pull request #881 from tzussman/patch-1
Update CMakeLists.txt to use mold 1.7.1
2022-11-19 18:54:39 -08:00
Tal Zussman 3f81358b54 Update CMakeLists.txt to use mold 1.7.1 2022-11-19 12:55:20 -05:00
Zhong Ruoyu ce3ac5d2f4 Dockerfile: perform cleanup at the end
As recommended in Docker's documentation [^1]:

> In addition, when you clean up the apt cache by removing
> `/var/lib/apt/lists` it reduces the image size, since the apt cache
> is not stored in a layer.

This reduces the image size by ~0.5GiB.

[^1]: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-20 01:29:16 +08:00
Christoph Erhardt a2c7ad822b Fix out-of-bounds error on aarch64 with _GLIBCXX_ASSERTIONS enabled (again)
The square-bracket operator of `std::span` has undefined behaviour for
out-of-bounds element accesses. Resorting to iterators yields defined
behaviour.

This issue (originally described in #298) had been fixed in the past by
commit 03ba8f9325, but it has since
resurfaced with commit 193c245781.

Signed-off-by: Christoph Erhardt <[email protected]>
2022-11-19 14:01:52 +01:00
Rui Ueyama 72fffee54f Merge pull request #875 from Paulo-21/patch-1
Update "compile mold" command to use mold 1.7.1
2022-11-19 03:29:34 -08:00
Pmimo c77c41eaf8 Update compile mold command to 1.7.1 2022-11-19 11:27:50 +01:00
Rui Ueyama d1a0e2c2c5 Refactor 2022-11-19 17:57:12 +08:00
Rui Ueyama bbb03c9096 Update README 2022-11-19 12:27:49 +08:00
Rui Ueyama 0b9f4b0e45 Update README 2022-11-19 12:04:02 +08:00
Rui Ueyama f689d25798 Update sponsor list 2022-11-19 11:34:06 +08:00
Rui Ueyama dae1edc578 [ELF] Remove duplicate rpaths
Fixes https://github.com/rui314/mold/issues/873
2022-11-19 11:08:07 +08:00
Rui Ueyama df00fe8a58 Remove "commercial/free edition" from the version message
Commercial version will get a different brand name ("sold") and
maintained in a separate repository than this repo.
2022-11-18 17:21:46 +08:00
Rui Ueyama 428218ce30 Merge pull request #867 from ishitatsuyuki/property
[ELF] Add support for more .gnu.note.property variants.
2022-11-17 23:04:26 -08:00
Rui Ueyama 20eaa1d19c Revert "[ELF] Do not include non-memory-mapped sections into build-id computation"
This reverts commit e228abfd5a because
it makes the linker to produce different executables with the same
build-id.

Fixes https://github.com/rui314/mold/issues/872
2022-11-18 14:45:16 +08:00
Tatsuyuki Ishi d30d743cb0 [ELF] Add support for more .gnu.note.property variants.
This makes .gnu.note.property handling generic instead of hardcoded against the
GNU_PROPERTY_X86_FEATURE_1_AND value.
The behavior is based on specification inside GNU gold header comments;
unfortunately a formal document does not exist yet and the comments seem to be
the best reference that can be found.

The support for the actual properties in wild seems scarce; neither gcc nor
clang emits the correct property even when passed -march=x86-64-v3 and using
__builtin_popcnt, for example. Therefore, this does not include a test yet for
the new properties.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-18 11:18:13 +09:00
Tatsuyuki Ishi 14a6e2012d [ELF] Update .note.gnu.property constants.
Includes value required to handle the "ABI needed" and "ABI used" tags, but
doesn't include the feature values yet (because we don't have a use for it).

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-18 10:38:59 +09:00
Tatsuyuki Ishi 94fb470e29 [ELF] Workaround binutils injecting own .note.gnu.property section.
binutils has learned to inject some "x86 ISA needed/used" properties.
Try to disable their emission with -mx86-used-note=no if supported, since
now that mold is gaining support for these properties, they will cause an
mismatch with the expectations.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-18 10:38:59 +09:00
Rui Ueyama 968e970adb Merge pull request #868 from ZhongRuoyu/fix-empty-prefix
Fix symlinks when `CMAKE_INSTALL_PREFIX` is `/` or empty
2022-11-17 16:50:41 -08:00
Zhong Ruoyu 2d0a38436c Tweak symlink installation
This removes the extra slash in the symlink
installation output. It works because both
NEW_ABS and NEW_DIR are absolute paths, and
therefore always begin with a slash.

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-18 00:42:00 +08:00
Zhong Ruoyu 9513f63a29 Fix symlinks when CMAKE_INSTALL_PREFIX is / or empty
As suggested by https://github.com/rui314/mold/pull/846#issuecomment-1318000326,
if `CMAKE_INSTALL_PREFIX` is set to `/` or empty,
then `mold_install_relative_symlink` would see an
empty `CMAKE_INSTALL_PREFIX` and fail. Appending
an additional slash takes care of that.

See #868.

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-18 00:39:56 +08:00
Rui Ueyama 8b373d38ce Emit REL-type relocations for --emit-relocs no i386 and ARM32
Previously, we always emit RELA-type relocations for --emit-relocs.
Now, we emit relocation tables in the target's native relocation format.
2022-11-16 16:39:19 +08:00
Rui Ueyama 456d34bde3 Fix .eh_frame relocations we create for --emit-relocs 2022-11-16 14:22:51 +08:00
Rui Ueyama 622c2d479c [ELF] Make -emit-relocs to create a .rela.eh_frame section 2022-11-16 13:56:34 +08:00
Rui Ueyama d835d016e0 [ELF] Preserve SHF_MERGE and SHF_STRING bits
Even though section flags don't matter at runtime, we want to preserve
these flags for `-r`.
2022-11-16 11:53:39 +08:00
Rui Ueyama 700a475597 Refactor 2022-11-16 11:45:09 +08:00
Rui Ueyama c2a34cc1e3 Refactor 2022-11-15 15:36:15 +08:00
Rui Ueyama 1f67ccfa20 Show license information in the --help message 2022-11-13 20:25:14 +08:00
Rui Ueyama 2b7b45c8d1 Automatically download the latest repo files 2022-11-13 19:17:16 +08:00
Rui Ueyama c9803c8984 Merge pull request #859 from ZhongRuoyu/fix-test-permission
`test/elf/section-order.sh`: fix file permission
2022-11-13 01:24:44 -08:00
Zhong Ruoyu c55931dae5 test/elf/section-order.sh: fix file permission
Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-13 16:52:59 +08:00
Rui Ueyama 94122f4ffd Link against libc++ instead of libstdc++ if -DMOLD_MOSTLY_STATIC 2022-11-13 16:31:00 +08:00
Rui Ueyama 22f3b7ed24 Bump mold version to 1.7.0 2022-11-13 08:14:59 +08:00
Rui Ueyama a67e9f54f8 Attempt to unbreak CI 2022-11-12 13:51:48 +08:00
Rui Ueyama 63933a9221 Retry if pthread_create fails with spurious error
https://github.com/oneapi-src/oneTBB/commit/137c1a88b690acf3525e0f279720ac489ce66481
is not included in 2021.7.0, so we still need to apply it separately.
2022-11-10 17:54:25 +08:00
Rui Ueyama e25802f84f Upgrade vendored OneTBB to version 2021.7.0
Fixes https://github.com/rui314/mold/issues/666
2022-11-10 17:42:51 +08:00
Rui Ueyama 73a467a3c9 Simplify 2022-11-10 14:16:55 +08:00
Rui Ueyama 91378b9c2f Simplify 2022-11-10 14:05:34 +08:00
Rui Ueyama 7f2a2c4233 Merge pull request #854 from ZhongRuoyu/git-packed-refs
Allow `update-git-hash.cmake` to get packed refs
2022-11-09 04:49:26 -08:00
Zhong Ruoyu ddd5264abd Allow update-git-hash.cmake to get packed refs
`git pack-refs` (often called by `git gc`) may
move refs from `.git/refs` to
`.git/packed-refs`. If that is the case, a lookup
in `.git/refs` will fail.

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-09 19:07:30 +08:00
Rui Ueyama 5797a59ab9 Merge pull request #853 from ishitatsuyuki/cpp-version-precedence
[ELF] Fix precedence with C++ version script rules.
2022-11-09 00:52:50 -08:00
Tatsuyuki Ishi 9875150677 [ELF] Fix precedence with C++ version script rules.
Earlier entries in a version scripts always get the precedence. Don't let a
non-C++ symbol rule override a preceding C++ symbol rule.

MultiGlob is modified to return the necessary rule index information to the
caller. Since the rule index might not always be contiguous within a
MultiGlob instance (e.g. interleaving ordinary/C++ rules), the meaning of
MultiGlob's `val` is changed so that the minimum `val` itself is returned,
instead of the `val` with minimum index (the earliest added one).

MultiGlob is also used for the macOS backend, but since it does not care
about order yet the code is left as-is.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-09 14:56:06 +09:00
Rui Ueyama c9ff2a21c5 Fix -Wdeprecated-declarations 2022-11-07 15:13:17 +08:00
Rui Ueyama bd555b33ab Do not run tests if ARM32
I don't know why, but ctests fail only on ARM32 on Docker.
2022-11-06 15:55:40 +08:00
Rui Ueyama 51427cd7d2 Do not use mimalloc on 32-bit targets 2022-11-06 15:55:23 +08:00
Rui Ueyama 0e0bd8a4f0 80 columns 2022-11-06 14:40:20 +08:00
Rui Ueyama a8321acb88 Do not try to run executables created with --section-order
We can create peculiar executables with --section-order, and some ELF
loaders don't like some of them. Since the flag is intended to be used
for embedded/kernel programming, we don't need to verify that the
executables work on regular Linux macines.

Fixes https://github.com/rui314/mold/issues/838
2022-11-06 13:05:56 +08:00
Rui Ueyama 3127508040 Refactor 2022-11-05 21:06:52 +08:00
Rui Ueyama 4fc877779b Allow to use ccache 2022-11-05 12:41:21 +08:00
Rui Ueyama dae7dca0b9 80 columns 2022-11-04 17:00:41 +08:00
Rui Ueyama b43b31eae8 [ELF] Add --execute-only 2022-11-03 14:00:06 +08:00
Rui Ueyama bedcb1bf56 Merge pull request #848 from ZhongRuoyu/cmake-absolute-prefix
Fix symlinks for absolute `CMAKE_INSTALL_*`
2022-11-02 21:02:51 -07:00
Zhong Ruoyu 9fdb09ae0e Emit a message on each symlink installation
Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-02 19:19:00 +08:00
Zhong Ruoyu f1b50695e5 Fix symlinks for absolute CMAKE_INSTALL_*
Related: #738 #846

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-02 19:18:59 +08:00
Rui Ueyama 9fcd6e965d Merge pull request #846 from ZhongRuoyu/cmake-install
Respect prefix set by `cmake --install`
2022-11-01 19:28:37 -07:00
Rui Ueyama 228b7464bf Use OpenSSL 3.0.7
We are using OpenSSL only to compute SHA-256 hashes, so we are unaffeted
by CVE-2022-3786 and CVE-2022-3602. But I think we still want to use the
newer version of OpenSSL to avoid unnecessary confusion.
2022-11-02 07:28:37 +08:00
Zhong Ruoyu ce87053c78 Respect prefix set by cmake --install
Currently, `CMAKE_INSTALL_FULL_{LIBEXEC,BIN,MAN}DIR`
are used for symlink creation. The problem with
that is it only works when the prefix is not
overridden by `cmake --install <build-dir> --prefix <prefix>`,
because their values are evaluated and hard-coded
into `cmake_install.cmake` during configuration
time, rather than installation time.

While it would be nice if we can get their
overridden values during installation, only
`CMAKE_INSTALL_PREFIX` reflects the overriden
value. (See "INSTALL A PROJECT", cmake(1)).

Therefore, the logic for symlink creation is
rewritten and extracted into a function named
`mold_install_symlink`. Now only `CMAKE_INSTALL_PREFIX`
is used during installation.

A new problem with this change is, if environment
variable `DESTDIR` is not set and `CMAKE_INSTALL_PREFIX`
is set to a relative path (which is perfectly
valid), a slash `/` would be incorrectly
prepended. To avoid that, and maintain
consistency with CMake's behaviour, the install
prefix is first turned into absolute path, stored
in `PREFIX_ABS`.

By the way, it seems okay to use the
`MAKE_DIRECTORY` and `CREATE_LINK` subcommands
under command `file`. So, it shouldn't be needed
to invoke a subprocess.

Signed-off-by: Zhong Ruoyu <[email protected]>
2022-11-02 02:42:16 +08:00
Rui Ueyama 554caeeb34 Refactor 2022-11-01 14:09:34 +08:00
Rui Ueyama 3bddfc5fd2 Refactor 2022-11-01 14:06:09 +08:00
Rui Ueyama 127758047c Merge pull request #845 from ishitatsuyuki/icf-opt1
[ELF] ICF optimizations and simplifications
2022-10-31 19:43:09 -07:00
Tatsuyuki Ishi 8449ead2a7 [ELF] icf: Add counter for eliminated sections.
We can already print them, but a counter is more handy when the
executable is large and dumping everything would be too much.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-01 11:10:36 +09:00
Tatsuyuki Ishi aab34e52cf [ELF] icf: Simplify sweeping logic
This isn't perf intensive at all but sweeping through the sections seems
to be the more obvious way compared to going through the symbol
indirections.

Also, reword the comments and timing name to better reflect what it's
doing.

FWIW, it's slightly (5ms) faster.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-01 11:10:36 +09:00
Tatsuyuki Ishi 8186b8a242 [ELF] icf: Use a bitmap to skip converged nodes faster.
A node converges if its descendants contains no cycles and its hash has reached
all its descendants. Previously we checked for convergence on each iteration by
comparing hashes, but this could be tracked more cheaply through a separate
bitset.

This improves cache efficiency and results in a ~50ms speed up for Chromium out
of 310ms for the ICF pass.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-11-01 11:10:36 +09:00
Rui Ueyama bd77bcdab4 Update comments 2022-11-01 09:18:52 +08:00
Rui Ueyama f82c0c0f83 Merge pull request #844 from LinuxUserGD/patch-1 2022-10-31 16:13:29 -07:00
Linux User 7607e3cbc2 Update CMakeLists.txt 2022-10-31 18:50:50 +01:00
Rui Ueyama 00acf3d2f6 Test whether we need -latomic or not
Based on https://github.com/MariaDB/server/commit/7de110af8c25697855ac4925b2c91f757b356655

Fixes https://github.com/rui314/mold/issues/843
2022-10-31 21:21:46 +08:00
Rui Ueyama 2942ba83d4 Merge pull request #841 from glaubitz/m68k-atomic-fix 2022-10-30 12:45:33 -07:00
John Paul Adrian Glaubitz 30bd1c26c8 [ELF][m68k] Linking against libatomic is required on m68k
Signed-off-by: John Paul Adrian Glaubitz <[email protected]>
2022-10-30 18:33:46 +01:00
Rui Ueyama ba640ceac6 [ELF] Add ppc64 to the supported emulations list 2022-10-30 17:07:51 +08:00
Rui Ueyama 097b872536 Add a link to the psABI document 2022-10-30 15:16:07 +08:00
Rui Ueyama ab5d04b670 Update README 2022-10-30 11:52:39 +08:00
Rui Ueyama 5fe60e3b6d [ELF][m68k] Add initial m68k support 2022-10-30 11:10:14 +08:00
Rui Ueyama e0ff005333 Attempt to fix build for SuSE
https://github.com/rui314/mold/issues/838
2022-10-29 19:38:12 +08:00
Rui Ueyama 98df612168 [ELF][x86-64] Allow PC32 after TLSGD/TLSLD
It looks like SuSE Leap's gcc11 produces such relocation.
2022-10-29 15:02:00 +08:00
Rui Ueyama 15979f9a10 [ELF] Change default for --section-order 2022-10-29 14:49:09 +08:00
Rui Ueyama 5218f73773 [ELF] Make it possible to define symbols with --section-order 2022-10-29 14:27:21 +08:00
Rui Ueyama 064ab23511 [ELF] Make it possible to specify an alignment with --section-order 2022-10-29 12:56:32 +08:00
Rui Ueyama 774e5c3bb6 [ELF] Set p_vaddr to non-loadable segments
It looks like the runtime doesn't care the non-loadable segments'
p_addr values, but `strip` does. `strip` corrupts an executable if
no value is set to p_vaddr.

Fixes https://github.com/rui314/mold/issues/839
2022-10-29 12:55:12 +08:00
Rui Ueyama 28598c7703 Merge pull request #837 from marxin/remove-unused-var 2022-10-28 05:11:10 -07:00
Martin Liska 91bcc51e60 fix -Wunused-variable warning
Fixes:
elf/passes.cc:1516:9: warning: unused variable 'type' [-Wunused-variable]
2022-10-28 13:49:46 +02:00
Rui Ueyama b5a4d98ef2 [ELF] Try to keep segment's paddr to be equal to its vaddr 2022-10-28 18:15:09 +08:00
Rui Ueyama b365d6ba1a [ELF] Define -z pack-relative-relocs as an alias for --pack-dyn-relocs=relr
This is for compatibility with GNU ld.
2022-10-28 16:25:33 +08:00
Rui Ueyama 2e9a4ccd10 [ELF] Do not handle .multiboot specially
Now that we have --section-order, we need to handle .multiboot in a
special manner.
2022-10-28 16:25:06 +08:00
Rui Ueyama 2fbdf1f982 Improve --section-order 2022-10-28 16:25:06 +08:00
Rui Ueyama 20173ad233 Merge pull request #835 from hwti/elf-linker-script-relative-sysroot
[ELF] Fix absolute paths handling in linker scripts
2022-10-27 18:37:25 -07:00
Rui Ueyama 602f423241 [ELF] Make --section-roder to recognize #ehdr and #phdr special names 2022-10-28 08:50:36 +08:00
Rui Ueyama 460b443736 [ELF] Add --section-order
This is an experimental flag.

Context:
https://docs.google.com/document/d/1bVRrKsB6iMGmay1odsmS44PyUzPXgQBH83gYGMY6LWM
2022-10-27 21:14:39 +08:00
Rui Ueyama 99b886f9c4 [ELF] Add --start-stop
This is an experimental flag and may be deleted in the future.

If --start-stop is given, mold creates __start_/__stop_ symbols for
sections whose names are not in C identifier style.
2022-10-27 16:14:20 +08:00
Rui Ueyama feb9cd5859 [ELF] Define __phys_{start,stop} symbols if --physical-image-base is given 2022-10-27 11:08:55 +08:00
Rui Ueyama c921959b81 [ELF] Add --physical-image-base
This is an experimental option and may be removed in the future.

Context:
https://docs.google.com/document/d/1bVRrKsB6iMGmay1odsmS44PyUzPXgQBH83gYGMY6LWM
2022-10-27 09:42:16 +08:00
Rui Ueyama 7816fd8249 [ELF] --section-align: validate alignment value 2022-10-27 09:00:54 +08:00
Rui Ueyama 1483ed9ac6 [ELF] Allow a decimal number as an argument for --section-align 2022-10-27 08:52:22 +08:00
Rui Ueyama 0cc131c065 Simplify 2022-10-27 08:44:39 +08:00
Rui Ueyama 2e11c57a6e Simplify 2022-10-27 08:36:51 +08:00
Rui Ueyama 67738238be [ELF] Add --section-align=<sectname>=<value> option
This is an experimental option and may be removed in the future.

Context:
https://docs.google.com/document/d/1bVRrKsB6iMGmay1odsmS44PyUzPXgQBH83gYGMY6LWM
2022-10-27 08:30:35 +08:00
Loïc Yhuel 467b08a782 [ELF] Fix absolute paths handling in linker scripts
With toolchains using a relative sysroot, like crosstool-ng, gcc/clang will call the linker
with something like "--sysroot=/path/bin/../machine/sysroot".
The is_in_sysroot detection wasn't working in this case.

Since to_abs_path() uses lexically_normal() for relative paths, it seems consistent to do
the same for absolute paths instead of modifying is_in_sysroot().
2022-10-26 19:57:28 +02:00
Rui Ueyama 903fff7701 Merge pull request #834 from ishitatsuyuki/dso-output-esym 2022-10-26 03:07:19 -07:00
Tatsuyuki Ishi 430818b366 [ELF] Use to_output_esym for symtab entries from DSOs.
DSO symtab handling was relatively simple, but it led to wrong entries when
copyrel was used.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-26 18:03:14 +08:00
Rui Ueyama dda70ce920 Merge pull request #832 from JamesWidman/opt-find_subsection
[Mach-O] Use binary search in find_subsection()
2022-10-25 23:38:14 -07:00
Rui Ueyama f7876a9f2a Merge pull request #833 from ishitatsuyuki/sec-reloc
[ELF] Fix section relocations getting bogus r_type.
2022-10-25 21:59:47 -07:00
Tatsuyuki Ishi 303cba23b5 [ELF] Fix section relocations getting bogus r_type.
They should inherit the original type instead of using a value from the wrong
enum (which showed up as R_X86_64_GOT32).

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-26 12:48:28 +08:00
James Widman 13eda8051a [Mach-O] Use binary search in find_subsection()
When using ld64.mold to link clang for Mach-O, most of the time was
spent in the first call to find_subsection() from
parse_compact_unwind(). Changing its linear search to a binary search
cut an 18-second link time down to about 3 seconds.

Signed-off-by: James Widman <[email protected]>
2022-10-25 18:07:25 -07:00
Rui Ueyama 0763dd5639 Improve error check 2022-10-25 16:52:13 +08:00
Rui Ueyama 95276c98fe 80 columns 2022-10-25 16:41:59 +08:00
Rui Ueyama 5a0ad2aa59 Show a warning on an invalid multiboot section 2022-10-25 16:41:14 +08:00
Rui Ueyama 4db7574e84 Place "multiboot" section right after the ELF header
This is an experimental feature to support Multiboot specification.

https://github.com/rui314/mold/issues/563
2022-10-25 10:01:12 +08:00
Rui Ueyama 997e53472d Merge pull request #819 from ishitatsuyuki/tsan-disable
Remove lock from clear_symbols and add a justification for disabling TSan
2022-10-24 03:10:51 -07:00
Tatsuyuki Ishi 3a308270bd Remove lock from clear_symbols and add a justification for disabling TSan
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-24 17:40:06 +08:00
Rui Ueyama af6475caa7 Do not use hexdump command
hexdump is not universally available, while od seems to be defined by POSIX.
2022-10-24 16:19:01 +08:00
Rui Ueyama d0210b85b9 Run tests before install 2022-10-24 16:19:01 +08:00
Rui Ueyama 73cdbb320d Merge pull request #818 from ishitatsuyuki/opt
Some trivial optimizations
2022-10-24 00:37:47 -07:00
Tatsuyuki Ishi 6088d954f6 [ELF] Remove redundant lock in mark_live_objects.
There are no writes to `sym` apart from the fast_mark which is already atomic.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-24 14:54:57 +08:00
Tatsuyuki Ishi 20ca4e38f4 Use relaxed for update_{minimum,maximum}.
We only need the RMW to be atomic and doesn't need the ordering guarantee wrt
other memory locations.
Might make ARM a little faster (no machine code change for x86).

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-24 14:54:13 +08:00
Rui Ueyama 616f8b2205 Update comments 2022-10-24 13:54:39 +08:00
Rui Ueyama 4379dc4afa Prepare to distribute binary packages for arm, ppc64le and s390x 2022-10-24 13:54:39 +08:00
Rui Ueyama c5106da010 Remove unused file 2022-10-24 11:46:27 +08:00
Rui Ueyama 0d6e373ffc Refactor 2022-10-24 11:33:58 +08:00
Rui Ueyama 6361875a15 Revert "[ELF] Add --start-stop"
This reverts commit 1cfa4a93ae because
it was committed by accident.
2022-10-24 07:29:17 +08:00
Rui Ueyama 9c7970f0af Add a test 2022-10-24 07:16:27 +08:00
Rui Ueyama 59ea52933d [ELF] Honor section alignment when creating copy relocations 2022-10-24 06:58:03 +08:00
Rui Ueyama 4fa41e1ad4 Fix -Wdeprecated-declarations
Fixes https://github.com/rui314/mold/issues/816
2022-10-24 06:28:13 +08:00
Rui Ueyama ce35e19c6d Merge pull request #815 from polluks/patch-1 2022-10-23 12:39:57 -07:00
Stefan 8643520464 Fixed typos 2022-10-23 20:31:29 +02:00
Rui Ueyama d01deafff0 Merge pull request #810 from ishitatsuyuki/comdat-early 2022-10-23 05:24:48 -07:00
Rui Ueyama 8f74e48dfb Merge pull request #814 from ishitatsuyuki/fast-mark
Add an optimized mark function for faster GC passes.
2022-10-23 04:18:35 -07:00
Tatsuyuki Ishi 59a713fb4c Add an optimized mark function for faster GC passes.
Atomic RMW operations can take hundreds of cycles and tends to be massively
slower than a fence-free alternative.

Add a fast_mark function that optimistically tests if the node is already
visited first, which allows avoiding the fence penalty if we can early exit.

When testing on Chromium, this roughly improved the associated pass's runtime
by 20--30%.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-23 18:19:32 +08:00
Rui Ueyama 626619a8b0 Enable transparent huge pages for output file if THP is available 2022-10-23 18:17:47 +08:00
Rui Ueyama 585eae2030 Fix CI 2022-10-23 17:29:57 +08:00
Tatsuyuki Ishi 730e970e79 [ELF] Run COMDAT elimination inside resolve_symbols.
We've long had a hack to de-prioritize GNU_UNIQUE symbols, due to an edge case
where these symbols could get higher resolution priority yet eliminated in the
later COMDAT elimination stage. But since GNU_UNIQUE symbols were supposed to
have a resolution as strong as GLOBAL symbols, it created unsoundness in the
resolution and could have cause false duplicate symbol errors in later stages.

The root cause is that COMDAT elimination, unlike ICF and SHF_MERGE, removes
symbols from the candidate pool and can actually affect resolution result. Hence
we ought to do the elimination before the final resolution.

Doing so requires fully wiping the resolution instead of just the ones from
eliminated archive members. This has a performance hit that we need to keep an
eye on.

On another note this wiping of resolution is actually justifiable. The old
algorithm did not handle the case of an archive member overriding a DSO symbol.
So wiping is the correct way even in absence of COMDAT elimination.

One caveat is that currently the symbol resolution stage doesn't actually care
about the is_alive flag which is how COMDAT symbols are killed. There are two
other users of this flag: mergeable sections, and eh_frame. The eh_frame section
is already handled with special logic, and we can treat it as if it had no
symbols to resolve. Mergeable sections needs a bit more careful handling, but a
good way is to just leave them unmerged and update the references in the same
later pass where merge happens. This has been done in a prior commit.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-23 14:27:26 +08:00
Tatsuyuki Ishi 26c503ad30 [ELF] Defer initialize_mergeable_sections.
To allow symbol resolution to skip !is_alive sections.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-23 14:18:28 +08:00
Tatsuyuki Ishi 1a99724eca [ELF] Split out 1st pass of symbol resolution as finalize_archive_extraction.
This pass is rather different in that it runs the resolve pass with lower
priority for archive symbols in order to determine extraction. Split it out to
better convey its difference.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-23 13:53:03 +08:00
Tatsuyuki Ishi 03dad9ba14 [ELF] Simplify do_resolve_symbols.
We can skip the is_alive condition by doing the compaction beforehand.

This also allows splitting this function into two phases clearly which will
happen later.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-23 13:05:38 +08:00
Tatsuyuki Ishi e7a7f9e39a [ELF] Ensure all symbols from LTO objects are cleared.
Make sure clear_symbols() are called on IR object files too, so that we'll never
have references to symbols inside IR object files after LTO. Can't really think
of an example that causes problem, but for sanity.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-23 13:00:44 +08:00
Rui Ueyama e228abfd5a git [ELF] Do not include non-memory-mapped sections into build-id computation 2022-10-23 12:41:59 +08:00
Rui Ueyama 61f28d270c Fix CI 2022-10-23 12:24:58 +08:00
Rui Ueyama 9535e080e9 Simplify 2022-10-23 09:31:19 +08:00
Rui Ueyama f9750d163d Merge pull request #809 from sicherha/fix_SHN_XINDEX_out_of_bounds
Fix name lookup for section symbols when `st_shndx == SHN_XINDEX`
2022-10-22 18:15:55 -07:00
Christoph Erhardt f8cb32e59a Fix name lookup for section symbols when st_shndx == SHN_XINDEX
When the section-header index has the escape value `SHN_XINDEX`, the
actual index must be looked up in the separate `SHT_SYMTAB_SHNDX` table.
Trying to use `SHN_XINDEX` (= 0xffff) as an index results in an
out-of-bounds read. The error can be observed when running the
`x86_64_many-sections.sh` test on RHEL 8 or 9 (but not on Fedora,
because there the assembler doesn't emit section symbols).

Instead of using `st_shndx` directly, call the pre-existing helper
method `get_shndx()` to get the correct behaviour.

Signed-off-by: Christoph Erhardt <[email protected]>
2022-10-22 14:41:42 +02:00
Rui Ueyama 4508d93d08 Merge pull request #808 from ishitatsuyuki/refactor 2022-10-22 05:21:16 -07:00
Tatsuyuki Ishi fb4b67ae18 [ELF] Move ElfSym memset to inside of loop.
Should eliminate redundant assignments and improve memory locality.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-22 20:16:54 +09:00
Rui Ueyama 1e98ec0b38 Merge pull request #807 from ishitatsuyuki/symtab-garbage
[ELF] Fix uninitialized ElfSym paddings
2022-10-22 03:58:07 -07:00
Tatsuyuki Ishi 0b50ebda47 [ELF] Fix uninitialized ElfSym paddings
Designated initializer doesn't initialize the padding; make sure we memset
them by ourselves.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-22 19:37:35 +09:00
Rui Ueyama 5469f5ac1b Revert "Always enable assert()"
Turned out that what I wrote as a commit message for
800fcdc5d4 is wrong. mold is 5% slower
for linking clang with assertions enabled.
2022-10-22 16:24:22 +08:00
Rui Ueyama 068ce4654f Add a CI for a non-assert build
Even if we always enable assert(), we want code to be able to compile
with assert() being expanded to a null statement. I.e., we should not
write code that has side effect in assert().

In order to add a test case for it, I added a cmake option to force
disable assert().
2022-10-22 16:03:45 +08:00
Rui Ueyama 800fcdc5d4 Always enable assert()
Our assertions are cheap, and the cost of branch instructions that
are never taken is almost free thanks to hardware branch prediction.
So I don't think it's worth to disable it for release builds.
2022-10-22 15:21:08 +08:00
Rui Ueyama 0d7a5a7284 Create config.h 2022-10-22 14:40:47 +08:00
Rui Ueyama 7db72a871c Remove Makefile
It's been deprecated for some time.
2022-10-22 14:40:47 +08:00
Rui Ueyama 9842388cd0 Do not halt even if there's a big input text section 2022-10-22 13:24:49 +08:00
Rui Ueyama 29c4403ec4 Refactor 2022-10-22 13:03:25 +08:00
Rui Ueyama 74124636dc [ELF] Make less number of range extension thunks 2022-10-22 12:32:32 +08:00
Rui Ueyama 193c245781 Refactor 2022-10-22 12:22:57 +08:00
Rui Ueyama 5cdc761746 Print out the number of bytes occupied by range extension thunks 2022-10-22 11:01:27 +08:00
Rui Ueyama d35d3922f0 Fix typo
We set a value to .ARM.exidx's sh_shlink to appease GNU binutil's
strip command (this value is not used by runtime), but even strip
doesn't care what value is set! We set a wrong value by accident.

Fixes https://github.com/rui314/mold/issues/804
2022-10-22 08:24:00 +08:00
Rui Ueyama c4850273c7 [ELF] Handle GNU-unique symbols as if they were weak
https://github.com/rui314/mold/issues/524
2022-10-21 20:58:17 +08:00
Rui Ueyama 86f3130e44 [ELF] Remove the special logic for STB_GNU_UNIQUE
We've been treating STB_GNU_UNIQUE symbols as if they were weak.
The rationale of doing is this:

1. GCC sometimes creates GNU-unique symbols instead of weak ones
   for comdats, and

2. after COMDAT de-duplication, we used to report a duplicate symbol
   error for GNU-unique symbols

However, (2) is no longer the case because we do not report a symbol
duplication error if its section is dead.

I'm not sure if this new logic will work for all programs, but I want
to give it a shot.

https://github.com/rui314/mold/issues/524
2022-10-21 17:15:51 +08:00
Rui Ueyama 2dc1fecb02 Update comments 2022-10-21 17:15:51 +08:00
Rui Ueyama 33e3a75a79 Merge pull request #802 from ZhongRuoyu/disable-subprocess-cc-on-macos 2022-10-20 23:14:45 -07:00
Zhong Ruoyu 3fe46175f3 Do not build subprocess.cc on macOS
Signed-off-by: Zhong Ruoyu <[email protected]>
2022-10-21 12:44:27 +08:00
Rui Ueyama 861bccce72 [ELF] Do not emit non-memory-allocated sections if --oformat=binary 2022-10-21 08:24:15 +08:00
Rui Ueyama 60a75d0549 Fix CI 2022-10-21 07:46:49 +08:00
Rui Ueyama 610042a8e9 [ELF] Set .ARM.exidx's sh_link to .text
GNU binutil's strip command doesn't like an .ARM.exidx section with
sh_link set to 0. arm-linux-gnueabihf-strip actually crashes on my
machine when the command is run on such a file.

https://github.com/rui314/mold/issues/801
2022-10-21 07:11:52 +08:00
Rui Ueyama 1cfa4a93ae [ELF] Add --start-stop 2022-10-21 07:11:52 +08:00
Rui Ueyama 23905bba19 Merge pull request #800 from ZhongRuoyu/fix-mold-run-output 2022-10-20 11:07:42 -07:00
Zhong Ruoyu eb391f99cf Fix mold -run output on unsupported platforms
Signed-off-by: Zhong Ruoyu <[email protected]>
2022-10-21 00:29:37 +08:00
Rui Ueyama 78713299e3 Merge pull request #799 from ksco/fix-findnull
Fix a bug in find_null
2022-10-20 06:37:06 -07:00
Yang Liu 1f32a6daf6 Fix a bug in find_null
Signed-off-by: Yang Liu <[email protected]>
2022-10-20 21:14:09 +08:00
Rui Ueyama 6b00a6c2ca Merge pull request #798 from ishitatsuyuki/emit-relocs
[ELF] Fix --emit-relocs r_sym indices.
2022-10-20 05:34:58 -07:00
Tatsuyuki Ishi f18af9e2f4 [ELF] Fix --emit-relocs r_sym indices.
536e0e7d ("[elf] Align .symtab's ELF attributes with .dynsym's.") fixed the
issue of .dynsym and .symtab disagreeing with each other, but in the
process introduced change to the symtab index. --emit-relocs was assuming
the old assignment logic, which caused mismatches as soon as any
global-to-local demotion happened.

Fix this to use the demotion-aware assignment logic by directly filling in
the indices as we assign them to symtab, and by making get_output_sym_idx
to test global/local using the new is_local() helper.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-20 20:43:29 +09:00
Rui Ueyama 288cd5b400 [ELF][i386] Fix assertion failure
https://github.com/rui314/mold/issues/794
2022-10-19 20:14:58 +08:00
Rui Ueyama 48dd520014 Clarify and simplify the sign-off guideline
The more we write to this file, the more it is unclear what exatly
a contributor is agreeing to.
2022-10-19 20:13:01 +08:00
Rui Ueyama 2aa24949c7 [ELF][i386] Allow R_386_PC32 after R_386_TLS_{GD,LDM}
I don't know why GCC sometimes creates a PC32 relocation instead of
PLT32 after a TLS_GD/TLS_LDM. I believe it's strictly speaking a
violation of the psABI. But we need to handle such input.

Fixes https://github.com/rui314/mold/issues/794
2022-10-19 19:33:18 +08:00
Rui Ueyama 323ad30e25 Bump mold version to 1.6.0 2022-10-19 14:58:59 +08:00
Rui Ueyama 96f7b664e9 Fix CI 2022-10-19 14:38:22 +08:00
Rui Ueyama a137bc4111 Avoid reading /proc/self/exe on FreeBSD
Fixes https://github.com/rui314/mold/issues/792
2022-10-19 13:48:46 +08:00
Rui Ueyama 0f6a03a9ba Merge pull request #791 from ksco/simplify 2022-10-18 13:43:11 -07:00
Yang Liu 17cb7d8dce Simplify
Signed-off-by: Yang Liu <[email protected]>
2022-10-18 21:26:42 +08:00
Rui Ueyama 0df3a2bf7b Fix CI 2022-10-18 18:11:05 +08:00
Rui Ueyama d5c59fc919 Use strerror() instead of its thread-safe versions
Since errno_string is not in a performance-critical path, it's ok for
that function to be a bit inefficient.

https://github.com/rui314/mold/issues/790
2022-10-18 17:59:07 +08:00
Rui Ueyama 0364934636 Fix -Wunused-result
Fixes https://github.com/rui314/mold/issues/790
2022-10-18 17:20:10 +08:00
Rui Ueyama edd10419dd [ELF] Save a little bit space for copy relocations 2022-10-18 17:16:00 +08:00
Rui Ueyama c1c6749940 [ELF] Ignore .note.gnu.build-id in input object files 2022-10-18 16:31:04 +08:00
Rui Ueyama 750d536401 Update comments 2022-10-18 16:30:55 +08:00
Rui Ueyama e56a3751bf [ELF][s390x] Do not allow misaligned R_390_*DBL relocs 2022-10-18 16:28:52 +08:00
Rui Ueyama ad87ac28b7 Simplify 2022-10-18 13:05:35 +08:00
Rui Ueyama 7cb97e153a [ELF] Fix -repro 2022-10-18 12:51:14 +08:00
Rui Ueyama db4bcd4138 Untabify 2022-10-18 12:41:11 +08:00
Rui Ueyama 1493a5cfd3 Merge pull request #788 from akien-mga/armv7-no-latomic 2022-10-17 14:20:47 -07:00
Rémi Verschelde 04033a1b52 CMake: Only add -latomic for armv6, not armv7
This matches what was done in `Makefile` in #561, but the check was extended
in the CMake port.

It builds fine without `-latomic` on Mageia 9 armv7hl nodes, fails with
explicit `-latomic`.

Signed-off-by: Rémi Verschelde <[email protected]>
2022-10-17 18:09:31 +02:00
Rui Ueyama d2b7b04be3 [ELF] Do not allow DSO to keep other DSO
Previously, an undefined symbol in a DSO can mark other DSO as "needed".
But that lead to mark more DSOs needed as necessary. It is also a bit
counter-intuitive.

The new behavior is still slightly different from GNU ld, but I believe
it's now compatible with LLVM lld.

Fixes https://github.com/rui314/mold/issues/786
2022-10-17 20:59:20 +08:00
Rui Ueyama bc22065684 Merge pull request #787 from ishitatsuyuki/pch
Add precompiled headers for CMake builds.
2022-10-17 03:09:26 -07:00
Tatsuyuki Ishi bf2d6bda28 workflows: Disable PCH for the default configuration.
As stated in the comment, have at least one permutation that checks for missing
(implicit) header includes.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-17 17:03:32 +08:00
Tatsuyuki Ishi d1cc5844c6 Add precompiled headers for CMake builds.
Since we're having lots of arch-generic permutations, headers seems to be a good
place where potential perf improvement are possible.

The feature is gated against CMake 3.16.0 or later, which is when it was
implemented.

Measured on 3990X, with this change it takes:
ninja  1166.22s user 34.45s system 5378% cpu 22.322 total
While before it took:
ninja  1776.81s user 51.30s system 7172% cpu 25.489 total

So a 10% improvement for workstation users and even more if you don't.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-17 16:58:42 +08:00
Rui Ueyama c5e9c30ce8 Fix CI 2022-10-17 14:25:25 +08:00
Rui Ueyama 2fb054d4a5 Refactor 2022-10-17 14:19:48 +08:00
Rui Ueyama 8fb6fb295b Rgefactor 2022-10-17 12:00:09 +08:00
Rui Ueyama 341705e4a2 Merge pull request #783 from ishitatsuyuki/icf-followup 2022-10-16 05:45:14 -07:00
Tatsuyuki Ishi 53848be39f [ELF] Replace killed_by_icf with is_killed_by_icf().
One less bit of state and should be clearer how the property is computed.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-16 10:06:13 +08:00
Rui Ueyama b3ed06aa39 Merge pull request #782 from ishitatsuyuki/icf-shndx
[ELF] Assign the correct SHNDX to symbols killed by ICF.
2022-10-15 04:42:29 -07:00
Tatsuyuki Ishi 207d8535f8 [ELF] Clarify ICF related struct fields.
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-15 18:10:29 +08:00
Tatsuyuki Ishi 519595c0c3 [ELF] Assign the correct SHNDX to symbols killed by ICF.
We have been setting the symbol value and size correctly, but the SHNDX was
erroneously set to UND, potentially causing undefined symbol errors. Fix this by
forwarding the appropriate value from the `leader`.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-15 18:10:03 +08:00
Rui Ueyama 916f8b196a Merge pull request #781 from marxin/use-checkout-v3
fix Github Actions warning:
2022-10-15 02:07:03 -07:00
Martin Liska 38b2843f84 fix Github Actions warning:
Node.js 12 actions are deprecated. For more information see:
https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/
2022-10-15 10:24:18 +02:00
Rui Ueyama df371c392b [ELF] Add more relocation overflow checks 2022-10-15 14:07:21 +08:00
Rui Ueyama 5171c9164e Refactor 2022-10-14 19:49:41 +08:00
Rui Ueyama c74486172b Make compile faster 2022-10-14 19:25:50 +08:00
Rui Ueyama 8fb9cb8815 Refactor 2022-10-14 18:17:05 +08:00
Rui Ueyama dd4048b7c4 Simplify 2022-10-14 15:27:25 +08:00
Rui Ueyama d3dc5e31eb Fix CI 2022-10-14 15:10:07 +08:00
Rui Ueyama dcc0dff67d Encourage users to migrate to cmake 2022-10-14 14:36:25 +08:00
Rui Ueyama b1382fd5e6 Update documentation 2022-10-14 13:13:37 +08:00
Rui Ueyama f4ce278729 [ELF][S390X] Support more relocation types 2022-10-14 12:59:57 +08:00
Rui Ueyama 30dd63cc0a [ELF] Do not create .plt.got for ppc64/ppc64le 2022-10-14 12:09:24 +08:00
Rui Ueyama 2dbbc54c28 Merge pull request #776 from marxin/fix-2-warnings
fix 2 -Wextra warnings
2022-10-13 19:14:47 -07:00
Rui Ueyama 55552b1481 Update comments 2022-10-13 21:12:29 +08:00
Rui Ueyama 1e24ce1f50 Update man page and comments 2022-10-13 20:53:57 +08:00
Rui Ueyama 687bd8ab65 Update comments 2022-10-13 16:33:15 +08:00
Rui Ueyama 08f34e3d67 [ELF][S390X] Skip tests affected by a GCC bug 2022-10-13 16:22:28 +08:00
Rui Ueyama 83e0632935 Update README 2022-10-13 15:16:38 +08:00
Rui Ueyama 2e7a0ad5f4 Update README 2022-10-13 15:07:36 +08:00
Rui Ueyama 1c63e0577a Document CMake options
Fixes https://github.com/rui314/mold/issues/734
2022-10-13 14:59:02 +08:00
Rui Ueyama c45db5c659 Fix CI 2022-10-13 14:08:47 +08:00
Rui Ueyama 0d205aa5e8 [ELF] Do not create PREINIT .dynamic entries if not necessary
Fixes https://github.com/rui314/mold/issues/773
2022-10-13 12:00:16 +08:00
Rui Ueyama 501a9fb57d [ELF][S390X] Set GOT[0] to _DYNAMIC
s390x psABI requires GOT[0] to be set to the link-time value of _DYNAMIC.
It also defines that GOT[1] as reserved.

https://sourceware.org/bugzilla/show_bug.cgi?id=29662
2022-10-13 09:42:26 +08:00
Rui Ueyama 33d388cc48 [ELF][S390X] Do not set GOT[0] unless -static-pie
It looks like we don't have to conservatively set GOT[0] to .dynamic
on s390x. glibc's -static-pie depends on GOT[0], so we need to set for
it, though.
2022-10-12 16:41:37 +08:00
Rui Ueyama 5010c0e66a Fix SuSE builder 2022-10-12 14:23:35 +08:00
Rui Ueyama a484df6885 Refactor 2022-10-12 12:34:00 +08:00
Rui Ueyama ce12303229 Fix CI 2022-10-12 12:10:14 +08:00
Rui Ueyama fb046cc6ed Refactor 2022-10-12 12:10:14 +08:00
Rui Ueyama f172cada2f Refactor 2022-10-11 15:28:17 +08:00
Rui Ueyama 5a5d25895c [ELF] Attempt to fix CI 2022-10-11 15:03:44 +08:00
Rui Ueyama bdecfeef7f Refactor 2022-10-11 14:43:20 +08:00
Rui Ueyama 86fd0241e0 Update comments 2022-10-11 14:24:27 +08:00
Rui Ueyama c3e8fea746 Fix build and refactor 2022-10-11 14:00:46 +08:00
Rui Ueyama 336a7b89c3 Merge pull request #778 from toppa102/freebsd-fix
Fix /proc/self behaviour on FreeBSD
2022-10-10 22:56:17 -07:00
Rui Ueyama 0d368b44de [ELF] Fix -Wtype-limits
These relocations are defined as follows

  R_SPARC_GOTDATA_OP_HIX22    (G >> 10) ^ (G >> 31)
  R_SPARC_GOTDATA_OP_LOX10    (G & 0x3ff) | ((G >> 31) & 0x1c00)

where G is an offset into .got. Since .got is much smaller than 4 GiB,
(G >> 31) is always zero, so we can ignore that part.

https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter6-24/index.html
2022-10-11 13:53:15 +08:00
Rui Ueyama 12ff7bb067 [ELF][PPC64V1] Enable QEMU tests on GitHub Actions
All tests now pass on my machine for ppc64v1 except a few disabled ones.
2022-10-11 13:24:24 +08:00
Rui Ueyama 305f0f801e [ELF][PPC64V1] Fix more tests 2022-10-11 12:46:56 +08:00
toppa102 e11aa75543 Fixed /proc/self behaviour on FreeBSD 2022-10-10 22:28:34 +03:00
Martin Liska 2d79d1f65c fix 2 -Wextra warnings
Fixes:
macho/input-files.cc:310:31: warning: missing initializer for member ‘mold::macho::Subsection<mold::macho::ARM64>::replacer’ [-Wmissing-field-initializers]
elf/mold-wrapper.c:68:15: warning: passing argument 1 of ‘debug_print’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]

Signed-off-by: Martin Liska <[email protected]>
2022-10-10 17:42:05 +02:00
Rui Ueyama e4c682b645 [ELF][ARM32] Support R_ARM_THM_JUMP19
Fixes https://github.com/rui314/mold/issues/774
2022-10-10 12:55:06 +08:00
Rui Ueyama cc3d9a274e Merge pull request #772 from ishitatsuyuki/issue-606
[ELF] Rework version_specified handling.
2022-10-09 19:32:33 -07:00
Rui Ueyama 2d5c9e0ff7 Refactor 2022-10-10 10:19:11 +08:00
Rui Ueyama 1a58a0a6d4 Merge pull request #771 from marxin/fix-compiler-warnings
fix compiler warnings
2022-10-09 19:18:03 -07:00
Tatsuyuki Ishi 2e099a12a7 [ELF] Rework version_specified handling.
We should only override the default export behavior in executable if the default
version was actually overridden by a linker script (i.e. local: *;). Other
options, such as --dynamic-list and --export-dynamic-symbol[-list], doesn't
actually change this default, and should not be considered for this override.

Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-09 21:23:30 +08:00
Martin Liska 8352edfe94 fix compiler warnings
Fixes:

```
elf/arch-ppc64v1.cc:278:9: warning: unused variable 'loc' [-Wunused-variable]
elf/arch-ppc64v1.cc:285:10: warning: variable 'check' set but not used [-Wunused-but-set-variable]
elf/output-chunks.cc:1400:32: warning: unused variable 'sym' [-Wunused-variable]
```

Signed-off-by: Martin Liska <[email protected]>
2022-10-09 14:22:47 +02:00
Rui Ueyama 87f0262ce8 Refactor 2022-10-09 12:43:40 +08:00
Rui Ueyama 3b987874f4 Update comments 2022-10-09 12:32:21 +08:00
Rui Ueyama 4d9de802fc Update README 2022-10-09 11:33:17 +08:00
Rui Ueyama 6102ed531c Merge pull request #769 from AlexTMjugador/main
README.md: mention that installing clang is not needed for Rust when using the latest GCC
2022-10-08 20:13:08 -07:00
Rui Ueyama c9ec169d50 Merge pull request #770 from tosch42/spell
Fix spelling of "unknown"
2022-10-08 20:12:16 -07:00
Rui Ueyama 944d0fd3f6 [ELF][PPC64V1] Add initial PPC64 big-endian ELFv1 ABI
Since around 2015, IBM seems to have been encouraging their customers to
use PowerPC systems in little-endian mode. They ratified the little-
endian ABI for PPC64. Since it was completely a binary-compatibility-
breaking change anyway, they didn't only change the endianness but made a
few improvements to the ABI they have been wanting to make. So the new ABI
was born.

The new ABI is called the ELFv2 ABI. The original, big-endian-only one
is retrospectively called ELFv1. Technically, the new ABI supports both
little endiannesses. but effectively all systems that employ ELFv2 are
using the processor in the little-endian mode.

There are still many PowerPC systems that use the original ELF ABI in the
little-endian mode for binary compatibility. This commit is for such systems.

We already support ELFv2. Look at arch-ppc64v2.cc.

Currently, "73% tests passed, 63 tests failed out of 234" for the ppc64v1
target according to ctest.
2022-10-09 11:09:50 +08:00
Tom Schwindl 88e275f2b1 Fix spelling of "unknown"
Signed-off-by: Tom Schwindl <[email protected]>
2022-10-08 20:24:11 +02:00
Alejandro González d91ebb6c0c README.md: mention that installing clang is not needed for Rust when using the latest GCC
As explained in the "A classic way to use mold" section, the latest GCC releases support `mold` out of the box. Rust can take advantage of this to use `mold` too, without going through a system-wide `clang` linker.

I've just tested this on my Linux workstation that runs Debian unstable.
2022-10-08 19:40:30 +02:00
Rui Ueyama e48b7e767b Refactor 2022-10-08 09:49:45 +08:00
Rui Ueyama 8d0f3ee849 [ELF][S390X] Set _DYNAMIC to GOT[0]
https://github.com/rui314/mold/issues/767
2022-10-08 09:34:43 +08:00
Rui Ueyama fc66759444 [ELF] Set the .dynamic address to the first entry of .got
glibc for ARM64 prior to August 2021 arguably wrongly assumes that
the GOT first entry contains the address of the .dynamic section.
That's been removed in the following commit, but without satisfying
that assumption, mold can't create working static position-independent
execuables for ARM64.

https://sourceware.org/git/?p=glibc.git;h=43d06ed218fc8be58987bdfd00e21e5720f0b862
2022-10-08 09:06:28 +08:00
Rui Ueyama 8bedc4d7e7 Refactor 2022-10-08 07:15:21 +08:00
Rui Ueyama c2baf10d6b Disable static PIE tests on ARM64
They are failing only on GitHub Actions. I believe they don't work
with older versions of glibc. Until we fix them, I want to disable
them so that the tests are green.
2022-10-08 06:59:01 +08:00
Rui Ueyama 5dc906dc6b [ELF][S390] TLSLE computes an offset from TP, so we don't need a GOT entry for it 2022-10-08 06:55:53 +08:00
Rui Ueyama 8ea3316c7f Refactor 2022-10-08 06:55:53 +08:00
Rui Ueyama 5ff581b363 Merge pull request #766 from marxin/tests-use-skip
tests: use skip function call when possible
2022-10-06 01:10:46 -07:00
Rui Ueyama 7778ef7fa3 Merge pull request #765 from marxin/machine-test
tests: add missing MACHINE check in x86_64_large-bss.sh
2022-10-06 01:10:20 -07:00
Martin Liska ec4c1ae981 tests: use skip function call when possible
Signed-off-by: Martin Liska <[email protected]>
2022-10-06 09:53:05 +02:00
Martin Liska 1906d210ab tests: add missing MACHINE check in x86_64_large-bss.sh
Signed-off-by: Martin Liska <[email protected]>
2022-10-06 09:49:59 +02:00
Rui Ueyama 864d91569d Enable more -static-pie tests for ARM64 and i386 2022-10-06 15:32:22 +08:00
Rui Ueyama c05e2691c0 Merge pull request #764 from marxin/cleanup-test
remove extra OK message from a test
2022-10-06 00:24:25 -07:00
Rui Ueyama d3ab0c732c Fix CI 2022-10-06 15:23:18 +08:00
Rui Ueyama bd16377bd8 [ELF][i386] Implement R_386_GOT32X relaxation 2022-10-06 15:20:17 +08:00
Martin Liska c38bf979ce remove extra OK message from a test
Signed-off-by: Martin Liska <[email protected]>
2022-10-06 09:12:57 +02:00
Rui Ueyama 8251c0d467 Enable tests that depend on zstdcat, gdb and dwarfdump 2022-10-06 14:06:18 +08:00
Rui Ueyama 52fc5877f7 Refactor 2022-10-06 13:15:55 +08:00
Rui Ueyama e1275942c8 Factor out common piece of code to common.inc 2022-10-06 12:07:31 +08:00
Rui Ueyama 9e4e81b061 Fix -Wnarrowing on i386
FIxes https://github.com/rui314/mold/issues/763
2022-10-06 09:36:56 +08:00
Rui Ueyama c7f181c30b Rename S390 S390X
s390 technically means the 32-bit ESA/390 ISA, so s390x is more precise.
2022-10-06 09:34:30 +08:00
Rui Ueyama 03721f9abe Update comments 2022-10-05 22:31:31 +08:00
Rui Ueyama a79a6059e2 Refactor 2022-10-05 21:22:56 +08:00
Rui Ueyama 1145900c51 Refactor 2022-10-05 21:13:17 +08:00
Rui Ueyama 6bbf82f3b0 Refactor 2022-10-05 20:52:02 +08:00
Rui Ueyama 7aae0bf7ba [ELF][S390] Implement TLS GD -> LE relaxation 2022-10-05 20:31:38 +08:00
Rui Ueyama 2b0ab0e974 [ELF][S390] Fix linker-synthesized __tls_get_offset
__tls_get_offset is supposed to return an offset from TP. Previously,
we instead returned an offset from the beginning of the TLS block.
2022-10-05 18:08:49 +08:00
Rui Ueyama 4bf21122e1 Refactor 2022-10-05 16:21:24 +08:00
Rui Ueyama b6b6c178b4 Fix comments 2022-10-05 15:48:28 +08:00
Rui Ueyama 5eb81b4e00 Refactor 2022-10-05 15:43:56 +08:00
Rui Ueyama f5aeeeb01d Add comments 2022-10-05 15:20:42 +08:00
Rui Ueyama 1efc14d45d [ELF][S390] Allow R_390_TLS_LDO64 in non-memory-allocated sections 2022-10-05 14:27:36 +08:00
Rui Ueyama da5d4380cf Merge pull request #759 from ishitatsuyuki/docs
[elf] Update push/pop-state documentation.
2022-10-04 23:13:43 -07:00
Tatsuyuki Ishi 9d873f555e [elf] Update push/pop-state documentation.
Closes: https://github.com/rui314/mold/issues/755
Signed-off-by: Tatsuyuki Ishi <[email protected]>
2022-10-05 14:03:44 +08:00
Rui Ueyama a39086fa3b Fix CI 2022-10-05 13:45:01 +08:00
Rui Ueyama ffac38ae9e [ELF][S390] Support more relocations, enabling tests
It seems all tests pass with this change, so I'm enabling them.
2022-10-05 13:02:47 +08:00
Rui Ueyama 182db15da7 [ELF] Add initial s390x support
The implementation is far from complete, but mold/s390 is already able
to dynamically link a "Hello World" program for s390 which can
successfully run on qemu-s390x.
2022-10-05 10:13:21 +08:00
Rui Ueyama 4801234609 Refactor 2022-10-05 10:06:53 +08:00
Rui Ueyama 6ea03e6433 [ELF] Use $ instead of @ for lihnker-synthesized symbols
gdb recognizes "@" as an operator, so it's better to use a different symbol.
2022-10-04 16:46:21 +08:00
Rui Ueyama 5000a5f05d Merge pull request #754 from ZhongRuoyu/dist-fail-early
dist.sh: do not proceed when `cmake --build` fails
2022-10-04 00:34:51 -07:00
Zhong Ruoyu 89f8990dde dist.sh: do not proceed when cmake --build fails
Signed-off-by: Zhong Ruoyu <[email protected]>
2022-10-04 14:11:49 +08:00
Rui Ueyama e5adf2fe49 [ELF][i386] Make PLT entries to start with ENDBR32
Unlike GNU ld, we do not create .plt.sec section.

%ecx is defined as a scratch register, so we can use it in our PLT entries.
2022-10-04 13:07:36 +08:00
Rui Ueyama ed0996a590 Refactor 2022-10-04 10:04:08 +08:00
Rui Ueyama f19c242901 Fix CI 2022-10-03 23:16:28 +08:00
Rui Ueyama 443a9c6d2d Fix CI 2022-10-03 23:00:15 +08:00
Rui Ueyama d414f8ff6b Attempt to fix CI 2022-10-03 22:29:14 +08:00
Rui Ueyama 1205b1c18b [ELF] Do not truncate section size to 2^32
Fixes https://github.com/rui314/mold/issues/753
2022-10-03 21:55:28 +08:00
Rui Ueyama 1b86708683 [ELF] Resurrect .plt.got
This reverts commit ef04befcba and
6c964df459.

Even though these commits are not technically wrong, they caused a
regression on ARM32 and PPC64LE. It is because glibc cannot handle
IRELATIVE relocations in .rela.plt on these targets. Compare the
following two source source files:

  https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/dl-machine.h;h=005d089501fa78654e16103de9ec901af7be4ff2;hb=HEAD#l530

  https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/arm/dl-machine.h;h=b0521c15496b9967a1c00a0ca7214bad2337be14;hb=HEAD#l464

ARM doesn't handle IRELATIVE for some reason, though x86-64 does.
I guess this is glibc's bug, but apparently we can't create IRELATIVE
relocations in .rela.plt. So we need to create it in .rela.dyn.
2022-10-03 21:45:15 +08:00
Rui Ueyama ef04befcba [ELF] Do not emit .plt.got
If a symbol has both PLT and GOT entries, we used to create its PLT
entry in .plt.got so that the runtime needs to look up the symbol
exactly once. This optimization however doesn't seem to be useful
because the number of such symbols is usually very small (<100 for
a large program). So I don't think its worth its complexity.
2022-10-03 17:21:45 +08:00
Rui Ueyama 6c964df459 [ELF] Do not always create a GOT entry for IFUNC 2022-10-03 17:07:43 +08:00
Rui Ueyama c74c001891 Merge pull request #749 from erneestoc/bundle-loader-bug
Makes xcode tests run
2022-10-02 22:52:24 -07:00
Rui Ueyama 324658fe1c Rename a function 2022-10-03 13:04:46 +08:00
Rui Ueyama c89d742be6 Refactor 2022-10-03 13:04:46 +08:00
Rui Ueyama 0f611697e0 Merge pull request #751 from rafalh/patch-1 2022-10-02 08:14:52 -07:00
Rafał Harabień 850627e7c9 Add Linux Mint support in install-build-deps.sh
Should fix following error on Linux Mint 21:
```
Error: don't know anything about build dependencies on linuxmint-21
```
2022-10-02 16:30:30 +02:00
Rui Ueyama c4e8fab106 Fix CI 2022-10-02 17:08:35 +08:00
Rui Ueyama a7f1e20594 [ELF] Emit local symbols for .got, .plt and .plt.got
These symbols are purely informational. They are not used for symbol
resolution. But they are useful for making disassembly readable.
2022-10-02 16:51:45 +08:00
Ernesto Cambuston 48ee718f23 Makes xcode tests run, Signed-off-by: Ernesto Cambuston <[email protected]> 2022-10-02 00:13:47 -07:00
Rui Ueyama ae91e487df Do not include .sh in test name 2022-10-02 14:44:07 +08:00
Rui Ueyama 1709ad5996 [ELF] Do not create a PLT entry for IFUNC if -fno-plt 2022-10-02 14:26:33 +08:00
Rui Ueyama 424f43772a Refactor 2022-10-02 13:59:59 +08:00
Rui Ueyama 3663b60074 [ELF] Place a section header at the very end of a file
Conventionally, a section header is placed at the very end of a file.
It's not technically required to do so, though.
2022-10-02 13:01:25 +08:00
Rui Ueyama 3c123cfe9d [ELF] Always set alignment 1 to PT_GNU_RELRO and PT_GNU_STACK
Alignment values are not significant for these types of segments,
so I don't want to set them to meainingful values.
2022-10-02 12:42:48 +08:00
Rui Ueyama 0fa4cff086 [ELF] Fix thread-pointer calculation
TLS segments were always page-aligned before 9c7b95f108,
so we didn't notice, but our expressions to compute TP and TLS offset
were wrong.
2022-10-02 11:41:04 +08:00
Rui Ueyama 2f5366f764 Refactor 2022-10-01 16:16:28 +08:00
Rui Ueyama 92c2c9bc8f Refactor 2022-10-01 16:13:52 +08:00
Rui Ueyama 19db4bbe13 [ELF] Create symbols for range extension thunks 2022-10-01 15:52:59 +08:00
Rui Ueyama 0929788ccc Refactor 2022-10-01 11:43:26 +08:00
Rui Ueyama fb4c21a65a Raise the support level of SPARC64 to match other targets 2022-10-01 11:22:40 +08:00
Rui Ueyama b36562fc94 Show skipped tests
https://github.com/rui314/mold/pull/745
2022-10-01 10:57:43 +08:00
Rui Ueyama 5cf3ad6fe7 Handle MOLD_ENABLE_QEMU_TESTS 2022-10-01 10:57:04 +08:00
Rui Ueyama 930964ab38 [ELF] Do not run target-specific tests on unintended targets
This commit reduces the number of tests that are invoked only to
print out the "skipped" message.
2022-10-01 10:29:40 +08:00
Rui Ueyama 56de92f65f Fix CI 2022-10-01 10:25:25 +08:00
Rui Ueyama 7d8bf817e9 [ELF] Reorganize test files 2022-09-30 15:59:53 +08:00
Rui Ueyama 438d67bcf5 Refactor 2022-09-30 12:30:32 +08:00
Rui Ueyama 020863f238 Merge pull request #740 from SupervisedThinking/up_mold_binary_install_path
CMakeLists: allow custom mold binary install path
2022-09-29 19:35:10 -07:00
Rui Ueyama 322ce8eb9d [ELF] Do not create an unnecessary gap before an executable segment 2022-09-30 10:24:56 +08:00
Rui Ueyama 57f2099a36 Revert "Detect skipped tests"
This reverts commit 313629e1af because
it made `ctest` to print out too many lines after the test summary
report for skipped test names.
2022-09-30 10:20:50 +08:00
Rui Ueyama 2fcfb9b470 [ELF] Do not allocate disk space for .relro_padding
With this change, there should be no file size penalty on specifying
a large value to `-z max-page-size`.
2022-09-30 10:18:55 +08:00
Rui Ueyama 8ef01f87b1 Merge pull request #743 from sicherha/detect_skipped_tests 2022-09-29 18:23:39 -07:00
Rui Ueyama 5eed32bcf2 [ELF] Do not discard .gnu.warning sections
.gnu.warning* section may contain strings referenced by other sections,
so we can't discard them unconditionally. In this commit, we simply
keep all .gnu.warning* sections.
2022-09-30 08:57:23 +08:00
Rui Ueyama c7767dd36d Merge pull request #744 from lkr0n/fix_gnu_warn_segfault
only ignore .gnu.warning. sections if emit_relocs is true
2022-09-29 16:02:58 -07:00
Louis Kronberg 5b147db113 only ignore .gnu.warning. sections if emit_relocs is true
This attempts to fix Issue #567 where mold would segfault
trying to emit relocations for .gnu.warning. sections.
2022-09-29 16:52:23 +02:00
Christoph Erhardt 313629e1af Detect skipped tests
This allows CTest to differentiate between tests that were executed
successfully and tests that were skipped.

Signed-off-by: Christoph Erhardt <[email protected]>
2022-09-29 16:43:17 +02:00
Rui Ueyama 260182d439 Fix CI 2022-09-29 21:52:32 +08:00
Rui Ueyama 458f88b6cf Fix CI 2022-09-29 21:00:58 +08:00
Rui Ueyama 34ee49f3de [ELF] Add a padding at end of a PT_GNU_RELRO segment
We used to extend just a load segment containing RELRO sections.
strip command shrinks such segment so that it fits its contents.
So we need a dummy section for padding.

https://github.com/rui314/mold/issues/732
2022-09-29 20:02:49 +08:00
Rui Ueyama 5746060cde Refactor 2022-09-29 20:02:33 +08:00
SupervisedThinking f7f2ef6182 CMakeLists: allow custom mold binary install path
- https://cmake.org/cmake/help/latest/command/install.html
- ${CMAKE_INSTALL_BINDIR} defaults to bin if not set
2022-09-29 11:49:57 +02:00
Rui Ueyama 886dbe6b24 Refactor 2022-09-29 16:52:33 +08:00
Rui Ueyama d1d6c00e0c Improve a script 2022-09-29 15:53:08 +08:00
Rui Ueyama 3bf3709799 Add a missing dependency
We need `util-linux` package for `hexdump`
2022-09-29 15:46:06 +08:00
Rui Ueyama d504b24419 [ELF] Fix test for PPC64LE
`readelf --symbols` print out auxiliary information after a symbol name
for PPC64, so something like `foo$` didn't match.

     7: 0000000000000000    80 FUNC    GLOBAL DEFAULT    3 main [<localentry>: 8]
     8: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND .TOC.
     9: 0000000000000000    72 FUNC    GLOBAL DEFAULT   11 foo  [<localentry>: 8]
    10: 0000000000000000    40 FUNC    GLOBAL DEFAULT   19 bar

Fixes https://github.com/rui314/mold/issues/739
2022-09-29 15:43:18 +08:00
Rui Ueyama b8ab2fd016 Refactor 2022-09-29 15:04:23 +08:00
Rui Ueyama a5448f2a48 Merge pull request #738 from jpalus/cmake-absolute-install-paths
cmake: use unambiguously absolute paths for install directories
2022-09-29 00:02:36 -07:00
Rui Ueyama e541ad3811 [ELF] Do not try to merge RELRO with a RO segment
Because of db415be21c, a RELRO segment
no longer follow a read-only data segment. So this optimization doesn't
do anything.
2022-09-29 14:08:58 +08:00
Rui Ueyama db415be21c [ELF] Place readonly data after ELF headers
They are both read-only, so by placing them next to each other, we can
reduce the number of the load segments by one, which translates to one
less mmap(2) at runtime.
2022-09-29 13:36:45 +08:00
Rui Ueyama 33de554f41 Revert "Revert recent memory layout changes"
This reverts commit e62de0b31f so that
we can try this for 1.5.2.
2022-09-29 13:22:03 +08:00
Rui Ueyama 6abf26ef38 [ELF] Sort loadable segments on p_vaddr 2022-09-29 11:47:04 +08:00
Rui Ueyama 1ed941f3f0 Bump mold version to 1.5.1 2022-09-28 17:33:54 +08:00
Rui Ueyama e62de0b31f Revert recent memory layout changes
This commit reverts the following commits:

- 3d33349691 [ELF] Fix -max-page-size
- 14b2ba3b74 Refactor
- 41c762b6c5 Update comments
- d22e85f345 [ELF] Save one memory page for RELRO
- 2941d75c71 [ELF] Do not page-align loadable segments
- a2552b6fd1 Refactor
- 919f58dc5d Fix CI
- ef6e6ecdf4 Refactor
- 9c7b95f108 [ELF] Do not page-align RELRO and TLS segments

I'll revert these changes because they caused a regression on systems
with large page size. Even though I believe most users are not
affected, it's a critical bug as mold creates completely unusable
output files for certain systems. In order to release 1.5.1, I'll
revert all of them.
2022-09-28 17:24:19 +08:00
Rui Ueyama 56b9ad9c62 Merge pull request #736 from sicherha/fix_test_failure_with_clang15
Disable PIE in `run-clang.sh` unit test
2022-09-27 19:57:34 -07:00
Jan Palus 9464a12960 cmake: use unambiguously absolute paths for install directories
as per https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html
CMAKE_INSTALL_<dir> can be both relative and absolute. so far mold made
assumption these are relative only, but handle both cases by using
CMAKE_INSTALL_FULL_<dir> instead.

Signed-off-by: Jan Palus <[email protected]>
2022-09-27 23:48:07 +02:00
Christoph Erhardt 7aa87220c4 Disable PIE in run-clang.sh unit test
This fixes the following error when Clang 15 is installed on the system:
> mold: error: out/test/elf/x86_64/run-clang/a.o:(.text):
> R_X86_64_32 relocation at offset 0x5 against symbol `.rodata' can not
> be used; recompile with -fPIC

The error occurs because Clang 15 assumes PIE to be enabled by default:
https://releases.llvm.org/15.0.0/tools/clang/docs/ReleaseNotes.html#build-system-changes

Attempting to produce a position-independent executable from an object
file that was created by `cc` without `-fPIE` caused the linker to bail
out.

Signed-off-by: Christoph Erhardt <[email protected]>
2022-09-27 15:16:44 +02:00
Rui Ueyama 95ad47ca7d Bump mold version to 1.5.0 2022-09-27 14:23:31 +08:00
Rui Ueyama 9db16a87f5 Improve build script 2022-09-27 11:50:09 +08:00
Rui Ueyama 3d33349691 [ELF] Fix -max-page-size
Fixes https://github.com/rui314/mold/issues/732
2022-09-27 11:19:31 +08:00
Rui Ueyama 5fab142c73 Refactor 2022-09-27 07:44:36 +08:00
Rui Ueyama 0a4ec45949 Merge pull request #731 from marxin/remove-duplicate 2022-09-26 14:28:22 -07:00
Martin Liska c9fbc48a6c remove duplicate in CMakeLists.txt
Signed-off-by: Martin Liska <[email protected]>
2022-09-26 21:22:01 +02:00
Rui Ueyama 14b2ba3b74 Refactor 2022-09-26 22:24:01 +08:00
Rui Ueyama 41c762b6c5 Update comments 2022-09-26 21:26:08 +08:00
Rui Ueyama d22e85f345 [ELF] Save one memory page for RELRO 2022-09-26 21:08:07 +08:00
Rui Ueyama 07ecfbcfe1 Refactor 2022-09-26 17:36:20 +08:00
Rui Ueyama c623b4e3f0 [ELF] Handle .eh_frame_seg as a special symbol for .eh_frame
Looks like Intel C/C++ compiler emits this symbol in .eh_frame.
I don't excatly know how this symbol is being used, but it looks like
this symbol is supposed to refer the beginning of the output .eh_frame.

Fixes https://github.com/rui314/mold/issues/685
2022-09-26 16:15:31 +08:00
Rui Ueyama 747c205773 [ELF] Do not copy .llvm_addrsig to an output file 2022-09-26 14:17:30 +08:00
Rui Ueyama 2941d75c71 [ELF] Do not page-align loadable segments
Previously, we align each loadable segment and the beginning of
readable-writable non-RELRO segment to page boundaries. That's one
posible layout, but it made too many holes in files that resulted
in output file bloat.

That bloat is most visible on targets supporting large pages.
We conservatively link output files for the largest possible page
size so that the output will run both on large pages and usual small
pages. As an example, we use 64 KiB as a page size for ARM64.
So we wasted up to 64 KiB for each loadable segment. A "hello world"
program for exapmle became almost 200 KiB on ARM64 because of that
reason.

This commit changes the file layout. Loadable segments are no longer
aligned to page boundaries. Loadable segments are still placed into
different memory pages. A page at a boundary of two loadable segments
are doubly mapped to memory.

This change reduces output file size. It is most noticeable when an
output is small -- a "hello world" on ARM64 is now smaller than 10
KiB now.
2022-09-26 13:48:03 +08:00
Rui Ueyama a2552b6fd1 Refactor 2022-09-25 20:27:00 +08:00
Rui Ueyama 919f58dc5d Fix CI 2022-09-25 20:27:00 +08:00
Rui Ueyama ef6e6ecdf4 Refactor 2022-09-25 17:53:44 +08:00
Rui Ueyama 9c7b95f108 [ELF] Do not page-align RELRO and TLS segments
Previously, we always aligned RELRO and TLS segments to page boundaries.
Paddings for doing that bloated up resulting output files.
2022-09-25 17:42:59 +08:00
Rui Ueyama 6f6e0a6611 Refactor 2022-09-25 14:19:38 +08:00
Rui Ueyama 3c1425fe88 [ELF] Do not set W bit to PT_TLS
Since the actual writability is controlled by the W bit of the LOAD
segments, I don't think it actually matters. But in the spirit, TLS
segment contains the initial TLS image and should not be writable.
2022-09-24 17:47:54 +08:00
Rui Ueyama 101c64b427 Downgrade required CMake version
CMake 3.13 should be reasonably old enough. Ubuntu 20.04 includes
CMake 3.16.3. I wanted to support Ubuntu 18.04's CMake, but that was
3.10.2, and that's too old.
2022-09-24 13:34:55 +08:00
Rui Ueyama 622e18bd40 Fix CI 2022-09-24 09:18:26 +08:00
Rui Ueyama 652ab0c1da Fix CI 2022-09-24 08:15:25 +08:00
Rui Ueyama 64f6a89a0d Fix CI 2022-09-23 23:06:41 +08:00
Rui Ueyama b93ad52a73 Enable separate compilation for each source file and target pair
Almost all functions and data types are template in mold, which are
parameterized to target type (e.g. X86_64 or ARM32). I'm generally
happy with that design, but there's one drawback; as we add more
targets, compilation gets slower.

Now we support more than 10 targets, so the compiler has to do 10x
more work than it originall did. As a result, compiling output-chunks.cc
(one of our largest file) took more than 30 seconds to compile on a
Threadripper machine. I hasitated to add a support for a new target
because of this problem. We needed to fix it.

This commit tweaks the build system configs so that the build system
generates bunch of intermediate .cc files. Each generated .cc file
instantiates an original .cc file for one target.

The total amount of required computation doesn't change by this hack,
but now we can parallelize the compilation step. That works well on a
modern multi-core machine.

I generally don't like to tweak build system to workaround a tool's
issue (that's why I'm creating mold in the first place), but it looks
like there's no way to speed up compilation other than this. Or,
maybe we can write a faster C++ compiler someday, but that's another
topic.
2022-09-23 22:28:40 +08:00
Rui Ueyama 6b23821276 Fix build failure if MOLD_DEBUG_X86_64_ONLY is set 2022-09-23 11:45:37 +08:00
Rui Ueyama d37bc2eb2d Merge pull request #727 from marxin/enable-compress-debug-sections-zstd-for-GCC
Enable compress-debug-sections-zstd for GCC
2022-09-22 07:09:42 -07:00
Martin Liska b5f9d936da Enable compress-debug-sections-zstd for GCC
The compression option is directly passed to linker,
so GCC can handle it as well.

Signed-off-by: Martin Liska <[email protected]>
2022-09-22 16:06:16 +02:00
Rui Ueyama 97bf734b67 Merge pull request #726 from marxin/mold-V-alignment
fix output of mold -V
2022-09-22 06:27:34 -07:00
Martin Liska 38dfad1c6f fix output of mold -V
Add missing space and remove trailing newline.

Signed-off-by: Martin Liska <[email protected]>
2022-09-22 15:10:49 +02:00
Rui Ueyama 302a9ed8d8 Add missing targets and emulations 2022-09-22 19:53:01 +08:00
Rui Ueyama 264ca5aab4 Bug fix 2022-09-22 15:56:23 +08:00
Rui Ueyama b2ffaf9294 Refactor 2022-09-22 15:08:01 +08:00
Rui Ueyama a1e0ca8a7f Refactor 2022-09-22 14:15:02 +08:00
Rui Ueyama 54f50a7cb4 Refactor 2022-09-22 12:59:41 +08:00
Rui Ueyama ae53c4202a Refactor 2022-09-22 12:44:04 +08:00
Rui Ueyama 1dd2b9e662 Update comments 2022-09-22 12:05:59 +08:00
Rui Ueyama 4d02762fd9 Add a comment 2022-09-22 11:56:17 +08:00
Rui Ueyama 218038c7a4 [ELF][RISCV] Support big-endian RISC-V targets
These are experimental, but gcc already supports them with `-mbig-endian`.
2022-09-22 11:35:37 +08:00
Rui Ueyama c320f546fe Refactor 2022-09-22 09:01:59 +08:00
Rui Ueyama 068e52859b [ELF] Rename PPC64LE PPC64V2
The original PPC64 psABI was big-endian only which is retrospectively
called the ELF v1 ABI. Around 2016, IBM decided to push little-endian
use of PowerPC platform and ratified a new psABI which is called the
ELF v2 ABI.

Changing the endianness inevitably breaks ABI compatibility, so IBM
took that chance to clean up the ABI. So the difference between v1 and
v2 are not just endianness.

However, v2 isn't technically little-endian only. You can still create
an ELFv2-based big-endian system. Such systems are not popular though.

In many systems, the original ELFv1 big-endian system is called
"ppc64" and the v2 little-endian system is called "ppc64le". But these
names are not very accurate. I think ppc64v1 and ppc64v2 (or ppc64v2le
and ppc64v2be) are more accurate.
2022-09-21 21:23:24 +08:00
Rui Ueyama 615978a132 [ELF] Emit addend if both --pack-dyn-relocs and --no-apply-dynamic-relocs are given 2022-09-21 19:48:22 +08:00
Rui Ueyama 867c285332 [ELF] Allow more than 2^11 range extension thunks
It's unlikely that we have so many range extension thunks in a single
binary, but I don't think we need to save 4 bytes here.
2022-09-21 19:27:34 +08:00
Rui Ueyama d92cef9559 Attempt to fix ASAN issue
https://github.com/rui314/mold/issues/723
2022-09-21 18:05:55 +08:00
Rui Ueyama b67335fadd Merge pull request #722 from marxin/fix-Wreorder 2022-09-21 02:01:14 -07:00
Rui Ueyama 18a424dbc9 Merge pull request #724 from marxin/fix-i586-warning 2022-09-21 02:00:48 -07:00
Martin Liska d315ff2eff fix i686 ld warning
ld: warning: out/test/elf/i686/tls-module-base-i386/a.o:
missing .note.GNU-stack section implies executable stack

Signed-off-by: Martin Liska <[email protected]>
2022-09-21 10:17:48 +02:00
Martin Liska 5c275ce843 fix -Wreoder warning
Fixes:

elf/elf.h:2107:6: warning: ‘mold::elf::SparcEB64Rela::r_type’ will be initialized after [-Wreorder]
 2107 |   u8 r_type;
      |      ^~~~~~
elf/elf.h:2106:8: warning:   ‘mold::ub24 mold::elf::SparcEB64Rela::r_type_data’ [-Wreorder]
 2106 |   ub24 r_type_data; // SPARC-specific: used for R_SPARC_OLO10

Signed-off-by: Martin Liska <[email protected]>
2022-09-21 09:57:48 +02:00
Rui Ueyama 46f74fe7ed Refactor 2022-09-21 12:11:17 +08:00
Rui Ueyama 8e922416ee Do not build with mimalloc on i386
mimalloc on i386 seems to cause a runtime error on i386.

Fixes https://github.com/rui314/mold/issues/718
2022-09-21 11:06:32 +08:00
Rui Ueyama 24affd8ed3 Refactor 2022-09-21 10:18:48 +08:00
Rui Ueyama 0e4db53774 Update comments 2022-09-21 09:38:40 +08:00
Rui Ueyama 57442144ef [ELF][SPARC] Handle more relocations 2022-09-21 09:26:33 +08:00
Rui Ueyama 3f45f68a5d Enable SPARC64 tests on GitHub Actions 2022-09-21 08:49:55 +08:00
Rui Ueyama 3625c40f04 [ELF][SPARC] Disable gdb-index tests
These tests don't pass even with GNU ld on SPARC64. gdb doesn't seem
to be able to set a breakpoint to a function in an DSO for some resaon.
2022-09-21 08:06:01 +08:00
Rui Ueyama 9094e2eee1 [ELF][SPARC] Write little-endian values to .gdb_index 2022-09-21 08:04:29 +08:00
Rui Ueyama 5e8e35940a Refactor 2022-09-21 07:34:04 +08:00
Rui Ueyama 3400567fee Update comments 2022-09-20 23:04:07 +08:00
Rui Ueyama 91ce29d30c Update comments 2022-09-20 22:58:31 +08:00
Rui Ueyama 3564b71c4f Update comments 2022-09-20 22:52:15 +08:00
Rui Ueyama e9714be08f Refactor 2022-09-20 22:10:46 +08:00
Rui Ueyama 99d6478b35 Disable on SPARC64 2022-09-20 21:00:51 +08:00
Rui Ueyama 321d8208cb [ELF][SPARC] Make LTO plugin work 2022-09-20 21:00:18 +08:00
Rui Ueyama 76ab25d486 [ELF][SPARC64] Fix struct definition 2022-09-20 18:18:31 +08:00
Rui Ueyama 3b58bf52a7 [ELF][SPARC] Fix R_SPARC_OLO10 2022-09-20 17:44:05 +08:00
Rui Ueyama ce85848867 Add more constants 2022-09-20 17:20:33 +08:00
Rui Ueyama 9784632214 [ELF][SPARC] Fix endianness issue
Bitfields are placed in an address-increasing order on both little
and big-endian hosts. Therefore, the following struct has the same
layout on both endiannesses.

  struct {
    uint32_t x : 8;
    uint32_t y : 16;
  }

The address of `x` is smaller than `y`, though you can't take an
address of a bitfield using the `&` operator in C.

However, if two bitfields are placed the same byte, they are placed in
a reverse order. In LE, bitfields are placed from LSB to MSB, while in
BE, they are placed from MSB to LSB. Therefore, members something like
this

  struct {
    uint32_t x : 4;
    uint32_t y : 4;
  }

needs to be swapped.
2022-09-20 16:04:24 +08:00
Rui Ueyama e6625d0c86 Refactor 2022-09-20 16:03:59 +08:00
Rui Ueyama 1f53fd2adf Refactor 2022-09-20 14:18:29 +08:00
Rui Ueyama c0cd1f5d58 Revert "[ELF][SPARC] Enable SPARC64 tests" to unbreak CI
This reverts commit 8c4d65179f.
2022-09-20 14:09:56 +08:00
Rui Ueyama 8c4d65179f [ELF][SPARC] Enable SPARC64 tests 2022-09-20 13:50:45 +08:00
Rui Ueyama f1ccc05c3f [ELF][SPARC] Simplify TLS handling
__tls_get_addr is not available in statically-linked executables,
so we have been relaxing all TLSGD and TLSLD relocations for static
executables. However, I noticed that it's much easier to provide a
replacement function for __tls_get_addr instead of implementing all
TLS relaxations.

With this change, all mold's unit tests pass on qemu-sparc64.
2022-09-20 13:30:52 +08:00
Rui Ueyama 46c38ca738 [ELF][SPARC] Fix .eh_frame_hdr 2022-09-20 11:32:49 +08:00
Rui Ueyama 546c552644 Fix tests 2022-09-20 11:21:21 +08:00
Rui Ueyama a1a8058b3d [ELF][SPARC] Fix TLSLD relaxation 2022-09-20 11:17:59 +08:00
Rui Ueyama d8fa5343ec [ELF][SPARC] Fix TLSGD relaxation 2022-09-20 09:45:25 +08:00
Rui Ueyama 9dcc70f6ee [ELF][SPARC] Fix more test failures 2022-09-19 21:03:37 +08:00
Rui Ueyama 139aae9444 [ELF][SPARC64] Relax R_SPARC_GOTDATA relocations 2022-09-19 17:13:21 +08:00
Rui Ueyama 956433bd8e Refactor 2022-09-19 15:33:01 +08:00
Rui Ueyama 6952ba3953 [ELF][SPARC64] Relax TLSGD and TLSLD relocations 2022-09-19 15:16:31 +08:00
Rui Ueyama 0788013cd8 [ELF] Create symbols for relocations referring mergeable section symbols
Previously, a relocation is associated either to a symbol or a section
fragment. Now, we synthesize a symbol for each unnamed piece of section
fragment and associate it to a relocation.
2022-09-19 15:16:31 +08:00
Rui Ueyama 39847a4a27 Refactor 2022-09-19 12:07:21 +08:00
Rui Ueyama 6f6a814439 Move a struct member for better packing 2022-09-19 11:10:41 +08:00
Rui Ueyama a789e48452 [ELF] Use a tagged pointer to store the origin of a pointer
A symbol can belong to an input section, an output section or a section
fragment. They are mutually exclusive, and their data structures are
aligned to at least 4 bytes boundaries. So we can use a tagged pointer
to store all of them in a single pointer-size object.
2022-09-19 11:04:00 +08:00
Rui Ueyama 7b6db0308e Merge pull request #715 from marxin/sparc64-unused-code
sparc64: remove unused code
2022-09-18 17:10:32 -07:00
Rui Ueyama 3e185623c1 Merge pull request #716 from marxin/fix-test
fix a test warning
2022-09-18 17:10:10 -07:00
Martin Liska 459cdcb6f1 fix a test warning
Fixes:
./test/elf/tls-gd2.sh: line 15: [: missing `]'

Signed-off-by: Martin Liska <[email protected]>
2022-09-18 19:22:46 +02:00
Martin Liska a9e697660b sparc64: remove unused code
Signed-off-by: Martin Liska <[email protected]>
2022-09-18 19:18:55 +02:00
Rui Ueyama dbfb584ab9 [ELF][SPARC64] Fix more tests 2022-09-18 22:23:30 +08:00
Rui Ueyama 154031af66 Refactor 2022-09-18 21:56:19 +08:00
Rui Ueyama ee11979a76 [ELF] Do not allow --no-apply-dynamic-relocs on REL-type targets 2022-09-18 21:36:50 +08:00
Rui Ueyama b87398be69 Merge pull request #712 from wangp/which
Don't use which(1) or perl
2022-09-18 06:10:54 -07:00
Rui Ueyama 982b06da2d Merge pull request #714 from marxin/fix-Worder-issues 2022-09-18 05:53:34 -07:00
Rui Ueyama 5c8cd76d2e [ELF][SPARC] Support TLS relocations 2022-09-18 19:43:46 +08:00
Rui Ueyama b28e771756 [ELF][SPARC64] Fill .plt.got contents 2022-09-18 17:26:03 +08:00
Martin Liska 1174cf7406 fix -Wreorder warnings
Fixes:

elf/elf.h: In constructor 'mold::elf::EB64Rel::EB64Rel(mold::u64, mold::u32, mold::u32, mold::i64)':
elf/elf.h:1928:8: warning: 'mold::elf::EB64Rel::r_type' will be initialized after [-Wreorder]
 1928 |   ub32 r_type;
      |        ^~~~~~
elf/elf.h:1927:8: warning:   'mold::ub32 mold::elf::EB64Rel::r_sym' [-Wreorder]
 1927 |   ub32 r_sym;
      |        ^~~~~
elf/elf.h:1923:3: warning:   when initialized here [-Wreorder]
 1923 |   EB64Rel(u64 r_offset, u32 r_type, u32 r_sym, i64 r_addend = 0)
      |   ^~~~~~~
elf/elf.h: In constructor 'mold::elf::EB32Rel::EB32Rel(mold::u64, mold::u32, mold::u32, mold::i64)':
elf/elf.h:1938:6: warning: 'mold::elf::EB32Rel::r_type' will be initialized after [-Wreorder]
 1938 |   u8 r_type;
      |      ^~~~~~

Signed-off-by: Martin Liska <[email protected]>
2022-09-18 11:01:34 +02:00
Rui Ueyama 190da9ce92 [ELF][SPARC64] Support non-position-independent executables 2022-09-18 15:26:23 +08:00
Rui Ueyama 874876ab82 Refactor 2022-09-18 13:26:29 +08:00
Rui Ueyama 4698c1364a Refactor 2022-09-18 13:04:56 +08:00
Rui Ueyama adde0130f5 [ELF] Do not promote hidden symbols to dynamic symbols
Fixes https://github.com/rui314/mold/pull/713
2022-09-18 12:32:56 +08:00
Rui Ueyama 562b52906f [ELF][SPARC64] Support more relocations
With this commit, we can now build a dynamically-linked "hello world"
program for SPARC64.
2022-09-18 12:08:26 +08:00
Peter Wang c56429b1d6 Use sed instead of perl 2022-09-17 14:00:17 +10:00
Peter Wang 7cdf0abbb3 Use command -v instead of which 2022-09-17 13:41:02 +10:00
Peter Wang d4004cf054 Directly run clang to check if available
"which clang" may find a symlink from "clang" to ccache,
thus succeeding even when clang is not installed on the system.
2022-09-17 13:40:40 +10:00
Rui Ueyama c1bdbe2327 Add a comment 2022-09-16 21:56:31 +08:00
Rui Ueyama ce78dfd4b2 Simplify 2022-09-16 21:39:01 +08:00
Rui Ueyama 2331492840 Refactor 2022-09-16 21:23:45 +08:00
Rui Ueyama a189dae361 [ELF][SPARC64] Implement features
With this commit, mold/SPARC64 can create an output file for
hello-dynamic.sh test, though the output didn't work correctly.
2022-09-16 18:06:19 +08:00
Rui Ueyama d72e96f02e [ELF] Add a stub for SPARC64 2022-09-16 14:00:21 +08:00
Rui Ueyama 1fdb13c816 [ELF] Add data types for SPARC64 2022-09-16 13:50:02 +08:00
Rui Ueyama 8af8eced16 [ELF] Rename PPC64 PPC64LE 2022-09-16 13:35:35 +08:00
Rui Ueyama d92d6c0cd7 Update comments 2022-09-16 12:36:03 +08:00
Rui Ueyama 20edf1b44b Refactor 2022-09-16 10:29:23 +08:00
Rui Ueyama 441d53c302 Refactor 2022-09-16 09:38:19 +08:00
Rui Ueyama 26168f662a Refactor 2022-09-16 08:51:30 +08:00
Rui Ueyama 2af061f7fd Update comments 2022-09-15 19:53:47 +08:00
Rui Ueyama fea0734c41 Use one more word for each symbol
I don't think saving 32 bits here doesn't worth the cost of worrying
about possible integer overflow.
2022-09-15 18:03:40 +08:00
Rui Ueyama f0f8b30a2d Create an Docker multi-architecture build image
So that we can create binary distribution images on any machine.
2022-09-15 15:30:21 +08:00
Rui Ueyama 9d748ba967 Do not depend on compiler optimization too much
This is a performance critical part, and I started to think that
depending on a smart compiler optimization is a bit too risky.
2022-09-15 12:50:02 +08:00
Rui Ueyama 37863ccbaf Update comments 2022-09-15 11:52:37 +08:00
Rui Ueyama fb5b828996 Simplify 2022-09-15 11:20:57 +08:00
Rui Ueyama 7d36399035 Merge branch 'marxin-use-CC-in-tests' 2022-09-15 10:03:07 +08:00
Martin Liska 0825208628 tests: fix test/elf/exception-mcmodel-large.sh
Fix syntax error:
line 31: int main() {}: command not found

Signed-off-by: Martin Liska <[email protected]>
2022-09-15 10:02:39 +08:00
Rui Ueyama 86d67b1b17 Simplify 2022-09-15 10:02:22 +08:00
Rui Ueyama 8612d411d1 Refactor 2022-09-15 10:02:22 +08:00
Rui Ueyama 3fd9ca553b Merge pull request #710 from marxin/fix-a-test
tests: fix test/elf/exception-mcmodel-large.sh
2022-09-14 18:50:16 -07:00
Rui Ueyama 62c077924a Simplify 2022-09-15 09:14:13 +08:00
Rui Ueyama 3795b71350 Refactor 2022-09-15 08:52:46 +08:00
Martin Liska bcb21edcb1 tests: use $CC instead of cc
Signed-off-by: Martin Liska <[email protected]>
2022-09-14 21:46:41 +02:00
Martin Liska 30617cc632 tests: fix test/elf/exception-mcmodel-large.sh
Fix syntax error:
line 31: int main() {}: command not found

Signed-off-by: Martin Liska <[email protected]>
2022-09-14 21:46:20 +02:00
Rui Ueyama 183e4db696 Support big-endian hosts
Note that we do not support any big-endian targets. This commit enables
users to use mold to cross compile little-endian binaries on big-endian
hosts.
2022-09-14 21:43:29 +08:00
Rui Ueyama 1b9dc06150 Revert "Enable IFUNC tests on RISC-V" to fix CI
This reverts commit 70b69b2698.
2022-09-14 18:24:39 +08:00
Rui Ueyama 34b63e7f92 Fix CI 2022-09-14 17:57:20 +08:00
Rui Ueyama 045bc58da5 Simplify 2022-09-14 17:55:43 +08:00
Rui Ueyama 70b69b2698 Enable IFUNC tests on RISC-V 2022-09-14 17:45:37 +08:00
Rui Ueyama 0856d3f76e Check if $CC accepts -static
Fixes https://github.com/rui314/mold/issues/707
2022-09-14 17:24:48 +08:00
Rui Ueyama f8176051c1 Simplify 2022-09-14 16:26:39 +08:00
Rui Ueyama 80f2c66413 Simplify 2022-09-14 16:19:10 +08:00
Rui Ueyama e00ddceda2 Simplify 2022-09-14 16:08:10 +08:00
Rui Ueyama 9e6e472b2a Attempt to suppress GCC's -Walloc-size-larger-than warnings
https://github.com/rui314/mold/issues/708
2022-09-14 15:20:29 +08:00
Rui Ueyama c55e1216b8 Disable -Wsign-compare and -Wunused-function
https://github.com/rui314/mold/issues/708
2022-09-14 15:12:50 +08:00
Rui Ueyama 07636727c7 Fix -Wunused-variable warnings
https://github.com/rui314/mold/issues/708
2022-09-14 15:00:49 +08:00
Rui Ueyama 75c142f321 Fix GCC's -Wclass-memaccess warnings
GCC prints out bunch of the following warnings when compiling mold.

```
macho/input-files.cc:717:11: warning: 'void* memset(void*, int, size_t)'
  clearing an object of non-trivial type 'struct mold::macho::MachSection';
  use assignment or value-initialization instead [-Wclass-memaccess]

  717 |     memset(hdr, 0, sizeof(*hdr));
      |     ~~~~~~^~~~~~~~~~~~~~~~~~~~~~
```

This commit suppresses it by making our classes trivial.

https://github.com/rui314/mold/issues/708
2022-09-14 14:49:42 +08:00
Rui Ueyama 8e2e6d8603 Set LIBDIR for mold-wrapper.so
Fixes https://github.com/rui314/mold/issues/697
2022-09-14 14:28:17 +08:00
Rui Ueyama fdcb3c2efe Refactor 2022-09-14 14:10:36 +08:00
Rui Ueyama b4c148dd0c Do not over-specify distro version 2022-09-14 11:24:23 +08:00
Rui Ueyama 4e5fe38a2d Simplify 2022-09-14 11:17:07 +08:00
Rui Ueyama 9fd8313657 Simplify 2022-09-13 17:27:37 +08:00
Rui Ueyama 6a71ec6804 Fix CI 2022-09-13 16:21:22 +08:00
Rui Ueyama 896e49cc9e Update the man page 2022-09-13 15:53:03 +08:00
Rui Ueyama 258ec353b0 [ELF] Support reading ZSTD-compressed debug info section 2022-09-13 15:47:59 +08:00
Rui Ueyama 7b9bfe08a6 [ELF] Delete support of .zdebug-style compressed debug sections
lld 15 stopped supporting it since its usage is very low if not none.

https://reviews.llvm.org/D126793
2022-09-13 15:47:59 +08:00
Rui Ueyama a3512e6636 Fix CI 2022-09-13 15:47:59 +08:00
Rui Ueyama 145369e1f1 Fix CI 2022-09-13 15:33:39 +08:00
Rui Ueyama 26591cfd41 Fix CI 2022-09-13 15:09:14 +08:00
Rui Ueyama c8abc0f189 Suppress compiler warnings 2022-09-13 14:55:20 +08:00
Rui Ueyama ede7a5aaa3 [ELF] Support --compress-debug-sections=zstd
Fixes https://github.com/rui314/mold/issues/700
2022-09-13 14:55:20 +08:00
Rui Ueyama 5919914c54 Add libzstd to support --compress-debug-sections=zstd 2022-09-13 14:55:17 +08:00
Rui Ueyama 34bd56e15b Update comments 2022-09-13 13:10:02 +08:00
Rui Ueyama da550493b6 Refactor 2022-09-13 13:04:35 +08:00
Rui Ueyama b1055b42b7 Refactor 2022-09-13 11:58:14 +08:00
Rui Ueyama b941a37b1e Make addresses given to --start-address multiples of 65536
On PPC64, page size is 65536 bytes or 0x10000 bytes.

https://github.com/rui314/mold/issues/554
2022-09-12 19:37:58 +08:00
Rui Ueyama c0ff4a246b [ELF][PPC64] Handle R_PPC64_DTPREL64 against non-alloc sections
https://github.com/rui314/mold/issues/554
2022-09-12 16:27:00 +08:00
Rui Ueyama ed1acd31c8 Do not use fgrep or egrep
fgrep and egrep are deprecated.

Fixes https://github.com/rui314/mold/issues/705
2022-09-12 16:22:55 +08:00
Rui Ueyama cda57cfe4a [ELF] Do not create multiple NOTE segments 2022-09-12 15:58:20 +08:00
Rui Ueyama 428aac7458 Update comments 2022-09-12 15:07:53 +08:00
Rui Ueyama 629f2157dd Update comments 2022-09-12 14:43:32 +08:00
Rui Ueyama 8dc8ddb5d4 Update comments 2022-09-12 14:18:27 +08:00
Rui Ueyama d4c7062bd4 Simplify 2022-09-12 13:11:32 +08:00
Rui Ueyama bbf591288e Add a comment 2022-09-12 13:06:15 +08:00
Rui Ueyama f702c398b2 Update comments 2022-09-12 12:16:45 +08:00
Rui Ueyama a5eb8b9d24 [ELF][PPC64] REL24's range is not 2^23 but 2^25 2022-09-12 10:33:38 +08:00
Rui Ueyama 56c7761485 Simplify 2022-09-12 10:22:04 +08:00
Rui Ueyama 6d588cc3a8 [ELF] Add more error checks 2022-09-11 20:42:47 +08:00
Rui Ueyama e01f8b4993 [ELF] Add more error checks 2022-09-11 19:33:53 +08:00
Rui Ueyama cbf786bb59 Simplify 2022-09-11 18:24:32 +08:00
Rui Ueyama 2ee1b530b6 Update comments 2022-09-11 17:39:16 +08:00
Rui Ueyama b96a4150d9 [ELF][x86] Simplify TLS relaxation
Previously, TLSLD relocation on i386/x86-64 produced different values
than it normally does if it's relaxed. If relaxed, it's evaluated to
TP, which is the end address of the TLS block. If not relaxed, it's
evaluated to the beginning of the TLS block.

As a result, we needed to adjust subsequent DTPOFF relocations so that
it is based on the end of the TLS block only when the corresponding
symbol was relaxed. This is very tricky.

I believe the psABI TLS spec specifies this way because they couldn't
find a replacement instruction sequence that is equal to or shorter
then the replacing instructions. `mov %fs:0, %rax` is 9 bytes long,
so it barely fits the location where a TLSLD refers to.

I found that `xor %eax, %eax; mov %fs(%rax), %rax` is shorter than
`mov %fs:0, %rax` while it yields the same value. The two instructions
are 5 bytes in total. By saving 4 bytes, we can now add another `add`
instruction to adjust the resulting value, so that the result is always
relative to the beginning of the TLS block.
2022-09-11 16:40:02 +08:00
Rui Ueyama a6777e9c50 Refactor 2022-09-11 16:33:19 +08:00
Rui Ueyama 20effdc21c [ELF][i386] Enable more TLS tests 2022-09-11 16:04:42 +08:00
Rui Ueyama c7ad760534 Add comments 2022-09-11 11:49:27 +08:00
Rui Ueyama 7406b18042 Add a test 2022-09-11 11:35:49 +08:00
Rui Ueyama c97d6088e5 Remove dead code
https://github.com/rui314/mold/pull/703
2022-09-11 10:36:20 +08:00
Rui Ueyama 01240db538 Simplify 2022-09-11 10:14:41 +08:00
Rui Ueyama 055e2423e5 Update comments 2022-09-10 21:11:34 +08:00
Rui Ueyama ac7a7cb1f3 Update comments 2022-09-10 20:52:43 +08:00
Rui Ueyama fe59dc9dff [ELF][PPC64] Create range extension thunks for unreachable branches
With this, all tests pass on PPC64. We need to support TOC-less
functions, though.
2022-09-10 19:55:44 +08:00
Rui Ueyama 3bf132f7ed Revert "Improve a test"
This reverts commit 100fcaebf4 to unbreak CI.
2022-09-10 19:54:17 +08:00
Rui Ueyama 100fcaebf4 Improve a test 2022-09-10 19:17:24 +08:00
Rui Ueyama 1b3e0fe0fb Update a test 2022-09-10 18:18:54 +08:00
Rui Ueyama b0776d9496 Style changes 2022-09-10 17:59:13 +08:00
Rui Ueyama 7b6dc763ad Update a help message 2022-09-10 17:44:41 +08:00
Rui Ueyama 43e3aa24ef Merge pull request #701 from rod-salazar/lto_lib_doc_text
Remove 'Ignored' helptext for lto_library and order_file
2022-09-10 02:42:39 -07:00
Rui Ueyama 1cbae48a29 Style changes 2022-09-10 17:34:55 +08:00
Rui Ueyama 623549ff45 Style changes 2022-09-10 17:31:38 +08:00
Rodrigo Salazar 042910a848 Remove 'Ignored' helptext for lto_library and order_file 2022-09-10 02:20:39 -07:00
Rui Ueyama 0f7c87ca03 Add a comment 2022-09-10 16:43:17 +08:00
Rui Ueyama febb696a39 [ELF][PPC64] Enable PPC64 tests if -DMOLD_ENABLE_QEMU_TESTS is specified 2022-09-10 15:59:24 +08:00
Rui Ueyama 594dd50246 Simplify 2022-09-10 15:57:52 +08:00
Rui Ueyama 6aa2c56497 [ELF][PPC64] Enable CI 2022-09-10 15:39:28 +08:00
Rui Ueyama 5ca3d4bf48 [ELF][PPC64] Handle .toc just like .got 2022-09-10 15:35:15 +08:00
Rui Ueyama a3a0582ac4 Fix CI 2022-09-10 14:32:51 +08:00
Rui Ueyama 6b5f0f1c92 [ELF][PPC64] Fix various issues for ppc64 2022-09-10 13:47:28 +08:00
Rui Ueyama 5dcb2b634e Improve error messages 2022-09-09 21:07:22 +08:00
Rui Ueyama aa9cab8323 Simplify 2022-09-09 20:45:19 +08:00
Rui Ueyama d739547feb [ELF][PPC64] Handle relocations against non-alloc sections 2022-09-09 20:02:53 +08:00
Rui Ueyama 20d80b806a [ELF][PPC64] Remove an unnecessary padding from .plt.got 2022-09-09 19:33:33 +08:00
Rui Ueyama 21be1d7d5e Update comments 2022-09-09 19:24:54 +08:00
Rui Ueyama 1227897ab0 Update README 2022-09-09 18:08:02 +08:00
Rui Ueyama 3cd25344a8 Simplify 2022-09-09 17:55:46 +08:00
Rui Ueyama 3e02327858 Fix invalid uses of a relocation 2022-09-09 17:52:42 +08:00
Rui Ueyama c8013ae871 Update comments 2022-09-09 17:49:32 +08:00
Rui Ueyama 98b35d3793 [ELF][PPC64] Add R_PPC64_TLSLD, R_PPC64_DTPREL16_HA and R_PPC64_DTPREL16_LO 2022-09-09 17:23:24 +08:00
Rui Ueyama 5a8181f7ff [ELF][PPC64] Add R_PPC64_GOT_TLSLD16_HA 2022-09-09 17:19:18 +08:00
Rui Ueyama f38e131242 [ELF][PPC64] Add R_PPC64_REL64 2022-09-09 17:15:35 +08:00
Rui Ueyama 2dd6bbbb3d [ELF][PPC64] Restore r2 after function call 2022-09-09 17:09:20 +08:00
Rui Ueyama 87e2e06530 Add links to psABI documents 2022-09-09 14:24:02 +08:00
Rui Ueyama 678dcfb748 Update comments 2022-09-09 12:57:19 +08:00
Rui Ueyama 7b4afc48ff [ELF][PPC64] Fix PLT contents
With this change, tests/elf/hello-dynamic.sh passes.
2022-09-09 12:47:03 +08:00
Rui Ueyama fda84aa648 Simplify 2022-09-09 07:46:04 +08:00
Rui Ueyama 12b5699750 Add a comment 2022-09-08 21:51:11 +08:00
Rui Ueyama aa300ae973 [ELF][PPC64] Remove an unnecessary section 2022-09-08 21:36:57 +08:00
Rui Ueyama c1de2d53dc [ELF][PPC64] Do not disk space to .got.plt 2022-09-08 19:29:17 +08:00
Rui Ueyama da7c3f878f Style changes 2022-09-08 17:31:52 +08:00
Rui Ueyama 5bf05b5f45 Merge pull request #699 from ksco/eh-frame-hdr
Fix --no-eh-frame-hdr option
2022-09-08 02:28:24 -07:00
Yang Liu 410e1633e2 Add a no-eh-frame-header test
Signed-off-by: Yang Liu <[email protected]>
2022-09-08 17:07:12 +08:00
Yang Liu 5871018d10 Fix --no-eh-frame-hdr
Signed-off-by: Yang Liu <[email protected]>
2022-09-08 17:07:12 +08:00
Rui Ueyama 2940d93123 Remove dead code 2022-09-08 17:02:04 +08:00
Rui Ueyama be0131fc63 [ELF] Add initial PPC64LE support 2022-09-08 16:57:59 +08:00
Rui Ueyama 4d8d3fda55 Refactor 2022-09-08 16:57:59 +08:00
Rui Ueyama 28f89b0a4f Merge pull request #698 from ksco/global-pointer
Fix global-pointer value
2022-09-08 00:05:18 -07:00
Yang Liu 906abcedee Fix global-pointer value
Global-pointer's value should be .sdata+0x800.

Signed-off-by: Yang Liu <[email protected]>
2022-09-08 14:24:35 +08:00
Rui Ueyama 1950def5f9 Refactor 2022-09-06 21:19:56 +08:00
Rui Ueyama 692fe9af1e Update comments 2022-09-06 18:15:57 +08:00
Rui Ueyama ff6afd39e0 Simplify 2022-09-06 18:03:03 +08:00
Rui Ueyama a712f91a03 Rename a variable 2022-09-06 13:24:20 +08:00
Rui Ueyama 8c7f31cafa [ELF] Copy ELF st_type when creating symbol aliases for --defsym
Fixes https://github.com/rui314/mold/issues/695
2022-09-06 12:54:34 +08:00
Rui Ueyama eede5a08b0 Update comments 2022-09-06 10:18:39 +08:00
Rui Ueyama ecdb6034af Update comments 2022-09-05 20:50:04 +08:00
Rui Ueyama 85d496dd45 Update a comment 2022-09-05 20:42:29 +08:00
Rui Ueyama 00273f3d0c Simplify 2022-09-05 20:21:43 +08:00
Rui Ueyama 7cad171a34 Rename functions 2022-09-05 20:17:05 +08:00
Rui Ueyama 1d0c1f8f32 [ELF] Add overflow checks 2022-09-05 20:16:00 +08:00
Rui Ueyama 7ffdcd49d8 [ELF] Set a unique dummy value to linker-synthesized symbols 2022-09-05 17:33:34 +08:00
Rui Ueyama bb0e601eaa Avoid GNU assembler's bug
Looks like GNU assembler produces illegal code and relocations for
_GLOBAL_OFFSET_TABLE, so we can't directly use that symbol in our
tests.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106834
2022-09-05 17:06:19 +08:00
Rui Ueyama f34c345c7c Move code so that the relocations are in the same order in all switch-cases 2022-09-05 14:43:35 +08:00
Rui Ueyama d2bba23541 Update comments 2022-09-05 14:21:54 +08:00
Rui Ueyama fc49faa896 Style changes 2022-09-05 14:19:36 +08:00
Rui Ueyama ddbf501a66 Merge pull request #687 from ksco/relax
[RISCV] Add zero-page relaxation
2022-09-04 22:58:42 -07:00
Rui Ueyama be13b5de4a [ELF] Replace branch instructions with NOPs 2022-09-05 13:44:53 +08:00
Rui Ueyama 72e26d9b69 [ELF] Do not convert a call with a weak undef to a nop in DSO
On ARM, calling an remaining weak undefined symbol is a NOP. But
if such symbol was promoted to a dynamic symbol, we need to execute
that call instruction instead of converting to a NOP because it may
be resolved to a defined symbol at runtime.

Fixes https://github.com/rui314/mold/issues/660
2022-09-05 13:34:59 +08:00
Rui Ueyama 6d51493e46 [ELF] Avoid accidental string prefix matches 2022-09-05 10:10:20 +08:00
Rui Ueyama 25c7aee1d9 [ELF] Merge .rodata mergeable strings to .rodata.str
Fixes https://github.com/rui314/mold/issues/511
2022-09-05 09:46:58 +08:00
Rui Ueyama fafc0d0b94 Simplify 2022-09-04 20:34:44 +08:00
Rui Ueyama 8d16301665 [ELF][RISC-V] Do not allocate r_deltas for non-executable sections
RISC-V relocations are relaxed only for executable code sections,
so we don't need to allocate r_deltas for non-executable sections.
2022-09-04 19:23:02 +08:00
Rui Ueyama 98042443ac Simplify 2022-09-04 19:19:43 +08:00
Rui Ueyama 2c86726c06 Suppress a dynamic linker's warning message
glibc started printing out the following warning message on process
startup, starting from glibc 2.36:

  warning: direct reference to protected function `baz' in
  `out/test/elf/x86_64/protected/b.so' may break pointer equality

It does so if it finds there's a canonical PLT against a protected
symbol. The warning message itself makes sense -- there's no way to
create a canonical PLT against a protected symbol while keeping
pointer equality, and you should recompile object files with `-fPIC`
to suppress this warning.

However, in this test, we intentionally create a canonical PLT against
a protected symbol, so we just want to silence it.

Fixes https://github.com/rui314/mold/issues/689
2022-09-04 17:54:51 +08:00
Rui Ueyama ba43a01c8c Merge pull request #690 from ksco/script-test
Improve linker-script-defsym tests
2022-09-04 02:48:41 -07:00
Yang Liu c1dc0e6c2d Improve linker-script-defsym tests
Signed-off-by: Yang Liu <[email protected]>
2022-09-04 17:42:48 +08:00
Rui Ueyama 3ab548971f [ELF] Make riscv_resize_sections faster
We saw a significant performance improvement with this commit. Here
is before and after numbers when cross-building mold for RV32 on a
simulated 16-core machine:

  $ time ./mold.old @rsp
  real    0m45.216s
  user    0m0.016s
  sys     0m0.000s

  $ time ./mold.new @rsp
  real    0m0.240s
  user    0m0.014s
  sys     0m0.003s

  $ ls -lh mold
  -rwxrwxr-x 1 ruiu ruiu 152M Sep  4 17:16 mold
2022-09-04 17:24:07 +08:00
Yang Liu b6549580be [RISCV] Add zero-page relaxation
Signed-off-by: Yang Liu <[email protected]>
2022-09-04 16:02:53 +08:00
Rui Ueyama 807af97a4b Revert "wip" which was made by mistake 2022-09-04 15:46:46 +08:00
Rui Ueyama 682fc41c0d [ELF] Do not try to relax dead input sections
It's just a waste of time.
2022-09-04 15:31:13 +08:00
Rui Ueyama ff0679f7b8 wip 2022-09-04 15:03:03 +08:00
Yang Liu e58f55959d Update comments 2022-09-04 02:06:09 +08:00
Yang Liu 9507f11f77 Remove redundant code 2022-09-04 02:06:09 +08:00
Yang Liu 114fb21b8e Update comments
Signed-off-by: Yang Liu <[email protected]>
2022-09-04 02:06:09 +08:00
1487 changed files with 186238 additions and 41883 deletions

No files matched your search

+21 -84
View File
@@ -6,41 +6,21 @@ on:
branches: [ main ]
env:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
MOLD_SUPPRESS_MACHO_WARNING: 1
jobs:
build-clang-make:
build-clang:
strategy:
matrix:
target:
- test
- test-asan
- test-tsan
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: rui314/setup-mold@staging
- name: install-build-deps
run: sudo apt-get update && sudo ./install-build-deps.sh
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: make test
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
make -j$(nproc) CC=clang CXX=clang++ ${{ matrix.target }}
build-clang-cmake:
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
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: rui314/setup-mold@staging
- name: install-build-deps
run: sudo apt-get update && sudo ./install-build-deps.sh
run: sudo ./install-build-deps.sh update
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: build and test
@@ -52,52 +32,25 @@ jobs:
cmake --build . -j$(nproc)
ctest . -j$(nproc)
build-gcc-make:
build-gcc:
runs-on: ubuntu-20.04
container: gcc:11.1.0
steps:
- uses: actions/checkout@v2
- name: install-build-deps
run: |
dpkg --add-architecture i386
apt-get update
./install-build-deps.sh
apt-get install -y sudo qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: make test-all
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
make -j$(nproc) CC=gcc CXX=g++ test-all
build-gcc-cmake:
runs-on: ubuntu-20.04
container: gcc:11.1.0
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: install-build-deps
run: |
# Install cross toolchains
dpkg --add-architecture i386
apt-get update
./install-build-deps.sh
apt-get install -y sudo qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
ln -sf /usr/bin/aarch64-linux-gnu-g++-10 /usr/bin/aarch64-linux-gnu-g++
./install-build-deps.sh update
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
ln -sf /usr/bin/$i-linux-gnu-g++-10 /usr/bin/$i-linux-gnu-g++
done
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
ln -sf /usr/bin/riscv64-linux-gnu-gcc-10 /usr/bin/riscv64-linux-gnu-gcc
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
# Install a RV32 toolchain from third party since it's not available
# as an Ubuntu package.
@@ -109,6 +62,7 @@ jobs:
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/usr/local/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 /usr/bin/riscv32-linux-gnu-g++
shell: bash
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: build and test
@@ -128,32 +82,16 @@ jobs:
build/out
build/Testing
build-macos-make:
strategy:
matrix:
target:
- test
- test-asan
runs-on: macos-11
steps:
- uses: actions/checkout@v2
- uses: rui314/setup-mold@staging
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: make test
run: |
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
make -j$(sysctl -n hw.logicalcpu) ${{ matrix.target }}
build-macos-cmake:
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'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: build and test
@@ -163,12 +101,11 @@ jobs:
cd build
cmake ${{ matrix.target }} ..
cmake --build . -j$(sysctl -n hw.physicalcpu)
ctest . -j$(sysctl -n hw.physicalcpu)
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: build
run: |
mkdir build
+1
View File
@@ -1,6 +1,7 @@
*.o
*.so
*~
**/.DS_Store
.cache
compile_commands.json
/mold
+269 -73
View File
@@ -1,17 +1,58 @@
cmake_minimum_required(VERSION 3.18)
project(mold VERSION 1.4.2)
# You can customize a build by specifying CMake options. An option may be
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
# means true while `OFF` or `0` means false.
#
# Here are a couple of common cmake options:
#
# -DCMAKE_C_COMPILER=<command-name>
#
# Specifies a C compiler name to use. The default value is `cc`.
#
# -DCMAKE_CXX_COMPILER=<command-name>
#
# Specifies a C++ compiler name to use. The default value is `c++`.
#
# -DCMAKE_INSTALL_PREFIX=<directory>
#
# Specifies an install target directory. The default value is `/usr/local`.
#
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
#
# Specifies a build type. The default is `Release` which is the right
# option unless you are debugging mold.
#
# An example of a cmake command line is shown below:
#
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
#
# where `..` refers this directory.
#
# With cmake, you may run `cmake --install .` instead of `make install` to
# install build artifacts to system directories. If you want to install
# artifacts to a temporary target directory, run `cmake --install . --prefix
# <dir>`.
#
# You can see the current cmake variables and their values by running
# `cmake -N -L .` in a build directory.
#
# Note that in this file, we provide various dials and knobs to configure
# how to build mold. However, as a policy, we do not provide a way to
# enable/disable any individual mold's feature. In other words, we do not
# provide options like `--enable-foo` or `--disable-foo`. The motivation
# behind it is build reproducibility. We want to guarantees that all builds
# of the mold linker of the same version will have the exactly same set of
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.13)
project(mold VERSION 1.8.0)
include(CMakeDependentOption)
include(GNUInstallDirs)
# Add -fuse-ld=mold if accepted by the compiler.
option(MOLD_USE_MOLD "Use mold to build mold" ON)
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
include(CheckLinkerFlag)
check_linker_flag(CXX -fuse-ld=mold CXX_SUPPORTS_FUSE_MOLD)
if(CXX_SUPPORTS_FUSE_MOLD)
add_link_options(-fuse-ld=mold)
endif()
add_link_options(-fuse-ld=mold)
endif()
add_executable(mold)
@@ -22,36 +63,44 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
target_compile_options(mold PRIVATE
-fno-exceptions
-fno-unwind-tables
-fno-asynchronous-unwind-tables)
-fno-asynchronous-unwind-tables
-Wno-sign-compare
-Wno-unused-function)
endif()
# Build mold with -flto if MOLD_LTO=On
# Build mold with -flto if -DMOLD_LTO=ON
option(MOLD_LTO "Build mold with link-time optimization enabled")
if(MOLD_LTO)
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
endif()
# Handle MOLD_USE_ASAN and MOLD_USE_TSAN
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
if(MOLD_USE_ASAN)
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
endif()
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
if(MOLD_USE_TSAN)
target_compile_options(mold PRIVATE -fsanitize=thread)
target_link_options(mold PRIVATE -fsanitize=thread)
endif()
# Static link libstdc++ and libcrypto if MOLD_MOSTLY_STATIC=On
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
#
# This option is intended to be used by `./dist.sh` script to create a
# mold binary that works on various Linux distros. You probably don't
# need nor want to set this to ON.
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
if(MOLD_MOSTLY_STATIC)
target_link_options(mold PRIVATE -static-libstdc++)
target_link_libraries(mold PRIVATE libcrypto.a)
endif()
# Setup zlib
# Find zlib. If libz.so is not found, we compile a bundled one and
# statically-link it to mold.
find_package(ZLIB QUIET)
if(ZLIB_FOUND)
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
@@ -62,13 +111,45 @@ else()
target_link_libraries(mold PRIVATE zlibstatic)
endif()
# Setup mimalloc
# Find zstd compression library. Just like zlib, if libzstd.so is not
# found, we compile a bundled one and statically-link it to mold.
include(CheckIncludeFile)
check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H)
target_link_libraries(mold PRIVATE zstd)
else()
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
# Find mimalloc. mimalloc is an alternative malloc implementation
# optimized for multi-threaded applications.
#
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
#
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
# be stable on 32-bit targets.
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(
"static_assert(sizeof(void *) == 4); int main() {}" P32)
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
"NOT APPLE;NOT ANDROID" OFF)
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
# By default, we build a bundled mimalloc and statically-link it to
# mold. If you want to dynamically link to the system's
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
if(MOLD_USE_MIMALLOC)
if(MOLD_USE_SYSTEM_MIMALLOC)
find_package(mimalloc REQUIRED)
@@ -76,8 +157,8 @@ if(MOLD_USE_MIMALLOC)
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
else()
function(mold_add_mimalloc)
set(MI_BUILD_STATIC ON)
option(MI_BUILD_TESTS "Build test executables" OFF)
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
target_link_libraries(mold PRIVATE mimalloc-static)
@@ -87,7 +168,12 @@ if(MOLD_USE_MIMALLOC)
endif()
endif()
# Setup TBB
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
# Use of this library is mandatory.
#
# By default, we build a bundled one and statically-link the library
# to mold. If you want to link to the system's libtbb2.so, pass
# -DMOLD_USE_SYSTEM_TBB=ON.
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
if(MOLD_USE_SYSTEM_TBB)
find_package(TBB REQUIRED)
@@ -105,16 +191,12 @@ else()
mold_add_tbb()
endif()
# MOLD_X86_64_ONLY is a developer-only option. You should not use it
# for creating an executable for production use.
option(MOLD_X86_64_ONLY "Developer-only option" OFF)
if(MOLD_X86_64_ONLY)
set(CMAKE_BUILD_TYPE "Debug")
target_compile_options(mold PRIVATE -ffunction-sections -fdata-sections
-DMOLD_DEBUG_X86_64_ONLY)
target_link_options(mold PRIVATE -Wl,--gc-sections)
# Check if this is a commercial version of mold (a.k.a. "sold")
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.md")
set(MOLD_IS_SOLD ON)
endif()
# We always use Clang to build mold on Windows. MSVC can't compile mold.
if(WIN32)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR
@@ -122,23 +204,15 @@ if(WIN32)
endif()
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
target_sources(mold PRIVATE
elf/lto-win32.cc
macho/lto-win32.cc)
else()
include(CheckLibraryExists)
check_library_exists(m pow "" LIBM_FOUND)
if(LIBM_FOUND)
target_link_libraries(mold PRIVATE m)
endif()
target_sources(mold PRIVATE
elf/lto-unix.cc
elf/subprocess.cc
macho/lto-unix.cc)
endif()
# Build mold-wrapper.so
if(NOT APPLE AND NOT WIN32)
add_library(mold-wrapper SHARED)
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
@@ -154,13 +228,29 @@ if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(riscv64|armv)")
# If atomics doesn't work by default, add -latomic.
# We need the flag on riscv, armv6 and m68k.
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("#include <atomic>
int main() {
std::atomic_uint8_t a;
std::atomic_uint16_t b;
std::atomic_uint32_t c;
std::atomic_uint64_t d;
return ++a + ++b + ++c + ++d;
}" HAVE_FULL_ATOMIC_SUPPORT)
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
target_link_libraries(mold PRIVATE atomic)
endif()
set_property(SOURCE main.cc elf/lto-unix.cc macho/output-chunks.cc APPEND PROPERTY
COMPILE_DEFINITIONS "MOLD_VERSION=\"${CMAKE_PROJECT_VERSION}\"")
# Add -pthread
if(NOT APPLE AND NOT WIN32)
target_compile_options(mold PRIVATE -pthread)
target_link_options(mold PRIVATE -pthread)
endif()
# 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}
@@ -172,14 +262,23 @@ add_custom_target(git_hash
add_dependencies(mold git_hash)
target_sources(mold PRIVATE
compress.cc
demangle.cc
elf/arch-arm32.cc
elf/arch-arm64.cc
elf/arch-i386.cc
elf/arch-riscv.cc
elf/arch-x86-64.cc
# Create config.h file
configure_file(config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
# (e.g. X86_64) as its type parameter. Since we suport more than 10
# targets, compiling a single source file for all the targets is very
# slow.
#
# As a workaround, we create a .cc file for each target and spawn many
# 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 HPPA32)
list(APPEND MOLD_ELF_TEMPLATE_FILES
elf/cmdline.cc
elf/dwarf.cc
elf/gc-sections.cc
@@ -187,36 +286,109 @@ target_sources(mold PRIVATE
elf/input-files.cc
elf/input-sections.cc
elf/linker-script.cc
elf/lto.cc
elf/main.cc
elf/mapfile.cc
elf/output-chunks.cc
elf/passes.cc
elf/relocatable.cc
elf/subprocess.cc
elf/thunks.cc
filepath.cc
git-hash.cc
glob.cc
hyperloglog.cc
macho/arch-arm64.cc
macho/arch-x86-64.cc
)
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
list(APPEND MOLD_MACHO_TEMPLATE_FILES
macho/cmdline.cc
macho/dead-strip.cc
macho/input-files.cc
macho/input-sections.cc
macho/lto.cc
macho/main.cc
macho/mapfile.cc
macho/output-chunks.cc
macho/tapi.cc
macho/yaml.cc
)
function(mold_instantiate_templates SOURCE TARGET)
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
#define MOLD_TARGET ${TARGET}
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
")
target_sources(mold PRIVATE ${PATH})
endfunction()
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
endforeach()
endforeach()
if(MOLD_IS_SOLD)
foreach (SOURCE IN LISTS MOLD_MACHO_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_MACHO_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
endforeach()
endforeach()
endif()
# Add other non-template source files.
target_sources(mold PRIVATE
compress.cc
demangle.cc
elf/arch-alpha.cc
elf/arch-arm32.cc
elf/arch-arm64.cc
elf/arch-hppa32.cc
elf/arch-i386.cc
elf/arch-m68k.cc
elf/arch-ppc32.cc
elf/arch-ppc64v1.cc
elf/arch-ppc64v2.cc
elf/arch-riscv.cc
elf/arch-s390x.cc
elf/arch-sh4.cc
elf/arch-sparc64.cc
elf/arch-x86-64.cc
elf/elf.cc
filepath.cc
git-hash.cc
glob.cc
hyperloglog.cc
main.cc
multi-glob.cc
perf.cc
strerror.cc
tar.cc
third-party/rust-demangle/rust-demangle.c
uuid.cc
)
if(MOLD_IS_SOLD)
target_sources(mold PRIVATE
macho/arch-arm64.cc
macho/arch-x86-64.cc
macho/yaml.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")
if(MOLD_IS_SOLD)
target_precompile_headers(mold PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>"
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/macho/mold.h>")
else()
target_precompile_headers(mold PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>")
endif()
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
# to enable caching
target_compile_options(mold PRIVATE -fpch-preprocess)
endif()
include(CTest)
if(BUILD_TESTING)
@@ -225,35 +397,59 @@ if(BUILD_TESTING)
add_custom_command(
TARGET mold POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
BYPRODUCTS ld ld64
BYPRODUCTS ld
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM)
if(MOLD_IS_SOLD)
add_custom_command(
TARGET mold POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
BYPRODUCTS ld64
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM)
endif()
endif()
if(${APPLE})
add_subdirectory(test/macho)
if(MOLD_IS_SOLD)
add_subdirectory(test/macho)
endif()
elseif(${UNIX})
add_subdirectory(test/elf)
endif()
endif()
if(NOT CMAKE_SKIP_INSTALL_RULES)
install(TARGETS mold)
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
install(CODE "
set(DEST \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}\")
file(RELATIVE_PATH RELPATH
/${CMAKE_INSTALL_LIBEXECDIR}/mold /${CMAKE_INSTALL_BINDIR}/mold)
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory
\${DEST}/${CMAKE_INSTALL_LIBEXECDIR}/mold)
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink \${RELPATH}
\${DEST}/${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold
\${DEST}/${CMAKE_INSTALL_BINDIR}/ld.mold)
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold
\${DEST}/${CMAKE_INSTALL_BINDIR}/ld64.mold)
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink mold.1
\${DEST}/${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)")
function(mold_install_relative_symlink OLD NEW)
install(CODE "
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld.mold)
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
if(MOLD_IS_SOLD)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld64.mold)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld.sold)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
${CMAKE_INSTALL_BINDIR}/ld64.sold)
endif()
endif()
+7 -21
View File
@@ -1,30 +1,16 @@
# Contributing to mold
Summary: When you send a patch that is longer than 10 lines, you must
agree that the patch is released under the dual license of the GNU
AGPLv3 and the MIT license. As an indication of agreement, please add
a `Signed-off-by` line to your commit message using `git commit -s`.
Here is the long official version:
mold is currently licensed under the GNU AGPLv3, but I want to relicense
it under a more liberal license in the future. To make this possible,
I want all patches to be submitted under the dual license of the GNU
AGPLv3 and the MIT license (the "Dual License") from the beginning.
To make it clear that contributors have agreed to release their
patches under the Dual License, I introduced a sign-off procedure just
like the Linux kernel project did. The sign-off is a simple line at
the end of the git commit message, which certifies that you wrote it
or otherwise have the right to pass it on as an open-source patch. The
rules are pretty simple: if you can certify the below:
We request all patches to mold to be released under the dual license
of the GNU AGPLv3 and the MIT license. Please read the following and
sign-off your patch as an indication of agreement.
> ## Developer's Certificate of Origin
>
> By making a contribution to this project, I certify that:
>
> 1. The contribution was created in whole or in part by me and I
> have the right to submit it under the Dual License; or
> have the right to submit it under the Dual License of the GNU
> AGPLv3 and the MIT license; or
>
> 2. The contribution is based upon previous work that, to the best
> of my knowledge, is covered under the the Dual License and I
@@ -42,7 +28,7 @@ rules are pretty simple: if you can certify the below:
> maintained indefinitely and may be redistributed consistent with
> this project or the open source license(s) involved.
then you just add a line saying:
If you agree, you just add a line saying:
```
Signed-off-by: Random J Developer <[email protected]>
@@ -55,4 +41,4 @@ for you.
We assume that any contribution that's 10 lines or less does not meet
the threshold of originality and therefore copyright does not apply.
You don't need to add a "Signed-off-by" line to such small commits.
You don't need to add a "Signed-off-by" line to such small patches.
+12 -10
View File
@@ -1,7 +1,7 @@
# This dockerfile creates a reproducible build environment for mold.
#
# $ docker build -t rui314/mold-builder:v1-$(uname -m) .
# $ docker push rui314/mold-builder:v1-$(uname -m)
# $ docker buildx create --use
# $ docker buildx build --platform x86_64,aarch64,arm,ppc64le,s390x -t rui314/mold-builder:latest --push .
FROM ubuntu:18.04
ENV TZ=Europe/London
@@ -9,13 +9,15 @@ RUN apt-get update && \
apt-get install -y --no-install-recommends software-properties-common && \
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential wget \
libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
cd / && \
wget -O- -q https://www.openssl.org/source/openssl-3.0.5.tar.gz | tar xzf - && \
mv openssl-3.0.5 openssl && \
cd openssl && \
./Configure --prefix=/usr/local && \
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
\
mkdir /openssl && cd /openssl && \
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
./Configure --prefix=/usr/local --libdir=lib && \
make -j$(nproc) && \
make -j$(nproc) install && \
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.0/cmake-3.24.0-linux-$(uname -m).tar.gz | tar -C /usr/local --strip-components=1 -xzf -
ldconfig && \
mkdir /cmake && cd /cmake && \
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
rm -rf /var/lib/apt/lists/* /cmake /openssl
+34
View File
@@ -989,3 +989,37 @@ For the third-party/zlib component:
Jean-loup Gailly Mark Adler
[email protected] [email protected]
For the third-party/zstd component:
BSD License
For Zstandard software
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-253
View File
@@ -1,253 +0,0 @@
VERSION = 1.4.2
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
LIBDIR = $(PREFIX)/lib
LIBEXECDIR = $(PREFIX)/libexec
MANDIR = $(PREFIX)/share/man
DOCDIR = $(PREFIX)/share/doc
INSTALL = install
INSTALL_PROGRAM = $(INSTALL)
INSTALL_DATA = $(INSTALL) -m 644
OS := $(shell uname -s)
ARCH := $(shell uname -m)
ifeq ($(OS), Darwin)
TESTS := $(wildcard test/macho/*.sh)
else
TESTS := $(wildcard test/elf/*.sh)
endif
D = $(DESTDIR)
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
# name `c++`.
ifeq ($(origin CXX), default)
CXX = c++
endif
# If you want to keep symbols in the installed binary, run make with
# `STRIP=true` to run /bin/true instead of the strip command.
STRIP = strip
SRCS = $(wildcard *.cc elf/*.cc macho/*.cc)
OBJS = $(SRCS:%.cc=out/%.o) out/rust-demangle.o out/git-hash.o
IS_ANDROID = 0
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
IS_ANDROID = 1
endif
# If you want to compile mold for debugging, invoke make as
# `make CXXFLAGS=-g`.
CFLAGS = -O2
CXXFLAGS = -O2
MOLD_CXXFLAGS := -std=c++20 -fno-exceptions -fno-unwind-tables \
-fno-asynchronous-unwind-tables \
-DMOLD_VERSION=\"$(VERSION)\" -DLIBDIR="\"$(LIBDIR)\""
ifeq ($(OS), OpenBSD)
MOLD_LDFLAGS := -pthread -lz -lm
else
MOLD_LDFLAGS := -pthread -lz -lm -ldl
endif
LTO = 0
ifeq ($(LTO), 1)
CXXFLAGS += -flto -O3
LDFLAGS += -flto
endif
# By default, we want to use mimalloc as a memory allocator. mimalloc
# is disabled on macOS and Android because it didn't work on those hosts.
USE_MIMALLOC = 1
ifeq ($(OS), Darwin)
USE_MIMALLOC = 0
else ifeq ($(IS_ANDROID), 1)
USE_MIMALLOC = 0
endif
ifeq ($(USE_MIMALLOC), 1)
ifdef SYSTEM_MIMALLOC
MOLD_CXXFLAGS += -DUSE_SYSTEM_MIMALLOC
MOLD_LDFLAGS += -lmimalloc
else
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
MOLD_CXXFLAGS += -Ithird-party/mimalloc/include
MOLD_LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
endif
endif
# Note: Do NOT specify `SYSTEM_TBB=1` unless your system-wide OneTBB
# library is compiled with https://github.com/oneapi-src/oneTBB/pull/824.
# mold with an unpatched OneTBB is unstable when doing LTO if the system
# is under high load.
ifdef SYSTEM_TBB
MOLD_LDFLAGS += -ltbb
else
TBB_LIB = out/tbb/libs/libtbb.a
MOLD_LDFLAGS += $(TBB_LIB)
MOLD_CXXFLAGS += -Ithird-party/tbb/include
endif
ifeq ($(OS), Linux)
ifeq ($(IS_ANDROID), 0)
# glibc before 2.17 need librt for clock_gettime
MOLD_LDFLAGS += -Wl,-push-state -Wl,-as-needed -lrt -Wl,-pop-state
endif
endif
NEEDS_LIBCRYPTO = 1
ifeq ($(OS), Darwin)
NEEDS_LIBCRYPTO = 0
endif
ifeq ($(NEEDS_LIBCRYPTO), 1)
MOLD_LDFLAGS += -lcrypto
endif
# '-latomic' flag is needed building on armv6/riscv64 systems.
# Seems like '-atomic' would be better but not working.
ifneq (,$(filter armv6% riscv64, $(ARCH)))
MOLD_LDFLAGS += -latomic
endif
# -Wc++11-narrowing is a fatal error on Android, so disable it.
ifeq ($(IS_ANDROID), 1)
MOLD_CXXFLAGS += -Wno-c++11-narrowing
endif
ifeq ($(OS), Linux)
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
endif
DEPFLAGS = -MT $@ -MMD -MP -MF out/$*.d
all: mold mold-wrapper.so
-include $(SRCS:%.cc=out/%.d)
out/git-hash.cc: FORCE
cmake -DSOURCE_DIR=. -DOUTPUT_FILE=out/git-hash.cc -P update-git-hash.cmake
FORCE:
out/git-hash.o: out/git-hash.cc
$(CXX) $(MOLD_CXXFLAGS) $(CXXFLAGS) -c -o $@ $<
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
ln -sf mold ld
ln -sf mold ld64
mold-wrapper.so: elf/mold-wrapper.c
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
out/rust-demangle.o: third-party/rust-demangle/rust-demangle.c
$(CC) $(CFLAGS) -c -o $@ $<
out/%.o: %.cc out/elf/.keep out/macho/.keep
$(CXX) $(MOLD_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS) -c -o $@ $<
out/elf/.keep out/macho/.keep:
mkdir -p $(@D)
touch $@
$(MIMALLOC_LIB):
mkdir -p out/mimalloc
(cd out/mimalloc; CFLAGS=-DMI_USE_ENVIRON=0 cmake -G'Unix Makefiles' ../../third-party/mimalloc)
$(MAKE) -C out/mimalloc mimalloc-static
$(TBB_LIB):
mkdir -p out/tbb
(cd out/tbb; cmake -G'Unix Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS="$(CXXFLAGS) -D__TBB_DYNAMIC_LOAD_ENABLED=0" -DTBB_STRICT=OFF ../../third-party/tbb)
$(MAKE) -C out/tbb tbb
(cd out/tbb; ln -sf *_relwithdebinfo libs)
test tests check: all
ifeq ($(OS), Darwin)
@$(MAKE) $(TESTS) --no-print-directory
else
@$(MAKE) $(TESTS) --no-print-directory --output-sync
endif
@if test -t 1; then \
printf '\e[32mPassed all tests\e[0m\n'; \
else \
echo 'Passed all tests'; \
fi
test-arch:
TEST_CC=${TRIPLE}-gcc \
TEST_CXX=${TRIPLE}-g++ \
TEST_GCC=${TRIPLE}-gcc \
TEST_GXX=${TRIPLE}-g++ \
OBJDUMP=${TRIPLE}-objdump \
MACHINE=${MACHINE} \
QEMU="qemu-${MACHINE} -L /usr/${TRIPLE}" \
$(MAKE) test
test-all: all
$(MAKE) test-arch TRIPLE=x86_64-linux-gnu MACHINE=x86_64
$(MAKE) test-arch TRIPLE=i686-linux-gnu MACHINE=i386
$(MAKE) test-arch TRIPLE=aarch64-linux-gnu MACHINE=aarch64
$(MAKE) test-arch TRIPLE=arm-linux-gnueabihf MACHINE=arm
$(MAKE) test-arch TRIPLE=riscv64-linux-gnu MACHINE=riscv64
# macOS's GNU make hasn't been updated since 3.8.1 perhaps due a concern
# of GPLv3. The --output-sync flag was introduced in GNU Make 4.0, so we
# can't use that flag on macOS.
#
# `tail -r | tail -r` is a poor-man's way to enable full buffering on a
# command output. `tail -r` outputs an input from the last line to the
# first.
$(TESTS):
ifeq ($(OS), Darwin)
@set -o pipefail; ./$@ 2>&1 | tail -r | tail -r
else
@./$@
endif
install: all
$(INSTALL) -d $D$(BINDIR)
$(INSTALL_PROGRAM) mold $D$(BINDIR)
$(STRIP) $D$(BINDIR)/mold
$(INSTALL) -d $D$(LIBDIR)/mold
ifneq ($(OS), Darwin)
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
endif
$(INSTALL) -d $D$(LIBEXECDIR)/mold
cmake -DSOURCE=$D$(BINDIR)/mold -DDEST=$D$(LIBEXECDIR)/mold/ld -P create-symlink.cmake
$(INSTALL) -d $D$(MANDIR)/man1
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
ln -sf mold.1 $D$(MANDIR)/man1/ld.mold.1
$(INSTALL) -d $D$(DOCDIR)/mold
$(INSTALL_DATA) LICENSE $D$(DOCDIR)/mold/
ln -sf mold $D$(BINDIR)/ld.mold
ln -sf mold $D$(BINDIR)/ld64.mold
uninstall:
rm -f $D$(BINDIR)/mold $D$(BINDIR)/ld.mold $D$(BINDIR)/ld64.mold
rm -f $D$(MANDIR)/man1/mold.1
rm -rf $D$(LIBDIR)/mold
test-asan test-ubsan:
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=address -fsanitize=undefined -O0 -g' LDFLAGS='-fsanitize=address -fsanitize=undefined' test
test-tsan:
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=thread -O0 -g' LDFLAGS=-fsanitize=thread test
clean:
rm -rf *~ mold mold-wrapper.so out ld ld64 mold-*-linux.tar.gz
.PHONY: all test tests check clean test-arch test-all test-asan test-ubsan test-tsan $(TESTS)
+78 -20
View File
@@ -1,5 +1,13 @@
# mold: A Modern Linker
[![CI](https://github.com/rui314/mold/actions/workflows/ci.yml/badge.svg)](https://github.com/rui314/mold/actions/workflows/ci.yml)
[![build result](https://build.opensuse.org/projects/home:marxin:mold/packages/mold/badge.svg?type=default)](https://build.opensuse.org/package/show/home:marxin:mold/mold)
<i>This is a repo of a free, AGPL-licensed version of the linker.
If you are looking for a commercial, non-AGPL version of the same linker,
please visit the
[repo of the sold linker](https://github.com/bluewhalesystems/sold).</i>
mold is a faster drop-in replacement for existing Unix linkers.
It is several times faster than the LLVM lld linker, the second-fastest
open-source linker which I originally created a few years ago.
@@ -22,7 +30,12 @@ mold is so fast that it is only 2x _slower_ than `cp` on the same
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
if you find mold is not faster than other linkers.
mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
s390x, SPARC64 and m68k.
mold/macOS is commercial software. For mold/macOS, please visit
https://github.com/bluewhalesystems/sold.
## Why does the speed of linking matter?
@@ -60,25 +73,30 @@ necessary packages. You may want to run it as root.
```shell
git clone https://github.com/rui314/mold.git
cd mold
git checkout v1.4.2
make -j$(nproc) CXX=clang++
sudo make install
mkdir mold/build
cd mold/build
git checkout v1.8.0
../install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
cmake --build . -j $(nproc)
sudo cmake --install .
```
You may need to pass a C++20 compiler command name to `make`.
In the above case, `clang++` is passed. If it doesn't work for you,
You may need to pass a C++20 compiler command name to `cmake`.
In the above case, `c++` is passed. If it doesn't work for you,
try a specific version of a compiler such as `g++-10` or `clang++-12`.
By default, `mold` is installed to `/usr/local/bin`.
By default, `mold` is installed to `/usr/local/bin`. You can change
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
options, see the comments in `CMakeLists.txt`.
If you don't use a recent enough Linux distribution, or if for any reason
`make` in the above commands doesn't work for you, you can use Docker to
`cmake` in the above commands doesn't work for you, you can use Docker to
build it in a Docker environment. To do so, just run `./dist.sh` in this
directory instead of running `make -j$(nproc)`. The shell script pulls a
Docker image, builds mold and auxiliary files inside it, and packs
them into a single tar file `mold-$version-$arch-linux.tar.gz`.
You can extract the tar file anywhere and use `mold` executable in it.
directory instead of `cmake`. The shell script pulls a Docker image,
builds mold and auxiliary files inside it, and packs them into a
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
the tar file anywhere and use `mold` executable in it.
## How to use
@@ -107,7 +125,7 @@ following flags to use `mold` instead of `/usr/bin/ld`:
`ld` is actually a symlink to `mold`. So, all you need is to pass
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
If you haven't installed `mold` to any `$PATH`, you can still pass
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
take an absolute path as an argument for `-fuse-ld` though.
@@ -123,8 +141,15 @@ linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
where `/path/to/mold` is an absolute path to `mold` exectuable.
Please make sure you have installed `clang`.
where `/path/to/mold` is an absolute path to `mold` exectuable. In the
above example, we use `clang` as a linker driver as it can always take
the `-fuse-ld` option. If your GCC is recent enough to recognize the
option, you 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"]
```
If you want to use mold for all projects, put the above snippet to
`~/.cargo/config.toml`.
@@ -144,6 +169,30 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
</details>
<details><summary>If you are using Nim</summary>
Create `config.nims` in your project directory with the following:
```nim
when findExe("mold").len > 0 and defined(linux):
switch("passL", "-fuse-ld=mold")
```
where `mold` must be included in the PATH environment variable. In this example
The above example uses `gcc` as the linker driver.
Use the `fuse-ld` option. If your GCC is recent enough to recognize this option.
If you want to use mold for all projects, put the above snippet to `~/.config/config.nims`.
If you are using macOS, you can modify config.nims in a similar manner. Here is an example with mold installed via Homebrew.
```nim
when findExe("ld64.mold").len > 0 and defined(macosx):
switch("passL", "-fuse-ld=ld64.mold")
```
</details>
<details><summary>mold -run</summary>
It is sometimes very hard to pass an appropriate command line option
@@ -251,7 +300,7 @@ concern. By purchasing a license, you are guaranteed that mold will be
maintained for you. Please [contact us](mailto:[email protected])
for a commercial license inquiry.
## Acknowledgement
## Sponsors
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
and [OpenCollective](https://opencollective.com/mold-linker).
@@ -259,7 +308,16 @@ We thank you to everybody who sponsors our project. In particular,
we'd like to acknowledge the following people and organizations who
have sponsored $128/month or more:
- [300baud](https://github.com/300baud)
- [Mercury](https://github.com/MercuryTechnologies)
- [Wei Wu](https://github.com/lazyparser)
### Corporate sponsors
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
- [Uber](https://uber.com)
- [Signal Slot Inc.](https://github.com/signal-slot)
### Individual sponsors
- [300baud](https://github.com/300baud)
- [Johan Andersson](https://github.com/repi)
- [Wei Wu](https://github.com/lazyparser)
- [kyle-elliott](https://github.com/kyle-elliott)
+2 -2
View File
@@ -20,7 +20,7 @@
// that provides functions and variables used in your program get
// linked. To make this efficient, static library functions are
// usually separated to each object file in an archive file. You can
// see the contents of libc.a by runnning `ar t
// see the contents of libc.a by running `ar t
// /usr/lib/x86_64-linux-gnu/libc.a`.
#pragma once
@@ -166,7 +166,7 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
template <typename C>
std::vector<MappedFile<C> *>
read_archive_members(C &ctx, MappedFile<C> *mf) {
switch (get_file_type(mf)) {
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
case FileType::AR:
return read_fat_archive_members(ctx, mf);
case FileType::THIN_AR:
+16 -2
View File
@@ -29,8 +29,22 @@ read_response_file(C &ctx, std::string_view path) {
auto read_unquoted = [&](i64 i) {
std::string buf;
while (i < mf->size && !isspace(data[i]))
buf.append(1, data[i++]);
while (i < mf->size) {
if (data[i] == '\\' && i + 1 < mf->size) {
buf.append(1, data[i + 1]);
i += 2;
continue;
}
if (!isspace(data[i])) {
buf.append(1, data[i++]);
continue;
}
break;
}
vec.push_back(save_string(ctx, buf));
return i;
};
+42 -50
View File
@@ -1,11 +1,13 @@
// This file implements a multi-threaded zlib compression routine.
// This file implements a multi-threaded zlib and zstd compression
// routine.
//
// Multiple pieces of raw compressed data in zlib-format can be merged
// just by concatenation as long as each zlib stream is flushed with
// Z_SYNC_FLUSH. In this file, we split input data into multiple
// shards, compress them individually and concatenate them. We then
// append a header, a trailer and a checksum so that the concatenated
// data is valid zlib-format data.
// zlib-compressed data can be merged just by concatenation as long as
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
// split input data into multiple shards, compress them individually
// and concatenate them. We then append a header, a trailer and a
// checksum so that the concatenated data is valid zlib-format data.
//
// zstd-compressed data can be merged in the same way.
//
// Using threads to compress data has a downside. Since the dictionary
// is reset on boundaries of shards, compression ratio is sacrificed
@@ -16,8 +18,13 @@
#include <tbb/parallel_for_each.h>
#include <zlib.h>
#include <zstd.h>
#define CHECK(fn) do { int r = (fn); assert(r == Z_OK); } while (0)
#define CHECK(fn) \
do { \
[[maybe_unused]] int r = (fn); \
assert(r == Z_OK); \
} while (0)
namespace mold {
@@ -35,7 +42,7 @@ static std::vector<std::string_view> split(std::string_view input) {
return shards;
}
static std::vector<u8> do_compress(std::string_view input) {
static std::vector<u8> zlib_compress(std::string_view input) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, we chose compression
// level 1.
@@ -85,6 +92,7 @@ static std::vector<u8> do_compress(std::string_view input) {
assert(strm.avail_out > 0);
buf.resize(buf.size() - strm.avail_out);
buf.shrink_to_fit();
deflateEnd(&strm);
return buf;
}
@@ -98,20 +106,18 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = do_compress(inputs[i]);
shards[i] = zlib_compress(inputs[i]);
});
// Combine checksums
checksum = adlers[0];
for (i64 i = 1; i < inputs.size(); i++)
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
}
i64 ZlibCompressor::size() const {
i64 size = 2; // +2 for header
for (const std::vector<u8> &shard : shards)
size += shard.size();
return size + 6; // +6 for trailer and checksum
// Comput the total size
compressed_size = 8; // the header and the trailer
for (std::vector<u8> &shard : shards)
compressed_size += shard.size();
}
void ZlibCompressor::write_to(u8 *buf) {
@@ -130,7 +136,7 @@ void ZlibCompressor::write_to(u8 *buf) {
});
// Write a trailer
u8 *end = buf + size();
u8 *end = buf + compressed_size;
end[-6] = 3;
end[-5] = 0;
@@ -138,56 +144,42 @@ void ZlibCompressor::write_to(u8 *buf) {
*(ub32 *)(end - 4) = checksum;
}
GzipCompressor::GzipCompressor(std::string_view input) {
static std::vector<u8> zstd_compress(std::string_view input) {
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
constexpr int level = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
level);
assert(!ZSTD_isError(sz));
buf.resize(sz);
buf.shrink_to_fit();
return buf;
}
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
std::vector<u32> crc(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
crc[i] = crc32(0, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = do_compress(inputs[i]);
shards[i] = zstd_compress(inputs[i]);
});
// Combine checksums
checksum = crc[0];
for (i64 i = 1; i < inputs.size(); i++)
checksum = crc32_combine(checksum, crc[i], inputs[i].size());
uncompressed_size = input.size();
compressed_size = 0;
for (std::vector<u8> &shard : shards)
compressed_size += shard.size();
}
i64 GzipCompressor::size() const {
i64 size = 10; // +10 for header
for (const std::vector<u8> &shard : shards)
size += shard.size();
return size + 10; // +10 for trailer and checksum
}
void GzipCompressor::write_to(u8 *buf) {
// Write a zlib-format header
memset(buf, 0, 10);
buf[0] = 0x1f; // magic
buf[1] = 0x8b; // magic
buf[2] = 0x08; // compression method is zlib
buf[9] = 0xff; // made on unknown OS
void ZstdCompressor::write_to(u8 *buf) {
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 10; // +10 for header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
});
// Write a trailer
u8 *end = buf + size();
end[-10] = 3; // two-byte zlib stream terminator
end[-9] = 0;
*(ul32 *)(end - 8) = checksum;
*(ul32 *)(end - 4) = uncompressed_size;
}
} // namespace mold
+3
View File
@@ -0,0 +1,3 @@
#define MOLD_VERSION "@mold_VERSION@"
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
#cmakedefine01 MOLD_IS_SOLD
-7
View File
@@ -1,7 +0,0 @@
# Create a symbolic link with a relative path. GNU ln takes the --relative
# option to do that, but that's not portable, so we do it in CMake.
cmake_minimum_required(VERSION 3.14)
get_filename_component(DIR "${DEST}" DIRECTORY)
file(RELATIVE_PATH PATH "/${DIR}" "/${SOURCE}")
file(CREATE_LINK "${PATH}" "${DEST}" SYMBOLIC)
+14 -16
View File
@@ -1,4 +1,4 @@
#!/bin/bash -x
#!/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. `mold-1.0.3-x86_64-linux.tar.gz`).
@@ -7,29 +7,27 @@
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
# and librt, as they almost always exist on any Linux systems.
set -e
# Unlike Linux, macOS's uname returns arm64 for aarch64.
arch=$(uname -m)
[ $arch = arm64 ] && arch=aarch64
if [ $arch != x86_64 -a $arch != aarch64 ]; then
echo "Error: no docker image for $arch"
if [ $# -ne 1 ]; then
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
exit 1
fi
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
arch=$1
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
{ echo "Error: no docker image for $arch"; exit 1; }
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
dest=mold-$version-$arch-linux
image=rui314/mold-builder:v1-$arch
set -e -x
docker images -q $image 2> /dev/null || docker pull $image
docker run -it --rm -v "$(pwd):/mold" -e "OWNER=$(id -u):$(id -g)" $image \
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
bash -c "mkdir /tmp/build &&
cd /tmp/build &&
cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On -DCMAKE_BUILD_TYPE=Release /mold &&
cmake --build . -j\$(nproc)
cmake --install . --prefix $dest --strip
cmake --build . -j\$(nproc) &&
[ $arch = arm ] || ctest -j\$(nproc) &&
cmake --install . --prefix $dest --strip &&
tar czf /mold/$dest.tar.gz $dest &&
cp mold mold-wrapper.so /mold &&
chown \$OWNER /mold/mold /mold/mold-wrapper.so /mold/$dest.tar.gz"
+4 -4
View File
@@ -23,10 +23,10 @@ process.
To prevent this type of attack, the stack area is no longer executable
since the early 2000s. On Linux, the stack's executable-ness is
controlled by a bit in an executable, and the loader respect that
controlled by a bit in an executable, and the loader respects that
bit. The bit is set by the linker.
GCC had (and still have) a feature that depends on the executable
GCC had (and still has) a feature that depends on the executable
stack, so they invented a way to tell the linker to mark the stack
executable. Specifically, if an object file contains a
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
@@ -48,8 +48,8 @@ feature which still depends on the executable stack.
# How to fix it?
If you know what you are doing, pass `-z execstack` to mold. Beaware
If you know what you are doing, pass `-z execstack` to mold. Beware
that this will significantly weaken your program's security.
If you don't want to pass `-z execstack`, rewrite your code so that
your code does not depends on the executable stack.
your code does not depend on the executable stack.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+174 -101
View File
@@ -229,7 +229,119 @@ such as system-dependent library search paths, are hard-coded.
depends only on its command-line arguments.
.
.\"=============================================================================
.Sh OPTIONS
.Sh MOLD-SPECIFIC OPTIONS
.Bl -tag -width 6n -compact
.Pp
.It Fl -chroot Ns = Ns Ar dir
Set
.Ar dir
to root directory.
.Pp
.It Fl -color-diagnostics Ns = Ns Op Sy auto | always | never
.It Fl -color-diagnostics
.It Fl -no-color-diagnostics
.Pp
Show diagnostics messages in color using ANSI escape sequences.
.Ar auto
means that
.Nm
prints out messages in color only if the standard output is connected to a TTY. \
Default is
.Ar auto .
.Pp
.It Fl -fork
.It Fl -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
.Nm
process.
.Fl -fork
hides that latency. By default, it does fork.
.Pp
.It Fl -perf
Print performance statistics.
.Pp
.It Fl -repro
Archive input files as a tar file.
.Pp
.It Fl -reverse-sections
Reverses the order of input sections before assigning them the offsets \
in the output file.
.Pp
This option is useful for finding a bug that depends on an initialization \
order of global objects. In C++, constructors of global objects in a single \
source file are guaranteed to be executed in the source order, but \
there's no such guarantee across compilation units. Usually, constructors \
are executed in the order given to the linker, but depending on it is a mistake.
.Pp
By reversing the order of input sections using
.Fl -reverse-sections ,
you can easily test that your program works in the reversed initialization order.
.Pp
.It Fl -run Cm command Ar arg Ar
Run
.Cm command
with
.Nm
as
.Pa /usr/bin/ld .
.Pp
.It Fl -shuffle-sections
.It Fl -shuffle-sections Ns = Ns Ar number
Randomizes the output by shuffling the order of input sections before \
assigning them the offsets in the output file. If
.Ar number
is given, it's used as a seed for the random number generator, so that \
the linker produces the same output as for the same seed. If no seed \
is given, a random number is used as a seed.
.Pp
This option is useful for benchmarking. Modern CPUs are sensitive to program's \
memory layout. A seeming benign change in program layout (such as a small \
size increase of a function in the middle of a program) can affect program's \
performance. Therefore, \
even if you write new code and get a good benchmark result, it is hard \
to say whether or not the new code improves the programs performance. \
It is possible that the new memory layout happens to perform better.
.Pp
By running a benchmark multiple time with shuffling memory layout using
.Fl -shuffle-sections ,
you can isolate your program's real performance number from the randomness \
caused by memory layout changes.
.Pp
.It Fl -stats
Print input statistics.
.Pp
.It Fl -thread-count Ns = Ns Ar count
Use
.Ar count
number of threads.
.Pp
.It Fl -threads
.It Fl -no-threads
Use multiple threads.
By default,
.Nm
uses as many threads as the number of cores or 32, whichever is the smallest.
The reason why it is capped to 32 is because
.Nm
doesn't scale well beyond that point.
To use only one thread, pass
.Fl -no-threads
or
.Fl -thread-count Ns = Ns Sy 1 .
.Pp
.Pp
.It Fl -quick-exit
.It Fl -no-quick-exit
Use or do not use
.Dv quick_exit
to exit.
.
.El \" End of options list
.
.\"-----------------------------------------------------------------------------
.Sh GNU-COMPATIBLE OPTIONS
.Bl -tag -width 6n -compact
.It Fl -help
Report usage information to stdout and exit.
@@ -366,6 +478,26 @@ Instead of generating an executable or a shared object file, combine
input object files to generate another object file that can be used as
an input to a linker.
.Pp
.It Fl -relocatable-merge-sections
By default,
.Nm
doesn't merge input sections by name when merging input object files into a \
single output object file for
.Fl r .
For example,
.Ar .text.foo
and
.Ar .text.bar
aren't merged for
.Fl r
even though they are merged into
.Ar .text
according to the default section merging rules.
.Pp
This option changes the behavior so that
.Nm
merges input sections by name by the default section merging rules.
.Pp
.It Fl s , Fl -strip-all
Omit
.Li \.symtab
@@ -468,29 +600,15 @@ sets a random 128-bit UUID.
sets
.Ar hexstring .
.Pp
.It Fl -chroot Ns = Ns Ar dir
Set
.Ar dir
to root directory.
.Pp
.It Fl -color-diagnostics Ns = Ns Op Sy auto | always | never
.It Fl -color-diagnostics
.It Fl -no-color-diagnostics
.Pp
Show diagnostics messages in color using ANSI escape sequences.
.Ar auto
means that
.Nm
prints out messages in color only if the standard output is connected to a TTY. \
Default is
.Ar auto .
.Pp
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
.Pp
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zlib-gnu
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zstd
Compress DWARF debug info
.Pf ( Sy .debug_*
sections) using the zlib compression algorithm.
sections) using the zlib or zstd compression algorithm.
.Fl zlib-gabi
is an alias for
.Fl zlib .
.Pp
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
Define
@@ -520,7 +638,7 @@ Write a dependency file to
The contents of the written file is readable by
.Cm make ,
which defines only one rule with the linker's output file as a target \
and all input fiels as its prerequisite. Users are expected to include \
and all input files as its prerequisite. Users are expected to include \
the generated dependency file into a Makefile to automate the \
dependency management. This option is analogous to the compiler's
.Fl MM Fl MF
@@ -534,6 +652,11 @@ Same as
except that it implies
.Fl -Bsymbolic .
.Pp
If
.Ar file
does not exist in the current directory, it is searched from library \
search paths for the sake of compatibility with GNU ld.
.Pp
.It Fl -eh-frame-hdr
.It Fl -no-eh-frame-hdr
Create
@@ -551,10 +674,6 @@ instructs the linker to leave relocation sections in the output file. \
Some post-link binary analysis or optimization tools such as LLVM Bolt \
need them.
.Pp
.Nm
always creates RELA-type relocation sections even if the native \
ELF format is REL-type so that it is easy to read addends.
.Pp
.It Fl -enable-new-dtags
.It Fl -disable-new-dtags
By default,
@@ -567,6 +686,19 @@ mold emits DT_RPATH for
.Fl -rpath
instead.
.Pp
.It Fl -execute-only
Traditionally, most processors require both executable and readable bits to 1 \
to make the page executable, which allows machine code to read itself 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.
.Pp
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.
.Pp
This option marks text segments execute-only. This option currently works only \
on some ARM64 processors.
.Pp
.It Fl -exclude-libs Ns = Ns Ar libraries Ns ...
Mark all symbols in the given
@@ -596,16 +728,6 @@ Call
.Ar symbol
at unload-time.
.Pp
.It Fl -fork
.It Fl -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
.Nm
process.
.Fl -fork
hides that latency.
.Pp
.It Fl -gc-sections
.It Fl -no-gc-sections
Remove unreferenced sections.
@@ -641,7 +763,7 @@ is given,
tries to merge all identical functions. This reduces the size of the output \
most, but it is not
.Dq safe
optimization. It is guaranteed in C and C++ that two pointeres pointing two \
optimization. It is guaranteed in C and C++ that two pointers pointing two \
different functions will never be equal, but
.Fl -icf=all
breaks that assumption as two functions have the same address after merging. \
@@ -719,16 +841,10 @@ package management command such as
.Cm rpm
to embed metadata regarding a package to each executable file.
.Pp
.It Fl -perf
Print performance statistics.
.Pp
.It Fl -pie , -pic-executable
.It Fl -no-pie , -no-pic-executable
Create a position-independent executable.
.Pp
.It Fl -preload
Preload object files.
.Pp
.It Fl -print-gc-sections
.It Fl -no-print-gc-sections
Print removed unreferenced sections.
@@ -762,12 +878,6 @@ by as-needed basis but don't want to change the global state of
you can append "--push-state --as-needed -lfoo --pop-state" to the \
linker command line options.
.Pp
.It Fl -quick-exit
.It Fl -no-quick-exit
Use
.Dv quick_exit
to exit.
.Pp
.It Fl -relax
.It Fl -no-relax
Rewrite machine instructions with more efficient ones for some relocations.
@@ -778,11 +888,6 @@ Like
.Fl -undefined ,
except the new symbol must be defined by the end of the link.
.Pp
.It Fl -repro
Embed input files into
.Dv .repro
section.
.Pp
.It Fl -retain-symbols-file Ns = Ns Ar file
Keep only symbols listed in
.Ar file .
@@ -792,7 +897,7 @@ is a text file
containing a symbol name on each line.
.Nm
discards all local
symbols as well as global sybmol that are not in
symbols as well as global symbol that are not in
.Ar file .
Note that this option removes symbols only from
.Dv .symtab
@@ -800,23 +905,11 @@ section and does not affect
.Dv .dynsym
section, which is used for dynamic linking.
.Pp
.It Fl -reverse-sections
Reverses the order of input sections before assigning them the offsets \
in the output file.
.Pp
.It Fl -rpath Ns = Ns Ar dir
Add
.Ar dir
to runtime search path.
.Pp
.It Fl -run Cm command Ar arg Ar
Run
.Cm command
with
.Nm
as
.Pa /usr/bin/ld .
.Pp
.It Fl -section-start Ns = Ns Ar section Ns = Ns Ar address
Set
.Ar address
@@ -829,15 +922,6 @@ is a hexadecimal number that may start with an optional
.It Fl -shared , -Bshareable
Create a share library.
.Pp
.It Fl -shuffle-sections
.It Fl -shuffle-sections Ns = Ns Ar number
Randomizes the output by shuffleing the order of input sections before \
assigning them the offsets in the output file. If
.Ar number
is given, it's used as a seed for the random number generator, so that \
the linker produces the same output as for the same seed. If no seed \
is given, it uses a random number as a seed.
.Pp
.It Fl -spare-dynamic-tags Ns = Ns Ar number
Reserve given
.Ar number
@@ -860,35 +944,21 @@ needed but want to avoid the overhead of running
.It Fl -static
Do not link against shared libraries.
.Pp
.It Fl -stats
Print input statistics.
.Pp
.It Fl -sysroot Ns = Ns Ar dir
Set target system root directory to
.Ar dir .
.Pp
.It Fl -thread-count Ns = Ns Ar count
Use
.Ar count
number of threads.
.Pp
.It Fl -threads
.It Fl -no-threads
Use multiple threads.
By default,
.Nm
uses as many threads as the number of cores or 32, whichever is the smallest.
The reason why it is capped to 32 is because
.Nm
doesn't scale well beyond that point.
To use only one thread, pass
.Fl -no-threads
or
.Fl -thread-count Ns = Ns Sy 1 .
.Pp
.It Fl -trace
Print name of each input file.
.Pp
.It Fl -undefined-version
.It Fl -no-undefined-version
By default,
.Nm
warns on a symbol specified by a version script or by
.Fl -export-dynamic-symbol
if it is not defined. You can silence the warning by
.Fl -undefined-version .
.Pp
.It Fl -unique Ns = Ns Ar pattern
Don't merge input sections that match
.Ar pattern .
@@ -900,6 +970,10 @@ How to handle undefined symbols.
.It Fl -version-script Ns = Ns Ar file
Read version script from
.Ar file .
If
.Ar file
does not exist in the current directory, it is searched from library search paths \
for the sake of compatibility with GNU ld.
.Pp
.It Fl -warn-common
.It Fl -no-warn-common
@@ -1046,7 +1120,7 @@ disables the feature.
.It Fl z Cm noseparate-code
If one memory page contains multiple segments,
the page protection bits are set in such a way that needed attributes \
(writable or executable) are satisifed for all segments.
(writable or executable) are satisfied for all segments.
This usually happens at a boundary of two segments with two different \
attributes.
.Pp
@@ -1132,7 +1206,6 @@ Mark object to interpose all DSOs but executable.
.It Fl -no-allow-shlib-undefined
.It Fl -no-copy-dt-needed-entries
.It Fl -no-fatal-warnings
.It Fl -no-undefined-version
.It Fl -nostdlib
.It Fl -rpath-link Ns = Ns Ar dir
.It Fl -sort-common
@@ -1154,7 +1227,7 @@ Ignored
.Sh SEE ALSO
.Xr gold 1 ,
.Xr ld 1 ,
.Xr elf 5
.Xr elf 5 ,
.Xr ld.so 8
.
.\"=============================================================================
+342
View File
@@ -0,0 +1,342 @@
// 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_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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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) {
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
ctx.extra.got->add_symbol(sym, rel.r_addend);
} else {
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
}
break;
case R_ALPHA_SREL32:
scan_pcrel(ctx, sym, rel);
break;
case R_ALPHA_BRSGP:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_ALPHA_TLSGD:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_ALPHA_TLSLDM:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_ALPHA_GOTTPREL:
case R_ALPHA_TPRELHI:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
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:
case R_ALPHA_TPRELLO:
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 we cannot find in any other targets.
//
// Referring an external symbol with a non-zero addend is a bad practice.
// To see why, assume that we have two R_ALPHA_LITERAL relocs to symbol X
// with addend 8 and 16. To satisfy that request, we need to create not
// only one but two GOT entries for symbol X with different addends.
//
// 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 addends 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) {
i64 n = 0;
for (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 = nullptr;
if (ctx.reldyn)
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
+339 -236
View File
@@ -10,10 +10,9 @@
//
// ARM processors runs in either ARM mode or Thumb mode. The mode can
// be switched using BX (branch and mode exchange)-family instructions.
// We need to use such instructions to, for example, call a function
// We need to use that instructions to, for example, call a function
// encoded in Thumb from a function encoded in ARM. Sometimes, the
// linker even has to emit an interworking thunk code to switch between
// them.
// linker even has to emit an interworking thunk code to switch mode.
//
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
// byte boundaries.
@@ -27,6 +26,8 @@
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
// aligned). Likewise, if a function pointer has value 0x2001, it
// refers a Thumb function at 0x2000.
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
#include "mold.h"
@@ -38,6 +39,71 @@ namespace mold::elf {
using E = ARM32;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
return 0;
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:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET2:
return *(il32 *)loc;
case R_ARM_THM_JUMP11:
return sign_extend(*(ul16 *)loc, 10) << 1;
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);
u32 I1 = !(J1 ^ S);
u32 I2 = !(J2 ^ S);
u32 imm10 = bits(*(ul16 *)loc, 9, 0);
u32 imm11 = bits(*(ul16 *)(loc + 2), 10, 0);
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
return sign_extend(val, 24);
}
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_TLS_CALL:
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 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);
}
case R_ARM_PREL31:
return sign_extend(*(ul32 *)loc, 30);
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);
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
return sign_extend(val, 15);
}
default:
unreachable();
}
}
static void write_mov_imm(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
@@ -72,53 +138,93 @@ static void write_thm_mov_imm(u8 *loc, u32 val) {
}
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
static const u32 plt0[] = {
0xe52de004, // push {lr}
0xe59fe004, // ldr lr, 2f
0xe08fe00e, // 1: add lr, pc, lr
0xe5bef008, // ldr pc, [lr, #8]!
0x00000000, // 2: .word .got.plt - 1b - 8
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
};
memcpy(buf, plt0, sizeof(plt0));
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
for (Symbol<E> *sym : symbols) {
static const u32 plt[] = {
0xe59fc004, // 1: ldr ip, 2f
0xe08cc00f, // add ip, ip, pc
0xe59cf000, // ldr pc, [ip]
0x00000000, // 2: .word sym@PLTGOT - 1b
};
u8 *ent = buf + sizeof(plt0) + sym->get_plt_idx(ctx) * sizeof(plt);
memcpy(ent, plt, sizeof(plt));
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
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:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET2:
*(ul32 *)loc = val;
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);
break;
case R_ARM_CALL:
case R_ARM_PLT32:
case R_ARM_JUMP24:
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS:
write_mov_imm(loc, val);
break;
case R_ARM_PREL31:
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
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:
write_thm_mov_imm(loc, val);
break;
default:
unreachable();
}
}
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
0xe5be'f008, // ldr pc, [lr, #8]!
0x0000'0000, // 2: .word .got.plt - 1b - 8
0xe320'f000, // nop
0xe320'f000, // nop
0xe320'f000, // nop
};
for (Symbol<E> *sym : symbols) {
static const u32 plt[] = {
0xe59fc004, // 1: ldr ip, 2f
0xe08cc00f, // add ip, ip, pc
0xe59cf000, // ldr pc, [ip]
0x00000000, // 2: .word sym@GOT - 1b
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
memcpy(ent, plt, sizeof(plt));
*(ul32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
}
static const ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0x0000'0000, // 2: .word sym@GOT - 1b
};
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
// ARM does not use .eh_frame for exception handling. Instead, it uses
@@ -134,79 +240,71 @@ static bool is_jump_reachable(i64 val) {
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
i64 trampoline_idx = 0;
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<std::unique_ptr<RangeExtensionThunk<E>>> thunks =
output_section->thunks;
auto get_trampoline_addr = [&](u64 addr) {
for (;;) {
assert(!thunks.empty());
i64 disp = output_section->shdr.sh_addr + thunks[0]->offset - addr;
if (is_jump_reachable(disp))
return disp;
thunks = thunks.subspan(1);
}
};
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_ARM_NONE || rel.r_type == R_ARM_V4BX)
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define T (sym.get_addr(ctx) & 1)
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
#define GOT ctx.got->shdr.sh_addr
auto get_thumb_thunk_addr = [&] {
RangeExtensionRef ref = extra.range_extn[i];
assert(ref.thunk_idx != -1);
u64 addr = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx);
assert(is_jump_reachable(addr + A - P));
return 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 << ")";
};
auto get_arm_thunk_addr = [&] {
return get_thumb_thunk_addr() + 4;
};
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
u64 T = S & 1;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto get_trampoline_addr = [&](u64 addr) {
for (; trampoline_idx < output_section->thunks.size(); trampoline_idx++) {
RangeExtensionThunk<E> &thunk = *output_section->thunks[trampoline_idx];
i64 disp = output_section->shdr.sh_addr + thunk.offset - addr;
if (is_jump_reachable(disp))
return disp;
}
unreachable();
};
auto get_thumb_thunk_addr = [&] { return get_thunk_addr(i); };
auto get_arm_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
continue;
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_ARM_REL32:
*(ul32 *)loc = S + A - P;
continue;
break;
case R_ARM_THM_CALL: {
// THM_CALL relocation refers 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.
if (sym.esym().is_undef_weak()) {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
write_thm_b_imm(loc, 4);
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
continue;
*(ul32 *)loc = 0x8000'f3af; // NOP.W
break;
}
u64 val = S + A - P;
i64 val = S + A - P;
if (is_jump_reachable(val)) {
if (T) {
write_thm_b_imm(loc, val);
@@ -219,19 +317,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
}
continue;
break;
}
case R_ARM_BASE_PREL:
*(ul32 *)loc = GOT + A - P;
continue;
break;
case R_ARM_GOTOFF32:
*(ul32 *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(ul32 *)loc = GOT + G + A - P;
continue;
break;
case R_ARM_GOT_BREL:
*(ul32 *)loc = G + A;
continue;
case R_ARM_CALL: {
break;
case R_ARM_CALL:
case R_ARM_PLT32: {
// Just like THM_CALL, ARM_CALL relocation refers either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
@@ -239,11 +341,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
if (sym.esym().is_undef_weak()) {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0xeb00'0001;
continue;
*(ul32 *)loc = 0xe320'f000; // NOP
break;
}
u64 val = S + A - P;
@@ -255,11 +357,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
} else {
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
}
continue;
break;
}
case R_ARM_JUMP24:
if (sym.esym().is_undef_weak()) {
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | 1;
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
} else {
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
// takes an immediate; it takes only a register. So if mode switch
@@ -270,14 +372,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
}
continue;
case R_ARM_THM_JUMP11:
break;
case R_ARM_THM_JUMP11: {
assert(T);
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(S + A - P, 11, 1);
continue;
i64 val = S + A - P;
check(val, -(1 << 11), 1 << 11);
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 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;
break;
}
case R_ARM_THM_JUMP24:
if (sym.esym().is_undef_weak()) {
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | 1;
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0x8000'f3af; // NOP.W
} else {
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
@@ -286,88 +409,85 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
val = get_thumb_thunk_addr() + A - P;
write_thm_b_imm(loc, val);
}
continue;
break;
case R_ARM_MOVW_PREL_NC:
write_mov_imm(loc, ((S + A) | T) - P);
continue;
break;
case R_ARM_MOVW_ABS_NC:
write_mov_imm(loc, (S + A) | T);
continue;
break;
case R_ARM_THM_MOVW_PREL_NC:
write_thm_mov_imm(loc, ((S + A) | T) - P);
continue;
case R_ARM_PREL31:
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | ((S + A - P) & 0x7fff'ffff);
continue;
break;
case R_ARM_PREL31: {
i64 val = S + A - P;
check(val, -(1LL << 30), 1LL << 30);
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
}
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov_imm(loc, (S + A) | T);
continue;
break;
case R_ARM_MOVT_PREL:
write_mov_imm(loc, (S + A - P) >> 16);
continue;
break;
case R_ARM_THM_MOVT_PREL:
write_thm_mov_imm(loc, (S + A - P) >> 16);
continue;
break;
case R_ARM_MOVT_ABS:
write_mov_imm(loc, (S + A) >> 16);
continue;
break;
case R_ARM_THM_MOVT_ABS:
write_thm_mov_imm(loc, (S + A) >> 16);
continue;
break;
case R_ARM_TLS_GD32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
continue;
break;
case R_ARM_TLS_LDM32:
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
continue;
break;
case R_ARM_TLS_LDO32:
*(ul32 *)loc = S + A - ctx.tls_begin;
continue;
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
continue;
break;
case R_ARM_TLS_LE32:
*(ul32 *)loc = S + A - ctx.tls_begin + E::tls_tp_offset;
continue;
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
if (sym.get_tlsdesc_idx(ctx) == -1) {
*(ul32 *)loc = S - ctx.tls_begin + E::tls_tp_offset;
} else if (A & 1) {
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
if (A & 1)
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
else
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
} else {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
*(ul32 *)loc = S - ctx.tp_addr;
}
continue;
break;
case R_ARM_TLS_CALL:
if (sym.get_tlsdesc_idx(ctx) == -1) {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
} else {
if (sym.has_tlsdesc(ctx)) {
// BL <tls_trampoline>
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
}
continue;
case R_ARM_THM_TLS_CALL:
if (sym.get_tlsdesc_idx(ctx) == -1) {
// BL -> NOP
*(ul32 *)loc = 0x8000'f3af;
} else {
// BL -> NOP
*(ul32 *)loc = 0xe320'f000;
}
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_trampoline_addr(P + 4), 4);
write_thm_b_imm(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else {
// BL -> NOP.W
*(ul32 *)loc = 0x8000'f3af;
}
continue;
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
#undef S
#undef A
#undef P
#undef T
#undef G
#undef GOT
}
}
@@ -377,7 +497,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_ARM_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -389,36 +509,30 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? addend : this->get_addend(rel))
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : get_addend(*this, rel);
switch (rel.r_type) {
case R_ARM_ABS32:
if (!frag) {
if (std::optional<u64> val = get_tombstone(sym)) {
*(ul32 *)loc = *val;
break;
}
}
*(ul32 *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym))
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.tls_begin;
*(ul32 *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
#undef S
#undef A
}
}
@@ -432,7 +546,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_ARM_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -442,49 +556,52 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
continue;
}
if (sym.get_type() == STT_GNU_IFUNC)
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVT_ABS:
case R_ARM_TARGET1:
scan_abs_dyn_rel(ctx, sym, rel);
scan_dyn_absrel(ctx, sym, rel);
break;
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_PLT32:
case R_ARM_JUMP24:
case R_ARM_THM_JUMP24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TARGET2:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_ARM_MOVT_PREL:
case R_ARM_THM_MOVT_PREL:
case R_ARM_PREL31:
scan_pcrel_rel(ctx, sym, rel);
scan_pcrel(ctx, sym, rel);
break;
case R_ARM_TLS_GD32:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_ARM_TLS_LDM32:
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_ARM_TLS_IE32:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_ARM_TLS_GOTDESC:
if (!ctx.relax_tlsdesc || sym.is_imported)
sym.flags |= NEEDS_TLSDESC;
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_THM_JUMP11:
case R_ARM_THM_JUMP19:
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_THM_MOVW_PREL_NC:
@@ -501,41 +618,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// For range extension thunks
template <>
bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel) {
// We pessimistically assume that PLT entries are unreacahble.
if (sym.has_plt(ctx))
return false;
// 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;
// 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.
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))
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);
i64 A = isec.get_addend(rel);
i64 P = isec.get_addr() + rel.r_offset;
return is_jump_reachable(S + A - P);
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
@@ -543,10 +625,10 @@ void RangeExtensionThunk<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 u32 hdr[] = {
0xe08e0000, // add r0, lr, r0
0xe5901004, // ldr r1, [r0, #4]
0xe12fff11, // bx r1
static ul32 hdr[] = {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
};
// This is a range extension and mode switch thunk.
@@ -577,14 +659,6 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
}
}
template <typename E>
static OutputSection<E> *find_exidx_section(Context<E> &ctx) {
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
return osec.get();
return nullptr;
}
// 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.
@@ -594,13 +668,7 @@ static OutputSection<E> *find_exidx_section(Context<E> &ctx) {
// likely that it's due to some historical reason.
//
// This function sorts .ARM.exidx records.
void sort_arm_exidx(Context<E> &ctx) {
Timer t(ctx, "sort_arm_exidx");
OutputSection<E> *osec = find_exidx_section(ctx);
if (!osec)
return;
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
// .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
@@ -620,11 +688,11 @@ void sort_arm_exidx(Context<E> &ctx) {
ul32 val;
};
if (osec->shdr.sh_size % sizeof(Entry))
if (osec.shdr.sh_size % sizeof(Entry))
Fatal(ctx) << "invalid .ARM.exidx section size";
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
// Entry's addresses are relative to themselves. In order to sort
// records by addresses, we first translate them so that the addresses
@@ -637,7 +705,7 @@ void sort_arm_exidx(Context<E> &ctx) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (sign_extend(ent[i].val, 30) + offset);
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
@@ -653,4 +721,39 @@ void sort_arm_exidx(Context<E> &ctx) {
});
}
void fixup_arm_exidx_section(Context<E> &ctx) {
Timer t(ctx, "fixup_arm_exidx_section");
auto find_exidx = [&]() -> OutputSection<E> * {
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
return osec;
return nullptr;
};
OutputSection<E> *exidx = find_exidx();
if (!exidx)
return;
// Sort .ARM.exidx contents
sort_exidx(ctx, *exidx);
// .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) {
auto find_text = [&]() -> Chunk<E> * {
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->name == ".text")
return chunk;
return nullptr;
};
if (Chunk<E> *text = find_text()) {
exidx->shdr.sh_link = text->shndx;
ctx.shdr->copy_buf(ctx);
}
}
}
} // namespace mold::elf
+237 -225
View File
@@ -1,3 +1,22 @@
// This file contains ARM64-specific code. Being new, the ARM64's ELF
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
// instruction set that supports PC-relative load/store instructions.
//
// Unlike ARM32, instructions length doesn't vary. All ARM64
// instructions are 4 bytes long.
//
// Branch instructions used for function call can jump within ±128 MiB.
// We need to create range extension thunks to support binaries whose
// .text is larger than that.
//
// Unlike most other targets, the TLSDESC access model is used by default
// for -fPIC to access thread-local variables instead of the less
// efficient GD model. You can still enable GD but it needs the
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
// implement GD → LE relaxation.
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
#include "mold.h"
namespace mold::elf {
@@ -5,93 +24,81 @@ namespace mold::elf {
using E = ARM64;
static void write_adrp(u8 *buf, u64 val) {
u32 hi = bits(val, 32, 14);
u32 lo = bits(val, 13, 12);
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
}
static void write_adr(u8 *buf, u64 val) {
u32 hi = bits(val, 20, 2);
u32 lo = bits(val, 1, 0);
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
}
static void write_movn_movz(u8 *buf, i64 val) {
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
if (val >= 0)
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
else
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
}
static u64 page(u64 val) {
return val & ~(u64)0xfff;
return val & 0xffff'ffff'ffff'f000;
}
static void write_plt_header(Context<E> &ctx, u8 *buf) {
// Write PLT header
static const u32 plt0[] = {
0xa9bf7bf0, // stp x16, x30, [sp,#-16]!
0x90000010, // adrp x16, .got.plt[2]
0xf9400211, // ldr x17, [x16, .got.plt[2]]
0x91000210, // add x16, x16, .got.plt[2]
0xd61f0220, // br x17
0xd503201f, // nop
0xd503201f, // nop
0xd503201f, // nop
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
0x9000'0010, // adrp x16, .got.plt[2]
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
0x9100'0210, // add x16, x16, .got.plt[2]
0xd61f'0220, // br x17
0xd503'201f, // nop
0xd503'201f, // nop
0xd503'201f, // nop
};
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
u64 plt = ctx.plt->shdr.sh_addr;
memcpy(buf, plt0, sizeof(plt0));
memcpy(buf, insn, sizeof(insn));
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
}
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u8 *ent = buf + E::plt_hdr_size + sym.get_plt_idx(ctx) * E::plt_size;
static const u32 data[] = {
0x90000010, // adrp x16, .got.plt[n]
0xf9400211, // ldr x17, [x16, .got.plt[n]]
0x91000210, // add x16, x16, .got.plt[n]
0xd61f0220, // br x17
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const 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]
0xd61f'0220, // br x17
};
u64 gotplt = sym.get_gotplt_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(ent, data, sizeof(data));
write_adrp(ent, page(gotplt) - page(plt));
*(ul32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
*(ul32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
memcpy(buf, insn, sizeof(insn));
write_adrp(buf, page(gotplt) - page(plt));
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
}
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
write_plt_header(ctx, buf);
for (Symbol<E> *sym : symbols)
write_plt_entry(ctx, buf, *sym);
}
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const ul32 insn[] = {
0x9000'0010, // adrp x16, GOT[n]
0xf940'0211, // ldr x17, [x16, GOT[n]]
0xd61f'0220, // br x17
0xd503'201f, // nop
};
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
u64 got = sym.get_got_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
for (Symbol<E> *sym : symbols) {
u8 *ent = buf + sym->get_pltgot_idx(ctx) * ARM64::pltgot_size;
static const u32 data[] = {
0x90000010, // adrp x16, GOT[n]
0xf9400211, // ldr x17, [x16, GOT[n]]
0xd61f0220, // br x17
0xd503201f, // nop
};
u64 got = sym->get_got_addr(ctx);
u64 plt = sym->get_plt_addr(ctx);
memcpy(ent, data, sizeof(data));
write_adrp(ent, page(got) - page(plt));
*(ul32 *)(ent + 4) |= bits(got, 11, 3) << 10;
}
memcpy(buf, insn, sizeof(insn));
write_adrp(buf, page(got) - page(plt));
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
}
template <>
@@ -100,236 +107,258 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_AARCH64_ABS64:
*(ul64 *)loc = val;
return;
break;
case R_AARCH64_PREL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
return;
break;
case R_AARCH64_PREL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
return;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
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_AARCH64_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
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 << ")";
};
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
#define GOT ctx.got->shdr.sh_addr
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_AARCH64_ABS64:
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
continue;
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_AARCH64_LDST8_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
continue;
break;
case R_AARCH64_LDST16_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
continue;
break;
case R_AARCH64_LDST32_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
continue;
break;
case R_AARCH64_LDST64_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
continue;
break;
case R_AARCH64_LDST128_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
continue;
break;
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
continue;
case R_AARCH64_MOVW_UABS_G0:
break;
case R_AARCH64_MOVW_UABS_G0: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul32 *)loc |= bits(val, 15, 0) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G0_NC:
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
continue;
case R_AARCH64_MOVW_UABS_G1:
break;
case R_AARCH64_MOVW_UABS_G1: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc |= bits(val, 31, 16) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G1_NC:
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
continue;
case R_AARCH64_MOVW_UABS_G2:
break;
case R_AARCH64_MOVW_UABS_G2: {
i64 val = S + A;
check(val, 0, 1LL << 48);
*(ul32 *)loc |= bits(val, 47, 32) << 5;
break;
}
case R_AARCH64_MOVW_UABS_G2_NC:
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
continue;
break;
case R_AARCH64_MOVW_UABS_G3:
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
continue;
break;
case R_AARCH64_ADR_GOT_PAGE: {
i64 val = page(G + GOT + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
continue;
break;
}
case R_AARCH64_ADR_PREL_PG_HI21: {
i64 val = page(S + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
continue;
break;
}
case R_AARCH64_ADR_PREL_LO21: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
check(val, -(1LL << 20), 1LL << 20);
write_adr(loc, val);
continue;
break;
}
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26: {
if (sym.esym().is_undef_weak()) {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc |= 1;
continue;
*(ul32 *)loc = 0xd503'201f; // nop
break;
}
i64 lo = -(1 << 27);
i64 hi = 1 << 27;
i64 val = S + A - P;
if (val < lo || hi <= val) {
RangeExtensionRef ref = extra.range_extn[i];
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
assert(lo <= val && val < hi);
}
*(ul32 *)loc |= (val >> 2) & 0x3ffffff;
continue;
if (val < -(1 << 27) || (1 << 27) <= val)
val = get_thunk_addr(i) + A - P;
*(ul32 *)loc |= bits(val, 27, 2);
break;
}
case R_AARCH64_CONDBR19:
case R_AARCH64_LD_PREL_LO19: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
check(val, -(1LL << 20), 1LL << 20);
*(ul32 *)loc |= bits(val, 20, 2) << 5;
continue;
break;
}
case R_AARCH64_PREL16: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
check(val, -(1LL << 15), 1LL << 15);
*(ul16 *)loc = val;
continue;
break;
}
case R_AARCH64_PREL32: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
check(val, -(1LL << 31), 1LL << 32);
*(ul32 *)loc = val;
continue;
break;
}
case R_AARCH64_PREL64:
*(ul64 *)loc = S + A - P;
continue;
break;
case R_AARCH64_LD64_GOT_LO12_NC:
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
continue;
break;
case R_AARCH64_LD64_GOTPAGE_LO15: {
i64 val = G + GOT + A - page(GOT);
overflow_check(val, 0, 1 << 15);
check(val, 0, 1 << 15);
*(ul32 *)loc |= bits(val, 14, 3) << 10;
continue;
break;
}
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
continue;
break;
}
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
continue;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
i64 val = S + A - ctx.tp_addr;
check(val, -(1 << 15), 1 << 15);
write_movn_movz(loc, val);
break;
}
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
i64 val = S + A - ctx.tp_addr;
check(val, -(1LL << 31), 1LL << 31);
write_movn_movz(loc, val >> 16);
break;
}
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
i64 val = S + A - ctx.tp_addr;
check(val, -(1LL << 47), 1LL << 47);
write_movn_movz(loc, val >> 32);
break;
}
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
i64 val = S + A - ctx.tls_begin + E::tls_tp_offset;
overflow_check(val, 0, (i64)1 << 24);
i64 val = S + A - ctx.tp_addr;
check(val, 0, 1LL << 24);
*(ul32 *)loc |= bits(val, 23, 12) << 10;
continue;
break;
}
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
*(ul32 *)loc |= bits(S + A - ctx.tls_begin + E::tls_tp_offset, 11, 0) << 10;
continue;
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
case R_AARCH64_TLSGD_ADR_PAGE21: {
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
continue;
break;
}
case R_AARCH64_TLSGD_ADD_LO12_NC:
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
continue;
case R_AARCH64_TLSDESC_ADR_PAGE21: {
if (ctx.relax_tlsdesc && !sym.is_imported) {
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
i64 val = (S + A - ctx.tls_begin + E::tls_tp_offset);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
*(ul32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
} else {
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
if (sym.has_tlsdesc(ctx)) {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
i64 val = (S + A - ctx.tp_addr);
check(val, -(1LL << 32), 1LL << 32);
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
}
continue;
}
break;
case R_AARCH64_TLSDESC_LD64_LO12:
if (ctx.relax_tlsdesc && !sym.is_imported) {
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
u32 offset_lo = (S + A - ctx.tls_begin + E::tls_tp_offset) & 0xffff;
*(ul32 *)loc = 0xf2800000 | (offset_lo << 5);
} else {
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
}
continue;
case R_AARCH64_TLSDESC_ADD_LO12:
if (ctx.relax_tlsdesc && !sym.is_imported) {
// add x0, x0, #0 -> nop
*(ul32 *)loc = 0xd503201f;
} else {
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
}
break;
case R_AARCH64_TLSDESC_ADD_LO12:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
} else {
// add x0, x0, #0 -> nop
*(ul32 *)loc = 0xd503'201f;
}
continue;
break;
case R_AARCH64_TLSDESC_CALL:
if (ctx.relax_tlsdesc && !sym.is_imported) {
if (!sym.has_tlsdesc(ctx)) {
// blr x2 -> nop
*(ul32 *)loc = 0xd503201f;
*(ul32 *)loc = 0xd503'201f;
}
continue;
break;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
@@ -339,7 +368,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_AARCH64_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -350,34 +379,38 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
continue;
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
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 << ")";
};
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? (u64)addend : (u64)rel.r_addend)
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_AARCH64_ABS64:
if (!frag) {
if (std::optional<u64> val = get_tombstone(sym)) {
*(ul64 *)loc = *val;
break;
}
}
*(ul64 *)loc = S + A;
continue;
case R_AARCH64_ABS32:
*(ul32 *)loc = S + A;
continue;
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_AARCH64_ABS32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
break;
}
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
#undef S
#undef A
}
}
@@ -391,7 +424,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_AARCH64_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -401,38 +434,38 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
continue;
}
if (sym.get_type() == STT_GNU_IFUNC)
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_AARCH64_ABS64:
scan_abs_dyn_rel(ctx, sym, rel);
scan_dyn_absrel(ctx, sym, rel);
break;
case R_AARCH64_ADR_GOT_PAGE:
case R_AARCH64_LD64_GOT_LO12_NC:
case R_AARCH64_LD64_GOTPAGE_LO15:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_AARCH64_ADR_PREL_PG_HI21:
scan_pcrel_rel(ctx, sym, rel);
scan_pcrel(ctx, sym, rel);
break;
case R_AARCH64_TLSGD_ADR_PAGE21:
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
if (!ctx.relax_tlsdesc || sym.is_imported)
sym.flags |= NEEDS_TLSDESC;
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
@@ -453,6 +486,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_AARCH64_PREL16:
case R_AARCH64_PREL32:
case R_AARCH64_PREL64:
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
@@ -465,40 +503,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// For range extension thunks
template <>
bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel) {
// We pessimistically assume that PLT entries are unreacahble.
if (sym.has_plt(ctx))
return false;
// 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;
if (isec2->offset == -1)
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);
i64 A = rel.r_addend;
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -(1 << 27) <= val && val < (1 << 27);
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const u32 data[] = {
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0xd61f0200, // br x16
static const ul32 data[] = {
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
};
static_assert(E::thunk_size == sizeof(data));
+506
View File
@@ -0,0 +1,506 @@
// HP/PA (Hewlett-Packard Precision Architecture), also known as PA-RISC,
// is a RISC ISA developed by HP in the '80s. It was replaced by Itanium
// which was a joint project between Intel and HP in the early 2000s. No
// new HP/PA processors have been created after 2005.
//
// Thread pointer (TP) is stored to the control register cr27, which is an
// alias for tr3.
//
// $ltp (linkage table pointer) is $r19
// $dp is $r27
#include "mold.h"
namespace mold::elf {
using E = HPPA32;
static u64 RND(u64 x) { return (x + 0x1000) & ~(u64)0x1fff; }
static u64 L(u64 x) { return x & 0xffff'f800; }
static u64 R(u64 x) { return x & 0x0000'07ff; }
static u64 LR(u64 x, u64 a) { return L(x + RND(a)); }
static u64 RR(u64 x, u64 a) { return R(x + RND(a)) + a - RND(a); }
static u64 get_gp(Context<E> &ctx) {
if (ctx.arg.pic)
return ctx.gotplt->shdr.sh_addr;
return ctx.extra.global->value;
}
static u32 dis_assemble_17(u32 val) {
return ((val & 0x10000) >> 16) |
((val & 0x0f800) << 5) |
((val & 0x00400) >> 8) |
((val & 0x003ff) << 3);
}
static u32 dis_assemble_21(u32 val) {
return (bits(val, 6, 2) << 16) | (bits(val, 8, 7) << 14) |
(bits(val, 1, 0) << 12) | (bits(val, 19, 9) << 1) |
bit(val, 20);
}
static u32 dis_low_sign_ext(u32 val, u32 len) {
return (bits(val, len - 2, 0) << 1) | bit(val, len - 1);
}
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_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_PARISC_PCREL32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_PARISC_SEGREL32:
*(ub32 *)loc = val;
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);
u64 SB = 0;
u64 GP = get_gp(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;
#define S sym.get_addr(ctx)
#define A get_addend(loc, rel)
#define P (get_addr() + rel.r_offset)
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
#define GOT ctx.got->shdr.sh_addr
switch (rel.r_type) {
case R_PARISC_DIR32:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_PARISC_DIR21L:
*(ub32 *)loc |= dis_assemble_21(bits(LR(S, A), 31, 11));
break;
case R_PARISC_DIR14R:
*(ub32 *)loc |= dis_low_sign_ext(RR(S, A), 14);
break;
case R_PARISC_PCREL32: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc = addr + A - P - 8;
break;
}
case R_PARISC_PCREL21L: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc |= dis_assemble_21(bits(addr + A - P - 8, 31, 11));
break;
}
case R_PARISC_PCREL17F: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc |= dis_assemble_17((addr + A - P - 8) >> 2);
break;
}
case R_PARISC_PCREL14R: {
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
*(ub32 *)loc |= dis_low_sign_ext(R(addr - P - 8 + A), 14);
break;
}
case R_PARISC_DPREL21L:
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - GP, A), 31, 11));
break;
case R_PARISC_DPREL14R:
*(ub32 *)loc |= dis_low_sign_ext(RR(S - GP, A), 14);
break;
case R_PARISC_DLTIND21L:
assert(A == 0);
*(ub32 *)loc |= dis_assemble_21(bits(G + GOT - GP, 31, 11));
break;
case R_PARISC_DLTIND14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(G + GOT - GP), 14);
break;
case R_PARISC_SEGREL32:
*(ub32 *)loc = S + A - SB;
break;
case R_PARISC_PLABEL32:
if (sym.is_remaining_undef_weak()) {
*(ub32 *)loc = 0;
} else if (ctx.arg.pic) {
if (sym.is_imported)
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, sym.get_dynsym_idx(ctx), 0);
else
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, 0, sym.get_opd_addr(ctx) + 2);
*(ub32 *)loc = 0;
} else {
*(ub32 *)loc = sym.get_opd_addr(ctx) + 2;
}
break;
case R_PARISC_TPREL21L:
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.tp_addr, A), 31, 11));
break;
case R_PARISC_TPREL14R:
*(ub32 *)loc |= dis_low_sign_ext(RR(S - ctx.tp_addr, A), 14);
break;
case R_PARISC_LTOFF_TP21L:
assert(A == 0);
*(ub32 *)loc |=
dis_assemble_21(bits(LR(sym.get_gottp_addr(ctx) - GP, A), 31, 11));
break;
case R_PARISC_LTOFF_TP14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_gottp_addr(ctx) - GP), 14);
break;
case R_PARISC_TLS_GD21L:
assert(A == 0);
*(ub32 *)loc |=
dis_assemble_21(bits(LR(sym.get_tlsgd_addr(ctx) - GP, A), 31, 11));
break;
case R_PARISC_TLS_GD14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_tlsgd_addr(ctx) - GP), 14);
break;
case R_PARISC_TLS_LDM21L:
assert(A == 0);
*(ub32 *)loc |=
dis_assemble_21(bits(LR(ctx.got->get_tlsld_addr(ctx) - GP, A), 31, 11));
break;
case R_PARISC_TLS_LDM14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(ctx.got->get_tlsld_addr(ctx) - GP), 14);
break;
case R_PARISC_TLS_LDO21L:
assert(A == 0);
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.dtp_addr, A), 31, 11));
break;
case R_PARISC_TLS_LDO14R:
assert(A == 0);
*(ub32 *)loc |= dis_low_sign_ext(R(S - ctx.dtp_addr), 14);
break;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? frag_addend : get_addend(loc, rel))
switch (rel.r_type) {
case R_PARISC_DIR32:
case R_PARISC_SEGREL32:
*(ub32 *)loc = S + A;
break;
default:
Error(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
#undef S
#undef A
}
}
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on PA-RISC";
if (sym.is_func()) {
switch (rel.r_type) {
case R_PARISC_PCREL32:
case R_PARISC_PCREL21L:
case R_PARISC_PCREL17F:
case R_PARISC_PCREL14R:
case R_PARISC_PLABEL32:
break;
default:
Error(ctx) << *this << ": " << rel
<< " may not refer to a function symbol " << sym;
}
}
switch (rel.r_type) {
case R_PARISC_DIR32:
scan_dyn_absrel(ctx, sym, rel);
break;
case R_PARISC_DIR21L:
case R_PARISC_DIR14R:
scan_absrel(ctx, sym, rel);
break;
case R_PARISC_PCREL32:
case R_PARISC_PCREL21L:
case R_PARISC_PCREL17F:
case R_PARISC_PCREL14R:
case R_PARISC_DPREL21L:
case R_PARISC_DPREL14R:
if (sym.is_func() || sym.esym().is_undef_weak())
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
else
scan_pcrel(ctx, sym, rel);
break;
case R_PARISC_DLTIND21L:
case R_PARISC_DLTIND14R:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PARISC_PLABEL32:
if (!sym.is_remaining_undef_weak()) {
if (ctx.arg.pic)
file.num_dynrel++;
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
}
break;
case R_PARISC_LTOFF_TP21L:
case R_PARISC_LTOFF_TP14R:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PARISC_TLS_GD21L:
case R_PARISC_TLS_GD14R:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PARISC_TLS_LDM21L:
case R_PARISC_TLS_LDM14R:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PARISC_SEGREL32:
case R_PARISC_TPREL21L:
case R_PARISC_TPREL14R:
case R_PARISC_TLS_LDO21L:
case R_PARISC_TLS_LDO14R:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 gp = get_gp(ctx);
static const ub32 opd_pic_entry[] = {
0x2a60'0000, // addil LR'<OFFSET>, r19, r1
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
0x0ec0'1095, // ldw 0(r22), r21
0xeaa0'c000, // bv r0(r21)
0x0ec8'1093, // ldw 4(r22), r19
};
static const ub32 opd_nopic_entry[] = {
0x2b60'0000, // addil LR'<OFFSET>, dp, r1
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
0x0ec0'1095, // ldw 0(r22), r21
0xeaa0'c000, // bv r0(r21)
0x0ec8'1093, // ldw 4(r22), r19
};
static const ub32 local_pic_entry[] = {
0x2a60'0000, // addil L%0, r19, r1
0x3435'0000, // ldo 0(r1), r21
0xeaa0'c000, // bv r0(r21)
0x0800'0240, // nop
0x0800'0240, // nop
};
static const ub32 local_nopic_entry[] = {
0x2b60'0000, // addil L%0, dp, r1
0x3435'0000, // ldo 0(r1), r21
0xeaa0'c000, // bv r0(r21)
0x0800'0240, // nop
0x0800'0240, // nop
};
static_assert(E::thunk_size == sizeof(opd_pic_entry));
static_assert(E::thunk_size == sizeof(opd_nopic_entry));
static_assert(E::thunk_size == sizeof(local_pic_entry));
static_assert(E::thunk_size == sizeof(local_nopic_entry));
for (i64 i = 0; i < symbols.size(); i++) {
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
Symbol<E> &sym = *symbols[i];
i64 val;
if (sym.has_opd(ctx)) {
val = sym.get_opd_addr(ctx) - gp;
if (ctx.arg.pic)
memcpy(loc, opd_pic_entry, E::thunk_size);
else
memcpy(loc, opd_nopic_entry, E::thunk_size);
} else {
assert(!sym.is_imported);
val = sym.get_addr(ctx) - gp;
if (ctx.arg.pic)
memcpy(loc, local_pic_entry, E::thunk_size);
else
memcpy(loc, local_nopic_entry, E::thunk_size);
}
loc[0] |= dis_assemble_21(bits(LR(val, 0), 31, 11));
loc[1] |= dis_low_sign_ext(RR(val, 0), 14);
}
}
void HppaOpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
sym->set_opd_idx(ctx, symbols.size());
symbols.push_back(sym);
}
void HppaOpdSection::update_shdr(Context<E> &ctx) {
if (!symbols.empty())
this->shdr.sh_size = symbols.size() * ENTRY_SIZE + TRAILER_SIZE;
this->shdr.sh_link = ctx.extra.opd->shndx;
}
void HppaOpdSection::copy_buf(Context<E> &ctx) {
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
u64 gp = get_gp(ctx);
memset(buf, 0, this->shdr.sh_size);
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
if (!ctx.arg.pic && !sym.is_imported) {
buf[i * 2] = sym.get_addr(ctx, NO_OPD);
buf[i * 2 + 1] = gp;
}
}
}
void HppaRelOpdSection::update_shdr(Context<E> &ctx) {
this->shdr.sh_link = ctx.extra.opd->shndx;
if (ctx.arg.pic) {
this->shdr.sh_size = ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>);
} else {
this->shdr.sh_size = 0;
for (Symbol<E> *sym : ctx.extra.opd->symbols)
if (sym->is_imported)
this->shdr.sh_size += sizeof(ElfRel<E>);
}
}
void HppaRelOpdSection::copy_buf(Context<E> &ctx) {
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset);
for (Symbol<E> *sym : ctx.extra.opd->symbols) {
if (ctx.arg.pic || sym->is_imported) {
memset(rel, 0, sizeof(*rel));
rel->r_offset = sym->get_opd_addr(ctx);
rel->r_type = R_PARISC_IPLT;
if (sym->is_imported)
rel->r_sym = sym->get_dynsym_idx(ctx);
else
rel->r_addend = sym->get_addr(ctx, NO_OPD);
rel++;
}
}
// This code stub must immediately follow the OPD element that the last
// .rela.plt relocation refers to, because that's how the runtime finds
// this piece of code on process startup.
//
// The code stub loads the _dl_runtime_resolve address to $r20 call the
// function with its GP in $r19. The magic bytes, 0xc0ffee and 0xdeadbeef,
// are used by the runtime to identify this code stub, so they must be
// exactly that bytes.
//
// HPPA's runtime assumes that this stub is immediately followed by .got.
// The files created by mold don't actually satisfy that constraint
// because we don't intermix executable code and data. So, instead, we
// reserve three padding words after the code stub and set
// _GLOBAL_OFFSET_TABLE_ there as if that particular place is the
// beginning of a .got. This trick works because the runtime only uses
// _GLOBAL_OFFSET_TABLE_ to access GOT[1],
//
// Due to the existence of this code stub, .opd must be readable,
// writable and executable. Writable executable segment is bad from the
// security standpoint, but we have no choice other than doing it.
//
// https://sourceware.org/git/?p=glibc.git;f=sysdeps/hppa/dl-machine.h;h=1d5194856601e025c#l223
ub32 insn[] = {
0x0e80'1095, // 1: ldw 0(r20), r21
0xeaa0'c000, // bv r0(r21)
0x0e88'1095, // ldw 4(r20), r21
0xea9f'1fdd, // b,l 1b, r20
0xd680'1c1e, // depwi 0, 31, 2, r20
0x00c0'ffee, // (_dl_runtime_resolve's address)
0xdead'beef, // (%r19 for _dl_runtime_resolve)
0x0000'0000, // (_GLOBAL_OFFSET_TABLE_ is set to here)
0x0000'0000, // (runtime uses this word to identify this ELF module)
0x0000'0000, // (padding)
};
memcpy(ctx.buf + this->shdr.sh_offset +
ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>),
insn, sizeof(insn));
}
} // namespace mold::elf
+306 -212
View File
@@ -1,96 +1,179 @@
// i386 is similar to x86-64 but lacks PC-relative memory access
// instructions. So it's not straightforward to support position-
// independent code (PIC) on that target.
//
// If an object file is compiled with -fPIC, a function that needs to load
// a value from memory first obtains its own address with the following
// code
//
// call __x86.get_pc_thunk.bx
//
// where __x86.get_pc_thunk.bx is defined as
//
// __x86.get_pc_thunk.bx:
// mov (%esp), %ebx # move the return address to %ebx
// ret
//
// . With the function's own address (or, more precisely, the address
// immediately after the call instruction), the function can compute an
// absolute address of a variable with its address + link-time constant.
//
// Executing call-mov-ret isn't very cheap, and allocating one register to
// store PC isn't cheap too, especially given that i386 has only 8
// general-purpose registers. But that's the cost of PIC on i386. You need
// to pay it when creating a .so and a position-independent executable.
//
// When a position-independent function calls another function, it sets
// %ebx to the address of .got. Position-independent PLT entries use that
// register to load values from .got.plt/.got.
//
// If we are creating a position-dependent executable (PDE), we can't
// assume that %ebx is set to .got. For PDE, we need to create position-
// dependent PLT entries which don't use %ebx.
//
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
#include "mold.h"
namespace mold::elf {
using E = I386;
// Emitting position-independent code (PIC) for i386 is a bit tricky
// because i386 doesn't support PC-relative memory access instructions.
// By default, i386 executables are not PIC. If PIC, %ebx is used to
// store the location of .got and access all data with offsets from .got.
static void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 plt0[] = {
0xff, 0xb3, 0, 0, 0, 0, // pushl GOTPLT+4(%ebx)
0xff, 0xa3, 0, 0, 0, 0, // jmp *GOTPLT+8(%ebx)
0x90, 0x90, 0x90, 0x90, // nop
};
memcpy(buf, plt0, sizeof(plt0));
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
} else {
static const u8 plt0[] = {
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+8
0x90, 0x90, 0x90, 0x90, // nop
};
memcpy(buf, plt0, sizeof(plt0));
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_NONE:
return 0;
case R_386_8:
case R_386_PC8:
return *loc;
case R_386_16:
case R_386_PC16:
return *(ul16 *)loc;
case R_386_32:
case R_386_PC32:
case R_386_GOT32:
case R_386_GOT32X:
case R_386_PLT32:
case R_386_GOTOFF:
case R_386_GOTPC:
case R_386_TLS_LDM:
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
case R_386_TLS_GD:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
return *(ul32 *)loc;
}
unreachable();
}
template <>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_NONE:
break;
case R_386_8:
case R_386_PC8:
*loc = val;
break;
case R_386_16:
case R_386_PC16:
*(ul16 *)loc = val;
break;
case R_386_32:
case R_386_PC32:
case R_386_GOT32:
case R_386_GOT32X:
case R_386_PLT32:
case R_386_GOTOFF:
case R_386_GOTPC:
case R_386_TLS_LDM:
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
case R_386_TLS_GD:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
*(ul32 *)loc = val;
break;
default:
unreachable();
}
}
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
i64 idx) {
u8 *ent = buf + E::plt_hdr_size + sym.get_plt_idx(ctx) * E::plt_size;
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 data[] = {
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)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 7) = 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)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr + 4;
}
}
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)
0x68, 0, 0, 0, 0, // pushl $reloc_offset
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
0xcc, // (padding)
};
memcpy(ent, data, sizeof(data));
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 data[] = {
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
0x68, 0, 0, 0, 0, // pushl $reloc_offset
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
0xcc, // (padding)
};
memcpy(ent, data, sizeof(data));
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
}
*(ul32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
*(ul32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
write_plt_header(ctx, buf);
for (i64 i = 0; i < symbols.size(); i++)
write_plt_entry(ctx, buf, *symbols[i], i);
}
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 data[] = {
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0x66, 0x90, // nop
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)
};
for (i64 i = 0; i < symbols.size(); i++) {
u8 *ent = buf + i * sizeof(data);
memcpy(ent, data, sizeof(data));
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
}
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0x66, 0x90, // nop
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)
};
for (i64 i = 0; i < symbols.size(); i++) {
u8 *ent = buf + i * sizeof(data);
memcpy(ent, data, sizeof(data));
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
}
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx);
}
}
@@ -100,124 +183,141 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_386_NONE:
return;
case R_NONE:
break;
case R_386_32:
*(ul32 *)loc = val;
return;
break;
case R_386_PC32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
return;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
unreachable();
}
static u32 relax_got32x(u8 *loc) {
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
if (loc[0] == 0x8b)
return 0x8d00 | loc[1];
return 0;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_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_386_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
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 << ")";
};
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
#define GOT ctx.got->shdr.sh_addr
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;
switch (rel.r_type) {
case R_386_8: {
i64 val = S + A;
overflow_check(val, 0, 1 << 8);
check(val, 0, 1 << 8);
*loc = val;
continue;
break;
}
case R_386_16: {
i64 val = S + A;
overflow_check(val, 0, 1 << 16);
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
continue;
break;
}
case R_386_32:
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
continue;
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_386_PC8: {
i64 val = S + A - P;
overflow_check(val, -(1 << 7), 1 << 7);
check(val, -(1 << 7), 1 << 7);
*loc = val;
continue;
};
break;
}
case R_386_PC16: {
i64 val = S + A - P;
overflow_check(val, -(1 << 15), 1 << 15);
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
continue;
break;
}
case R_386_PC32:
case R_386_PLT32:
*(ul32 *)loc = S + A - P;
continue;
break;
case R_386_GOT32:
case R_386_GOT32X:
*(ul32 *)loc = G + A;
continue;
break;
case R_386_GOT32X:
if (sym.has_got(ctx)) {
*(ul32 *)loc = G + A;
} else {
u32 insn = relax_got32x(loc - 2);
assert(insn);
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - GOT;
}
break;
case R_386_GOTOFF:
*(ul32 *)loc = S + A - GOT;
continue;
break;
case R_386_GOTPC:
*(ul32 *)loc = GOT + A - P;
continue;
break;
case R_386_TLS_GOTIE:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
continue;
break;
case R_386_TLS_LE:
*(ul32 *)loc = S + A - ctx.tls_end;
continue;
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_386_TLS_IE:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
continue;
break;
case R_386_TLS_GD:
if (sym.get_tlsgd_idx(ctx) == -1) {
if (sym.has_tlsgd(ctx)) {
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
} else {
// Relax GD to LE
switch (rels[i + 1].r_type) {
case R_386_PLT32: {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
};
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = ctx.tls_end - S - A;
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
break;
}
case R_386_GOT32: {
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %rax
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = ctx.tls_end - S - A;
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
break;
}
default:
@@ -225,77 +325,70 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
i++;
} else {
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
}
continue;
break;
case R_386_TLS_LDM:
if (ctx.got->tlsld_idx == -1) {
if (ctx.got->has_tlsld(ctx)) {
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
} else {
// Relax LD to LE
switch (rels[i + 1].r_type) {
case R_386_PLT32: {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
0x31, 0xc0, // xor %eax, %eax
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x31, 0xc0, // xor %eax, %eax
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
0x90, // nop
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
case R_386_GOT32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x8d, 0x74, 0x26, 0x00, // lea (%esi,1), %esi
0x66, 0x90,
};
memcpy(loc - 2, insn, sizeof(insn));
break;
}
default:
unreachable();
}
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
i++;
} else {
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
}
continue;
break;
case R_386_TLS_LDO_32:
if (ctx.got->tlsld_idx == -1)
*(ul32 *)loc = S + A - ctx.tls_end;
else
*(ul32 *)loc = S + A - ctx.tls_begin;
continue;
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_386_SIZE32:
*(ul32 *)loc = sym.esym().st_size + A;
continue;
break;
case R_386_TLS_GOTDESC:
if (sym.get_tlsdesc_idx(ctx) == -1) {
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
} else {
static const u8 insn[] = {
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)loc = S + A - ctx.tls_end;
} else {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
*(ul32 *)loc = S + A - ctx.tp_addr;
}
continue;
break;
case R_386_TLS_DESC_CALL:
if (ctx.arg.relax && !ctx.arg.shared) {
// call *(%rax) -> nop
if (!sym.has_tlsdesc(ctx)) {
// call *(%eax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
continue;
break;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
@@ -305,7 +398,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_386_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -316,81 +409,73 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
continue;
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
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 << ")";
};
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? addend : this->get_addend(rel))
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
#define GOT ctx.got->shdr.sh_addr
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 : get_addend(*this, rel);
u64 GOT = ctx.got->shdr.sh_addr;
switch (rel.r_type) {
case R_386_8: {
i64 val = S + A;
overflow_check(val, 0, 1 << 8);
check(val, 0, 1 << 8);
*loc = val;
continue;
break;
}
case R_386_16: {
i64 val = S + A;
overflow_check(val, 0, 1 << 16);
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
continue;
break;
}
case R_386_32:
if (!frag) {
if (std::optional<u64> val = get_tombstone(sym)) {
*(ul32 *)loc = *val;
continue;
}
}
*(ul32 *)loc = S + A;
continue;
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_386_PC8: {
i64 val = S + A;
overflow_check(val, -(1 << 7), 1 << 7);
check(val, -(1 << 7), 1 << 7);
*loc = val;
continue;
break;
}
case R_386_PC16: {
i64 val = S + A;
overflow_check(val, -(1 << 15), 1 << 15);
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
continue;
};
break;
}
case R_386_PC32:
*(ul32 *)loc = S + A;
continue;
break;
case R_386_GOTPC:
*(ul32 *)loc = GOT + A;
continue;
break;
case R_386_GOTOFF:
*(ul32 *)loc = S + A - GOT;
continue;
break;
case R_386_TLS_LDO_32:
if (std::optional<u64> val = get_tombstone(sym))
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.tls_begin;
continue;
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_386_SIZE32:
*(ul32 *)loc = sym.esym().st_size + A;
continue;
break;
default:
unreachable();
}
#undef S
#undef A
#undef GOT
}
}
@@ -404,75 +489,84 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_386_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.get_type() == STT_GNU_IFUNC)
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_386_8:
case R_386_16:
scan_abs_rel(ctx, sym, rel);
scan_absrel(ctx, sym, rel);
break;
case R_386_32:
scan_abs_dyn_rel(ctx, sym, rel);
scan_dyn_absrel(ctx, sym, rel);
break;
case R_386_PC8:
case R_386_PC16:
case R_386_PC32:
scan_pcrel_rel(ctx, sym, rel);
scan_pcrel(ctx, sym, rel);
break;
case R_386_GOT32:
case R_386_GOT32X:
case R_386_GOTPC:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_386_GOT32X: {
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_got32x(loc - 2);
if (!do_relax)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_386_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_386_TLS_GD:
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_GOT32)
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
if (relax_tlsgd(ctx, sym))
i++;
else
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_386_TLS_LDM:
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_GOT32)
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
if (ctx.arg.relax && !ctx.arg.shared)
if (relax_tlsld(ctx))
i++;
else
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_386_TLS_GOTDESC:
if (!ctx.arg.relax || ctx.arg.shared)
sym.flags |= NEEDS_TLSDESC;
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
+333
View File
@@ -0,0 +1,333 @@
// 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.)
//
// 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,
// m68k's psABI is actually simpler because m68k has PC-relative memory
// access instructions and therefore can support position-independent
// code without too much hassle.
//
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
#include "mold.h"
namespace mold::elf {
using E = M68K;
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const u8 insn[] = {
0x2f, 0x00, // move.l %d0, -(%sp)
0x2f, 0x3b, 0x01, 0x70, 0, 0, 0, 0, // move.l (GOTPLT+4, %pc), -(%sp)
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT+8, %pc])
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
*(ub32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0x20, 0x3c, 0, 0, 0, 0, // move.l PLT_OFFSET, %d0
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT_ENTRY, %pc])
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ub32 *)(buf + 10) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOT_ENTRY, %pc])
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 4) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 2;
}
template <>
void EhFrameSection<E>::apply_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_68K_32:
*(ub32 *)loc = val;
break;
case R_68K_PC32:
*(ub32 *)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;
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 write16 = [&](u64 val) {
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
};
auto write16s = [&](u64 val) {
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
};
auto write8 = [&](u64 val) {
check(val, 0, 1 << 8);
*loc = val;
};
auto write8s = [&](u64 val) {
check(val, -(1 << 7), 1 << 7);
*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);
break;
case R_68K_8:
write8(S + A);
break;
case R_68K_PC32:
case R_68K_PLT32:
*(ub32 *)loc = S + A - P;
break;
case R_68K_PC16:
case R_68K_PLT16:
write16s(S + A - P);
break;
case R_68K_PC8:
case R_68K_PLT8:
write8s(S + A - P);
break;
case R_68K_GOTPCREL32:
*(ub32 *)loc = GOT + A - P;
break;
case R_68K_GOTPCREL16:
write16s(GOT + A - P);
break;
case R_68K_GOTPCREL8:
write8s(GOT + A - P);
break;
case R_68K_GOTOFF32:
*(ub32 *)loc = G + A;
break;
case R_68K_GOTOFF16:
write16(G + A);
break;
case R_68K_GOTOFF8:
write8(G + A);
break;
case R_68K_TLS_GD32:
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
break;
case R_68K_TLS_GD16:
write16(sym.get_tlsgd_addr(ctx) + A - GOT);
break;
case R_68K_TLS_GD8:
write8(sym.get_tlsgd_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LDM32:
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
break;
case R_68K_TLS_LDM16:
write16(ctx.got->get_tlsld_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LDM8:
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LDO32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
case R_68K_TLS_LDO16:
write16s(S + A - ctx.dtp_addr);
break;
case R_68K_TLS_LDO8:
write8s(S + A - ctx.dtp_addr);
break;
case R_68K_TLS_IE32:
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
break;
case R_68K_TLS_IE16:
write16(sym.get_gottp_addr(ctx) + A - GOT);
break;
case R_68K_TLS_IE8:
write8(sym.get_gottp_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LE32:
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_68K_TLS_LE16:
write16(S + A - ctx.tp_addr);
break;
case R_68K_TLS_LE8:
write8(S + A - ctx.tp_addr);
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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_68K_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub32 *)loc = *val;
else
*(ub32 *)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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
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);
break;
case R_68K_PC32:
case R_68K_PC16:
case R_68K_PC8:
scan_pcrel(ctx, sym, rel);
break;
case R_68K_GOTPCREL32:
case R_68K_GOTPCREL16:
case R_68K_GOTPCREL8:
case R_68K_GOTOFF32:
case R_68K_GOTOFF16:
case R_68K_GOTOFF8:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_68K_PLT32:
case R_68K_PLT16:
case R_68K_PLT8:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_68K_TLS_GD32:
case R_68K_TLS_GD16:
case R_68K_TLS_GD8:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_68K_TLS_LDM32:
case R_68K_TLS_LDM16:
case R_68K_TLS_LDM8:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_68K_TLS_IE32:
case R_68K_TLS_IE16:
case R_68K_TLS_IE8:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_68K_TLS_LDO32:
case R_68K_TLS_LDO16:
case R_68K_TLS_LDO8:
case R_68K_TLS_LE32:
case R_68K_TLS_LE16:
case R_68K_TLS_LE8:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold::elf
+465
View File
@@ -0,0 +1,465 @@
// This file implements the PowerPC 32-bit ISA. For 64-bit PowerPC, see
// arch-ppc64v1.cpp and arch-ppc64v2.cpp.
//
// PPC32 is a RISC ISA. It has 32 general-purpose registers (GPRs).
// r0, r11 and r12 are reserved for static linkers, so we can use these
// registers in PLTs and range extension thunks. In addition to that, it
// has a few special registers. Notable ones are LR which holds a return
// address and CTR which we can use to store a branch target address.
//
// It feels that the PPC32 psABI is unnecessarily complicated at first
// glance, but that is mainly stemmed from the fact that the ISA lacks
// PC-relative load/store instructions. Since machine instructions cannot
// load data relative to its own address, it is not straightforward to
// support position-independent code (PIC) on PPC32.
//
// A position-independent function typically contains the following code
// in the prologue to obtain its own address:
//
// mflr r0 // save the current return address to %r0
// bcl 20, 31, 4 // call the next instruction as if it were a function
// mtlr r12 // save the return address to %r12
// mtlr r0 // restore the original return address
//
// An object file compiled with -fPIC contains a data section named
// `.got2` to store addresses of locally-defined global variables and
// constants. A PIC function usually computes its .got2+0x8000 and set it
// to %r30. This scheme allows the function to access global objects
// defined in the same input file with a single %r30-relative load/store
// instruction with a 16-bit offset, given that .got2 is smaller than
// 0x10000 (or 65536) bytes.
//
// Since each object file has its own .got2, %r30 refers to different
// places in a merged .got2 for two functions that are came from different
// input files. Therefore, %r30 makes sense only within a single function.
//
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
// _for each input file_ (that's what GNU ld seems to be doing), but that
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
// on a %r30 value.
//
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
#include "mold.h"
namespace mold::elf {
using E = PPC32;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ub32 insn[] = {
// Get the address of this PLT section
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
0x7d88'02a6, // 1: mflr r12
0x7c08'03a6, // mtlr r0
// Compute the runtime address of GOTPLT+12
0x3d8c'0000, // addis r12, r12, (GOTPLT - 1b)@higha
0x398c'0000, // addi r12, r12, (GOTPLT - 1b)@lo
// Compute the PLT entry offset
0x7d6c'5850, // sub r11, r11, r12
0x1d6b'0003, // mulli r11, r11, 3
// Load GOTPLT[2] and branch to GOTPLT[1]
0x800c'fff8, // lwz r0, -8(r12)
0x7c09'03a6, // mtctr r0
0x818c'fffc, // lwz r12, -4(r12)
0x4e80'0420, // bctr
0x6000'0000, // nop
0x6000'0000, // nop
0x6000'0000, // nop
0x6000'0000, // nop
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
ub32 *loc = (ub32 *)buf;
loc[4] |= higha(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
loc[5] |= lo(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
}
static const ub32 plt_entry[] = {
// Get the address of this PLT entry
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
0x7d88'02a6, // mflr r12
0x7c08'03a6, // mtlr r0
// Load an address from the GOT/GOTPLT entry and jump to that address
0x3d6c'0000, // addis r11, r12, OFFSET@higha
0x396b'0000, // addi r11, r11, OFFSET@lo
0x818b'0000, // lwz r12, 0(r11)
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static_assert(E::plt_size == sizeof(plt_entry));
memcpy(buf, plt_entry, sizeof(plt_entry));
ub32 *loc = (ub32 *)buf;
i64 offset = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
loc[4] |= higha(offset);
loc[5] |= lo(offset);
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static_assert(E::pltgot_size == sizeof(plt_entry));
memcpy(buf, plt_entry, sizeof(plt_entry));
ub32 *loc = (ub32 *)buf;
i64 offset = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 8;
loc[4] |= higha(offset);
loc[5] |= lo(offset);
}
template <>
void EhFrameSection<E>::apply_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_PPC_ADDR32:
*(ub32 *)loc = val;
break;
case R_PPC_REL32:
*(ub32 *)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);
u64 got2 = 0;
if (file.ppc32_got2)
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->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;
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;
case R_PPC_ADDR16:
case R_PPC_UADDR16:
case R_PPC_ADDR16_LO:
*(ub16 *)loc = lo(S + A);
break;
case R_PPC_ADDR16_HI:
*(ub16 *)loc = hi(S + A);
break;
case R_PPC_ADDR16_HA:
*(ub16 *)loc = ha(S + A);
break;
case R_PPC_ADDR24:
*(ub32 *)loc |= bits(S + A, 25, 2) << 2;
break;
case R_PPC_ADDR30:
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
break;
case R_PPC_PLT16_LO:
*(ub16 *)loc = lo(G + GOT - got2 - A);
break;
case R_PPC_PLT16_HI:
*(ub16 *)loc = hi(G + GOT - got2 - A);
break;
case R_PPC_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - got2 - A);
break;
case R_PPC_PLT32:
*(ub32 *)loc = G + GOT - got2 - A;
break;
case R_PPC_REL14:
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
break;
case R_PPC_REL16:
case R_PPC_REL16_LO:
*(ub16 *)loc = lo(S + A - P);
break;
case R_PPC_REL16_HI:
*(ub16 *)loc = hi(S + A - P);
break;
case R_PPC_REL16_HA:
*(ub16 *)loc = ha(S + A - P);
break;
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;
*(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;
*(ub32 *)loc |= bits(val, 25, 2) << 2;
break;
}
case R_PPC_REL32:
case R_PPC_PLTREL32:
*(ub32 *)loc = S + A - P;
break;
case R_PPC_GOT16:
case R_PPC_GOT16_LO:
*(ub16 *)loc = lo(G + A);
break;
case R_PPC_GOT16_HI:
*(ub16 *)loc = hi(G + A);
break;
case R_PPC_GOT16_HA:
*(ub16 *)loc = ha(G + A);
break;
case R_PPC_TPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC_TPREL16_HI:
*(ub16 *)loc = hi(S + A - ctx.tp_addr);
break;
case R_PPC_TPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC_DTPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC_DTPREL16_HI:
*(ub16 *)loc = hi(S + A - ctx.dtp_addr);
break;
case R_PPC_DTPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC_GOT_TLSGD16:
*(ub16 *)loc = sym.get_tlsgd_addr(ctx) - GOT;
break;
case R_PPC_GOT_TLSLD16:
*(ub16 *)loc = ctx.got->get_tlsld_addr(ctx) - GOT;
break;
case R_PPC_GOT_TPREL16:
*(ub16 *)loc = sym.get_gottp_addr(ctx) - GOT;
break;
case R_PPC_TLS:
case R_PPC_TLSGD:
case R_PPC_TLSLD:
case R_PPC_PLTSEQ:
case R_PPC_PLTCALL:
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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_PPC_ADDR32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub32 *)loc = *val;
else
*(ub32 *)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);
// Scan relocations
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];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
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:
case R_PPC_ADDR16_LO:
case R_PPC_ADDR16_HI:
case R_PPC_ADDR16_HA:
case R_PPC_ADDR24:
case R_PPC_ADDR30:
scan_absrel(ctx, sym, rel);
break;
case R_PPC_REL14:
case R_PPC_REL16:
case R_PPC_REL16_LO:
case R_PPC_REL16_HI:
case R_PPC_REL16_HA:
case R_PPC_REL32:
scan_pcrel(ctx, sym, rel);
break;
case R_PPC_GOT16:
case R_PPC_GOT16_LO:
case R_PPC_GOT16_HI:
case R_PPC_GOT16_HA:
case R_PPC_PLT16_LO:
case R_PPC_PLT16_HI:
case R_PPC_PLT16_HA:
case R_PPC_PLT32:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC_REL24:
case R_PPC_PLTREL24:
case R_PPC_PLTREL32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC_GOT_TLSGD16:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC_GOT_TLSLD16:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PPC_GOT_TPREL16:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PPC_LOCAL24PC:
case R_PPC_TLS:
case R_PPC_TLSGD:
case R_PPC_TLSLD:
case R_PPC_TPREL16_LO:
case R_PPC_TPREL16_HI:
case R_PPC_TPREL16_HA:
case R_PPC_DTPREL16_LO:
case R_PPC_DTPREL16_HI:
case R_PPC_DTPREL16_HA:
case R_PPC_PLTSEQ:
case R_PPC_PLTCALL:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const ub32 local_thunk[] = {
// Get this thunk's address
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4
0x7d88'02a6, // mflr r12
0x7c08'03a6, // mtlr r0
// Materialize the destination's address in %r11 and jump to that address
0x3d6c'0000, // addis r11, r12, OFFSET@higha
0x396b'0000, // addi r11, r11, OFFSET@lo
0x7d69'03a6, // mtctr r11
0x4e80'0420, // bctr
0x6000'0000, // nop
};
static_assert(E::thunk_size == sizeof(plt_entry));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
Symbol<E> &sym = *symbols[i];
if (sym.has_plt(ctx)) {
memcpy(loc, plt_entry, sizeof(plt_entry));
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - get_addr(i) - 8;
loc[4] |= higha(val);
loc[5] |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx) - get_addr(i) - 8;
loc[4] |= higha(val);
loc[5] |= lo(val);
}
}
}
} // namespace mold::elf
+653
View File
@@ -0,0 +1,653 @@
// This file contains code for the 64-bit PowerPC ELFv1 ABI that is
// commonly used for big-endian PPC systems. Modern PPC systems that use
// the processor in the little-endian mode use the ELFv2 ABI instead. For
// ELFv2, see arch-ppc64v2.cc.
//
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
// endianness. The most notable difference is, in ELFv1, a function
// pointer doesn't directly refer the entry point of a function but
// instead refers a data structure so-called "function descriptor".
//
// The function descriptor is essentially a pair of a function entry point
// address and a value that should be set to %r2 before calling that
// function. There is actually a third member, but we don't need to know
// what that is. In total, the function descriptor is 24 bytes long. Here
// is why we need it.
//
// PPC generally lacks PC-relative data access instructions. Position-
// independent code sets GOT + 0x8000 to %r2 and access global variables
// relative to %r2.
//
// Each ELF file has its own GOT. If a function calls another function in
// the same ELF file, it doesn't have to reset %r2. However, if it is in
// other file (e.g. other .so), it has to set a new value to %r2 so that
// the register contains the callee's GOT + 0x8000.
//
// In this way, you can't call a function just by knowing the function's
// entry point address. You also need to know a proper %r2 value for the
// function. This is why a function pointer refers a tuple of an address
// and a %r2 value.
//
// If a function call is made through PLT, PLT takes care of restoring %r2.
// Therefore, the caller has to restore %r2 only for function calls
// through function pointers.
//
// .opd (short for "official procedure descriptors") contains function
// descriptors.
//
// You can think OPD as this: even in other targets, a function can have a
// few different addresses for different purposes. It may not only have an
// entry point address but may also have PLT and/or GOT addresses.
// In PPCV1, it may have an OPD address in addition to these. OPD address
// is used for relocations that refers the address of a function as a
// function pointer.
#include "mold.h"
#include <algorithm>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
using E = PPC64V1;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
// reach. Thunks read addresses from .got.plt and jump there.
// Therefore, once PLT symbols are resolved and final addresses are
// written to .got.plt, thunks just skip .plt and directly jump to the
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ub32 insn[] = {
0x7d88'02a6, // mflr r12
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
0xe84b'0024, // ld r2,36(r11)
0x7d88'03a6, // mtlr r12
0x7d62'5a14, // add r11,r2,r11
0xe98b'0000, // ld r12,0(r11)
0xe84b'0008, // ld r2,8(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0010, // ld r11,16(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
}
// .plt.got is not necessary on PPC64 because range extension thunks
// directly read GOT entries and jump there.
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_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_PPC64_ADDR64:
*(ub64 *)loc = val;
break;
case R_PPC64_REL32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_PPC64_REL64:
*(ub64 *)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;
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;
u64 TOC = ctx.extra.TOC->value;
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);
break;
case R_PPC64_TOC16_LO:
*(ub16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS: {
i64 val = S + A - TOC;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc |= val & 0xfffc;
break;
}
case R_PPC64_TOC16_LO_DS:
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
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;
check(val, -(1 << 25), 1 << 25);
*(ub32 *)loc |= bits(val, 25, 2) << 2;
// If a callee is an external function, PLT saves %r2 to the
// caller's r2 save slot. We need to restore it after function
// return. To do so, there's usually a NOP as a placeholder
// after a BL. 0x6000'0000 is a NOP.
if (sym.has_plt(ctx) && *(ub32 *)(loc + 4) == 0x6000'0000)
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
break;
}
case R_PPC64_REL64:
*(ub64 *)loc = S + A - P;
break;
case R_PPC64_REL16_HA:
*(ub16 *)loc = ha(S + A - P);
break;
case R_PPC64_REL16_LO:
*(ub16 *)loc = lo(S + A - P);
break;
case R_PPC64_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - TOC);
break;
case R_PPC64_PLT16_HI:
*(ub16 *)loc = hi(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO:
*(ub16 *)loc = lo(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO_DS:
*(ub16 *)loc |= (G + GOT - TOC) & 0xfffc;
break;
case R_PPC64_GOT_TPREL16_HA:
*(ub16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_HA:
*(ub16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_LO:
*(ub16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_HA:
*(ub16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_LO:
*(ub16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_DTPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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);
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_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub64 *)loc = *val;
else
*(ub64 *)loc = S + A;
break;
case R_PPC64_ADDR32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_PPC64_DTPREL64:
*(ub64 *)loc = S + A - ctx.dtp_addr;
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);
// Scan relocations
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];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_OPD,
std::memory_order_relaxed);
// Any relocation except R_PPC64_REL24 is considered as an
// address-taking relocation.
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
switch (rel.r_type) {
case R_PPC64_ADDR64:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_TOC:
scan_toc_rel(ctx, sym, rel);
break;
case R_PPC64_GOT_TPREL16_HA:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC64_PLT16_HA:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
case R_PPC64_TOC16_LO_DS:
case R_PPC64_TOC16_DS:
case R_PPC64_REL16_HA:
case R_PPC64_REL16_LO:
case R_PPC64_PLT16_HI:
case R_PPC64_PLT16_LO:
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_LO:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// 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[] = {
// Store the caller's %r2
0xf841'0028, // std %r2, 40(%r1)
// Load an address of a function descriptor
0x3d82'0000, // addis %r12, %r2, foo@got@toc@ha
0xe98c'0000, // ld %r12, foo@got@toc@lo(%r12)
// Restore the callee's %r2
0xe84c'0008, // ld %r2, 8(%r12)
// Jump to the function
0xe98c'0000, // ld %r12, 0(%r12)
0x7d89'03a6, // mtctr %r12
0x4e80'0420, // bctr
};
// If the destination is .plt, read a function descriptor from .got.plt.
static const ub32 plt_thunk[] = {
// Store the caller's %r2
0xf841'0028, // std %r2, 40(%r1)
// Materialize an address of a function descriptor
0x3d82'0000, // addis %r12, %r2, foo@gotplt@toc@ha
0x398c'0000, // addi %r12, %r12, foo@gotplt@toc@lo
// Restore the callee's %r2
0xe84c'0008, // ld %r2, 8(%r12)
// Jump to the function
0xe98c'0000, // ld %r12, 0(%r12)
0x7d89'03a6, // mtctr %r12
0x4e80'0420, // bctr
};
// If the destination is a non-imported function, we directly jump
// to the function entry address.
static const ub32 local_thunk[] = {
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
0x6000'0000, // nop
0x6000'0000, // nop
0x6000'0000, // nop
};
static_assert(E::thunk_size == sizeof(pltgot_thunk));
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
if (sym.has_got(ctx)) {
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
i64 val = sym.get_got_addr(ctx) - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else if(sym.has_plt(ctx)) {
memcpy(loc, plt_thunk, sizeof(plt_thunk));
i64 val = sym.get_gotplt_addr(ctx) - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
loc[0] |= higha(val);
loc[1] |= lo(val);
}
}
}
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
if (isec && isec->name() == ".opd")
return isec.get();
return nullptr;
}
static ElfRel<E> *
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
[](const ElfRel<E> &r, i64 offset) {
return r.r_offset < offset;
});
if (it == rels.end())
return nullptr;
if (it->r_offset != offset)
return nullptr;
return &*it;
}
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(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
if (it == syms.end())
return nullptr;
if (it->r_offset != offset)
return nullptr;
return it->sym;
}
// Compiler creates an .opd entry for each function symbol. The intention
// is to make it possible to create an output .opd section just by linking
// input .opd sections in the same manner as we do to other normal input
// sections.
//
// However, in reality, .opd isn't a normal input section. It needs many
// special treatments as follows:
//
// 1. A function symbol refers not a .text but an .opd. Its address works
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
// R_PPC64_REL24 (which is used for branch instruction) needs a
// function's real address instead of the function's .opd address.
// We need to read .opd contents to find out a function entry point
// address to apply R_PPC64_REL24.
//
// 2. Output .opd entries are needed only for functions whose addresses
// are taken. Just copying input .opd sections to an output would
// produces lots of dead .opd entries.
//
// 3. In this design, all function symbols refer an .opd section, and that
// doesn't work well with graph traversal optimizations such as garbage
// collection or identical comdat folding. For example, garbage
// collector would mark an .opd alive which in turn mark all functions
// thatare referenced by .opd as alive, effectively keeping all
// functions as alive.
//
// The problem is that the compiler creates a half-baked .opd section, and
// the linker has to figure out what all these .opd entries and
// relocations are trying to achieve. It's like the compiler would emit a
// half-baked .plt section in an object file and the linker has to deal
// with that. That's not a good design.
//
// So, in this function, we undo what the compiler did to .opd. We remove
// function symbols from .opd and reattach them to their function entry
// points. We also rewrite relocations that directly refer an input .opd
// section so that they refer function symbols instead. We then mark input
// .opd sections as dead.
//
// After this function, we mark symbols with the NEEDS_OPD flag if the
// symbol needs an .opd entry. We then create an output .opd just like we
// do for .plt or .got.
void ppc64v1_rewrite_opd(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
InputSection<E> *opd = get_opd_section(*file);
if (!opd)
return;
opd->is_alive = false;
// Move symbols from .opd to .text.
std::vector<OpdSymbol> opd_syms;
for (Symbol<E> *sym : file->symbols) {
if (sym->file != file || sym->get_input_section() != opd)
continue;
if (u32 ty = sym->get_type(); ty != STT_FUNC && ty != STT_GNU_IFUNC)
continue;
ElfRel<E> *rel = get_relocation_at(ctx, *opd, sym->value);
if (!rel)
Fatal(ctx) << *file << ": cannot find a relocation in .opd for "
<< *sym << " at offset 0x" << std::hex << (u64)sym->value;
Symbol<E> *sym2 = file->symbols[rel->r_sym];
if (sym2->get_type() != STT_SECTION)
Fatal(ctx) << *file << ": bad relocation in .opd referring " << *sym2;
opd_syms.push_back({sym->value, sym});
sym->set_input_section(sym2->get_input_section());
sym->value = rel->r_addend;
}
// Sort symbols so that get_opd_sym_at() can do binary search.
sort(opd_syms);
// Rewrite relocations so that they directly refer to .opd.
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive || isec.get() == opd)
continue;
for (ElfRel<E> &r : isec->get_rels(ctx)) {
Symbol<E> &sym = *file->symbols[r.r_sym];
if (sym.get_input_section() != opd)
continue;
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
if (!real_sym)
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
<< " at offset 0x" << std::hex << (u64)r.r_addend;
r.r_sym = real_sym->sym_idx;
r.r_addend = 0;
}
}
});
}
// When a function is exported, the dynamic symbol for the function should
// refers the function's .opd entry. This function marks such symbols with
// NEEDS_OPD.
void ppc64v1_scan_symbols(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file && sym->is_exported)
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
sym->flags |= NEEDS_OPD;
});
// Functions referenced by the ELF header also have to have .opd entries.
auto mark = [&](std::string_view name) {
if (!name.empty())
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
sym.flags |= NEEDS_OPD;
};
mark(ctx.arg.entry);
mark(ctx.arg.init);
mark(ctx.arg.fini);
}
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
sym->set_opd_idx(ctx, symbols.size());
symbols.push_back(sym);
this->shdr.sh_size += ENTRY_SIZE;
}
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
for (Symbol<E> *sym : symbols) {
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
*buf++ = ctx.extra.TOC->value;
*buf++ = 0;
}
}
} // namespace mold::elf
+461
View File
@@ -0,0 +1,461 @@
// This file implements the PowerPC ELFv2 ABI which was standardized in
// 2014. Modern little-endian PowerPC systems are based on this ABI.
// The ABI is often referred to as "ppc64le". This shouldn't be confused
// with "ppc64" which refers the original, big-endian PowerPC systems.
//
// PPC64 is a bit tricky to support because PC-relative load/store
// instructions are generally not available. Therefore, it's not easy
// for position-independent code to load a value from, for example,
// .got, as we can't do that with [PC + the offset to the .got entry].
//
// We can get the program counter by the following four instructions
//
// mflr r1 // save the current link register to r1
// bl .+4 // branch to the next instruction as if it were a function
// mflr r0 // copy the return address to r0
// mtlr r1 // restore the original link register value
//
// , but that's too expensive to do if we do this for each load/store.
//
// As a workaround, most functions are compiled in such a way that r2 is
// assumed to always contain the address of .got + 0x8000. With this, we
// can for example load the first entry of .got with a single instruction
// `lw r0, -0x8000(r2)`. r2 is called the TOC pointer.
//
// There's only one .got for each ELF module. Therefore, if a callee is in
// the same ELF module, r2 doesn't have to be recomputed. Most function
// calls are usually within the same ELF module, so this mechanism is
// efficient.
//
// In PPC64, a function usually have two entry points, global and local.
// The global entry point is usually 8 bytes precedes the local entry
// point. In between is the following instructions:
//
// addis r2, r12, .TOC.@ha
// addi r2, r2, .TOC.@lo + 4;
//
// The global entry point assumes that the address of itself is in r12,
// and it computes its own TOC pointer from r12. It's easy to do so for
// the callee because the offset between its .got + 0x8000 and the
// function is known at link-time. The above code sequence then falls
// through to the local entry point that assumes r2 is .got + 0x8000.
//
// So, if a callee's TOC pointer is different from the current one
// (e.g. calling a function in another .so), we first load the callee's
// address to r12 (e.g. from .got.plt with a r2-relative load) and branch
// to that address. Then the callee computes its own TOC pointer using
// r12.
//
// Note on section names: the PPC64 psABI uses a weird naming convention
// which calls .got.plt .plt. We ignored that part because it's just
// confusing. Since the runtime only cares about segments, we should be
// able to name sections whatever we want.
//
// https://openpowerfoundation.org/specifications/64bitelfabi/
#include "mold.h"
namespace mold::elf {
using E = PPC64V2;
static u64 lo(u64 x) { return x & 0xffff; }
static u64 hi(u64 x) { return x >> 16; }
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
static u64 highest(u64 x) { return x >> 48; }
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
// .plt is used only for lazy symbol resolution on PPC64. All PLT
// calls are made via range extension thunks even if they are within
// reach. Thunks read addresses from .got.plt and jump there.
// Therefore, once PLT symbols are resolved and final addresses are
// written to .got.plt, thunks just skip .plt and directly jump to the
// resolved addresses.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const ul32 insn[] = {
// Get PC
0x7c08'02a6, // mflr r0
0x429f'0005, // bcl 20, 31, 4 // obtain PC
0x7d68'02a6, // mflr r11
0x7c08'03a6, // mtlr r0
// Compute the PLT entry index
0xe80b'002c, // ld r0, 44(r11)
0x7d8b'6050, // subf r12, r11, r12
0x7d60'5a14, // add r11, r0, r11
0x380c'ffcc, // addi r0, r12, -52
0x7800'f082, // rldicl r0, r0, 62, 2
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
0xe98b'0000, // ld r12, 0(r11)
0x7d89'03a6, // mtctr r12
0xe96b'0008, // ld r11, 8(r11)
0x4e80'0420, // bctr
// .quad .got.plt - .plt - 8
0x0000'0000,
0x0000'0000,
};
memcpy(buf, insn, sizeof(insn));
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
// When the control is transferred to a PLT entry, the PLT entry's
// address is already set to %r12 by the caller.
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx);
*(ul32 *)buf = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
}
// .plt.got is not necessary on PPC64 because range extension thunks
// directly read GOT entries and jump there.
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_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_PPC64_ADDR64:
*(ul64 *)loc = val;
break;
case R_PPC64_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_PPC64_REL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
i64 val = sym.esym().ppc_local_entry;
assert(val <= 7);
if (val == 0 || val == 1)
return 0;
if (val == 7)
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
return 1 << val;
}
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;
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;
u64 TOC = ctx.extra.TOC->value;
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;
case R_PPC64_TOC16_LO:
*(ul16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS:
case R_PPC64_TOC16_LO_DS:
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_REL24: {
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
val = get_thunk_addr(i) + A - P;
check(val, -(1 << 25), 1 << 25);
*(ul32 *)loc |= bits(val, 25, 2) << 2;
// If a callee is an external function, PLT saves %r2 to the
// caller's r2 save slot. We need to restore it after function
// return. To do so, there's usually a NOP as a placeholder
// after a BL. 0x6000'0000 is a NOP.
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
break;
}
case R_PPC64_REL64:
*(ul64 *)loc = S + A - P;
break;
case R_PPC64_REL16_HA:
*(ul16 *)loc = ha(S + A - P);
break;
case R_PPC64_REL16_LO:
*(ul16 *)loc = lo(S + A - P);
break;
case R_PPC64_PLT16_HA:
*(ul16 *)loc = ha(G + GOT - TOC);
break;
case R_PPC64_PLT16_HI:
*(ul16 *)loc = hi(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO:
*(ul16 *)loc = lo(G + GOT - TOC);
break;
case R_PPC64_PLT16_LO_DS:
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
break;
case R_PPC64_GOT_TPREL16_HA:
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_HA:
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSGD16_LO:
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_HA:
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_GOT_TLSLD16_LO:
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
break;
case R_PPC64_DTPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_HA:
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
break;
case R_PPC64_DTPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
break;
case R_PPC64_TPREL16_LO:
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
break;
case R_PPC64_GOT_TPREL16_LO_DS:
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
break;
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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);
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_PPC64_ADDR64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A;
break;
case R_PPC64_ADDR32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
break;
}
case R_PPC64_DTPREL64:
*(ul64 *)loc = S + A - ctx.dtp_addr;
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);
// Scan relocations
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];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
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:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_PPC64_REL24:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_PPC64_PLT16_HA:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSGD16_HA:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_PPC64_GOT_TLSLD16_HA:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
case R_PPC64_TOC16_LO:
case R_PPC64_TOC16_LO_DS:
case R_PPC64_TOC16_DS:
case R_PPC64_REL16_HA:
case R_PPC64_REL16_LO:
case R_PPC64_PLT16_HI:
case R_PPC64_PLT16_LO:
case R_PPC64_PLT16_LO_DS:
case R_PPC64_PLTSEQ:
case R_PPC64_PLTCALL:
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
case R_PPC64_DTPREL16_HA:
case R_PPC64_DTPREL16_LO:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
// If the destination is PLT, we read an address from .got.plt or .got
// and jump there.
static const ul32 plt_thunk[] = {
// Save r2 to the r2 save slot reserved in the caller's stack frame
0xf841'0018, // std r2, 24(r1)
// Jump to a PLT entry
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
};
// If the destination is a non-imported function, we directly jump
// to its local entry point.
static const ul32 local_thunk[] = {
// Jump to a local entry point
0x3d82'0000, // addis r12, r2, foo@toc@ha
0x398c'0000, // addi r12, r12, foo@toc@lo
0x7d89'03a6, // mtctr r12
0x4e80'0420, // bctr
0x6000'0000, // nop
};
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
if (sym.has_plt(ctx)) {
memcpy(loc, plt_thunk, sizeof(plt_thunk));
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - ctx.extra.TOC->value;
loc[1] |= higha(val);
loc[2] |= lo(val);
} else {
memcpy(loc, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
ctx.extra.TOC->value;
loc[0] |= higha(val);
loc[1] |= lo(val);
}
}
}
} // namespace mold::elf
+356 -317
View File
File diff suppressed because it is too large. Load diff
+541
View File
@@ -0,0 +1,541 @@
// This file contains code for the IBM z/Architecture 64-bit ISA, which is
// commonly referred to as "s390x" on Linux.
//
// z/Architecture is a 64-bit CISC ISA developed by IBM around 2000 for
// IBM's "big iron" mainframe computers. The computers are direct
// descendents of IBM System/360 all the way back in 1966. I've never
// actually seen a mainframe, and you probaly haven't either, but it looks
// like the mainframe market is still large enough to sustain its ecosystem.
// Ubuntu for example provides the official support for s390x as of 2022.
// Since they are being actively maintained, we need to support them.
//
// As an instruction set, s390x isn't particularly odd. It has 16 general-
// purpose registers. Instructions are 2, 4 or 6 bytes long and always
// aligned to 2 bytes boundaries. Despite unfamiliarty, I found that it
// just feels like a 64-bit i386 in a parallel universe.
//
// Here is the register usage in this ABI:
//
// r0-r1: reserved as scratch registers so we can use them in our PLT
// r2: parameter passing and return values
// r3-r6: parameter passing
// r12: address of GOT if position-independent code
// r14: return address
// r15: stack pointer
// a1: upper 32 bits of TP (thread pointer)
// a2: lower 32 bits of TP (thread pointer)
//
// TLS is supported on s390x in the same way as it is on other targets
// with one exeption. On other targets, __tls_get_addr is used to get an
// address of a thread-local variable. On s390x, __tls_get_offset is used
// instead. The difference is __tls_get_offset returns an address of a
// thread-local variable as an offset from TP. So 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/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
#include "mold.h"
namespace mold::elf {
using E = S390X;
static void write_mid20(u8 *loc, u64 val) {
*(ub32 *)loc |= (bits(val, 11, 0) << 16) | (bits(val, 19, 12) << 8);
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static u8 insn[] = {
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static u8 insn[] = {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static u8 insn[] = {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, // nopr
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = (sym.get_got_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
}
template <>
void EhFrameSection<E>::apply_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_390_PC32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
break;
case R_390_64:
*(ub64 *)loc = val;
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;
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;
switch (rel.r_type) {
case R_390_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_390_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
}
case R_390_12: {
i64 val = S + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
break;
}
case R_390_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
break;
}
case R_390_20: {
i64 val = S + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
break;
}
case R_390_32:
case R_390_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_390_PLT64:
*(ub64 *)loc = S + A;
break;
case R_390_PC12DBL:
case R_390_PLT12DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 12), 1 << 12);
*(ul16 *)loc = (val >> 1) & 0x0fff;
break;
}
case R_390_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
break;
}
case R_390_PC32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
break;
}
case R_390_PC64:
*(ub64 *)loc = S + A - P;
break;
case R_390_PC16DBL:
case R_390_PLT16DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 16), 1 << 16);
*(ub16 *)loc = val >> 1;
break;
}
case R_390_PC24DBL:
case R_390_PLT24DBL: {
i64 val = S + A - P;
check_dbl(val, -(1 << 24), 1 << 24);
*(ub32 *)loc |= bits(val, 24, 1);
break;
}
case R_390_PC32DBL:
case R_390_PLT32DBL:
if (ctx.is_static && &sym == ctx.tls_get_offset) {
// __tls_get_offset() in libc.a is stub code that calls abort().
// So we provide a replacement function.
*(ub32 *)loc = (ctx.extra.tls_get_offset->shdr.sh_addr - P) >> 1;
} else {
i64 val = S + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
}
break;
case R_390_GOT12:
case R_390_GOTPLT12: {
i64 val = G + A;
check(val, 0, 1 << 12);
*(ul16 *)loc = bits(val, 11, 0);
break;
}
case R_390_GOT16:
case R_390_GOTPLT16: {
i64 val = G + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
break;
}
case R_390_GOT20:
case R_390_GOTPLT20: {
i64 val = G + A;
check(val, 0, 1 << 20);
write_mid20(loc, val);
break;
}
case R_390_GOT32:
case R_390_GOTPLT32: {
i64 val = G + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_390_GOT64:
case R_390_GOTPLT64:
*(ub64 *)loc = G + A;
break;
case R_390_GOTOFF16:
case R_390_PLTOFF16: {
i64 val = S + A - GOT;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
break;
}
case R_390_GOTOFF32:
case R_390_PLTOFF32: {
i64 val = S + A - GOT;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
break;
}
case R_390_GOTOFF64:
case R_390_PLTOFF64:
*(ub64 *)loc = S + A - GOT;
break;
case R_390_GOTPC:
*(ub64 *)loc = GOT + A - P;
break;
case R_390_GOTPCDBL: {
i64 val = GOT + A - P;
check_dbl(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
break;
}
case R_390_GOTENT: {
i64 val = GOT + G + A - P;
check(val, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = val >> 1;
break;
}
case R_390_TLS_LE32:
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_LE64:
*(ub64 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GOTIE20:
write_mid20(loc, sym.get_gottp_addr(ctx) + A - GOT);
break;
case R_390_TLS_IEENT:
*(ub32 *)loc = (sym.get_gottp_addr(ctx) + A - P) >> 1;
break;
case R_390_TLS_GD32:
if (sym.has_tlsgd(ctx))
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GD64:
if (sym.has_tlsgd(ctx))
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else
*(ub64 *)loc = S + A - ctx.tp_addr;
break;
case R_390_TLS_GDCALL:
if (!sym.has_tlsgd(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
}
break;
case R_390_TLS_LDM32:
if (ctx.got->has_tlsld(ctx))
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
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;
break;
case R_390_TLS_LDCALL:
if (!ctx.got->has_tlsld(ctx)) {
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
memcpy(loc, nop, sizeof(nop));
}
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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);
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_390_32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_390_64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub64 *)loc = *val;
else
*(ub64 *)loc = S + A;
break;
case R_390_TLS_LDO64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub64 *)loc = *val;
else
*(ub64 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << 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);
// Scan relocations
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];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
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:
case R_390_20:
case R_390_32:
scan_absrel(ctx, sym, rel);
break;
case R_390_PC16:
case R_390_PC16DBL:
case R_390_PC32:
case R_390_PC32DBL:
case R_390_PC64:
scan_pcrel(ctx, sym, rel);
break;
case R_390_GOT12:
case R_390_GOT16:
case R_390_GOT20:
case R_390_GOT32:
case R_390_GOT64:
case R_390_GOTOFF16:
case R_390_GOTOFF32:
case R_390_GOTOFF64:
case R_390_GOTPLT12:
case R_390_GOTPLT16:
case R_390_GOTPLT20:
case R_390_GOTPLT32:
case R_390_GOTPLT64:
case R_390_GOTPC:
case R_390_GOTPCDBL:
case R_390_GOTENT:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_390_PLT12DBL:
case R_390_PLT16DBL:
case R_390_PLT24DBL:
case R_390_PLT32:
case R_390_PLT32DBL:
case R_390_PLT64:
case R_390_PLTOFF16:
case R_390_PLTOFF32:
case R_390_PLTOFF64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_390_TLS_GOTIE20:
case R_390_TLS_IEENT:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_390_TLS_GD32:
case R_390_TLS_GD64:
if (!relax_tlsgd(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_390_TLS_LDM32:
case R_390_TLS_LDM64:
if (!relax_tlsld(ctx))
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_390_TLS_LE32:
case R_390_TLS_LE64:
case R_390_TLS_LDO32:
case R_390_TLS_LDO64:
case R_390_TLS_GDCALL:
case R_390_TLS_LDCALL:
break;
default:
Fatal(ctx) << *this << ": scan_relocations: " << rel;
}
}
}
// __tls_get_offset() in libc.a just calls abort(), assuming that the
// linker always relaxes TLS calls for statically-linkd executables.
// We don't always do that because we believe --relax and --static
// should be orthogonal.
//
// This section provides a replacement for __tls_get_offset() in libc.a.
void S390XTlsGetOffsetSection::copy_buf(Context<E> &ctx) {
static const u8 insn[] = {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT
0xb9, 0x08, 0x00, 0x21, // agr %r2, %r1
0xe3, 0x20, 0x20, 0x08, 0x00, 0x04, // lg %r2, 8(%r2)
0xc0, 0x11, 0, 0, 0, 0, // lgfi %r1, TLS_BLOCK_SIZE
0xb9, 0x09, 0x00, 0x21, // sgr %r2, %r1
0x07, 0xfe, // br %r14
};
assert(this->shdr.sh_size == sizeof(insn));
u8 *loc = ctx.buf + this->shdr.sh_offset;
memcpy(loc, insn, sizeof(insn));
*(ub32 *)(loc + 2) = (ctx.got->shdr.sh_addr - this->shdr.sh_addr) >> 1;
*(ub32 *)(loc + 18) = ctx.tp_addr - ctx.tls_begin;
}
} // namespace mold::elf
+355
View File
@@ -0,0 +1,355 @@
// SH-4 (SuperH 4) is a 32-bit RISC ISA developed by Hitachi in the early
// '90s. Some relatively powerful systems were developed with SH-4.
// A notable example is Sega's Dreamcast game console which debuted in 1998.
// Hitachi later spun off its semiconductor division as an independent
// company, Renesas, and Renesas is still selling SH-4 processors for the
// embedded market. It has never been as popular as ARM is, and its
// popularity continues to decline though.
//
// SH-4's most distinctive feature compared to other RISC ISAs is that its
// instructions are 16 bits in length instead of more common 32 bits for
// better code density. This difference affects various aspects of its
// instruction set as shown below:
//
// - SH-4 has 16 general-purpose registers (GPRs) instead of the most
// commmon 32 GPR configuration to save one bit to specify a register.
//
// - Binary instructions such as ADD normally take three register in
// RISC ISAs (e.g. x ← y ⊕ z where x, y and z are registers), but
// SH-4's instructions take only two registers. The result of an
// operation is written to one of the source registers (e.g. x ← x ⊕ y).
//
// - Usual RISC ISAs have "load high" and "load low" instructions to set
// an immediate to most significant and least significant bits in a
// register to construct a full 32-bit value in a register. This
// technique is hard to use in SH-4, as 16 bit instructions are too
// small to contain large immediates. On SH-4, large immediates are
// loaded from memory using `mov.l` PC-relative load instruction.
//
// Beside these, SH-4 has a delay branch slot just like contemporary MIPS
// and SPARC. That is, one instruction after a branch instruction will
// always be executed even if the branch is taken. Delay branch slot allows
// a pipelined CPU to start and finish executing an instruction after a
// branch regardless of the branch's condition, simplifying the processor's
// implementation. It's considered a bad premature optimization nowadays,
// though. Modern RISC processors don't have it.
//
// Here are notes about the SH-4 psABI:
//
// - If a source file is compiled with -fPIC, each function starts
// with a piece of code to store the address of .got to %r12.
// We can use the register in our PLT for position-independent output.
//
// - Even though it uses the RELA-type relocations, relocation addends
// are stored not to the r_addend field but to the relocated section
// contents for some reason. Therefore, it's effectively REL.
//
// - It looks like the ecosystem has bit-rotted. Some tests, especially
// one using C++ exceptions, don't pass even with GNU ld.
//
// - GCC/SH4 tends to write dynamically-relocated data into .text, so the
// output from the linker contains lots of text relocations. That's not
// a problem with embedded programming, I guess.
#include "mold.h"
namespace mold::elf {
using E = SH4;
// Even though SH-4 uses RELA-type relocations, addends are stored to
// relocated places for some reason.
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_SH_DIR32:
case R_SH_REL32:
case R_SH_TLS_GD_32:
case R_SH_TLS_LD_32:
case R_SH_TLS_LDO_32:
case R_SH_TLS_IE_32:
case R_SH_TLS_LE_32:
case R_SH_TLS_DTPMOD32:
case R_SH_TLS_DTPOFF32:
case R_SH_TLS_TPOFF32:
case R_SH_GOT32:
case R_SH_PLT32:
case R_SH_GOTOFF:
case R_SH_GOTPC:
case R_SH_GOTPLT32:
return *(ul32 *)loc;
default:
return 0;
}
}
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
};
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;
} 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
};
static_assert(sizeof(insn) == E::plt_hdr_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(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
};
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>);
} 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
};
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>);
}
}
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
};
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_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
};
static_assert(sizeof(insn) == E::pltgot_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = sym.get_got_addr(ctx);
}
}
template <>
void EhFrameSection<E>::apply_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_SH_DIR32:
*(ul32 *)loc = val;
break;
case R_SH_REL32:
*(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 = get_addend(loc, 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;
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;
break;
case R_SH_GOT32:
*(ul32 *)loc = G;
break;
case R_SH_GOTPC:
*(ul32 *)loc = GOT + A - P;
break;
case R_SH_GOTOFF:
*(ul32 *)loc = S + A - GOT;
break;
case R_SH_TLS_GD_32:
*(ul32 *)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;
break;
case R_SH_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_SH_TLS_IE_32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
break;
case R_SH_TLS_LE_32:
*(ul32 *)loc = S + A - ctx.tp_addr;
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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 : get_addend(loc, rel);
switch (rel.r_type) {
case R_SH_DIR32:
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);
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
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;
case R_SH_GOT32:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_SH_PLT32:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_SH_TLS_GD_32:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_SH_TLS_LD_32:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_SH_TLS_IE_32:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_SH_GOTPC:
case R_SH_GOTOFF:
case R_SH_TLS_LDO_32:
case R_SH_TLS_LE_32:
break;
default:
Fatal(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold::elf
+658
View File
@@ -0,0 +1,658 @@
// SPARC is a RISC ISA developed by Sun Microsystems.
//
// The byte order of the processor is big-endian. Anything larger than a
// byte is stored in the "reverse" order compared to little-endian
// processors such as x86-64.
//
// All instructions are 4 bytes long and aligned to 4 bytes boundaries.
//
// A notable feature of SPARC is that, unlike other RISC ISAs, it doesn't
// need range extension thunks. It is because the SPARC's CALL instruction
// contains a whopping 30 bits immediate. The processor scales it by 4 to
// extend it to 32 bits (this is doable because all instructions are
// aligned to 4 bytes boundaries, so the least significant two bits are
// always zero). That means CALL's reach is PC ± 2 GiB, elinating the
// need of range extension thunks. It comes with the cost that the CALL
// instruction alone takes 1/4th of the instruction encoding space,
// though.
//
// SPARC has 32 general purpose registers. CALL instruction saves a return
// address to %o7, which is an alias for %r15. Thread pointer is stored to
// %g7 which is %r7.
//
// SPARC does not have PC-relative load/store instructions. To access data
// in the position-independent manner, we usually first set the address of
// .got to, for example, %l7, with the following piece of code
//
// sethi %hi(. - _GLOBAL_OFFSET_TABLE_), %l7
// add %l7, %lo(. - _GLOBAL_OFFSET_TABLE_), %l7
// call __sparc_get_pc_thunk.l7
// nop
//
// where __sparc_get_pc_thunk.l7 is defined as
//
// retl
// add %o7, %l7, %l7
//
// . SETHI and the following ADD materialize a 32 bits offset to .got.
// CALL instruction sets a return address to $o7, and the subsequent ADD
// adds it to the GOT offset to materialize the absolute address of .got.
//
// Note that we have a NOP after CALL and an ADD after RETL because of
// SPARC's delay branch slots. That is, the SPARC processor always
// executes one instruction after a branch even if the branch is taken.
// This may seem like an odd behavior, and indeed it is considered as such
// (that's a premature optimization for the early pipelined SPARC
// processors), but that's been a part of the ISA's spec so that's what it
// is.
//
// Note also that the .got address obtained this way is not shared between
// functions, so functions can use an arbitrary register to hold the .got
// 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://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
#include "mold.h"
namespace mold::elf {
using E = SPARC64;
// SPARC's PLT section is writable despite containing executable code.
// We don't need to write the PLT header entry because the dynamic loader
// will do that for us.
//
// We also don't need a .got.plt section to store the result of lazy PLT
// symbol resolution because the dynamic symbol resolver directly mutates
// instructions in PLT so that they jump to the right places next time.
// That's why each PLT entry contains lots of NOPs; they are a placeholder
// for the runtime to add more instructions.
//
// Self-modifying code is nowadays considered really bad from the security
// point of view, though.
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
memset(buf, 0, E::plt_hdr_size);
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static ub32 insn[] = {
0x0300'0000, // sethi (. - .PLT0), %g1
0x3068'0000, // ba,a %xcc, .PLT1
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
};
u64 plt0 = ctx.plt->shdr.sh_addr;
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
u64 entry = sym.get_plt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static ub32 entry[] = {
0x8a10'000f, // mov %o7, %g5
0x4000'0002, // call . + 8
0xc25b'e014, // ldx [ %o7 + 20 ], %g1
0xc25b'c001, // ldx [ %o7 + %g1 ], %g1
0x81c0'4000, // jmp %g1
0x9e10'0005, // mov %g5, %o7
0x0000'0000, // .quad $plt_entry - $got_entry
0x0000'0000,
};
memcpy(buf, entry, sizeof(entry));
*(ub64 *)(buf + 24) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 4;
}
template <>
void EhFrameSection<E>::apply_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_SPARC_64:
case R_SPARC_UA64:
*(ub64 *)loc = val;
break;
case R_SPARC_DISP32:
*(ub32 *)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;
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;
switch (rel.r_type) {
case R_SPARC_64:
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_SPARC_5: {
i64 val = S + A;
check(val, 0, 1 << 5);
*(ub32 *)loc |= bits(val, 4, 0);
break;
}
case R_SPARC_6: {
i64 val = S + A;
check(val, 0, 1 << 6);
*(ub32 *)loc |= bits(val, 5, 0);
break;
}
case R_SPARC_7: {
i64 val = S + A;
check(val, 0, 1 << 7);
*(ub32 *)loc |= bits(val, 6, 0);
break;
}
case R_SPARC_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*(u8 *)loc = val;
break;
}
case R_SPARC_10: {
i64 val = S + A;
check(val, 0, 1 << 10);
*(ub32 *)loc |= bits(val, 9, 0);
break;
}
case R_SPARC_LO10:
case R_SPARC_LOPLT10:
*(ub32 *)loc |= bits(S + A, 9, 0);
break;
case R_SPARC_11: {
i64 val = S + A;
check(val, 0, 1 << 11);
*(ub32 *)loc |= bits(val, 10, 0);
break;
}
case R_SPARC_13: {
i64 val = S + A;
check(val, 0, 1 << 13);
*(ub32 *)loc |= bits(val, 12, 0);
break;
}
case R_SPARC_16:
case R_SPARC_UA16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
break;
}
case R_SPARC_22: {
i64 val = S + A;
check(val, 0, 1 << 22);
*(ub32 *)loc |= bits(val, 21, 0);
break;
}
case R_SPARC_32:
case R_SPARC_UA32:
case R_SPARC_PLT32: {
i64 val = S + A;
check(val, 0, 1LL << 32);
*(ub32 *)loc = val;
break;
}
case R_SPARC_PLT64:
case R_SPARC_UA64:
case R_SPARC_REGISTER:
*(ub64 *)loc = S + A;
break;
case R_SPARC_DISP8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*(u8 *)loc = val;
break;
}
case R_SPARC_DISP16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
break;
}
case R_SPARC_DISP32:
case R_SPARC_PCPLT32: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val;
break;
}
case R_SPARC_DISP64:
*(ub64 *)loc = S + A - P;
break;
case R_SPARC_WDISP16: {
i64 val = S + A - P;
check(val, -(1 << 16), 1 << 16);
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
break;
}
case R_SPARC_WDISP19: {
i64 val = S + A - P;
check(val, -(1 << 20), 1 << 20);
*(ub32 *)loc |= bits(val, 20, 2);
break;
}
case R_SPARC_WDISP22: {
i64 val = S + A - P;
check(val, -(1 << 23), 1 << 23);
*(ub32 *)loc |= bits(val, 23, 2);
break;
}
case R_SPARC_WDISP30:
case R_SPARC_WPLT30: {
i64 val = S + A - P;
check(val, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(val, 31, 2);
break;
}
case R_SPARC_HI22:
case R_SPARC_HIPLT22:
case R_SPARC_LM22:
*(ub32 *)loc |= bits(S + A, 31, 10);
break;
case R_SPARC_GOT10:
*(ub32 *)loc |= bits(G, 9, 0);
break;
case R_SPARC_GOT13:
check(G, 0, 1 << 12);
*(ub32 *)loc |= bits(G, 12, 0);
break;
case R_SPARC_GOT22:
*(ub32 *)loc |= bits(G, 31, 10);
break;
case R_SPARC_GOTDATA_HIX22: {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
break;
}
case R_SPARC_GOTDATA_LOX10: {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
break;
}
case R_SPARC_GOTDATA_OP_HIX22:
// We always have to relax a GOT load to a load immediate if a
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
// an addend for a local symbol.
if (sym.is_imported || sym.is_ifunc()) {
*(ub32 *)loc |= bits(G, 31, 10);
} else if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
} else {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
}
break;
case R_SPARC_GOTDATA_OP_LOX10: {
if (sym.is_imported || sym.is_ifunc()) {
*(ub32 *)loc |= bits(G, 9, 0);
} else if (sym.is_absolute()) {
i64 val = S + A;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
} else {
i64 val = S + A - GOT;
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
}
break;
}
case R_SPARC_GOTDATA_OP:
if (sym.is_imported || sym.is_ifunc())
break;
if (sym.is_absolute()) {
// ldx [ %g2 + %g1 ], %g1 → nop
*(ub32 *)loc = 0x0100'0000;
} else {
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
}
break;
case R_SPARC_PC10:
case R_SPARC_PCPLT10:
*(ub32 *)loc |= bits(S + A - P, 9, 0);
break;
case R_SPARC_PC22:
case R_SPARC_PCPLT22:
case R_SPARC_PC_LM22:
*(ub32 *)loc |= bits(S + A - P, 31, 10);
break;
case R_SPARC_OLO10:
*(ub32 *)loc |= bits(bits(S + A, 9, 0) + rel.r_type_data, 12, 0);
break;
case R_SPARC_HH22:
*(ub32 *)loc |= bits(S + A, 63, 42);
break;
case R_SPARC_HM10:
*(ub32 *)loc |= bits(S + A, 41, 32);
break;
case R_SPARC_PC_HH22:
*(ub32 *)loc |= bits(S + A - P, 63, 42);
break;
case R_SPARC_PC_HM10:
*(ub32 *)loc |= bits(S + A - P, 41, 32);
break;
case R_SPARC_HIX22:
*(ub32 *)loc |= bits(~(S + A), 31, 10);
break;
case R_SPARC_LOX10:
*(ub32 *)loc |= bits(S + A, 9, 0) | 0b1'1100'0000'0000;
break;
case R_SPARC_H44:
*(ub32 *)loc |= bits(S + A, 43, 22);
break;
case R_SPARC_M44:
*(ub32 *)loc |= bits(S + A, 21, 12);
break;
case R_SPARC_L44:
*(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);
break;
case R_SPARC_TLS_GD_LO10:
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (ctx.arg.is_static)
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
else
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
break;
case R_SPARC_TLS_LDM_HI22:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
break;
case R_SPARC_TLS_LDM_LO10:
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
break;
case R_SPARC_TLS_LDO_HIX22:
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 31, 10);
break;
case R_SPARC_TLS_LDO_LOX10:
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 9, 0);
break;
case R_SPARC_TLS_IE_HI22:
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
break;
case R_SPARC_TLS_IE_LO10:
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
break;
case R_SPARC_TLS_LE_HIX22:
*(ub32 *)loc |= bits(~(S + A - ctx.tp_addr), 31, 10);
break;
case R_SPARC_TLS_LE_LOX10:
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
break;
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_TLS_LDO_ADD:
case R_SPARC_TLS_IE_LD:
case R_SPARC_TLS_IE_LDX:
case R_SPARC_TLS_IE_ADD:
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)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
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);
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_SPARC_64:
case R_SPARC_UA64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub64 *)loc = *val;
else
*(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;
break;
}
case R_SPARC_TLS_DTPOFF32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
case R_SPARC_TLS_DTPOFF64:
*(ub64 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << 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);
// Scan relocations
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];
if (!sym.file) {
record_undef_error(ctx, rel);
continue;
}
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
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:
case R_SPARC_7:
case R_SPARC_10:
case R_SPARC_11:
case R_SPARC_13:
case R_SPARC_16:
case R_SPARC_22:
case R_SPARC_32:
case R_SPARC_REGISTER:
case R_SPARC_UA16:
case R_SPARC_UA32:
case R_SPARC_UA64:
case R_SPARC_PC_HM10:
case R_SPARC_OLO10:
case R_SPARC_LOX10:
case R_SPARC_HM10:
case R_SPARC_M44:
case R_SPARC_HIX22:
case R_SPARC_LO10:
case R_SPARC_L44:
case R_SPARC_LM22:
case R_SPARC_HI22:
case R_SPARC_H44:
case R_SPARC_HH22:
scan_absrel(ctx, sym, rel);
break;
case R_SPARC_PLT32:
case R_SPARC_WPLT30:
case R_SPARC_WDISP30:
case R_SPARC_HIPLT22:
case R_SPARC_LOPLT10:
case R_SPARC_PCPLT32:
case R_SPARC_PCPLT22:
case R_SPARC_PCPLT10:
case R_SPARC_PLT64:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_SPARC_GOT13:
case R_SPARC_GOT10:
case R_SPARC_GOT22:
case R_SPARC_GOTDATA_HIX22:
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_SPARC_GOTDATA_OP_HIX22:
if (sym.is_imported)
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_SPARC_DISP16:
case R_SPARC_DISP32:
case R_SPARC_DISP64:
case R_SPARC_DISP8:
case R_SPARC_PC10:
case R_SPARC_PC22:
case R_SPARC_PC_LM22:
case R_SPARC_WDISP16:
case R_SPARC_WDISP19:
case R_SPARC_WDISP22:
case R_SPARC_PC_HH22:
scan_pcrel(ctx, sym, rel);
break;
case R_SPARC_TLS_GD_HI22:
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
case R_SPARC_TLS_LDM_HI22:
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
case R_SPARC_TLS_IE_HI22:
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_SPARC_GOTDATA_OP_LOX10:
case R_SPARC_GOTDATA_OP:
case R_SPARC_GOTDATA_LOX10:
case R_SPARC_TLS_GD_LO10:
case R_SPARC_TLS_GD_ADD:
case R_SPARC_TLS_LDM_LO10:
case R_SPARC_TLS_LDM_ADD:
case R_SPARC_TLS_LDO_HIX22:
case R_SPARC_TLS_LDO_LOX10:
case R_SPARC_TLS_LDO_ADD:
case R_SPARC_TLS_IE_ADD:
case R_SPARC_TLS_IE_LD:
case R_SPARC_TLS_IE_LDX:
case R_SPARC_TLS_IE_LO10:
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
case R_SPARC_SIZE32:
break;
default:
Fatal(ctx) << *this << ": scan_relocations: " << rel;
}
}
}
// __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
+284 -245
View File
@@ -1,3 +1,31 @@
// Supporting x86-64 is straightforward. Unlike its predecessor, i386,
// x86-64 supports PC-relative addressing for position-independent code.
// Being CISC, its instructions are variable in size. Branch instructions
// take 4 bytes offsets, so we don't need range extension thunks.
//
// The psABI specifies %r11 as neither caller- nor callee-saved. It's
// intentionally left out so that we can use it as a scratch register in
// PLT.
//
// Thread Pointer (TP) is stored not to a general-purpose register but to
// FS segment register. Segment register is a 64-bits register which can
// be used as a base address for memory access. Each thread has a unique
// FS value, and they access their thread-local variables relative to FS
// as %fs:offset_from_tp.
//
// The value of a segment register itself is not generally readable from
// the user space. As a workaround, libc initializes %fs:0 (the first word
// referenced by FS) to the value of %fs itself. So we can obtain TP just
// by `mov %fs:0, %rax` if we need it.
//
// For historical reasons, TP points past the end of the TLS block on x86.
// This is contrary to other psABIs which usually use the beginning of the
// 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/mold/wiki/i386-psabi.pdf
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
#include "mold.h"
namespace mold::elf {
@@ -17,53 +45,47 @@ using E = X86_64;
// Our PLT entry clobbers %r11, but that's fine because the resolver
// function (_dl_runtime_resolve) clobbers %r11 anyway.
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
memset(buf, 0xcc, this->shdr.sh_size);
// Write PLT header
static const u8 plt0[] = {
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0x41, 0x53, // push %r11
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, // (padding)
};
memcpy(buf, plt0, sizeof(plt0));
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 4;
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 2;
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
}
// Write PLT entries
static const u8 data[] = {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
};
for (Symbol<E> *sym : symbols) {
i64 idx = sym->get_plt_idx(ctx);
u8 *ent = buf + E::plt_hdr_size + idx * E::plt_size;
memcpy(ent, data, sizeof(data));
*(ul32 *)(ent + 6) = idx;
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
}
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
}
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
memset(buf, 0xcc, this->shdr.sh_size);
static const u8 data[] = {
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
0xcc, 0xcc, // (padding)
};
for (Symbol<E> *sym : symbols) {
u8 *ent = buf + sym->get_pltgot_idx(ctx) * E::pltgot_size;
memcpy(ent, data, sizeof(data));
*(ul32 *)(ent + 6) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 10;
}
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
}
template <>
@@ -72,22 +94,23 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_X86_64_NONE:
return;
case R_NONE:
break;
case R_X86_64_32:
*(ul32 *)loc = val;
return;
break;
case R_X86_64_64:
*(ul64 *)loc = val;
return;
break;
case R_X86_64_PC32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
return;
break;
case R_X86_64_PC64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
return;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
unreachable();
}
static u32 relax_gotpcrelx(u8 *loc) {
@@ -169,171 +192,171 @@ static u32 relax_gotpc32_tlsdesc(u8 *loc) {
// scan_relocations().
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<const ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
ElfRel<E> *dynrel = nullptr;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_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_X86_64_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
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 write8 = [&](u64 val) {
overflow_check(val, 0, 1 << 8);
*loc = val;
};
auto write8s = [&](u64 val) {
overflow_check(val, -(1 << 7), 1 << 7);
*loc = val;
};
auto write16 = [&](u64 val) {
overflow_check(val, 0, 1 << 16);
*(ul16 *)loc = val;
};
auto write16s = [&](u64 val) {
overflow_check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
};
auto write32 = [&](u64 val) {
overflow_check(val, 0, (i64)1 << 32);
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
};
auto write32s = [&](u64 val) {
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
check(val, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val;
};
auto write64 = [&](u64 val) {
*(ul64 *)loc = val;
};
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
#define GOT ctx.gotplt->shdr.sh_addr
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 GOTPLT = ctx.gotplt->shdr.sh_addr;
switch (rel.r_type) {
case R_X86_64_8:
write8(S + A);
continue;
case R_X86_64_16:
write16(S + A);
continue;
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_X86_64_32:
write32(S + A);
continue;
break;
case R_X86_64_32S:
write32s(S + A);
continue;
break;
case R_X86_64_64:
apply_abs_dyn_rel(ctx, sym, rel, loc, S, A, P, dynrel);
continue;
case R_X86_64_PC8:
write8s(S + A - P);
continue;
case R_X86_64_PC16:
write16s(S + A - P);
continue;
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
break;
case R_X86_64_PC8: {
i64 val = S + A - P;
check(val, -(1 << 7), 1 << 7);
*loc = val;
break;
}
case R_X86_64_PC16: {
i64 val = S + A - P;
check(val, -(1 << 15), 1 << 15);
*(ul16 *)loc = val;
break;
}
case R_X86_64_PC32:
write32s(S + A - P);
continue;
case R_X86_64_PC64:
write64(S + A - P);
continue;
case R_X86_64_PLT32:
write32s(S + A - P);
continue;
case R_X86_64_PLTOFF64:
write64(S + A - GOT);
break;
case R_X86_64_PC64:
*(ul64 *)loc = S + A - P;
break;
case R_X86_64_GOT32:
write32s(G + A);
continue;
break;
case R_X86_64_GOT64:
write64(G + A);
continue;
*(ul64 *)loc = G + A;
break;
case R_X86_64_GOTOFF64:
write64(S + A - GOT);
continue;
case R_X86_64_PLTOFF64:
*(ul64 *)loc = S + A - GOTPLT;
break;
case R_X86_64_GOTPC32:
write32s(GOT + A - P);
continue;
write32s(GOTPLT + A - P);
break;
case R_X86_64_GOTPC64:
write64(GOT + A - P);
continue;
*(ul64 *)loc = GOTPLT + A - P;
break;
case R_X86_64_GOTPCREL:
write32s(G + GOT + A - P);
continue;
write32s(G + GOTPLT + A - P);
break;
case R_X86_64_GOTPCREL64:
write64(G + GOT + A - P);
continue;
*(ul64 *)loc = G + GOTPLT + A - P;
break;
case R_X86_64_GOTPCRELX:
if (sym.get_got_idx(ctx) == -1) {
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
u32 insn = relax_gotpcrelx(loc - 2);
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
} else {
write32s(G + GOT + A - P);
}
continue;
break;
case R_X86_64_REX_GOTPCRELX:
if (sym.get_got_idx(ctx) == -1) {
if (sym.has_got(ctx)) {
write32s(G + GOTPLT + A - P);
} else {
u32 insn = relax_rex_gotpcrelx(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
} else {
write32s(G + GOT + A - P);
}
continue;
break;
case R_X86_64_TLSGD:
if (sym.get_tlsgd_idx(ctx) == -1) {
// Relax GD to LE
i64 val = S - ctx.tls_end + A + 4;
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
if (sym.has_tlsgd(ctx)) {
write32s(sym.get_tlsgd_addr(ctx) + A - P);
} else {
// Relax GD to LE. If we are creating an exectuable, the offset of
// a thread-local variable from TP is a link-time constant. So we
// don't need to call __tls_get_addr to obtain the address of a TLV.
i64 val = S - ctx.tp_addr;
assert(A == -4);
check(val, -(1LL << 31), 1LL << 31);
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32:
case R_X86_64_PC32:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
//
// or
//
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
};
memcpy(loc - 4, insn, sizeof(insn));
*(ul32 *)(loc + 8) = val;
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
@@ -345,37 +368,60 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
i++;
} else {
write32s(sym.get_tlsgd_addr(ctx) + A - P);
}
continue;
break;
case R_X86_64_TLSLD:
if (ctx.got->tlsld_idx == -1) {
// Relax LD to LE
if (ctx.got->has_tlsld(ctx)) {
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
} else {
// Relax LD to LE. If we are creating an executable, we don't need
// to call __tls_get_addr to obtain the address of the beginning
// of the current TLS block. TP points past the end of the TLS
// block, and we know the exact size of the TLS block we have
// created, so we can just subtract it from TP.
switch (rels[i + 1].r_type) {
case R_X86_64_PLT32: {
case R_X86_64_PLT32:
case R_X86_64_PC32: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// e8 00 00 00 00 call __tls_get_addr
static const u8 insn[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
break;
}
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCRELX: {
// The original instructions are the following:
//
// 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[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x90, // nop
0x31, 0xc0, // xor %eax, %eax
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));
break;
}
case R_X86_64_PLTOFF64: {
// The original instructions are the following:
//
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
// 48 01 d8 add %rbx, %rax
// ff d0 call *%rax
static const u8 insn[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x66, 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, // nop
0x31, 0xc0, // xor %eax, %eax
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
};
memcpy(loc - 3, insn, sizeof(insn));
break;
@@ -384,73 +430,63 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
unreachable();
}
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
assert(A == -4);
i++;
} else {
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
}
continue;
break;
case R_X86_64_DTPOFF32:
if (ctx.arg.relax && !ctx.arg.shared)
write32s(S + A - ctx.tls_end);
else
write32s(S + A - ctx.tls_begin);
continue;
write32s(S + A - ctx.dtp_addr);
break;
case R_X86_64_DTPOFF64:
if (ctx.arg.relax && !ctx.arg.shared)
write64(S + A - ctx.tls_end);
else
write64(S + A - ctx.tls_begin);
continue;
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_TPOFF32:
write32s(S + A - ctx.tls_end);
continue;
write32s(S + A - ctx.tp_addr);
break;
case R_X86_64_TPOFF64:
write64(S + A - ctx.tls_end);
continue;
*(ul64 *)loc = S + A - ctx.tp_addr;
break;
case R_X86_64_GOTTPOFF:
if (sym.get_gottp_idx(ctx) == -1) {
if (sym.has_gottp(ctx)) {
write32s(sym.get_gottp_addr(ctx) + A - P);
} else {
u32 insn = relax_gottpoff(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - ctx.tls_end + 4);
} else {
write32s(sym.get_gottp_addr(ctx) + A - P);
write32s(S - ctx.tp_addr);
assert(A == -4);
}
continue;
break;
case R_X86_64_GOTPC32_TLSDESC:
if (sym.get_tlsdesc_idx(ctx) == -1) {
if (sym.has_tlsdesc(ctx)) {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
} else {
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - ctx.tls_end + 4);
} else {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
write32s(S - ctx.tp_addr);
assert(A == -4);
}
continue;
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
continue;
break;
case R_X86_64_SIZE64:
write64(sym.esym().st_size + A);
continue;
*(ul64 *)loc = sym.esym().st_size + A;
break;
case R_X86_64_TLSDESC_CALL:
if (sym.get_tlsdesc_idx(ctx) == -1) {
if (!sym.has_tlsdesc(ctx)) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
continue;
break;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
@@ -472,7 +508,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_X86_64_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -483,47 +519,43 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
continue;
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
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 write8 = [&](u64 val) {
overflow_check(val, 0, 1 << 8);
*loc = val;
};
auto write16 = [&](u64 val) {
overflow_check(val, 0, 1 << 16);
*(ul16 *)loc = val;
};
auto write32 = [&](u64 val) {
overflow_check(val, 0, (i64)1 << 32);
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
};
auto write32s = [&](u64 val) {
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
check(val, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val;
};
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? (u64)addend : (u64)rel.r_addend)
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:
write8(S + A);
case R_X86_64_8: {
i64 val = S + A;
check(val, 0, 1 << 8);
*loc = val;
break;
case R_X86_64_16:
write16(S + A);
}
case R_X86_64_16: {
i64 val = S + A;
check(val, 0, 1 << 16);
*(ul16 *)loc = val;
break;
}
case R_X86_64_32:
write32(S + A);
break;
@@ -531,25 +563,22 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
write32s(S + A);
break;
case R_X86_64_64:
if (!frag) {
if (std::optional<u64> val = get_tombstone(sym)) {
*(ul64 *)loc = *val;
break;
}
}
*(ul64 *)loc = S + A;
break;
case R_X86_64_DTPOFF32:
if (std::optional<u64> val = get_tombstone(sym))
*(ul32 *)loc = *val;
else
write32s(S + A - ctx.tls_begin);
break;
case R_X86_64_DTPOFF64:
if (std::optional<u64> val = get_tombstone(sym))
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A - ctx.tls_begin;
*(ul64 *)loc = S + A;
break;
case R_X86_64_DTPOFF32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
write32s(S + A - ctx.dtp_addr);
break;
case R_X86_64_DTPOFF64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul64 *)loc = *val;
else
*(ul64 *)loc = S + A - ctx.dtp_addr;
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
@@ -562,9 +591,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
<< rel;
break;
}
#undef S
#undef A
}
}
@@ -583,7 +609,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_X86_64_NONE)
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
@@ -594,24 +620,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
continue;
}
if (sym.get_type() == STT_GNU_IFUNC)
sym.flags |= (NEEDS_GOT | NEEDS_PLT);
if (sym.is_ifunc())
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
switch (rel.r_type) {
case R_X86_64_8:
case R_X86_64_16:
case R_X86_64_32:
case R_X86_64_32S:
scan_abs_rel(ctx, sym, rel);
scan_absrel(ctx, sym, rel);
break;
case R_X86_64_64:
scan_abs_dyn_rel(ctx, sym, rel);
scan_dyn_absrel(ctx, sym, rel);
break;
case R_X86_64_PC8:
case R_X86_64_PC16:
case R_X86_64_PC32:
case R_X86_64_PC64:
scan_pcrel_rel(ctx, sym, rel);
scan_pcrel(ctx, sym, rel);
break;
case R_X86_64_GOT32:
case R_X86_64_GOT64:
@@ -619,7 +645,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_X86_64_GOTPC64:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCREL64:
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
case R_X86_64_GOTPCRELX: {
if (rel.r_addend != -4)
@@ -628,7 +654,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_gotpcrelx(loc - 2);
if (!do_relax)
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_X86_64_REX_GOTPCRELX: {
@@ -638,61 +664,74 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
if (!do_relax)
sym.flags |= NEEDS_GOT;
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
break;
}
case R_X86_64_PLT32:
case R_X86_64_PLTOFF64:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
break;
case R_X86_64_TLSGD: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
if (relax_tlsgd(ctx, sym))
i++;
else
sym.flags |= NEEDS_TLSGD;
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
break;
}
case R_X86_64_TLSLD: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (u32 ty = rels[i + 1].r_type;
ty != R_X86_64_PLT32 && ty != R_X86_64_PLTOFF64 &&
ty != R_X86_64_GOTPCREL && ty != R_X86_64_GOTPCRELX)
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
ty != R_X86_64_GOTPCRELX)
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
if (ctx.arg.relax && !ctx.arg.shared)
if (relax_tlsld(ctx))
i++;
else
ctx.needs_tlsld = true;
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTTPOFF: {
ctx.has_gottp_rel = true;
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
!sym.is_imported && relax_gottpoff(loc - 3);
if (!do_relax)
sym.flags |= NEEDS_GOTTP;
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
<< " against an invalid code sequence";
bool do_relax = ctx.relax_tlsdesc && !sym.is_imported;
if (!do_relax)
sym.flags |= NEEDS_TLSDESC;
if (!relax_tlsdesc(ctx, sym))
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
break;
}
case R_X86_64_GOTOFF64:
+205 -44
View File
@@ -16,7 +16,7 @@
namespace mold::elf {
static const char helpmsg[] = R"(
inline const char helpmsg[] = R"(
Options:
--help Report usage information
-v, --version Report version information
@@ -61,6 +61,8 @@ Options:
--Tdata Set address to .data
--Ttext Set address to .text
--allow-multiple-definition Allow multiple definitions
--apply-dynamic-relocs Apply link-time values for dynamic relocations (defualt)
--no-apply-dynamic-relocs
--as-needed Only set DT_NEEDED if used
--no-as-needed
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
@@ -70,7 +72,7 @@ Options:
--color-diagnostics=[auto,always,never]
Use colors in diagnostics
--color-diagnostics Alias for --color-diagnostics=always
--compress-debug-sections [none,zlib,zlib-gabi,zlib-gnu]
--compress-debug-sections [none,zlib,zlib-gabi,zstd]
Compress .debug_* sections
--dc Ignored
--dependency-file=FILE Write Makefile-style dependency rules to FILE
@@ -79,11 +81,11 @@ Options:
--no-demangle
--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 Read a list of dynamic symbols (implies -Bsymbolic)
--eh-frame-hdr Create .eh_frame_hdr section
--no-eh-frame-hdr
--enable-new-dtags Ignored
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries hidden
--export-dynamic-symbol Put symbols matching glob in the dynamic symbol table
--export-dynamic-symbol-list
@@ -103,7 +105,7 @@ Options:
--ignore-data-address-equality
Allow merging non-executable sections with --icf
--image-base ADDR Set the base address to a given value
--init SYMBOL Call SYMBOl at load-time
--init SYMBOL Call SYMBOL at load-time
--no-undefined Report undefined symbols (even with --shared)
--noinhibit-exec Create an output file even if errors occur
--oformat=binary Omit ELF, section and program headers
@@ -113,12 +115,12 @@ Options:
--perf Print performance statistics
--pie, --pic-executable Create a position independent executable
--no-pie, --no-pic-executable
--pop-state Pop state of flags governing input file handling
--pop-state Restore state of flags governing input file handling
--print-gc-sections Print removed unreferenced sections
--no-print-gc-sections
--print-icf-sections Print folded identical sections
--no-print-icf-sections
--push-state Pop state of flags governing input file handling
--push-state Save state of flags governing input file handling
--quick-exit Use quick_exit to exit (default)
--no-quick-exit
--relax Optimize instructions (default)
@@ -147,6 +149,8 @@ Options:
--threads Use multiple threads (default)
--no-threads
--trace Print name of each input file
--undefined-version Do not report version scripts that refer undefined symbols
--no-undefined-version Report version scripts that refer undefined symbols (default)
--unique PATTERN Don't merge input sections that match a given pattern
--unresolved-symbols [report-all,ignore-all,ignore-in-object-files,ignore-in-shared-libs]
How to handle unresolved symbols
@@ -181,6 +185,8 @@ Options:
-z nodump Mark DSO not available to dldump
-z now Disable lazy function resolution
-z origin Mark object requiring immediate $ORIGIN processing at runtime
-z pack-relative-relocs Alias for --pack-dyn-relocs=relr
-z nopack-relative-relocs
-z separate-loadable-segments
Separate all loadable segments to different pages
-z separate-code Separate code and data into different pages
@@ -191,8 +197,8 @@ Options:
-z notext
-z textoff
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf64-littleriscv
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf64lriscv)";
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 elf32-hppa-linux
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 hppalinux)";
static std::vector<std::string> add_dashes(std::string name) {
// Single-letter option
@@ -311,6 +317,63 @@ static bool is_file(std::string_view path) {
(st.st_mode & S_IFMT) != S_IFDIR;
}
template <typename E>
static std::vector<SectionOrder>
parse_section_order(Context<E> &ctx, std::string_view arg) {
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase |
std::regex_constants::optimize;
static std::regex re1(R"(^\s*(TEXT|DATA|RODATA|BSS)(?:\s|$))", flags);
static std::regex re2(R"(^\s*([a-zA-Z0-9_.][^\s]*|EHDR|PHDR)(?:\s|$))", flags);
static std::regex re3(R"(^\s*=(0x[0-9a-f]+|\d+)(?:\s|$))", flags);
static std::regex re4(R"(^\s*%(0x[0-9a-f]+|\d*)(?:\s|$))", flags);
static std::regex re5(R"(^\s*!(\S+)(?:\s|$))", flags);
std::vector<SectionOrder> vec;
arg = string_trim(arg);
while (!arg.empty()) {
SectionOrder ord;
std::cmatch m;
if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re1)) {
ord.type = SectionOrder::GROUP;
ord.name = m[1].str();
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re2)) {
ord.type = SectionOrder::SECTION;
ord.name = m[1].str();
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re3)) {
ord.type = SectionOrder::ADDR;
std::string s = m[1];
ord.value = std::stoull(s, nullptr, s.starts_with("0x") ? 16 : 10);
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re4)) {
ord.type = SectionOrder::ALIGN;
std::string s = m[1];
ord.value = std::stoull(s, nullptr, s.starts_with("0x") ? 16 : 10);
} else if (std::regex_search(arg.data(), arg.data() + arg.size(), m, re5)) {
ord.type = SectionOrder::SYMBOL;
ord.name = m[1].str();
} else {
Fatal(ctx) << "--section-order: parse error: " << arg;
}
vec.push_back(ord);
arg = arg.substr(m[0].length());
}
bool is_first = true;
for (SectionOrder &ord : vec) {
if (ord.type == SectionOrder::SECTION) {
if (is_first) {
is_first = false;
} else if (ord.name == "EHDR")
Fatal(ctx) << "--section-order: EHDR must be the first "
<< "section specifier: " << arg;
}
}
return vec;
}
template <typename E>
static std::variant<Symbol<E> *, u64>
parse_defsym_value(Context<E> &ctx, std::string_view s) {
@@ -340,12 +403,29 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
bool version_shown = false;
bool warn_shared_textrel = false;
std::optional<SeparateCodeKind> z_separate_code;
std::optional<bool> z_relro;
std::unordered_set<std::string_view> rpaths;
auto add_rpath = [&](std::string_view arg) {
if (rpaths.insert(arg).second) {
if (!ctx.arg.rpaths.empty())
ctx.arg.rpaths += ':';
ctx.arg.rpaths += arg;
}
};
// 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>)
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 (is_sparc<E>)
ctx.arg.apply_dynamic_relocs = false;
auto read_arg = [&](std::string name) {
for (std::string opt : add_dashes(name)) {
if (args[0] == opt) {
@@ -438,21 +518,46 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
SyncOut(ctx) << mold_version
<< "\n Supported emulations:\n elf_x86_64\n elf_i386\n"
<< " aarch64linux\n armelf_linux_eabi\n elf64lriscv\n"
<< " elf32lriscv";
<< " 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 hppalinux";
version_shown = true;
} else if (read_arg("m")) {
if (arg == "elf_x86_64") {
ctx.arg.emulation = MachineType::X86_64;
ctx.arg.emulation = X86_64::target_name;
} else if (arg == "elf_i386") {
ctx.arg.emulation = MachineType::I386;
ctx.arg.emulation = I386::target_name;
} else if (arg == "aarch64linux") {
ctx.arg.emulation = MachineType::ARM64;
ctx.arg.emulation = ARM64::target_name;
} else if (arg == "armelf_linux_eabi") {
ctx.arg.emulation = MachineType::ARM32;
ctx.arg.emulation = ARM32::target_name;
} else if (arg == "elf64lriscv") {
ctx.arg.emulation = MachineType::RISCV64;
ctx.arg.emulation = RV64LE::target_name;
} else if (arg == "elf64briscv") {
ctx.arg.emulation = RV64BE::target_name;
} else if (arg == "elf32lriscv") {
ctx.arg.emulation = MachineType::RISCV32;
ctx.arg.emulation = RV32LE::target_name;
} else if (arg == "elf32briscv") {
ctx.arg.emulation = RV32BE::target_name;
} else if (arg == "elf32ppc" || arg == "elf32ppclinux") {
ctx.arg.emulation = PPC32::target_name;
} else if (arg == "elf64ppc") {
ctx.arg.emulation = PPC64V1::target_name;
} else if (arg == "elf64lppc") {
ctx.arg.emulation = PPC64V2::target_name;
} else if (arg == "elf64_s390") {
ctx.arg.emulation = S390X::target_name;
} else if (arg == "elf64_sparc") {
ctx.arg.emulation = SPARC64::target_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;
} else if (arg == "hppalinux") {
ctx.arg.emulation = HPPA32::target_name;
} else {
Fatal(ctx) << "unknown -m argument: " << arg;
}
@@ -473,6 +578,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
} else if (read_flag("q") || read_flag("emit-relocs")) {
ctx.arg.emit_relocs = true;
ctx.arg.discard_locals = false;
} else if (read_arg("e") || read_arg("entry")) {
ctx.arg.entry = arg;
} else if (read_arg("Map")) {
@@ -496,6 +602,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
} else if (read_flag("start-lib")) {
remaining.push_back("--start-lib");
} else if (read_flag("start-stop")) {
ctx.arg.start_stop = true;
} else if (read_arg("dependency-file")) {
ctx.arg.dependency_file = arg;
} else if (read_arg("defsym")) {
@@ -573,6 +681,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.soname = arg;
} else if (read_flag("allow-multiple-definition")) {
ctx.arg.allow_multiple_definition = true;
} else if (read_flag("apply-dynamic-relocs")) {
ctx.arg.apply_dynamic_relocs = true;
} else if (read_flag("no-apply-dynamic-relocs")) {
ctx.arg.apply_dynamic_relocs = false;
} else if (read_flag("trace")) {
ctx.arg.trace = true;
} else if (read_flag("eh-frame-hdr")) {
@@ -595,6 +707,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.gdb_index = false;
} else if (read_flag("r") || read_flag("relocatable")) {
ctx.arg.relocatable = true;
ctx.arg.emit_relocs = true;
ctx.arg.discard_locals = false;
} else if (read_flag("relocatable-merge-sections")) {
ctx.arg.relocatable_merge_sections = true;
} else if (read_flag("perf")) {
ctx.arg.perf = true;
} else if (read_flag("pack-dyn-relocs=relr")) {
@@ -631,11 +747,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.enable_new_dtags = true;
} else if (read_flag("disable-new-dtags")) {
ctx.arg.enable_new_dtags = false;
} else if (read_flag("execute-only")) {
ctx.arg.execute_only = true;
} else if (read_arg("compress-debug-sections")) {
if (arg == "zlib" || arg == "zlib-gabi")
ctx.arg.compress_debug_sections = COMPRESS_GABI;
else if (arg == "zlib-gnu")
ctx.arg.compress_debug_sections = COMPRESS_GNU;
ctx.arg.compress_debug_sections = COMPRESS_ZLIB;
else if (arg == "zstd")
ctx.arg.compress_debug_sections = COMPRESS_ZSTD;
else if (arg == "none")
ctx.arg.compress_debug_sections = COMPRESS_NONE;
else
@@ -653,12 +771,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.oformat_binary = true;
} else if (read_arg("retain-symbols-file")) {
read_retain_symbols_file(ctx, arg);
} else if (read_arg("section-align")) {
size_t pos = arg.find('=');
if (pos == arg.npos || pos == arg.size() - 1)
Fatal(ctx) << "--section-align: syntax error: " << arg;
i64 value = parse_number(ctx, "section-align", arg.substr(pos + 1));
if (!has_single_bit(value))
Fatal(ctx) << "--section-align=" << arg << ": value must be a power of 2";
ctx.arg.section_align[arg.substr(0, pos)] = value;
} else if (read_arg("section-start")) {
size_t pos = arg.find('=');
if (pos == arg.npos || pos == arg.size() - 1)
Fatal(ctx) << "-section-start: syntax error: " << arg;
Fatal(ctx) << "--section-start: syntax error: " << arg;
ctx.arg.section_start[arg.substr(0, pos)] =
parse_hex(ctx, "section-start", arg.substr(pos + 1));
} else if (read_arg("section-order")) {
ctx.arg.section_order = parse_section_order(ctx, arg);
} else if (read_arg("Tbss")) {
ctx.arg.section_start[".bss"] = parse_hex(ctx, "Tbss", arg);
} else if (read_arg("Tdata")) {
@@ -685,15 +813,18 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.page_size = parse_number(ctx, "-z max-page-size", arg);
if (!has_single_bit(ctx.page_size))
Fatal(ctx) << "-z max-page-size " << arg << ": value must be a power of 2";
} else if (read_z_arg("start-stop-visibility")) {
if (arg != "hidden")
Fatal(ctx) << "-z start-stop-visibility: unsupported visibility: " << arg;
} else if (read_z_flag("noexecstack")) {
ctx.arg.z_execstack = false;
} else if (read_z_flag("relro")) {
ctx.arg.z_relro = true;
z_relro = true;
} else if (read_z_flag("norelro")) {
ctx.arg.z_relro = false;
z_relro = false;
} else if (read_z_flag("defs")) {
ctx.arg.z_defs = true;
} else if (read_z_flag("nodefs")) {
} else if (read_z_flag("undefs")) {
ctx.arg.z_defs = false;
} else if (read_z_flag("nodlopen")) {
ctx.arg.z_dlopen = false;
@@ -726,12 +857,20 @@ 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_z_flag("pack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = true;
} else if (read_z_flag("nopack-relative-relocs")) {
ctx.arg.pack_dyn_relocs_relr = false;
} else if (read_z_flag("separate-loadable-segments")) {
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
} else if (read_z_flag("separate-code")) {
ctx.arg.z_separate_code = SEPARATE_CODE;
z_separate_code = SEPARATE_CODE;
} else if (read_z_flag("noseparate-code")) {
ctx.arg.z_separate_code = NOSEPARATE_CODE;
z_separate_code = NOSEPARATE_CODE;
} else if (read_z_flag("dynamic-undefined-weak")) {
ctx.arg.z_dynamic_undefined_weak = true;
} else if (read_z_flag("nodynamic-undefined-weak")) {
ctx.arg.z_dynamic_undefined_weak = false;
} else if (read_flag("no-undefined")) {
ctx.arg.z_defs = true;
} else if (read_flag("fatal-warnings")) {
@@ -767,6 +906,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.ignore_data_address_equality = true;
} else if (read_arg("image-base")) {
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
} else if (read_arg("physical-image-base")) {
ctx.arg.physical_image_base = parse_number(ctx, "physical-image-base", arg);
} else if (read_flag("print-icf-sections")) {
ctx.arg.print_icf_sections = true;
} else if (read_flag("no-print-icf-sections")) {
@@ -861,17 +1002,16 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("error-unresolved-symbols")) {
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
} else if (read_arg("rpath")) {
if (!ctx.arg.rpaths.empty())
ctx.arg.rpaths += ":";
ctx.arg.rpaths += arg;
add_rpath(arg);
} else if (read_arg("R")) {
if (is_file(arg))
Fatal(ctx) << "-R" << arg
<< ": -R as an alias for --just-symbols is not supported";
if (!ctx.arg.rpaths.empty())
ctx.arg.rpaths += ":";
ctx.arg.rpaths += arg;
add_rpath(arg);
} else if (read_flag("undefined-version")) {
ctx.arg.undefined_version = true;
} else if (read_flag("no-undefined-version")) {
ctx.arg.undefined_version = false;
} else if (read_flag("build-id")) {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 20;
@@ -908,7 +1048,6 @@ 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("apply-dynamic-relocs")) {
} else if (read_arg("O")) {
} else if (read_flag("O0")) {
} else if (read_flag("O1")) {
@@ -929,7 +1068,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} 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")) {
} else if (read_flag("no-undefined-version")) {
} else if (read_arg("sort-section")) {
} else if (read_flag("sort-common")) {
} else if (read_flag("dc")) {
@@ -942,11 +1080,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
} else if (read_flag("warn-constructors")) {
} else if (read_flag("warn-execstack")) {
} else if (read_flag("no-warn-execstack")) {
} else if (read_flag("secure-plt")) {
} else if (read_arg("rpath-link")) {
} else if (read_z_flag("combreloc")) {
} else if (read_z_flag("nocombreloc")) {
} 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_arg("version-script")) {
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
// are treated as positional arguments even if they are actually not
@@ -1026,6 +1166,18 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
if (ctx.arg.relocatable)
ctx.arg.is_static = true;
// --section-order implies `-z separate-loadable-segments`
if (z_separate_code)
ctx.arg.z_separate_code = *z_separate_code;
else if (!ctx.arg.section_order.empty())
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
// --section-order implies `-z norelro`
if (z_relro)
ctx.arg.z_relro = *z_relro;
else if (!ctx.arg.section_order.empty())
ctx.arg.z_relro = false;
if (!ctx.arg.shared) {
if (!ctx.arg.filter.empty())
Fatal(ctx) << "-filter may not be used without -shared";
@@ -1033,12 +1185,22 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
Fatal(ctx) << "-auxiliary may not be used without -shared";
}
if (ctx.arg.thread_count == 0)
ctx.arg.thread_count = get_default_thread_count();
if (!ctx.arg.apply_dynamic_relocs && !E::is_rela)
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
<< E::target_name;
if (is_sparc<E> && ctx.arg.apply_dynamic_relocs)
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
if (!ctx.arg.section_start.empty() && !ctx.arg.section_order.empty())
Fatal(ctx) << "--section-start may not be used with --section-order";
if (ctx.arg.image_base % ctx.page_size)
Fatal(ctx) << "-image-base must be a multiple of -max-page-size";
if (ctx.arg.thread_count == 0)
ctx.arg.thread_count = get_default_thread_count();
if (char *env = getenv("MOLD_REPRO"); env && env[0])
ctx.arg.repro = true;
@@ -1059,11 +1221,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
ctx.arg.undefined.push_back(ctx.arg.entry);
// TLSDESC relocs must be always relaxed for statically-linked
// executables even if -no-relax is given. It is because a
// statically-linked executable doesn't contain a tranpoline
// function needed for TLSDESC.
ctx.relax_tlsdesc = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
// --oformat=binary implies --strip-all because without a section
// header, there's no way to identify the locations of a symbol
// table in an output file in the first place.
if (ctx.arg.oformat_binary)
ctx.arg.strip_all = true;
// By default, mold tries to ovewrite to an output file if exists
// because at least on Linux, writing to an existing file is much
@@ -1083,9 +1245,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
return remaining;
}
#define INSTANTIATE(E) \
template std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
} // namespace mold::elf
+20 -27
View File
@@ -77,9 +77,9 @@ read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
while (!data.empty()) {
if (data.size() < 4)
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
if (*(ul32 *)data.data() == 0xffffffff)
if (*(U32<E> *)data.data() == 0xffffffff)
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
i64 len = *(ul32 *)data.data() + 4;
i64 len = *(U32<E> *)data.data() + 4;
vec.push_back(data.substr(0, len));
data = data.substr(len);
}
@@ -116,15 +116,15 @@ std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
if (contents.size() < 14)
Fatal(ctx) << isec << ": corrupted header";
u32 len = *(ul32 *)contents.data() + 4;
u32 debug_info_offset = *(ul32 *)(contents.data() + 6);
u32 len = *(U32<E> *)contents.data() + 4;
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
std::string_view data = contents.substr(14, len - 14);
contents = contents.substr(len);
while (!data.empty()) {
u32 offset = *(ul32 *)data.data();
u32 offset = *(U32<E> *)data.data();
data = data.substr(4);
if (offset == 0)
break;
@@ -176,7 +176,7 @@ static std::tuple<u8 *, u8 *, u32>
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
// Read .debug_info to find the record at a given offset.
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
u32 dwarf_version = *(ul16 *)(cu + 4);
u32 dwarf_version = *(U16<E> *)(cu + 4);
u32 abbrev_offset;
// Skip a header.
@@ -184,14 +184,14 @@ find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
case 2:
case 3:
case 4:
abbrev_offset = *(ul32 *)(cu + 6);
abbrev_offset = *(U32<E> *)(cu + 6);
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
<< address_size;
cu += 11;
break;
case 5: {
abbrev_offset = *(ul32 *)(cu + 8);
abbrev_offset = *(U32<E> *)(cu + 8);
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
<< address_size;
@@ -309,13 +309,13 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
case DW_FORM_strx2:
case DW_FORM_addrx2:
case DW_FORM_ref2: {
u64 val = *(ul16 *)cu;
u64 val = *(U16<E> *)cu;
cu += 2;
return val;
}
case DW_FORM_strx3:
case DW_FORM_addrx3: {
u64 val = *(ul24 *)cu;
u64 val = *(U24<E> *)cu;
cu += 3;
return val;
}
@@ -326,13 +326,13 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
case DW_FORM_strx4:
case DW_FORM_addrx4:
case DW_FORM_ref4: {
u64 val = *(ul32 *)cu;
u64 val = *(U32<E> *)cu;
cu += 4;
return val;
}
case DW_FORM_data8:
case DW_FORM_ref8: {
u64 val = *(ul64 *)cu;
u64 val = *(U64<E> *)cu;
cu += 8;
return val;
}
@@ -359,8 +359,7 @@ inline u64 DebugInfoReader<E>::read(u64 form) {
}
}
// Read a range list from .debug_ranges starting at the given offset
// (until an end of list entry).
// Read a range list from .debug_ranges starting at the given offset.
template <typename E>
static std::vector<u64>
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
@@ -379,8 +378,7 @@ read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *range) {
return vec;
}
// Read a range list from .debug_rnglists starting at the given offset
// (until an end of list entry).
// Read a range list from .debug_rnglists starting at the given offset.
template <typename E>
static std::vector<u64>
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
@@ -501,7 +499,7 @@ read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
u8 *base = buf + *rnglists_base;
return read_rnglist_range(ctx, file, base + *(ul32 *)base, addrx);
return read_rnglist_range(ctx, file, base + *(U32<E> *)base, addrx);
}
// Handle a contiguous address range.
@@ -546,16 +544,11 @@ read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
return {};
}
#define INSTANTIATE(E) \
template std::vector<std::string_view> \
read_compunits(Context<E> &, ObjectFile<E> &); \
template std::vector<GdbIndexName> \
read_pubnames(Context<E> &, ObjectFile<E> &); \
template i64 \
estimate_address_areas(Context<E> &, ObjectFile<E> &); \
template std::vector<u64> \
read_address_areas(Context<E> &, ObjectFile<E> &, i64)
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
} // namespace mold::elf
+1042
View File
File diff suppressed because it is too large. Load diff
+1971 -1389
View File
File diff suppressed because it is too large. Load diff
+12 -17
View File
@@ -10,23 +10,27 @@
namespace mold::elf {
template <typename E>
static bool is_init_fini(const InputSection<E> &isec) {
static bool should_keep(const InputSection<E> &isec) {
u32 type = isec.shdr().sh_type;
u32 flags = isec.shdr().sh_flags;
std::string_view name = isec.name();
return type == SHT_INIT_ARRAY ||
return (flags & SHF_GNU_RETAIN) ||
type == SHT_NOTE ||
type == SHT_INIT_ARRAY ||
type == SHT_FINI_ARRAY ||
type == SHT_PREINIT_ARRAY ||
(std::is_same_v<E, ARM32> && type == SHT_ARM_EXIDX) ||
(is_arm32<E> && type == SHT_ARM_EXIDX) ||
name.starts_with(".ctors") ||
name.starts_with(".dtors") ||
name.starts_with(".init") ||
name.starts_with(".fini");
name.starts_with(".fini") ||
is_c_identifier(name);
}
template <typename E>
static bool mark_section(InputSection<E> *isec) {
return isec && isec->is_alive && !isec->is_visited.exchange(true);
return isec && isec->is_alive && fast_mark(isec->is_visited);
}
template <typename E>
@@ -34,13 +38,6 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
assert(isec->is_visited);
// A relocation can refer either a section fragment (i.e. a piece of
// string in a mergeable string section) or a symbol. Mark all
// section fragments as alive.
if (SectionFragmentRef<E> *refs = isec->rel_fragments.get())
for (i64 i = 0; refs[i].idx >= 0; i++)
refs[i].frag->is_alive.store(true, std::memory_order_relaxed);
// 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.
@@ -105,8 +102,7 @@ static void collect_root_set(Context<E> &ctx,
if (!(flags & SHF_ALLOC))
isec->is_visited = true;
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
(flags & SHF_GNU_RETAIN) || isec->shdr().sh_type == SHT_NOTE)
if (should_keep(*isec))
enqueue_section(isec.get());
}
});
@@ -194,9 +190,8 @@ void gc_sections(Context<E> &ctx) {
sweep(ctx);
}
#define INSTANTIATE(E) \
template void gc_sections(Context<E> &ctx);
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template void gc_sections(Context<E> &ctx);
} // namespace mold::elf
+57 -64
View File
@@ -90,6 +90,16 @@ template<> struct hash<Digest> {
namespace mold::elf {
class BitVector {
public:
BitVector(u32 size) : vec((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;
};
template <typename E>
static void uniquify_cies(Context<E> &ctx) {
Timer t(ctx, "uniquify_cies");
@@ -153,10 +163,6 @@ static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
return true;
}
static u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
template <typename E>
struct LeafHasher {
size_t operator()(InputSection<E> *isec) const {
@@ -290,26 +296,16 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
hash_symbol(*isec.file.symbols[rel.r_sym]);
hash(rel.r_type);
hash(rel.r_offset - fde.input_offset);
hash(isec.file.cies[fde.cie_idx].input_section.get_addend(rel));
hash(get_addend(isec.file.cies[fde.cie_idx].input_section, rel));
}
}
i64 frag_idx = 0;
for (i64 i = 0; i < isec.get_rels(ctx).size(); i++) {
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
hash(rel.r_offset);
hash(rel.r_type);
hash(isec.get_addend(rel));
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == i) {
SectionFragmentRef<E> &ref = isec.rel_fragments[frag_idx++];
hash('a');
isec.get_addend(rel);
hash((u64)ref.frag);
} else {
hash_symbol(*isec.file.symbols[rel.r_sym]);
}
hash(get_addend(isec, rel));
hash_symbol(*isec.file.symbols[rel.r_sym]);
}
return digest_final(sha);
@@ -381,19 +377,14 @@ static void gather_edges(Context<E> &ctx,
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> &isec = *sections[i];
assert(isec.icf_eligible);
i64 frag_idx = 0;
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
frag_idx++;
} else {
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.get_frag())
if (InputSection<E> *isec = sym.get_input_section())
if (isec->icf_eligible)
num_edges[i]++;
}
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.get_frag())
if (InputSection<E> *isec = sym.get_input_section())
if (isec->icf_eligible)
num_edges[i]++;
}
});
@@ -404,28 +395,22 @@ static void gather_edges(Context<E> &ctx,
tbb::parallel_for((i64)0, (i64)num_edges.size(), [&](i64 i) {
InputSection<E> &isec = *sections[i];
i64 frag_idx = 0;
i64 idx = edge_indices[i];
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
frag_idx++;
} else {
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.get_frag())
if (InputSection<E> *isec = sym.get_input_section())
if (isec->icf_eligible)
edges[idx++] = isec->icf_idx;
}
}
for (ElfRel<E> &rel : isec.get_rels(ctx)) {
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (InputSection<E> *isec = sym.get_input_section())
if (isec->icf_eligible)
edges[idx++] = isec->icf_idx;
}
});
}
template <typename E>
static i64 propagate(std::span<std::vector<Digest>> digests,
std::span<u32> edges, std::span<u32> edge_indices,
bool &slot, tbb::affinity_partitioner &ap) {
bool &slot, BitVector &converged,
tbb::affinity_partitioner &ap) {
static Counter round("icf_round");
round++;
@@ -433,7 +418,7 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
tbb::enumerable_thread_specific<i64> changed;
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
if (digests[slot][i] == digests[!slot][i])
if (converged.get(i))
return;
SHA256Hash sha;
@@ -442,14 +427,18 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
i64 begin = edge_indices[i];
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
for (i64 j : edges.subspan(begin, end - begin)) {
for (i64 j : edges.subspan(begin, end - begin))
sha.update(digests[slot][j].data(), HASH_SIZE);
}
digests[!slot][i] = digest_final(sha);
if (digests[slot][i] != digests[!slot][i])
if (digests[slot][i] == digests[!slot][i]) {
// This node has converged. Skip further iterations as it will
// yield the same hash.
converged.set(i);
} else {
changed.local()++;
}
}, ap);
slot = !slot;
@@ -524,10 +513,14 @@ void icf_sections(Context<E> &ctx) {
std::vector<InputSection<E> *> sections = gather_sections(ctx);
// We allocate 3 arrays to store hashes for each vertex.
// Index 0 and 1 are used for tree hashes from the previous iteration and the current iteration.
// They switch roles every iteration --- see `slot` below.
// Index 2 stores the initial, single-vertex hash --- this is combined with hashes from the connected vertices to
// form the tree hash described above.
//
// Index 0 and 1 are used for tree hashes from the previous
// iteration and the current iteration. They switch roles every
// iteration. See `slot` below.
//
// Index 2 stores the initial, single-vertex hash. This is combined
// with hashes from the connected vertices to form the tree hash
// described above.
std::vector<std::vector<Digest>> digests(3);
digests[0] = compute_digests<E>(ctx, sections);
digests[1].resize(digests[0].size());
@@ -537,6 +530,7 @@ void icf_sections(Context<E> &ctx) {
std::vector<u32> edge_indices;
gather_edges<E>(ctx, sections, edges, edge_indices);
BitVector converged(digests[0].size());
bool slot = 0;
// Execute the propagation rounds until convergence is obtained.
@@ -557,7 +551,7 @@ void icf_sections(Context<E> &ctx) {
// which is a necessary (but not sufficient) condition for convergence.
i64 num_changed = -1;
for (;;) {
i64 n = propagate<E>(digests, edges, edge_indices, slot, ap);
i64 n = propagate<E>(digests, edges, edge_indices, slot, converged, ap);
if (n == num_changed)
break;
num_changed = n;
@@ -570,7 +564,7 @@ void icf_sections(Context<E> &ctx) {
// count_num_classes requires sorting which is O(n log n), so do a little
// more work beforehand to amortize that log factor.
for (i64 i = 0; i < 10; i++)
propagate<E>(digests, edges, edge_indices, slot, ap);
propagate<E>(digests, edges, edge_indices, slot, converged, ap);
i64 n = count_num_classes<E>(digests[slot], ap);
if (n == num_classes)
@@ -606,26 +600,25 @@ void icf_sections(Context<E> &ctx) {
if (ctx.arg.print_icf_sections)
print_icf_sections(ctx);
// Re-assign input sections to symbols.
// Eliminate duplicate sections.
// Symbols pointing to eliminated sections will be redirected on the fly when
// exporting to the symtab.
{
Timer t(ctx, "reassign");
Timer t(ctx, "sweep");
static Counter eliminated("icf_eliminated");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file == file) {
InputSection<E> *isec = sym->get_input_section();
if (isec && isec->leader && isec->leader != isec) {
isec->kill();
isec->killed_by_icf = true;
}
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
isec->kill();
eliminated++;
}
}
});
}
}
#define INSTANTIATE(E) \
template void icf_sections(Context<E> &ctx);
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template void icf_sections(Context<E> &ctx);
} // namespace mold::elf
+345 -285
View File
File diff suppressed because it is too large. Load diff
+258 -159
View File
@@ -2,9 +2,14 @@
#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,
} Action;
template <typename E>
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
if (get_contents() != other.get_contents())
@@ -19,13 +24,13 @@ bool CieRecord<E>::equals(const CieRecord<E> &other) const {
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
x[i].r_type != y[i].r_type ||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
input_section.get_addend(x[i]) != other.input_section.get_addend(y[i]))
get_addend(input_section, x[i]) != get_addend(other.input_section, y[i]))
return false;
}
return true;
}
static inline i64 to_p2align(u64 alignment) {
static i64 to_p2align(u64 alignment) {
if (alignment == 0)
return 0;
return std::countr_zero(alignment);
@@ -38,19 +43,13 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
if (shndx < file.elf_sections.size())
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
if (name.starts_with(".zdebug")) {
sh_size = *(ub64 *)&contents[4];
p2align = to_p2align(shdr().sh_addralign);
compressed = true;
} else if (shdr().sh_flags & SHF_COMPRESSED) {
if (shdr().sh_flags & SHF_COMPRESSED) {
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
sh_size = chdr.ch_size;
p2align = to_p2align(chdr.ch_addralign);
compressed = true;
} else {
sh_size = shdr().sh_size;
p2align = to_p2align(shdr().sh_addralign);
compressed = false;
}
// Sections may have been compressed. We usually uncompress them
@@ -58,126 +57,56 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
// early for REL-type ELF types to read relocation addends from
// section contents. For RELA-type, we don't need to do this because
// addends are in relocations.
if constexpr (!is_rela<E>)
if constexpr (!E::is_rela)
uncompress(ctx);
output_section =
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
}
template <typename E>
void InputSection<E>::uncompress(Context<E> &ctx) {
if (!compressed || uncompressed)
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
return;
u8 *buf = new u8[sh_size];
uncompress_to(ctx, buf);
contents = {(char *)buf, sh_size};
contents = std::string_view((char *)buf, sh_size);
ctx.string_pool.emplace_back(buf);
uncompressed = true;
}
template <typename E>
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
if (!compressed || uncompressed) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
memcpy(buf, contents.data(), contents.size());
return;
}
auto do_uncompress = [&](std::string_view data) {
unsigned long size = sh_size;
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
Fatal(ctx) << *this << ": uncompress failed";
assert(size == sh_size);
};
if (name().starts_with(".zdebug")) {
// Old-style compressed section
if (!contents.starts_with("ZLIB") || contents.size() <= 12)
Fatal(ctx) << *this << ": corrupted compressed section";
do_uncompress(contents.substr(12));
return;
}
assert(shdr().sh_flags & SHF_COMPRESSED);
// New-style compressed section
if (contents.size() < sizeof(ElfChdr<E>))
Fatal(ctx) << *this << ": corrupted compressed section";
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
switch (hdr.ch_type) {
case ELFCOMPRESS_ZLIB: {
unsigned long size = sh_size;
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
Fatal(ctx) << *this << ": uncompress failed";
assert(size == sh_size);
break;
}
case ELFCOMPRESS_ZSTD:
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
Fatal(ctx) << *this << ": ZSTD_decompress failed";
break;
default:
Fatal(ctx) << *this << ": unsupported compression type: 0x"
<< std::hex << hdr.ch_type;
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
}
typedef enum { NONE, ERROR, COPYREL, PLT, CPLT, DYNREL, BASEREL } Action;
template <typename E>
static void
dispatch(Context<E> &ctx, InputSection<E> &isec, Action action,
Symbol<E> &sym, const ElfRel<E> &rel) {
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 (isec.shdr().sh_flags & SHF_WRITE)
return;
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;
};
switch (action) {
case NONE:
return;
case ERROR:
error();
return;
case COPYREL:
if (!ctx.arg.z_copyreloc) {
error();
} else 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;
return;
case PLT:
sym.flags |= NEEDS_PLT;
return;
case CPLT:
sym.flags |= NEEDS_CPLT;
return;
case DYNREL:
assert(sym.is_imported);
check_textrel();
isec.file.num_dynrel++;
return;
case BASEREL:
check_textrel();
if (!isec.is_relr_reloc(ctx, rel))
isec.file.num_dynrel++;
return;
default:
unreachable();
}
}
template <typename E>
static Action get_rel_action(Context<E> &ctx, const Action table[3][4],
Symbol<E> &sym) {
static Action get_rel_action(Context<E> &ctx, Symbol<E> &sym,
const Action table[3][4]) {
auto get_output_type = [&] {
if (ctx.arg.shared)
return 0;
@@ -200,84 +129,256 @@ static Action get_rel_action(Context<E> &ctx, const Action table[3][4],
}
template <typename E>
void InputSection<E>::scan_abs_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
// This is a decision table for absolute relocations that is smaller
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
// generally does not support dynamic relocations smaller than the
// word size, we need to report an error if a relocation cannot be
// resolved at link-time.
constexpr 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
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;
};
Action action = get_rel_action(ctx, table, sym);
dispatch(ctx, *this, action, sym, rel);
}
template <typename E>
static Action get_abs_dyn_action(Context<E> &ctx, Symbol<E> &sym) {
// This is a decision table for absolute relocations for the word
// size data (e.g. R_X86_64_64). Unlike the above, we can emit a
// dynamic relocation if we cannot resolve its address at link-time.
constexpr Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
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;
}
};
return get_rel_action(ctx, table, sym);
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++;
};
switch (action) {
case NONE:
break;
case ERROR:
error();
break;
case COPYREL:
if (!ctx.arg.z_copyreloc)
error();
copyrel();
break;
case DYN_COPYREL:
if (writable || !ctx.arg.z_copyreloc)
dynrel();
else
copyrel();
break;
case PLT:
sym.flags |= NEEDS_PLT;
break;
case CPLT:
sym.flags |= NEEDS_CPLT;
break;
case DYN_CPLT:
if (writable)
dynrel();
else
sym.flags |= NEEDS_CPLT;
break;
case DYNREL:
dynrel();
break;
case BASEREL:
check_textrel();
if (!isec.is_relr_reloc(ctx, rel))
isec.file.num_dynrel++;
break;
case IFUNC:
dynrel();
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
break;
default:
unreachable();
}
}
template <typename E>
void InputSection<E>::scan_abs_dyn_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
Action action = get_abs_dyn_action(ctx, sym);
dispatch(ctx, *this, action, sym, rel);
}
template <typename E>
void InputSection<E>::scan_pcrel_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel) {
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
// 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.
constexpr Action table[][4] = {
constexpr static Action table[3][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
};
Action action = get_rel_action(ctx, table, sym);
dispatch(ctx, *this, action, sym, rel);
return get_rel_action(ctx, sym, table);
}
template <typename E>
void InputSection<E>::apply_abs_dyn_rel(Context<E> &ctx, Symbol<E> &sym,
const ElfRel<E> &rel, u8 *loc,
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel) {
switch (get_abs_dyn_action(ctx, sym)) {
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
// This is a decision table for absolute relocations that is smaller
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
// generally does not support dynamic relocations smaller than the
// word size, we need to report an error if a relocation cannot be
// resolved at link-time.
constexpr static Action table[3][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 get_rel_action(ctx, sym, table);
}
template <typename E>
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
// This is a decision table for absolute relocations for the word
// 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.
constexpr 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 get_rel_action(ctx, sym, table);
}
template <typename E>
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
if (sym.is_ifunc())
return IFUNC;
// 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.
constexpr 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 get_rel_action(ctx, sym, table);
}
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));
}
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 apply_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, rel))
if (isec.is_relr_reloc(ctx, rel)) {
*(Word<E> *)loc = S + A;
} else {
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
*(Word<E> *)loc = S + A;
if (ctx.arg.apply_dynamic_relocs)
*(Word<E> *)loc = S + A;
}
break;
case DYN_COPYREL:
if (writable || !ctx.arg.z_copyreloc)
apply_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYN_CPLT:
if (writable)
apply_dynrel();
else
*(Word<E> *)loc = S + A;
break;
case DYNREL:
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
*(Word<E> *)loc = A;
apply_dynrel();
break;
case IFUNC: {
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;
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)
@@ -286,17 +387,17 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
// Copy data
if constexpr (is_riscv<E>) {
copy_contents_riscv(ctx, buf);
} else if (compressed) {
uncompress_to(ctx, buf);
} else {
memcpy(buf, contents.data(), contents.size());
uncompress_to(ctx, buf);
}
// Apply relocations
if (shdr().sh_flags & SHF_ALLOC)
apply_reloc_alloc(ctx, buf);
else
apply_reloc_nonalloc(ctx, buf);
if (!ctx.arg.relocatable) {
if (shdr().sh_flags & SHF_ALLOC)
apply_reloc_alloc(ctx, buf);
else
apply_reloc_nonalloc(ctx, buf);
}
}
// Get the name of a function containin a given offset.
@@ -365,12 +466,10 @@ void report_undef_errors(Context<E> &ctx) {
ctx.checkpoint();
}
#define INSTANTIATE(E) \
template struct CieRecord<E>; \
template class InputSection<E>; \
template void report_undef_errors(Context<E> &)
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template struct CieRecord<E>;
template class InputSection<E>;
template void report_undef_errors(Context<E> &);
} // namespace mold::elf
+40 -34
View File
@@ -239,7 +239,8 @@ void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
}
template <typename E>
MachineType get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
std::string_view
get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
@@ -247,11 +248,18 @@ MachineType get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf)
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
if (tok[2] == "elf64-x86-64")
return MachineType::X86_64;
return X86_64::target_name;
if (tok[2] == "elf32-i386")
return MachineType::I386;
return I386::target_name;
}
return MachineType::NONE;
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
tok[1] == "(")
if (MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::open(ctx, std::string(unquote(tok[2]))))
return get_machine_type(ctx, mf);
return "";
}
static bool read_label(std::span<std::string_view> &tok,
@@ -271,8 +279,7 @@ static bool read_label(std::span<std::string_view> &tok,
template <typename E>
static void
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
u16 ver_idx, bool is_cpp) {
ctx.version_specified = true;
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
bool is_global = true;
while (!tok.empty() && tok[0] != "}") {
@@ -292,11 +299,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script_commands( ctx, tok, ver_idx, false);
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_idx, true);
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
}
tok = skip(ctx, tok, "}");
@@ -305,11 +312,14 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
}
if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : VER_NDX_LOCAL);
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
ctx.default_version_from_version_script = true;
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), ver_idx, is_cpp});
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
ver_str, ver_idx, is_cpp});
} else {
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_LOCAL, is_cpp});
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
ver_str, VER_NDX_LOCAL, is_cpp});
}
tok = tok.subspan(1);
@@ -325,17 +335,21 @@ 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_idx, false);
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
tok = skip(ctx, tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
@@ -344,10 +358,9 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
}
template <typename E>
void parse_version_script(Context<E> &ctx, std::string path) {
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
std::vector<std::string_view> vec =
tokenize(ctx, current_file<E>->get_contents());
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = 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())
@@ -357,8 +370,6 @@ void parse_version_script(Context<E> &ctx, std::string path) {
template <typename E>
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
bool is_cpp) {
ctx.version_specified = true;
while (!tok.empty() && tok[0] != "}") {
if (tok[0] == "extern") {
tok = tok.subspan(1);
@@ -381,19 +392,19 @@ void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &to
if (tok[0] == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_GLOBAL, is_cpp});
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
"global", VER_NDX_GLOBAL, is_cpp});
tok = skip(ctx, tok.subspan(1), ";");
}
}
template <typename E>
void parse_dynamic_list(Context<E> &ctx, std::string path) {
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
std::vector<std::string_view> vec =
tokenize(ctx, current_file<E>->get_contents());
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
tok = skip(ctx, tok, "}");
@@ -403,16 +414,11 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
SyntaxError(ctx, tok[0]) << "trailing garbage token";
}
#define INSTANTIATE(E) \
template \
void parse_linker_script(Context<E> &, MappedFile<Context<E>> *); \
template \
MachineType get_script_output_type(Context<E> &, MappedFile<Context<E>> *); \
template \
void parse_version_script(Context<E> &, std::string); \
template \
void parse_dynamic_list(Context<E> &, std::string);
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
} // namespace mold::elf
+37 -24
View File
@@ -47,7 +47,7 @@
// functions. `onload` calls callbacks to notify about the pointers to
// other functions the linker plugin provides. I don't know why `onload`
// can't just return a list of functions or why the linker plugin can't
// define not only `onload` but other functions, but that's how it works.
// define not only `onload` but other functions, but that's what it is.
//
// Here is the steps to use the linker plugin:
//
@@ -248,14 +248,14 @@ get_input_section_name(const PluginSection section,
static PluginStatus
get_input_section_contents(const PluginSection section,
const char **section_contents,
size_t *len) {
size_t *len) {
LOG << "get_input_section_contents\n";
return LDPS_OK;
}
static PluginStatus
update_section_order(const PluginSection *section_list,
int num_sections) {
int num_sections) {
LOG << "update_section_order\n";
return LDPS_OK;
}
@@ -270,8 +270,8 @@ get_symbols_v1(const void *handle, int nsyms, PluginSymbol *psyms) {
unreachable();
}
// get_symbols teaches the LTO plugin as to how we resolved symbols.
// The plugin uses the symbol resolution info to optimizes the program.
// get_symbols teaches the LTO plugin as to how we have resolved symbols.
// The plugin uses the symbol resolution info to optimize the program.
//
// For example, if a definition in an IR file is not referenced by
// non-IR objects at all, the plugin may choose to completely inline
@@ -308,7 +308,8 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
if (sym.file->is_dso)
return LDPR_RESOLVED_DYN;
if (((ObjectFile<E> *)sym.file)->is_lto_obj)
if (((ObjectFile<E> *)sym.file)->is_lto_obj && !sym.is_wrapped)
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
};
@@ -351,10 +352,10 @@ static void restart_process(Context<E> &ctx) {
args.push_back("--:lto-pass2");
args.push_back(nullptr);
std::string self = std::filesystem::read_symlink("/proc/self/exe");
std::cout << std::flush;
std::cerr << std::flush;
std::string self = get_self_path();
execv(self.c_str(), (char * const *)args.data());
std::cerr << "execv failed: " << errno_string() << "\n";
_exit(1);
@@ -381,10 +382,10 @@ static PluginStatus allow_unique_segment_for_sections() {
static PluginStatus
unique_segment_for_sections(const char *segment_name,
uint64_t flags,
uint64_t align,
const PluginSection *section_list,
int num_sections) {
uint64_t flags,
uint64_t align,
const PluginSection *section_list,
int num_sections) {
LOG << "unique_segment_for_sections\n";
return LDPS_OK;
}
@@ -426,8 +427,10 @@ get_api_version(const char *plugin_identifier,
if (LAPI_V1 < minimal_api_supported)
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
std::string version = mold_version + "\0"s;
*linker_identifier = "mold";
*linker_version = MOLD_VERSION;
*linker_version = version.data();
if (LAPI_V1 <= maximal_api_supported) {
is_gcc_linker_api_v1 = true;
@@ -500,13 +503,14 @@ static void load_plugin(Context<E> &ctx) {
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
tv.emplace_back(LDPT_NULL, 0);
PluginStatus status = onload(tv.data());
[[maybe_unused]] PluginStatus status = onload(tv.data());
assert(status == LDPS_OK);
}
template <typename E>
static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
ElfSym<E> esym = {};
ElfSym<E> esym;
memset(&esym, 0, sizeof(esym));
switch (psym.def) {
case LDPK_DEF:
@@ -567,7 +571,7 @@ static bool is_llvm(Context<E> &ctx) {
// Returns true if a given linker plugin supports the get_symbols_v3 API.
// Any version of LLVM and GCC 12 or newer support it.
template <typename E>
static bool suppots_v3_api(Context<E> &ctx) {
static bool supports_v3_api(Context<E> &ctx) {
return is_gcc_linker_api_v1 || is_llvm(ctx);
}
@@ -644,7 +648,6 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
}
obj->elf_syms = *esyms;
obj->sym_fragments.resize(esyms->size());
obj->symvers.resize(esyms->size());
plugin_symbols.clear();
return obj;
@@ -655,7 +658,7 @@ template <typename E>
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
Timer t(ctx, "do_lto");
if (!ctx.arg.lto_pass2 && !suppots_v3_api(ctx))
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
restart_process(ctx);
assert(phase == 1);
@@ -677,6 +680,18 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
}
});
// Symbols specified by the --wrap option needs to be visible from
// regular object files.
for (std::string_view name : ctx.arg.wrap) {
get_symbol(ctx, name)->referenced_by_regular_obj = true;
std::string_view x = save_string(ctx, "__wrap_" + std::string(name));
std::string_view y = save_string(ctx, "__real_" + std::string(name));
get_symbol(ctx, x)->referenced_by_regular_obj = true;
get_symbol(ctx, y)->referenced_by_regular_obj = true;
}
// all_symbols_read_hook() calls add_input_file() and add_input_library()
LOG << "all symbols read\n";
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
@@ -693,12 +708,10 @@ void lto_cleanup(Context<E> &ctx) {
cleanup_hook();
}
#define INSTANTIATE(E) \
template ObjectFile<E> * \
read_lto_object(Context<E> &, MappedFile<Context<E>> *); \
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
template void lto_cleanup(Context<E> &)
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
template void lto_cleanup(Context<E> &);
} // namespace mold::elf
+4 -10
View File
@@ -1,5 +1,3 @@
#ifdef _WIN32
#include "mold.h"
#include "lto.h"
@@ -18,14 +16,10 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
template <typename E>
void lto_cleanup(Context<E> &ctx) {}
#define INSTANTIATE(E) \
template ObjectFile<E> * \
read_lto_object(Context<E> &, MappedFile<Context<E>> *); \
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
template void lto_cleanup(Context<E> &)
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
template void lto_cleanup(Context<E> &);
} // namespace mold::elf
#endif
+5
View File
@@ -0,0 +1,5 @@
#ifdef _WIN32
# include "lto-win32.cc"
#else
# include "lto-unix.cc"
#endif
+21 -11
View File
@@ -1,5 +1,7 @@
#pragma once
#include "../integers.h"
namespace mold {
enum PluginStatus {
@@ -73,27 +75,35 @@ enum PluginOutputFileType {
struct PluginInputFile {
const char *name;
int32_t fd;
uint64_t offset;
uint64_t filesize;
i32 fd;
u64 offset;
u64 filesize;
void *handle;
};
struct PluginSection {
const void *handle;
uint32_t shndx;
u32 shndx;
};
struct PluginSymbol {
char *name;
char *version;
char def;
char symbol_type;
char section_kind;
int32_t visibility;
uint64_t size;
#ifdef __LITTLE_ENDIAN__
u8 def;
u8 symbol_type;
u8 section_kind;
u8 padding;
#else
u8 padding;
u8 section_kind;
u8 symbol_type;
u8 def;
#endif
i32 visibility;
u64 size;
char *comdat_key;
int32_t resolution;
i32 resolution;
};
enum PluginSymbolKind {
@@ -143,7 +153,7 @@ enum PluginLevel {
};
enum PluginLinkerAPIVersion {
LAPI_V0 = 0,
LAPI_V0,
LAPI_V1,
};
+211 -185
View File
@@ -7,6 +7,7 @@
#include <functional>
#include <iomanip>
#include <map>
#include <regex>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -26,54 +27,87 @@ 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>
static MachineType get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
auto get_elf_type = [](u8 *buf) {
switch (ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)buf; ehdr.e_machine) {
std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *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 MachineType::I386;
return I386::target_name;
case EM_X86_64:
return MachineType::X86_64;
return X86_64::target_name;
case EM_ARM:
return MachineType::ARM32;
return ARM32::target_name;
case EM_AARCH64:
return MachineType::ARM64;
return ARM64::target_name;
case EM_RISCV:
return (ehdr.e_ident[EI_CLASS] == ELFCLASS64)
? MachineType::RISCV64 : MachineType::RISCV32;
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_PARISC:
return HPPA32::target_name;
default:
return MachineType::NONE;
return "";
}
};
switch (get_file_type(mf)) {
bool opt_plugin = !ctx.arg.plugin.empty();
switch (get_file_type(mf, opt_plugin)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
case FileType::GCC_LTO_OBJ:
return get_elf_type(mf->data);
case FileType::AR:
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return MachineType::NONE;
return "";
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
return get_elf_type(child->data);
return MachineType::NONE;
return "";
case FileType::TEXT:
return get_script_output_type(ctx, mf);
default:
return MachineType::NONE;
return "";
}
}
template <typename E>
static void
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
MachineType mt = get_machine_type(ctx, mf);
if (mt != ctx.arg.emulation)
std::string_view target = get_machine_type(ctx, mf);
if (target != ctx.arg.emulation)
Fatal(ctx) << mf->name << ": incompatible file type: "
<< ctx.arg.emulation << " is expected but got " << mt;
<< ctx.arg.emulation << " is expected but got " << target;
}
template <typename E>
@@ -131,7 +165,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (ctx.visited.contains(mf->name))
return;
FileType type = get_file_type(mf);
bool opt_plugin = !ctx.arg.plugin.empty();
FileType type = get_file_type(mf, opt_plugin);
switch (type) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, mf, ""));
@@ -143,7 +178,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
switch (get_file_type(child)) {
switch (get_file_type(child, opt_plugin)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
break;
@@ -172,13 +207,14 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
}
template <typename E>
static MachineType
static std::string_view
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
for (std::string_view arg : args)
if (!arg.starts_with('-'))
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
if (MachineType ty = get_machine_type(ctx, mf); ty != MachineType::NONE)
return ty;
if (std::string_view target = get_machine_type(ctx, mf);
!target.empty())
return target;
Fatal(ctx) << "-m option is missing";
}
@@ -188,10 +224,10 @@ MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
if (!mf)
return nullptr;
MachineType ty = get_machine_type(ctx, mf);
if (ty == MachineType::NONE || ty == E::machine_type)
std::string_view target = get_machine_type(ctx, mf);
if (target.empty() || target == E::target_name)
return mf;
Warn(ctx) << path << ": skipping incompatible file " << (int)ty
Warn(ctx) << path << ": skipping incompatible file " << target
<< " " << (int)E::e_machine;
return nullptr;
}
@@ -218,6 +254,18 @@ MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
Fatal(ctx) << "library not found: " << name;
}
template <typename E>
MappedFile<Context<E>> *find_from_search_paths(Context<E> &ctx, std::string name) {
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, name))
return mf;
for (std::string_view dir : ctx.arg.library_paths)
if (MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::open(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");
@@ -246,17 +294,26 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
} else if (arg == "--end-lib") {
ctx.in_lib = false;
} else if (remove_prefix(arg, "--version-script=")) {
parse_version_script(ctx, std::string(arg));
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--version-script: file not found: " << arg;
parse_version_script(ctx, mf);
} else if (remove_prefix(arg, "--dynamic-list=")) {
parse_dynamic_list(ctx, std::string(arg));
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
parse_dynamic_list(ctx, mf);
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
if (arg == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({arg, VER_NDX_GLOBAL, false});
ctx.version_specified = true;
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
"global", VER_NDX_GLOBAL, false});
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
parse_dynamic_list(ctx, std::string(arg));
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
if (!mf)
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
parse_dynamic_list(ctx, mf);
} else if (arg == "--push-state") {
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
ctx.in_lib});
@@ -281,81 +338,54 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
ctx.tg.wait();
}
// Since elf_main is a template, we can't run it without a type parameter.
// We speculatively run elf_main with X86_64, and if the speculation was
// wrong, re-run it with an actual machine type.
template <typename E>
static void show_stats(Context<E> &ctx) {
for (ObjectFile<E> *obj : ctx.objs) {
static Counter defined("defined_syms");
defined += obj->first_global - 1;
static Counter undefined("undefined_syms");
undefined += obj->symbols.size() - obj->first_global;
for (std::unique_ptr<InputSection<E>> &sec : obj->sections) {
if (!sec || !sec->is_alive)
continue;
static Counter alloc("reloc_alloc");
static Counter nonalloc("reloc_nonalloc");
if (sec->shdr().sh_flags & SHF_ALLOC)
alloc += sec->get_rels(ctx).size();
else
nonalloc += sec->get_rels(ctx).size();
}
static Counter comdats("comdats");
comdats += obj->comdat_groups.size();
static Counter removed_comdats("removed_comdat_mem");
for (auto &pair : obj->comdat_groups)
if (ComdatGroup *group = pair.first; group->owner != obj->priority)
removed_comdats += pair.second.size();
static Counter num_cies("num_cies");
num_cies += obj->cies.size();
static Counter num_unique_cies("num_unique_cies");
for (CieRecord<E> &cie : obj->cies)
if (cie.is_leader)
num_unique_cies++;
static Counter num_fdes("num_fdes");
num_fdes += obj->fdes.size();
}
static Counter num_bytes("total_input_bytes");
for (std::unique_ptr<MappedFile<Context<E>>> &mf : ctx.mf_pool)
num_bytes += mf->size;
static Counter num_input_sections("input_sections");
for (ObjectFile<E> *file : ctx.objs)
num_input_sections += file->sections.size();
static Counter num_output_chunks("output_chunks", ctx.chunks.size());
static Counter num_objs("num_objs", ctx.objs.size());
static Counter num_dsos("num_dsos", ctx.dsos.size());
if constexpr (needs_thunk<E>) {
static Counter num_thunks("num_thunks");
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
num_thunks += thunk->symbols.size();
}
Counter::print();
for (std::unique_ptr<MergedSection<E>> &sec : ctx.merged_sections)
sec->print_stats(ctx);
static int redo_main(int argc, char **argv, std::string_view target) {
if (target == I386::target_name)
return elf_main<I386>(argc, argv);
if (target == ARM64::target_name)
return elf_main<ARM64>(argc, argv);
if (target == ARM32::target_name)
return elf_main<ARM32>(argc, argv);
if (target == RV64LE::target_name)
return elf_main<RV64LE>(argc, argv);
if (target == RV64BE::target_name)
return elf_main<RV64BE>(argc, argv);
if (target == RV32LE::target_name)
return elf_main<RV32LE>(argc, argv);
if (target == RV32BE::target_name)
return elf_main<RV32BE>(argc, argv);
if (target == PPC32::target_name)
return elf_main<PPC32>(argc, argv);
if (target == PPC64V1::target_name)
return elf_main<PPC64V1>(argc, argv);
if (target == PPC64V2::target_name)
return elf_main<PPC64V2>(argc, argv);
if (target == S390X::target_name)
return elf_main<S390X>(argc, argv);
if (target == SPARC64::target_name)
return elf_main<SPARC64>(argc, argv);
if (target == M68K::target_name)
return elf_main<M68K>(argc, argv);
if (target == SH4::target_name)
return elf_main<SH4>(argc, argv);
if (target == ALPHA::target_name)
return elf_main<ALPHA>(argc, argv);
if (target == HPPA32::target_name)
return elf_main<HPPA32>(argc, argv);
unreachable();
}
template <typename E>
static int elf_main(int argc, char **argv) {
int elf_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";
Fatal(ctx) << "-run is supported only on Unix";
#endif
process_run_subcommand(ctx, argc, argv);
}
@@ -365,29 +395,13 @@ static int elf_main(int argc, char **argv) {
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
// If no -m option is given, deduce it from input files.
if (ctx.arg.emulation == MachineType::NONE)
if (ctx.arg.emulation.empty())
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
// Redo if -m is not x86-64.
if (ctx.arg.emulation != E::machine_type) {
#ifndef MOLD_DEBUG_X86_64_ONLY
switch (ctx.arg.emulation) {
case MachineType::I386:
return elf_main<I386>(argc, argv);
case MachineType::ARM64:
return elf_main<ARM64>(argc, argv);
case MachineType::ARM32:
return elf_main<ARM32>(argc, argv);
case MachineType::RISCV64:
return elf_main<RISCV64>(argc, argv);
case MachineType::RISCV32:
return elf_main<RISCV32>(argc, argv);
default:
unreachable();
}
#endif
unreachable();
}
if constexpr (is_x86_64<E>)
if (ctx.arg.emulation != X86_64::target_name)
return redo_main<E>(argc, argv, ctx.arg.emulation);
Timer t_all(ctx, "all");
@@ -398,11 +412,6 @@ static int elf_main(int argc, char **argv) {
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
<< ": " << errno_string();
if (ctx.arg.relocatable) {
combine_objects(ctx, file_args);
return 0;
}
// Fork a subprocess unless --no-fork is given.
std::function<void()> on_complete;
@@ -416,7 +425,7 @@ static int elf_main(int argc, char **argv) {
// Handle --wrap options if any.
for (std::string_view name : ctx.arg.wrap)
get_symbol(ctx, name)->wrap = true;
get_symbol(ctx, name)->is_wrapped = true;
// Handle --retain-symbols-file options if any.
if (ctx.arg.retain_symbols_file)
@@ -424,7 +433,7 @@ static int elf_main(int argc, char **argv) {
get_symbol(ctx, name)->write_to_symtab = true;
for (std::string_view arg : ctx.arg.trace_symbol)
get_symbol(ctx, arg)->traced = true;
get_symbol(ctx, arg)->is_traced = true;
// Parse input files
read_input_files(ctx, file_args);
@@ -444,17 +453,31 @@ static int elf_main(int argc, char **argv) {
apply_exclude_libs(ctx);
// Create a dummy file containing linker-synthesized symbols.
create_internal_file(ctx);
if (!ctx.arg.relocatable)
create_internal_file(ctx);
// Resolve symbols and fix the set of object files that are
// included to the final output.
// 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(ctx);
// Resolve mergeable section pieces to merge them.
register_section_pieces(ctx);
resolve_section_pieces(ctx);
// Remove redundant comdat sections (e.g. duplicate inline functions).
eliminate_comdats(ctx);
// Handle --relocatable. Since the linker's behavior is quite different
// from the normal one when the option is given, the logic is implemented
// to a separate file.
if (ctx.arg.relocatable) {
combine_objects(ctx);
return 0;
}
// Create .bss sections for common symbols.
convert_common_symbols(ctx);
@@ -465,7 +488,7 @@ static int elf_main(int argc, char **argv) {
// Parse symbol version suffixes (e.g. "foo@ver1").
parse_symbol_version(ctx);
// Set is_import and is_export bits for each symbol.
// Set is_imported and is_exported bits for each symbol.
compute_import_export(ctx);
// Read address-significant section information.
@@ -483,15 +506,21 @@ static int elf_main(int argc, char **argv) {
// Compute sizes of sections containing mergeable strings.
compute_merged_section_sizes(ctx);
// Create instances of linker-synthesized sections such as
// .got or .plt.
// Create linker-synthesized sections such as .got or .plt.
create_synthetic_sections(ctx);
// Bin input sections into output sections.
bin_sections(ctx);
// Make sure that there's no duplicate symbol
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Get a list of output sections.
append(ctx.chunks, collect_output_sections(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);
// Add synthetic symbols such as __ehdr_start or __end.
add_synthetic_symbols(ctx);
@@ -514,7 +543,7 @@ static int elf_main(int argc, char **argv) {
// were imported symbols.
//
// If we are linking an executable, weak undefs are converted to
// weakly imported symbol so that they'll have another chance to be
// weakly imported symbols so that they'll have another chance to be
// resolved.
claim_unresolved_symbols(ctx);
@@ -530,10 +559,6 @@ static int elf_main(int argc, char **argv) {
if (ctx.arg.repro)
write_repro_file(ctx);
// Make sure that all symbols have been resolved.
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Handle --require-defined
for (std::string_view name : ctx.arg.require_defined)
if (!get_symbol(ctx, name)->file)
@@ -563,9 +588,12 @@ static int elf_main(int argc, char **argv) {
if (!ctx.arg.soname.empty())
ctx.dynstr->add_string(ctx.arg.soname);
if constexpr (is_ppc64v1<E>)
ppc64v1_scan_symbols(ctx);
// Scan relocations to find symbols that need entries in .got, .plt,
// .got.plt, .dynsym, .dynstr, etc.
scan_rels(ctx);
scan_relocations(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
@@ -573,9 +601,7 @@ static int elf_main(int argc, char **argv) {
// Sort sections by section attributes so that we'll have to
// create as few segments as possible.
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
});
sort_output_sections(ctx);
// If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed
@@ -617,23 +643,8 @@ static int elf_main(int argc, char **argv) {
if (ctx.arg.emit_relocs)
create_reloc_sections(ctx);
// Update sh_size for each chunk and remove empty ones.
for (Chunk<E> *chunk : ctx.chunks)
chunk->update_shdr(ctx);
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
});
// Set section indices.
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
if (ctx.chunks[i]->kind() != HEADER)
ctx.chunks[i]->shndx = shndx++;
// Some types of section header refer other section by index.
// Recompute the section header to fill such fields with correct values.
for (Chunk<E> *chunk : ctx.chunks)
chunk->update_shdr(ctx);
// Compute the section header values for all sections.
compute_section_headers(ctx);
// Assign offsets to output sections
i64 filesize = set_osec_offsets(ctx);
@@ -645,7 +656,9 @@ static int elf_main(int argc, char **argv) {
if constexpr (is_riscv<E>)
filesize = riscv_resize_sections(ctx);
// Fix linker-synthesized symbol addresses.
// At this point, memory layout is fixed.
// Set actual addresses to linker-synthesized symbols.
fix_synthetic_symbols(ctx);
// Beyond this, you can assume that symbol addresses including their
@@ -656,7 +669,7 @@ static int elf_main(int argc, char **argv) {
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
filesize = compress_debug_sections(ctx);
// At this point, file layout is fixed.
// At this point, both memory and file layouts are fixed.
t_before_copy.stop();
@@ -667,22 +680,8 @@ static int elf_main(int argc, char **argv) {
Timer t_copy(ctx, "copy");
// Copy input sections to the output file
{
Timer t(ctx, "copy_buf");
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
std::string name =
chunk->name.empty() ? "(header)" : std::string(chunk->name);
Timer t2(ctx, name, &t);
chunk->copy_buf(ctx);
});
report_undef_errors(ctx);
}
if constexpr (std::is_same_v<E, ARM32>)
sort_arm_exidx(ctx);
// Copy input sections to the output file and apply relocations.
copy_chunks(ctx);
// Some part of .gdb_index couldn't be computed until other debug
// sections are complete. We have complete debug sections now, so
@@ -697,6 +696,9 @@ static int elf_main(int argc, char **argv) {
// Zero-clear paddings between sections
clear_padding(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);
@@ -740,13 +742,37 @@ static int elf_main(int argc, char **argv) {
return 0;
}
using E = MOLD_TARGET;
template void read_file(Context<E> &, MappedFile<Context<E>> *);
#ifdef MOLD_X86_64
extern template int elf_main<I386>(int, char **);
extern template int elf_main<ARM32>(int, char **);
extern template int elf_main<ARM64>(int, char **);
extern template int elf_main<RV32BE>(int, char **);
extern template int elf_main<RV32LE>(int, char **);
extern template int elf_main<RV64LE>(int, char **);
extern template int elf_main<RV64BE>(int, char **);
extern template int elf_main<PPC32>(int, char **);
extern template int elf_main<PPC64V1>(int, char **);
extern template int elf_main<PPC64V2>(int, char **);
extern template int elf_main<S390X>(int, char **);
extern template int elf_main<SPARC64>(int, char **);
extern template int elf_main<M68K>(int, char **);
extern template int elf_main<SH4>(int, char **);
extern template int elf_main<ALPHA>(int, char **);
extern template int elf_main<HPPA32>(int, char **);
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
}
#define INSTANTIATE(E) \
template void read_file(Context<E> &, MappedFile<Context<E>> *);
#else
INSTANTIATE_ALL;
template int elf_main<E>(int, char **);
#endif
} // namespace mold::elf
+2 -3
View File
@@ -109,9 +109,8 @@ void print_map(Context<E> &ctx) {
}
}
#define INSTANTIATE(E) \
template void print_map(Context<E> &ctx);
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template void print_map(Context<E> &ctx);
} // namespace mold::elf
+1 -1
View File
@@ -22,7 +22,7 @@ static char *get_mold_path() {
exit(1);
}
static void debug_print(char *fmt, ...) {
static void debug_print(const char *fmt, ...) {
if (!getenv("MOLD_WRAPPER_DEBUG"))
return;
+736 -375
View File
File diff suppressed because it is too large. Load diff
+1227 -570
View File
File diff suppressed because it is too large. Load diff
+1255 -410
View File
File diff suppressed because it is too large. Load diff
+144 -543
View File
@@ -1,596 +1,197 @@
// This file implements the -r or ---relocatable option. If the option
// is given to a linker, the linker creates not an executable nor a
// shared library file but instead create another object file by
// combining input object files.
// This file implements -r or --relocatable. That option forces the linker
// to combine input object files into another single large object file.
// Since the behavior of the linker when the option is given is quite
// different from that of the normal execution mode, we separate code for
// the feature into this separate file.
//
// The behavior of a linker with -r is pretty different from the one
// without -r, and adding code for -r to the regular code path could
// severely complicate it. Therefore, we implement -r as an
// independent feature from others. That's why this file share only a
// small amount of code with other files.
// The --relocatable option isn't used very often. After all, if you want
// to combine object files into a single file, you could use `ar`.
// However, some programs use it in a creative manner which is hard to be
// substituted with static archives, so we need to support this option in
// the same way as GNU ld does. A notable example is GHC (Glasgow Haskell
// Compiler). GHC has its own dynamic linker which can load a .o file (as
// opposed to a .so) into memory. GHC's module is not a shared object file
// but a combined object file.
//
// Since the -r option is a minor feature, we don't aim for speed in
// this file. This is a "better than nothing" implementation of the -r
// option.
// There are many different ways to combine object files into a single file.
// The simplest approach would be to just copy all sections from input files
// to an output file as-is with a few exceptions for singleton sections such
// as the symbol table or the string table. That works, but that's not
// compatible with GNU ld.
//
// Here is the strategy as to how to combine multiple object files
// into one:
// To be compatible with GNU ld, we need to do the followings:
//
// - Regular sections containing opaque data (e.g. ".text" or
// ".data") are just copied as-is without merging.
// - Regular sections containing opaque data (e.g. ".text" or ".data")
// are just copied as-is. Two sections with the same name are merged.
//
// - .symtab, .strtab and .shstrtab are merged.
//
// - COMDAT groups are uniquified.
//
// - Relocation sections are copied one by one, but we need to fix
// symbol indices.
// - Relocations are copied, but we need to fix symbol indices.
#include "mold.h"
#include "../archive-file.h"
#include "../output-file.h"
#include <unordered_map>
#include <unordered_set>
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
template <typename E> class RObjectFile;
// Create linker-synthesized sections
template <typename E>
class RChunk {
public:
RChunk() {
out_shdr.sh_addralign = 1;
}
static void r_create_synthetic_sections(Context<E> &ctx) {
auto push = [&]<typename T>(T *x) {
ctx.chunks.push_back(x);
ctx.chunk_pool.emplace_back(x);
return x;
};
virtual ~RChunk() = default;
virtual void update_shdr(Context<E> &ctx) {}
virtual void write_to(Context<E> &ctx) = 0;
std::string_view name;
i64 shndx = 0;
ElfShdr<E> in_shdr = {};
ElfShdr<E> out_shdr = {};
};
template <typename E>
class RInputSection : public RChunk<E> {
public:
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
void update_shdr(Context<E> &ctx) override;
void write_to(Context<E> &ctx) override;
RObjectFile<E> &file;
};
template <typename E>
class RSymtabSection : public RChunk<E> {
public:
RSymtabSection() {
this->name = ".symtab";
this->out_shdr.sh_type = SHT_SYMTAB;
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
this->out_shdr.sh_addralign = sizeof(Word<E>);
}
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
void add_global_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
void update_shdr(Context<E> &ctx) override;
void write_to(Context<E> &ctx) override;
std::unordered_map<std::string_view, i64> sym_map;
std::vector<ElfSym<E>> syms{1};
};
template <typename E>
class RStrtabSection : public RChunk<E> {
public:
RStrtabSection(std::string_view name) {
this->name = name;
this->out_shdr.sh_type = SHT_STRTAB;
this->out_shdr.sh_size = 1;
}
i64 add_string(std::string_view str);
void write_to(Context<E> &ctx) override;
std::unordered_map<std::string_view, i64> strings;
};
template <typename E>
class ROutputEhdr : public RChunk<E> {
public:
ROutputEhdr() {
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
}
void write_to(Context<E> &ctx) override;
};
template <typename E>
class ROutputShdr : public RChunk<E> {
public:
ROutputShdr() {
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
}
void update_shdr(Context<E> &ctx) override;
void write_to(Context<E> &ctx) override;
};
template <typename E>
class RObjectFile {
public:
RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf, bool is_alive);
void remove_comdats(Context<E> &ctx,
std::unordered_set<std::string_view> &groups);
template <typename T>
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
MappedFile<Context<E>> &mf;
std::span<ElfShdr<E>> elf_sections;
std::vector<std::unique_ptr<RInputSection<E>>> sections;
std::span<const ElfSym<E>> syms;
std::vector<i64> symidx;
std::unordered_set<std::string_view> defined_syms;
std::unordered_set<std::string_view> undef_syms;
i64 symtab_shndx = 0;
i64 first_global = 0;
bool is_alive;
const char *strtab = nullptr;
const char *shstrtab = nullptr;
};
template <typename E>
void RSymtabSection<E>::add_local_symbol(Context<E> &ctx, RObjectFile<E> &file,
i64 idx) {
ElfSym<E> sym = file.syms[idx];
assert(sym.st_bind == STB_LOCAL);
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common()) {
if (!file.sections[sym.st_shndx])
return;
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
}
std::string_view name = file.strtab + sym.st_name;
sym.st_name = ctx.r_strtab->add_string(name);
file.symidx[idx] = syms.size();
syms.push_back(sym);
ctx.ehdr = push(new OutputEhdr<E>(0));
ctx.shdr = push(new OutputShdr<E>);
ctx.eh_frame = push(new EhFrameSection<E>);
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
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>);
}
// Create SHT_GROUP (i.e. comdat group) sections. We uniquify comdat
// sections by signature. We want to propagate input comdat groups as
// output comdat groups if they are still alive after uniquification.
template <typename E>
void RSymtabSection<E>::add_global_symbol(Context<E> &ctx, RObjectFile<E> &file,
i64 idx) {
ElfSym<E> sym = file.syms[idx];
assert(sym.st_bind != STB_LOCAL);
static void create_comdat_group_sections(Context<E> &ctx) {
Timer t(ctx, "create_comdat_group_sections");
std::string_view name = file.strtab + sym.st_name;
auto [it, inserted] = sym_map.insert({name, syms.size()});
std::vector<std::vector<Chunk<E> *>> vec{ctx.objs.size()};
if (inserted) {
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common())
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
sym.st_name = ctx.r_strtab->add_string(name);
file.symidx[idx] = syms.size();
syms.push_back(sym);
return;
}
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
ObjectFile<E> &file = *ctx.objs[i];
file.symidx[idx] = it->second;
ElfSym<E> &existing = syms[it->second];
if (existing.is_undef() && !sym.is_undef()) {
if (!sym.is_abs() && !sym.is_common())
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
sym.st_name = existing.st_name;
existing = sym;
}
}
template <typename E>
void RSymtabSection<E>::update_shdr(Context<E> &ctx) {
this->out_shdr.sh_size = syms.size() * sizeof(ElfSym<E>);
this->out_shdr.sh_link = ctx.r_strtab->shndx;
}
template <typename E>
void RSymtabSection<E>::write_to(Context<E> &ctx) {
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->out_shdr.sh_offset);
for (i64 i = 1; i < syms.size(); i++)
buf[i] = syms[i];
}
template <typename E>
RInputSection<E>::RInputSection(Context<E> &ctx, RObjectFile<E> &file,
const ElfShdr<E> &shdr)
: file(file) {
this->name = file.shstrtab + shdr.sh_name;
this->in_shdr = shdr;
this->out_shdr = shdr;
}
template <typename E>
void RInputSection<E>::update_shdr(Context<E> &ctx) {
switch (this->in_shdr.sh_type) {
case SHT_GROUP:
this->out_shdr.sh_link = ctx.r_symtab->shndx;
this->out_shdr.sh_info = file.symidx[this->in_shdr.sh_info];
break;
case SHT_REL:
case SHT_RELA:
this->out_shdr.sh_link = ctx.r_symtab->shndx;
this->out_shdr.sh_info = file.sections[this->in_shdr.sh_info]->shndx;
break;
default:
if (this->in_shdr.sh_link) {
std::unique_ptr<RInputSection<E>> &sec =
file.sections[this->in_shdr.sh_info];
if (sec)
this->out_shdr.sh_link = sec->shndx;
else if (this->in_shdr.sh_link == file.symtab_shndx)
this->out_shdr.sh_link = ctx.r_symtab->shndx;
}
}
}
template <typename E>
void RInputSection<E>::write_to(Context<E> &ctx) {
if (this->in_shdr.sh_type == SHT_NOBITS)
return;
std::span<u8> contents = file.template get_data<u8>(ctx, this->in_shdr);
memcpy(ctx.buf + this->out_shdr.sh_offset, contents.data(), contents.size());
switch (this->in_shdr.sh_type) {
case SHT_GROUP: {
u32 *mem = (u32 *)(ctx.buf + this->out_shdr.sh_offset);
for (i64 i = 1; i < this->out_shdr.sh_size / sizeof(u32); i++)
mem[i] = file.sections[mem[i]]->shndx;
break;
}
case SHT_REL:
case SHT_RELA: {
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->out_shdr.sh_offset);
i64 size = this->out_shdr.sh_size / sizeof(ElfRel<E>);
for (i64 i = 0; i < size; i++) {
const ElfSym<E> &sym = file.syms[rel[i].r_sym];
if (sym.is_undef() || sym.is_abs() || sym.is_common() ||
file.sections[sym.st_shndx])
rel[i].r_sym = file.symidx[rel[i].r_sym];
else
memset(rel + i, 0, sizeof(ElfRel<E>));
}
i64 i = 0;
i64 j = 0;
for (; j < size; j++)
if (rel[j].r_type)
rel[i++] = rel[j];
for (; i < size; i++)
memset(rel + i, 0, sizeof(ElfRel<E>));
break;
}
}
}
template <typename E>
i64 RStrtabSection<E>::add_string(std::string_view str) {
auto [it, inserted] = strings.insert({str, this->out_shdr.sh_size});
if (inserted)
this->out_shdr.sh_size += str.size() + 1;
return it->second;
}
template <typename E>
void RStrtabSection<E>::write_to(Context<E> &ctx) {
for (auto [str, offset] : strings)
memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size());
}
template <typename E>
void ROutputEhdr<E>::write_to(Context<E> &ctx) {
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
memcpy(&hdr.e_ident, "\177ELF", 4);
hdr.e_ident[EI_CLASS] = (sizeof(Word<E>) == 8) ? ELFCLASS64 : ELFCLASS32;
hdr.e_ident[EI_DATA] = ELFDATA2LSB;
hdr.e_ident[EI_VERSION] = EV_CURRENT;
hdr.e_type = ET_REL;
hdr.e_machine = E::e_machine;
hdr.e_version = EV_CURRENT;
hdr.e_shoff = ctx.r_shdr->out_shdr.sh_offset;
hdr.e_ehsize = sizeof(ElfEhdr<E>);
hdr.e_shentsize = sizeof(ElfShdr<E>);
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr<E>);
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
}
template <typename E>
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk->shndx)
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
}
template <typename E>
void ROutputShdr<E>::write_to(Context<E> &ctx) {
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk->shndx)
hdr[chunk->shndx] = chunk->out_shdr;
}
template <typename E>
RObjectFile<E>::RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf,
bool is_alive)
: mf(mf), is_alive(is_alive) {
// Read ELF header and section header
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mf.data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mf.data + ehdr.e_shoff);
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
elf_sections = {sh_begin, sh_begin + num_sections};
sections.resize(num_sections);
// Read .shstrtab
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
? sh_begin->sh_link : ehdr.e_shstrndx;
shstrtab = (char *)(mf.data + elf_sections[shstrtab_idx].sh_offset);
// Read .symtab
for (i64 i = 1; i < elf_sections.size(); i++) {
ElfShdr<E> &shdr = elf_sections[i];
if (shdr.sh_type == SHT_SYMTAB) {
syms = get_data<const ElfSym<E>>(ctx, shdr);
strtab = (char *)(mf.data + elf_sections[shdr.sh_link].sh_offset);
symtab_shndx = i;
first_global = shdr.sh_info;
break;
}
}
symidx.resize(syms.size());
// Read sections
for (i64 i = 1; i < elf_sections.size(); i++) {
ElfShdr<E> &shdr = elf_sections[i];
switch (shdr.sh_type) {
case SHT_NULL:
case SHT_SYMTAB:
case SHT_STRTAB:
break;
default:
sections[i].reset(new RInputSection(ctx, *this, shdr));
}
}
// Read global symbols
for (i64 i = first_global; i < syms.size(); i++) {
std::string_view name = strtab + syms[i].st_name;
if (syms[i].is_defined())
defined_syms.insert(name);
else
undef_syms.insert(name);
}
}
// Remove duplicate comdat groups
template <typename E>
void RObjectFile<E>::remove_comdats(Context<E> &ctx,
std::unordered_set<std::string_view> &groups) {
for (i64 i = 1; i < sections.size(); i++) {
if (!sections[i])
continue;
ElfShdr<E> &shdr = sections[i]->in_shdr;
if (shdr.sh_type != SHT_GROUP)
continue;
// Get a comdat group signature and insert it into a set.
const ElfSym<E> &sym = syms[shdr.sh_info];
std::string_view signature = strtab + sym.st_name;
if (groups.insert(signature).second)
continue;
// If it is a duplicate, remove it and its members.
for (i64 j : this->template get_data<u32>(ctx, shdr).subspan(1))
sections[j] = nullptr;
sections[i] = nullptr;
}
}
template <typename E>
template <typename T>
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
T *begin = (T *)(mf.data + shdr.sh_offset);
T *end = (T *)(mf.data + shdr.sh_offset + shdr.sh_size);
return {begin, end};
}
template <typename E>
static std::vector<std::unique_ptr<RObjectFile<E>>>
open_files(Context<E> &ctx, std::span<std::string> args) {
std::vector<std::unique_ptr<RObjectFile<E>>> files;
bool whole_archive = false;
while (!args.empty()) {
const std::string &arg = args[0];
args = args.subspan(1);
if (arg == "--whole-archive") {
whole_archive = true;
continue;
}
if (arg == "--no-whole-archive") {
whole_archive = false;
continue;
}
if (arg.starts_with("--version-script=") ||
arg.starts_with("--dynamic-list=") ||
arg.starts_with("--export-dynamic-symbol=") ||
arg.starts_with("--export-dynamic-symbol-list="))
continue;
MappedFile<Context<E>> *mf = nullptr;
if (arg == "-l") {
mf = find_library(ctx, arg.substr(2));
} else {
if (arg.starts_with('-'))
for (ComdatGroupRef<E> &ref : file.comdat_groups) {
if (ref.group->owner != file.priority)
continue;
mf = MappedFile<Context<E>>::must_open(ctx, arg);
}
switch (get_file_type(mf)) {
case FileType::ELF_OBJ:
files.emplace_back(new RObjectFile<E>(ctx, *mf, true));
break;
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
files.emplace_back(new RObjectFile<E>(ctx, *child, whole_archive));
break;
default:
break;
Symbol<E> *sym = file.symbols[file.elf_sections[ref.sect_idx].sh_info];
assert(sym);
std::vector<Chunk<E> *> members;
for (u32 j : ref.members) {
const ElfShdr<E> &shdr = file.elf_sections[j];
if (shdr.sh_type == (E::is_rela ? SHT_RELA : SHT_REL)) {
InputSection<E> &isec = *file.sections[shdr.sh_info];
members.push_back(isec.output_section->reloc_sec.get());
} else {
InputSection<E> &isec = *file.sections[j];
members.push_back(isec.output_section);
}
}
vec[i].push_back(new ComdatGroupSection<E>(*sym, std::move(members)));
}
});
for (std::vector<Chunk<E> *> &vec2 : vec) {
for (Chunk<E> *chunk : vec2) {
ctx.chunks.push_back(chunk);
ctx.chunk_pool.emplace_back(chunk);
}
}
return files;
}
// Unresolved undefined symbols in the -r mode are simply propagated to an
// output file as undefined symbols. This function guarantees that
// unresolved undefined symbols belongs to some input file.
template <typename E>
static i64 assign_offsets(Context<E> &ctx) {
i64 offset = 0;
for (RChunk<E> *chunk : ctx.r_chunks) {
offset = align_to(offset, chunk->out_shdr.sh_addralign);
chunk->out_shdr.sh_offset = offset;
offset += chunk->out_shdr.sh_size;
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;
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];
if (!esym.is_undef())
continue;
std::scoped_lock lock(sym.mu);
if (sym.file &&
(!sym.esym().is_undef() || sym.file->priority <= file->priority))
continue;
sym.file = file;
sym.origin = 0;
sym.value = 0;
sym.sym_idx = i;
}
});
}
// Set output section in-file offsets. Output section memory addresses
// are left as zero.
template <typename E>
static u64 r_set_osec_offsets(Context<E> &ctx) {
u64 offset = 0;
for (Chunk<E> *chunk : ctx.chunks) {
offset = align_to(offset, chunk->shdr.sh_addralign);
chunk->shdr.sh_offset = offset;
offset += chunk->shdr.sh_size;
}
return offset;
}
static bool contains(std::unordered_set<std::string_view> &a,
std::unordered_set<std::string_view> &b) {
for (std::string_view x : b)
if (a.contains(x))
return true;
return false;
}
template <typename E>
void combine_objects(Context<E> &ctx, std::span<std::string> file_args) {
// Read object files
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
void combine_objects(Context<E> &ctx) {
compute_merged_section_sizes(ctx);
// Identify needed objects
std::unordered_set<std::string_view> undef_syms;
auto add_syms = [&](RObjectFile<E> &file) {
undef_syms.insert(file.undef_syms.begin(), file.undef_syms.end());
for (std::string_view name : file.defined_syms)
undef_syms.erase(name);
file.is_alive = true;
};
create_output_sections(ctx);
for (std::unique_ptr<RObjectFile<E>> &file : files)
if (file->is_alive)
add_syms(*file);
r_create_synthetic_sections(ctx);
for (;;) {
bool added = false;
for (std::unique_ptr<RObjectFile<E>> &file : files) {
if (!file->is_alive && contains(undef_syms, file->defined_syms)) {
add_syms(*file);
added = true;
}
}
if (!added)
break;
}
r_claim_unresolved_symbols(ctx);
std::erase_if(files, [](std::unique_ptr<RObjectFile<E>> &file) {
return !file->is_alive;
});
compute_section_sizes(ctx);
// Remove duplicate comdat groups
std::unordered_set<std::string_view> comdat_groups;
for (std::unique_ptr<RObjectFile<E>> &file : files)
file->remove_comdats(ctx, comdat_groups);
sort_output_sections(ctx);
// Create headers and linker-synthesized sections
ROutputEhdr<E> ehdr;
ROutputShdr<E> shdr;
RSymtabSection<E> symtab;
RStrtabSection<E> shstrtab(".shstrtab");
RStrtabSection<E> strtab(".strtab");
create_output_symtab(ctx);
ctx.r_chunks.push_back(&ehdr);
ctx.r_chunks.push_back(&shstrtab);
ctx.r_chunks.push_back(&strtab);
ctx.eh_frame->construct(ctx);
ctx.r_ehdr = &ehdr;
ctx.r_shdr = &shdr;
ctx.r_shstrtab = &shstrtab;
ctx.r_strtab = &strtab;
ctx.r_symtab = &symtab;
create_reloc_sections(ctx);
// Add input sections to output sections
for (std::unique_ptr<RObjectFile<E>> &file : files)
for (std::unique_ptr<RInputSection<E>> &sec : file->sections)
if (sec)
ctx.r_chunks.push_back(sec.get());
create_comdat_group_sections(ctx);
ctx.r_chunks.push_back(&symtab);
ctx.r_chunks.push_back(&shdr);
compute_section_headers(ctx);
// Assign output section indices
i64 shndx = 1;
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk != &ehdr && chunk != &shdr)
chunk->shndx = shndx++;
// Add section names to .shstrtab
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk->shndx)
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
// Copy symbols from input objects to an output object
for (std::unique_ptr<RObjectFile<E>> &file : files)
for (i64 i = 1; i < file->first_global; i++)
symtab.add_local_symbol(ctx, *file, i);
symtab.out_shdr.sh_info = symtab.syms.size();
for (std::unique_ptr<RObjectFile<E>> &file : files)
for (i64 i = file->first_global; i < file->syms.size(); i++)
symtab.add_global_symbol(ctx, *file, i);
// Finalize section header
for (RChunk<E> *chunk : ctx.r_chunks)
chunk->update_shdr(ctx);
// Open an output file
i64 filesize = assign_offsets(ctx);
std::unique_ptr<OutputFile<Context<E>>> out =
i64 filesize = r_set_osec_offsets(ctx);
ctx.output_file =
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
memset(out->buf, 0, filesize);
ctx.buf = out->buf;
ctx.buf = ctx.output_file->buf;
// Write to the output file
for (RChunk<E> *chunk : ctx.r_chunks)
chunk->write_to(ctx);
out->close(ctx);
copy_chunks(ctx);
clear_padding(ctx);
ctx.output_file->close(ctx);
ctx.checkpoint();
if (ctx.arg.print_map)
print_map(ctx);
if (ctx.arg.stats)
show_stats(ctx);
if (ctx.arg.perf)
print_timer_records(ctx.timer_records);
if (ctx.arg.quick_exit)
_exit(0);
}
#define INSTANTIATE(E) \
template void combine_objects(Context<E> &, std::span<std::string>);
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template void combine_objects(Context<E> &);
} // namespace mold::elf
+15 -11
View File
@@ -1,4 +1,7 @@
#if !defined(_WIN32) && !defined(__APPLE__)
#include "mold.h"
#include "config.h"
#include <filesystem>
#include <signal.h>
@@ -10,6 +13,7 @@
namespace mold::elf {
#ifdef MOLD_X86_64
// 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.
@@ -49,10 +53,11 @@ std::function<void()> fork_child() {
return [=] {
char buf[] = {1};
int n = write(pipefd[1], buf, 1);
[[maybe_unused]] int n = write(pipefd[1], buf, 1);
assert(n == 1);
};
}
#endif
template <typename E>
static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
@@ -62,13 +67,11 @@ static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
if (std::filesystem::is_regular_file(path, ec) && !ec)
return path;
#ifdef LIBDIR
// If not found, search $(LIBDIR)/mold, which is /usr/local/lib/mold
// If not found, search $(MOLD_LIBDIR)/mold, which is /usr/local/lib/mold
// by default.
path = LIBDIR "/mold/mold-wrapper.so";
path = MOLD_LIBDIR "/mold/mold-wrapper.so";
if (std::filesystem::is_regular_file(path, ec) && !ec)
return path;
#endif
// Look for ../lib/mold/mold-wrapper.so
path = self.parent_path() / "../lib/mold/mold-wrapper.so";
@@ -81,13 +84,13 @@ 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) {
std::string_view arg1 = argv[1];
assert(arg1 == "-run" || arg1 == "--run");
assert(argv[1] == "-run"s || argv[1] == "--run"s);
if (!argv[2])
Fatal(ctx) << "-run: argument missing";
// Get the mold-wrapper.so path
std::string self = std::filesystem::read_symlink("/proc/self/exe");
std::string self = get_self_path();
std::string dso_path = find_dso(ctx, self);
// Set environment variables
@@ -110,9 +113,10 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
}
#define INSTANTIATE(E) \
template void process_run_subcommand(Context<E> &, int, char **)
using E = MOLD_TARGET;
INSTANTIATE_ALL;
template void process_run_subcommand(Context<E> &, int, char **);
} // namespace mold::elf
#endif
+200 -85
View File
@@ -1,15 +1,24 @@
// 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.
// 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. If a branch target is further than that, we
// need to let it branch to a linker-synthesized code sequence that
// 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.
#include "mold.h"
@@ -18,6 +27,111 @@
namespace mold::elf {
// Branch reach in bytes.
//
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
// That means the range is [-2^27, 2^27).
//
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
// have twice longer range than its Thumb counterparts, but we
// conservatively use the Thumb's limitation.
//
// PPC's branch has 24 bits immediate, and the instructions are aligned
// to 4, therefore 26.
//
// Here is the summary of branch instructions reaches:
//
// ARM64: PC ± 128 MiB
// ARM32: PC ± 16 MiB
// PPC: PC ± 32 MiB
template <typename E>
static consteval i64 max_distance() {
if constexpr (is_arm64<E>) {
return 1LL << 27;
} else if constexpr (is_arm32<E>) {
return 1LL << 24;
} else if constexpr (is_ppc<E>) {
return 1LL << 25;
} else {
static_assert(is_hppa<E>);
return 1LL << 18;
}
}
// We create thunks for each 12.8/1.6/3.2 MiB code block for
// ARM64/ARM32/PPC, respectively.
template <typename E>
static constexpr i64 batch_size = max_distance<E>() / 10;
// We assume that a single thunk group is smaller than 100 KiB.
static constexpr i64 max_thunk_size = 102400;
// Returns true if a given relocation is of type used for function calls.
template <typename E>
static bool needs_thunk_rel(const ElfRel<E> &r) {
u32 ty = r.r_type;
if constexpr (is_arm64<E>) {
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
} else if constexpr (is_arm32<E>) {
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL;
} else if constexpr (is_ppc32<E>) {
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
} else if constexpr (is_ppc64<E>) {
return ty == R_PPC64_REL24;
} else {
static_assert(is_hppa<E>);
return ty == R_PARISC_PCREL17F || ty == R_PARISC_PCREL32;
}
}
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))
return false;
}
// (TODO)
if constexpr (is_hppa<E>)
if (sym.has_opd(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 -max_distance<E>() <= val && val < max_distance<E>();
}
template <typename E>
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
@@ -27,19 +141,61 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
}
}
// Scan relocations to collect symbols that need thunks.
template <typename E>
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
RangeExtensionThunk<E> &thunk) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
range_extn.resize(rels.size());
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (!needs_thunk_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) {
range_extn[i].thunk_idx = sym.extra.thunk_idx;
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
continue;
}
// Otherwise, add the symbol to the current thunk if it's not
// added already.
range_extn[i].thunk_idx = thunk.thunk_idx;
range_extn[i].sym_idx = -1;
if (sym.flags.exchange(-1) == 0) {
std::scoped_lock lock(thunk.mu);
thunk.symbols.push_back(&sym);
}
}
}
template <typename E>
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
std::span<InputSection<E> *> members = osec.members;
if (members.empty())
std::span<InputSection<E> *> m = osec.members;
if (m.empty())
return;
members[0]->offset = 0;
m[0]->offset = 0;
// Initialize input sections with a dummy offset so that we can
// distinguish sections that have got an address with the one who
// haven't.
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
members[i]->offset = -1;
tbb::parallel_for((i64)1, (i64)m.size(), [&](i64 i) {
m[i]->offset = -1;
});
// We create thunks from the beginning of the section to the end.
@@ -51,28 +207,30 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
i64 d = 0;
i64 offset = 0;
while (b < members.size()) {
// Move D foward as far as we can jump from B to D.
while (d < members.size() &&
offset - members[b]->offset < E::thunk_max_distance) {
offset = align_to(offset, 1 << members[d]->p2align);
members[d]->offset = offset;
offset += members[d]->sh_size;
while (b < m.size()) {
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
while (d < m.size() &&
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
m[b]->offset + max_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 GROUP_SIZE.
while (c < members.size() &&
members[c]->offset - members[b]->offset < E::thunk_group_size)
// 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 < m.size() &&
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
c++;
// Move A forward so that A is reachable from C.
if (c > 0) {
i64 c_end = members[c - 1]->offset + members[c - 1]->sh_size;
while (a < osec.thunks.size() &&
osec.thunks[a]->offset < c_end - E::thunk_max_distance)
reset_thunk(*osec.thunks[a++]);
}
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
while (a < osec.thunks.size() &&
osec.thunks[a]->offset + max_distance<E>() < c_offset)
reset_thunk(*osec.thunks[a++]);
// Create a thunk for input sections between B and C and place it at D.
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
@@ -82,49 +240,14 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
offset = align_to(offset, thunk.alignment);
thunk.offset = offset;
std::mutex mu;
// Scan relocations between B and C to collect symbols that need thunks.
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
[&](InputSection<E> *isec) {
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
std::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
range_extn.resize(rels.size());
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (!needs_thunk_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, sym, *isec, rel))
continue;
// If the symbol is already in another thunk, reuse it.
if (sym.extra.thunk_idx != -1) {
range_extn[i].thunk_idx = sym.extra.thunk_idx;
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
continue;
}
// Otherwise, add the symbol to this thunk if it's not added already.
range_extn[i].thunk_idx = thunk.thunk_idx;
range_extn[i].sym_idx = -1;
if (sym.flags.exchange(-1) == 0) {
std::scoped_lock lock(mu);
thunk.symbols.push_back(&sym);
}
}
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
scan_rels(ctx, *isec, thunk);
});
// Now that we know the number of symbols in the thunk, we can compute
// its size.
assert(thunk.size() < max_thunk_size);
offset += thunk.size();
// Sort symbols added to the thunk to make the output deterministic.
@@ -140,20 +263,17 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
}
// Scan relocations again to fix symbol offsets in the last thunk.
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
[&](InputSection<E> *isec) {
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::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
for (i64 i = 0; i < rels.size(); i++) {
if (range_extn[i].thunk_idx == thunk.thunk_idx) {
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
}
}
for (i64 i = 0; i < rels.size(); i++)
if (range_extn[i].thunk_idx == thunk.thunk_idx)
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
});
// Move B forward to point to the begining of the next group.
// Move B forward to point to the begining of the next batch.
b = c;
}
@@ -161,16 +281,11 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
reset_thunk(*osec.thunks[a++]);
osec.shdr.sh_size = offset;
for (InputSection<E> *isec : members)
osec.shdr.sh_addralign =
std::max<u32>(osec.shdr.sh_addralign, 1 << isec->p2align);
}
template
void create_range_extension_thunks(Context<ARM64> &, OutputSection<ARM64> &);
template
void create_range_extension_thunks(Context<ARM32> &, OutputSection<ARM32> &);
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || MOLD_HPPA32
using E = MOLD_TARGET;
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
#endif
} // namespace mold::elf
+1 -1
View File
@@ -20,7 +20,7 @@ std::string path_clean(std::string_view path) {
std::filesystem::path to_abs_path(std::filesystem::path path) {
if (path.is_absolute())
return path;
return path.lexically_normal();
return (std::filesystem::current_path() / path).lexically_normal();
}
+53 -18
View File
@@ -31,14 +31,32 @@ bool is_text_file(MappedFile<C> *mf) {
}
template <typename E, typename C>
inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
using namespace mold::elf;
const char *data = mf->get_contents().data();
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
// e_shstrndx is a 16-bit field. If .shstrtab's section index is
// too large, the actual number is stored to sh_link field.
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
? sh_begin->sh_link : ehdr.e_shstrndx;
for (ElfShdr<E> &sec : shdrs) {
// GCC LTO object contains only sections symbols followed by a common
// symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1` for older
// GCC releases).
//
// However, FAT LTO objects don't have any of the above mentioned symbols
// and can identify LTO by `.gnu.lto_.symtab.` section, similarly
// to what lto-plugin does. However, if LTO linker plug-in is not available,
// use the emitted assembly instead.
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (opt_plugin && name.starts_with (".gnu.lto_.symtab."))
return true;
if (sec.sh_type != SHT_SYMTAB)
continue;
@@ -49,8 +67,6 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
};
// GCC LTO object contains only sections symbols followed by a common
// symbol whose name is `__gnu_lto_v1` or `__gnu_lto_slim`.
i64 i = 1;
while (i < elf_syms.size() && skip(elf_syms[i].st_type))
i++;
@@ -68,30 +84,49 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
}
template <typename C>
FileType get_file_type(MappedFile<C> *mf) {
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
using namespace elf;
std::string_view data = mf->get_contents();
if (data.empty())
return FileType::EMPTY;
if (data.starts_with("\177ELF")) {
switch (*(ul16 *)(data.data() + 16)) {
case 1: {
// ET_REL
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
u8 byte_order = ((ElfEhdr<I386> *)data.data())->e_ident[EI_DATA];
if (ehdr.e_ident[elf::EI_CLASS] == elf::ELFCLASS32) {
if (is_gcc_lto_obj<elf::I386>(mf))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<elf::X86_64>(mf))
return FileType::GCC_LTO_OBJ;
if (byte_order == ELFDATA2LSB) {
auto &ehdr = *(ElfEhdr<I386> *)data.data();
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
}
return FileType::ELF_OBJ;
}
case 3: // ET_DYN
return FileType::ELF_DSO;
if (ehdr.e_type == ET_DYN)
return FileType::ELF_DSO;
} else {
auto &ehdr = *(ElfEhdr<M68K> *)data.data();
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
}
if (ehdr.e_type == ET_DYN)
return FileType::ELF_DSO;
}
return FileType::UNKNOWN;
}
+13 -7
View File
@@ -1,24 +1,28 @@
#!/bin/bash -x
#!/bin/bash
set -e
source /etc/os-release
set -x
# The first line for each distro installs a build dependency.
# The second line installs extra packages for `make test`.
# The second line installs extra packages for unittests.
#
# Feel free to send me a PR if you find a missing OS.
# Feel free to send me a PR if your OS is not on this list.
case "$ID-$VERSION_ID" in
ubuntu-20.* | pop-20.*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
apt-get install -y file bsdmainutils
apt-get install -y file
;;
ubuntu-22.* | pop-22.* | debian-11 | raspbian-*)
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
apt-get update
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
apt-get install -y file bsdmainutils
apt-get install -y file
;;
fedora-*)
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
dnf install -y glibc-static file libstdc++-static diffutils
dnf install -y glibc-static file libstdc++-static diffutils util-linux
;;
opensuse-leap-*)
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
@@ -29,9 +33,11 @@ opensuse-tumbleweed-*)
zypper install -y glibc-devel-static tar diffutils util-linux
;;
gentoo-*)
emerge-webrsync
emerge dev-util/cmake sys-libs/zlib
;;
arch-*)
pacman -Sy
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
;;
*)
+221
View File
@@ -0,0 +1,221 @@
// 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
-181
View File
@@ -1,181 +0,0 @@
// This file defines integral types for file input/output. You should
// use these types instead of the plain integers (such as u32 or i32)
// when reading from/writing to an mmap'ed file area.
//
// Here is why you need these types. In C/C++, all data accesses must
// be aligned. That is, if you read an N byte value from memory, the
// address of that value must be a multiple of N. For example, reading
// an u32 value from address 16 is legal, while reading from 18 is not
// (since 18 is not a multiple of 4.) An unaligned access yields an
// undefined behavior.
//
// All data members of the ELF data structures are naturally aligned,
// so it may look like we don't need the integral types defined in
// this file to access them. But there's a catch; if an object file is
// in an archive file (a .a file), the beginning of the file is
// guaranteed to be aligned only to a 2 bytes boundary, because ar
// aligns each member only to a 2 byte boundary. Therefore, any data
// members larger than 2 bytes may be unaligned in an mmap'ed memory,
// and thus you always need to use the integral types in this file to
// access it.
#pragma once
#include <cstdint>
#include <cstring>
#ifdef __BIG_ENDIAN__
#error "mold does not support big-endian hosts"
#endif
namespace mold {
template <typename T, size_t SIZE = sizeof(T)>
class LittleEndian {
public:
LittleEndian() { *this = 0; }
LittleEndian(T x) { *this = x; }
operator T() const {
T x = 0;
memcpy(&x, val, SIZE);
return x;
}
LittleEndian &operator=(T x) {
memcpy(&val, &x, SIZE);
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:
uint8_t val[SIZE];
};
using il16 = LittleEndian<int16_t>;
using il32 = LittleEndian<int32_t>;
using il64 = LittleEndian<int64_t>;
using ul16 = LittleEndian<uint16_t>;
using ul24 = LittleEndian<uint32_t, 3>;
using ul32 = LittleEndian<uint32_t>;
using ul64 = LittleEndian<uint64_t>;
template <typename T>
class BigEndian {
public:
BigEndian() = delete;
operator T() const {
T x;
memcpy(&x, val, sizeof(T));
return bswap(x);
}
BigEndian &operator=(T x) {
x = bswap(x);
memcpy(&val, &x, sizeof(T));
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;
}
private:
uint8_t val[sizeof(T)];
static T bswap(T x) {
// Compiler is usually smart enough to compile the following code into
// a single bswap instruction. See https://godbolt.org/z/7nvaM7qab
if constexpr (sizeof(T) == 2) {
return ((x & 0xff00) >> 8) |
((x & 0x00ff) << 8);
} else if constexpr (sizeof(T) == 4) {
return ((x & 0xff000000) >> 24) |
((x & 0x00ff0000) >> 8) |
((x & 0x0000ff00) << 8) |
((x & 0x000000ff) << 24);
} else {
return ((x & 0xff000000'00000000) >> 56) |
((x & 0x00ff0000'00000000) >> 40) |
((x & 0x0000ff00'00000000) >> 24) |
((x & 0x000000ff'00000000) >> 8) |
((x & 0x00000000'ff000000) << 8) |
((x & 0x00000000'00ff0000) << 24) |
((x & 0x00000000'0000ff00) << 40) |
((x & 0x00000000'000000ff) << 56);
}
}
};
using ib16 = BigEndian<int16_t>;
using ib32 = BigEndian<int32_t>;
using ib64 = BigEndian<int64_t>;
using ub16 = BigEndian<uint16_t>;
using ub32 = BigEndian<uint32_t>;
using ub64 = BigEndian<uint64_t>;
} // namespace mold
-709
View File
@@ -1,709 +0,0 @@
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::macho {
using E = ARM64;
static u64 page(u64 val) {
return val & 0xffff'ffff'ffff'f000;
}
static u64 page_offset(u64 hi, u64 lo) {
u64 val = page(hi) - page(lo);
return (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
}
template <>
void StubsSection<E>::copy_buf(Context<E> &ctx) {
ul32 *buf = (ul32 *)(ctx.buf + this->hdr.offset);
for (i64 i = 0; i < syms.size(); i++) {
static const u32 insn[] = {
0x90000010, // adrp x16, $ptr@PAGE
0xf9400210, // ldr x16, [x16, $ptr@PAGEOFF]
0xd61f0200, // br x16
};
static_assert(sizeof(insn) == E::stub_size);
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + word_size * i;
u64 this_addr = this->hdr.addr + E::stub_size * i;
memcpy(buf, insn, sizeof(insn));
buf[0] |= page_offset(la_addr, this_addr);
buf[1] |= bits(la_addr, 11, 3) << 10;
buf += 3;
}
}
template <>
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
ul32 *start = (ul32 *)(ctx.buf + this->hdr.offset);
ul32 *buf = start;
static const u32 insn0[] = {
0x90000011, // adrp x17, $__dyld_private@PAGE
0x91000231, // add x17, x17, $__dyld_private@PAGEOFF
0xa9bf47f0, // stp x16, x17, [sp, #-16]!
0x90000010, // adrp x16, $dyld_stub_binder@PAGE
0xf9400210, // ldr x16, [x16, $dyld_stub_binder@PAGEOFF]
0xd61f0200, // br x16
};
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
memcpy(buf, insn0, sizeof(insn0));
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
buf[0] |= page_offset(dyld_private, this->hdr.addr);
buf[1] |= bits(dyld_private, 11, 0) << 10;
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx);
buf[3] |= page_offset(stub_binder, this->hdr.addr - 12);
buf[4] |= bits(stub_binder, 11, 0) << 10;
buf += 6;
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
static const u32 insn[] = {
0x18000050, // ldr w16, addr
0x14000000, // b stubHelperHeader
0x00000000, // addr: .long <idx>
};
static_assert(sizeof(insn) == E::stub_helper_size);
memcpy(buf, insn, sizeof(insn));
buf[1] |= bits((start - buf - 1) * 4, 27, 2);
buf[2] = ctx.stubs.bind_offsets[i];
buf += 3;
}
}
template <>
std::vector<Relocation<E>>
read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr) {
std::vector<Relocation<E>> vec;
vec.reserve(hdr.nreloc);
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
for (i64 i = 0; i < hdr.nreloc; i++) {
i64 addend = 0;
switch (rels[i].type) {
case ARM64_RELOC_UNSIGNED:
case ARM64_RELOC_SUBTRACTOR:
switch (rels[i].p2size) {
case 2:
addend = *(il32 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
break;
case 3:
addend = *(il64 *)((u8 *)file.mf->data + hdr.offset + rels[i].offset);
break;
default:
unreachable();
}
break;
case ARM64_RELOC_ADDEND:
addend = rels[i++].idx;
break;
}
MachRel &r = rels[i];
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
Relocation<E> &rel = vec.back();
if (i > 0 && rels[i - 1].type == ARM64_RELOC_SUBTRACTOR)
rel.is_subtracted = true;
if (!rel.is_subtracted && rels[i].type != ARM64_RELOC_SUBTRACTOR)
rel.is_pcrel = r.is_pcrel;
if (r.is_extern) {
rel.sym = file.syms[r.idx];
rel.addend = addend;
continue;
}
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend) : addend;
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
if (!target)
Fatal(ctx) << file << ": bad relocation: " << r.offset;
rel.subsec = target;
rel.addend = addr - target->input_addr;
}
return vec;
}
template <>
void Subsection<E>::scan_relocations(Context<E> &ctx) {
for (Relocation<E> &r : get_rels()) {
Symbol<E> *sym = r.sym;
if (!sym)
continue;
if (sym->is_imported && sym->file->is_dylib)
((DylibFile<E> *)sym->file)->is_alive = true;
switch (r.type) {
case ARM64_RELOC_UNSIGNED:
if (sym->is_imported) {
if (r.p2size != 3) {
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
<< std::hex << r.offset << " against symbol `"
<< *sym << "' can not be used";
}
r.needs_dynrel = true;
}
break;
case ARM64_RELOC_GOT_LOAD_PAGE21:
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
case ARM64_RELOC_POINTER_TO_GOT:
sym->flags |= NEEDS_GOT;
break;
case ARM64_RELOC_TLVP_LOAD_PAGE21:
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
sym->flags |= NEEDS_THREAD_PTR;
break;
}
if (sym->is_imported)
sym->flags |= NEEDS_STUB;
}
}
template <>
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
std::span<Relocation<E>> rels = get_rels();
for (i64 i = 0; i < rels.size(); i++) {
Relocation<E> &r = rels[i];
u8 *loc = buf + r.offset;
i64 val = r.addend;
u64 pc = get_addr(ctx) + r.offset;
if (r.sym && !r.sym->file) {
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
continue;
}
// Compute a relocated value.
switch (r.type) {
case ARM64_RELOC_UNSIGNED:
case ARM64_RELOC_BRANCH26:
case ARM64_RELOC_PAGE21:
case ARM64_RELOC_PAGEOFF12:
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
break;
case ARM64_RELOC_SUBTRACTOR: {
Relocation<E> s = rels[++i];
assert(s.type == ARM64_RELOC_UNSIGNED);
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
val += val2 - val1;
break;
}
case ARM64_RELOC_GOT_LOAD_PAGE21:
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
case ARM64_RELOC_POINTER_TO_GOT:
val += r.sym->get_got_addr(ctx);
break;
case ARM64_RELOC_TLVP_LOAD_PAGE21:
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
val += r.sym->get_tlv_addr(ctx);
break;
default:
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
}
// An address of a thread-local variable is computed as an offset
// to the beginning of the first thread-local section.
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES)
val -= ctx.tls_begin;
// Write a computed value to the output buffer.
switch (r.type) {
case ARM64_RELOC_UNSIGNED:
case ARM64_RELOC_SUBTRACTOR:
case ARM64_RELOC_POINTER_TO_GOT:
if (r.is_pcrel)
val -= pc;
if (r.p2size == 2)
*(ul32 *)loc = val;
else if (r.p2size == 3)
*(ul64 *)loc = val;
else
unreachable();
break;
case ARM64_RELOC_BRANCH26: {
assert(r.is_pcrel);
val -= pc;
i64 lo = -(1 << 27);
i64 hi = 1 << 27;
if (val < lo || hi <= val) {
val = isec.osec.thunks[r.thunk_idx]->get_addr(r.thunk_sym_idx) - pc;
assert(lo <= val && val < hi);
}
*(ul32 *)loc |= bits(val, 27, 2);
break;
}
case ARM64_RELOC_PAGE21:
case ARM64_RELOC_GOT_LOAD_PAGE21:
case ARM64_RELOC_TLVP_LOAD_PAGE21:
assert(r.is_pcrel);
*(ul32 *)loc |= page_offset(val, pc);
break;
case ARM64_RELOC_PAGEOFF12:
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
assert(!r.is_pcrel);
u32 insn = *(ul32 *)loc;
i64 scale = 0;
if ((insn & 0x3b000000) == 0x39000000) {
scale = bits(insn, 31, 30);
if (scale == 0 && (insn & 0x04800000) == 0x04800000)
scale = 4;
}
*(ul32 *)loc |= bits(val, 11, scale) << 10;
break;
}
default:
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
}
}
}
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
sym->thunk_idx = -1;
sym->thunk_sym_idx = -1;
sym->flags &= (u8)~NEEDS_RANGE_EXTN_THUNK;
}
}
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
Subsection<E> &subsec, Relocation<E> &rel) {
// We pessimistically assume that PLT entries are unreacahble.
if (sym.stub_idx != -1)
return false;
// We create thunks with a pessimistic assumption that all
// out-of-section relocations would be out-of-range.
if (!sym.subsec || &sym.subsec->isec.osec != &subsec.isec.osec)
return false;
if (sym.subsec->output_offset == -1)
return false;
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 addr = sym.get_addr(ctx);
i64 pc = subsec.get_addr(ctx) + rel.offset;
i64 val = addr + rel.addend - pc;
return -(1 << 27) <= val && val < (1 << 27);
}
// We create a thunk no further than 100 MiB from any section.
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
// We create a thunk for each 10 MiB input sections.
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
// ARM64's call/jump instructions take 27 bits displacement, so they
// can refer only up to ±128 MiB. 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".
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
std::span<Subsection<E> *> members = osec.members;
if (members.empty())
return;
members[0]->output_offset = 0;
// Initialize input sections with a dummy offset so that we can
// distinguish sections that have got an address with the one who
// haven't.
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
members[i]->output_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.
i64 a = 0;
i64 b = 0;
i64 c = 0;
i64 d = 0;
i64 offset = 0;
while (b < members.size()) {
// Move D foward as far as we can jump from B to D.
while (d < members.size() &&
offset - members[b]->output_offset < MAX_DISTANCE) {
offset = align_to(offset, 1 << members[d]->p2align);
members[d]->output_offset = offset;
offset += members[d]->input_size;
d++;
}
// Move C forward so that C is apart from B by GROUP_SIZE.
while (c < members.size() &&
members[c]->output_offset - members[b]->output_offset < GROUP_SIZE)
c++;
// Move A forward so that A is reachable from C.
if (c > 0) {
i64 c_end = members[c - 1]->output_offset + members[c - 1]->input_size;
while (a < osec.thunks.size() &&
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
reset_thunk(*osec.thunks[a++]);
}
// Create a thunk for input sections between B and C and place it at D.
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
thunk.thunk_idx = osec.thunks.size() - 1;
thunk.offset = offset;
// Scan relocations between B and C to collect symbols that need thunks.
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
[&](Subsection<E> *subsec) {
std::span<Relocation<E>> rels = subsec->get_rels();
for (i64 i = 0; i < rels.size(); i++) {
Relocation<E> &r = rels[i];
if (!r.sym->file || r.type != ARM64_RELOC_BRANCH26)
continue;
// Skip if the destination is within reach.
if (is_reachable(ctx, *r.sym, *subsec, r))
continue;
// If the symbol is already in another thunk, reuse it.
if (r.sym->thunk_idx != -1) {
r.thunk_idx = r.sym->thunk_idx;
r.thunk_sym_idx = r.sym->thunk_sym_idx;
continue;
}
// Otherwise, add the symbol to this thunk if it's not added already.
r.thunk_idx = thunk.thunk_idx;
r.thunk_sym_idx = -1;
if (!(r.sym->flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
NEEDS_RANGE_EXTN_THUNK)) {
std::scoped_lock lock(thunk.mu);
thunk.symbols.push_back(r.sym);
}
}
});
// Now that we know the number of symbols in the thunk, we can compute
// its 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->value} <
std::tuple{b->file->priority, b->value};
});
// Assign offsets within the thunk to the symbols.
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
sym->thunk_idx = thunk.thunk_idx;
sym->thunk_sym_idx = i++;
}
// Scan relocations again to fix symbol offsets in the last thunk.
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
[&](Subsection<E> *subsec) {
for (Relocation<E> &r : subsec->get_rels())
if (r.thunk_idx == thunk.thunk_idx)
r.thunk_sym_idx = r.sym->thunk_sym_idx;
});
// Move B forward to point to the begining of the next group.
b = c;
}
while (a < osec.thunks.size())
reset_thunk(*osec.thunks[a++]);
osec.hdr.size = offset;
}
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.hdr.offset + offset;
static const u32 data[] = {
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0xd61f0200, // br x16
};
static_assert(ENTRY_SIZE == sizeof(data));
for (i64 i = 0; i < symbols.size(); i++) {
u64 addr = symbols[i]->get_addr(ctx);
u64 pc = output_section.hdr.addr + offset + i * ENTRY_SIZE;
u8 *loc = buf + i * ENTRY_SIZE;
memcpy(loc , data, sizeof(data));
*(ul32 *)loc |= page_offset(addr, pc);
*(ul32 *)(loc + 4) |= bits(addr, 11, 0) << 10;
}
}
#define ASSERT_RANGE(val, start, size) \
assert((start) <= (val) && (val) < ((start) + (size)))
// Rewrite
// ldr Xb/Wb, [Xa, #imm]
// with
// ldr Xb/Wb, [#base_addr + #imm]
// if we can.
static bool relax_ldr(ul32 *loc, u64 loc_addr, u64 base_addr) {
switch (*loc & 0xffc0'0000) {
case 0xf940'0000: { // ldr Xb, [Xa, #imm]
u64 imm = bits(*loc, 21, 10) * 8;
i64 disp = base_addr + imm - loc_addr;
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
// ldr Xc, [#base_addr + #imm]
*loc = 0x5800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
return true;
}
return false;
}
case 0xb940'0000: { // ldr Wb, [Xa, #imm]
u64 imm = bits(*loc, 21, 10) * 4;
i64 disp = base_addr + imm - loc_addr;
if (disp == sign_extend(disp, 20) && (disp & 0b11) == 0) {
// ldr Wc, [#base_addr + #imm]
*loc = 0x1800'0000 | (bits(disp, 20, 2) << 5) | bits(*loc, 4, 0);
return true;
}
return false;
}
}
return false;
}
static bool is_adrp(ul32 *loc) {
return (*loc & 0x9f00'0000) == 0x9000'0000;
}
static u64 get_adrp_imm(ul32 *loc) {
return (bits(*loc, 23, 5) << 14) + (bits(*loc, 30, 29) << 12);
}
template <typename E>
static void relax_adrp_ldr_got_ldr(Context<E> &ctx,
ul32 *loc1, ul32 *loc2, ul32 *loc3,
u64 addr1, u64 addr2, u64 addr3,
ObjectFile<E> *file) {
// We expect the following instructions for a GOT-indirect load:
//
// adrp Xa, _foo@GOTPAGE
// ldr Xb, [Xa, _foo@GOTPAGEOFF]
// ldr/ldrb/ldrsb/ldrsh/... Xc/Wc, [Xb, #imm]
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0xf940'0000)
return;
u64 got_page = page(addr1) + get_adrp_imm(loc1);
u64 pageoff = bits(*loc2, 21, 10) << 3;
if (got_page + pageoff < ctx.got.hdr.addr ||
ctx.got.hdr.addr + ctx.got.hdr.size <= got_page + pageoff)
Fatal(ctx) << *file << ": LDR_GOT_LDR out of range";
ASSERT_RANGE(got_page + pageoff, ctx.got.hdr.addr, ctx.got.hdr.size);
u64 got_value = *(ul64 *)(ctx.buf + ctx.got.hdr.offset + got_page + pageoff -
ctx.got.hdr.addr);
// If the GOT slot is already filled with a value and we can
// inline the value, do it.
if (got_value && relax_ldr(loc3, addr3, got_value)) {
*loc1 = 0xd503'201f; // nop
*loc2 = 0xd503'201f; // nop
return;
}
// If the GOT slot is close enough to PC, we can eliminate ADRP.
if (relax_ldr(loc2, addr2, got_page))
*loc1 = 0xd503'201f; // nop
}
static void relax_adrp_adrp(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
u64 addr1, u64 addr2) {
// adrp Xa, _foo@PAGE
// adrp Xa, _bar@PAGE
// ->
// ldr Xa, _foo@PAGE
// nop
if (is_adrp(loc1) && is_adrp(loc2) &&
page(addr1) + get_adrp_imm(loc1) == page(addr2) + get_adrp_imm(loc2) &&
bits(*loc1, 4, 0) == bits(*loc2, 4, 0))
*loc2 = 0xd503'201f; // nop
}
static void relax_adrp_ldr(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
u64 addr1, u64 addr2) {
// adrp Xa, _foo@PAGE
// ldr Xb/Wb, [Xa, _foo@PAGEOFF]
// ->
// nop
// ldr Xb/Wb, [_foo]
if (is_adrp(loc1) && relax_ldr(loc2, addr2, page(addr1) + get_adrp_imm(loc1)))
*loc1 = 0xd503'201f; // nop
}
static void relax_adrp_add(Context<E> &ctx, ul32 *loc1, ul32 *loc2,
u64 addr1, u64 addr2) {
// adrp Xa, _foo@PAGE
// add Xb, Xa, _foo@PAGEOFF
// ->
// nop
// adr Xb, _foo
if (!is_adrp(loc1) || (*loc2 & 0xffc0'0000) != 0x9100'0000)
return;
i64 disp = page(addr1) + get_adrp_imm(loc1) + bits(*loc2, 21, 10) - addr2;
if (disp == sign_extend(disp, 20)) {
*loc1 = 0xd503'201f; // nop
*loc2 = 0x1000'0000 | (bits(disp, 1, 0) << 29) |
(bits(disp, 20, 2) << 5) | bits(*loc2, 4, 0); // adr Xb, _foo
}
}
static Subsection<E> *find_subsection_by_addr(ObjectFile<E> *file, u32 addr) {
for (Subsection<E> *subsec : file->subsections)
if (subsec->input_addr <= addr &&
addr < subsec->input_addr + subsec->input_size)
return subsec;
return nullptr;
}
// On ARM, we generally need two or more instructions to materialize
// an address of an object in a register or jump to a function.
// However, if an object or a function is close enough to PC, a single
// instruction suffices.
//
// This function replaces such redundant instruction sequences with
// shorter instruction sequences. We pad holes with NOPs, so the total
// number of instructions won't change by this relaxation, but replacing
// instructions with NOPs generally makes program faster since the CPU
// has a special logic to skip NOPs as quickly as possible.
//
// Locations of relaxable instructions are in the
// LC_LINKER_OPTIMIZATION_HINT segment. That segment contains a
// sequence of ULEB-encoded integers.
//
// This pass is optional; an output file works correctly without
// applying these optimizations.
void apply_linker_optimization_hints(Context<E> &ctx) {
Timer t(ctx, "apply_linker_optimization_hints");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
std::string_view hints = file->get_linker_optimization_hints(ctx);
while (!hints.empty()) {
i64 type = read_uleb(hints);
if (type == 0)
return;
i64 nargs = read_uleb(hints);
switch (type) {
case LOH_ARM64_ADRP_LDR_GOT_LDR: {
assert(nargs == 3);
i64 input_addr1 = read_uleb(hints);
i64 input_addr2 = read_uleb(hints);
i64 input_addr3 = read_uleb(hints);
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
if (!subsec || !subsec->is_alive)
return;
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
ASSERT_RANGE(input_addr3, subsec->input_addr, subsec->input_size);
i64 offset1 = subsec->input_addr - input_addr1;
i64 offset2 = subsec->input_addr - input_addr2;
i64 offset3 = subsec->input_addr - input_addr3;
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
ul32 *loc1 = (ul32 *)(loc + offset1);
ul32 *loc2 = (ul32 *)(loc + offset2);
ul32 *loc3 = (ul32 *)(loc + offset3);
u64 addr1 = subsec->get_addr(ctx) + offset1;
u64 addr2 = subsec->get_addr(ctx) + offset2;
u64 addr3 = subsec->get_addr(ctx) + offset3;
relax_adrp_ldr_got_ldr(ctx, loc1, loc2, loc3, addr1, addr2, addr3,
file);
break;
}
case LOH_ARM64_ADRP_ADRP:
case LOH_ARM64_ADRP_LDR:
case LOH_ARM64_ADRP_ADD: {
assert(nargs == 2);
i64 input_addr1 = read_uleb(hints);
i64 input_addr2 = read_uleb(hints);
Subsection<E> *subsec = find_subsection_by_addr(file, input_addr1);
if (!subsec || !subsec->is_alive)
return;
ASSERT_RANGE(input_addr1, subsec->input_addr, subsec->input_size);
ASSERT_RANGE(input_addr2, subsec->input_addr, subsec->input_size);
i64 offset1 = subsec->input_addr - input_addr1;
i64 offset2 = subsec->input_addr - input_addr2;
u8 *loc = ctx.buf + subsec->isec.osec.hdr.offset + subsec->output_offset;
ul32 *loc1 = (ul32 *)(loc + offset1);
ul32 *loc2 = (ul32 *)(loc + offset2);
u64 addr1 = subsec->get_addr(ctx) + offset1;
u64 addr2 = subsec->get_addr(ctx) + offset2;
switch (type) {
case LOH_ARM64_ADRP_ADRP:
relax_adrp_adrp(ctx, loc1, loc2, addr1, addr2);
break;
case LOH_ARM64_ADRP_LDR:
relax_adrp_ldr(ctx, loc1, loc2, addr1, addr2);
break;
case LOH_ARM64_ADRP_ADD:
relax_adrp_add(ctx, loc1, loc2, addr1, addr2);
break;
}
break;
}
default:
// Skip unsupported optimizations hints.
for (i64 i = 0; i < nargs; i++)
read_uleb(hints);
}
}
});
}
} // namespace mold::macho
-216
View File
@@ -1,216 +0,0 @@
#include "mold.h"
namespace mold::macho {
using E = X86_64;
template <>
void StubsSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->hdr.offset;
for (i64 i = 0; i < syms.size(); i++) {
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
// and jump there.
static_assert(E::stub_size == 6);
buf[i * 6] = 0xff;
buf[i * 6 + 1] = 0x25;
*(ul32 *)(buf + i * 6 + 2) =
ctx.lazy_symbol_ptr.hdr.addr + i * word_size - (this->hdr.addr + i * 6 + 6);
}
}
template <>
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
u8 *start = ctx.buf + this->hdr.offset;
u8 *buf = start;
static const u8 insn0[] = {
0x4c, 0x8d, 0x1d, 0, 0, 0, 0, // lea $__dyld_private(%rip), %r11
0x41, 0x53, // push %r11
0xff, 0x25, 0, 0, 0, 0, // jmp *$dyld_stub_binder@GOT(%rip)
0x90, // nop
};
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
memcpy(buf, insn0, sizeof(insn0));
*(ul32 *)(buf + 3) =
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
*(ul32 *)(buf + 11) =
get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx) - this->hdr.addr - 15;
buf += 16;
for (i64 i = 0; i < ctx.stubs.syms.size(); i++) {
u8 insn[] = {
0x68, 0, 0, 0, 0, // push $bind_offset
0xe9, 0, 0, 0, 0, // jmp $__stub_helper
};
static_assert(sizeof(insn) == E::stub_helper_size);
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
*(ul32 *)(buf + 6) = start - buf - 10;
buf += 10;
}
}
static i64 get_reloc_addend(u32 type) {
switch (type) {
case X86_64_RELOC_SIGNED_1:
return 1;
case X86_64_RELOC_SIGNED_2:
return 2;
case X86_64_RELOC_SIGNED_4:
return 4;
default:
return 0;
}
}
static i64 read_addend(u8 *buf, const MachRel &r) {
if (r.p2size == 2)
return *(il32 *)(buf + r.offset) + get_reloc_addend(r.type);
if (r.p2size == 3)
return *(il64 *)(buf + r.offset) + get_reloc_addend(r.type);
unreachable();
}
template <>
std::vector<Relocation<E>>
read_relocations(Context<E> &ctx, ObjectFile<E> &file,
const MachSection &hdr) {
std::vector<Relocation<E>> vec;
vec.reserve(hdr.nreloc);
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
for (i64 i = 0; i < hdr.nreloc; i++) {
MachRel &r = rels[i];
i64 addend = read_addend((u8 *)file.mf->data + hdr.offset, r);
vec.push_back({r.offset, (u8)r.type, (u8)r.p2size});
Relocation<E> &rel = vec.back();
if (i > 0 && rels[i - 1].type == X86_64_RELOC_SUBTRACTOR)
rel.is_subtracted = true;
if (!rel.is_subtracted && rels[i].type != X86_64_RELOC_SUBTRACTOR)
rel.is_pcrel = r.is_pcrel;
if (r.is_extern) {
rel.sym = file.syms[r.idx];
rel.addend = addend;
continue;
}
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend + 4) : addend;
Subsection<E> *target = file.find_subsection(ctx, r.idx - 1, addr);
if (!target)
Fatal(ctx) << file << ": bad relocation: " << r.offset;
rel.subsec = target;
rel.addend = addr - target->input_addr;
}
return vec;
}
template <>
void Subsection<E>::scan_relocations(Context<E> &ctx) {
for (Relocation<E> &r : get_rels()) {
Symbol<E> *sym = r.sym;
if (!sym)
continue;
if (sym->is_imported && sym->file->is_dylib)
((DylibFile<E> *)sym->file)->is_alive = true;
switch (r.type) {
case X86_64_RELOC_UNSIGNED:
case X86_64_RELOC_SUBTRACTOR:
if (sym->is_imported) {
if (r.p2size != 3) {
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
<< std::hex << r.offset << " against symbol `"
<< *sym << "' can not be used";
}
r.needs_dynrel = true;
}
break;
case X86_64_RELOC_GOT:
case X86_64_RELOC_GOT_LOAD:
sym->flags |= NEEDS_GOT;
break;
case X86_64_RELOC_TLV:
sym->flags |= NEEDS_THREAD_PTR;
break;
}
if (sym->is_imported)
sym->flags |= NEEDS_STUB;
}
}
template <>
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
std::span<Relocation<E>> rels = get_rels();
for (i64 i = 0; i < rels.size(); i++) {
Relocation<E> &r = rels[i];
u64 val = r.addend;
if (r.sym && !r.sym->file) {
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
continue;
}
switch (r.type) {
case X86_64_RELOC_UNSIGNED:
case X86_64_RELOC_SIGNED:
case X86_64_RELOC_BRANCH:
case X86_64_RELOC_SIGNED_1:
case X86_64_RELOC_SIGNED_2:
case X86_64_RELOC_SIGNED_4:
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
break;
case X86_64_RELOC_SUBTRACTOR: {
Relocation<E> s = rels[++i];
assert(s.type == X86_64_RELOC_UNSIGNED);
assert(r.p2size == s.p2size);
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
val += val2 - val1;
break;
}
case X86_64_RELOC_GOT:
case X86_64_RELOC_GOT_LOAD:
val += r.sym->get_got_addr(ctx);
break;
case X86_64_RELOC_TLV:
val += r.sym->get_tlv_addr(ctx);
break;
default:
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
}
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES) {
// An address of a thread-local variable is computed as an offset
// to the beginning of the first thread-local section.
val -= ctx.tls_begin;
} else if (r.is_pcrel) {
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
}
if (r.p2size == 2)
*(ul32 *)(buf + r.offset) = val;
else if (r.p2size == 3)
*(ul64 *)(buf + r.offset) = val;
else
unreachable();
}
}
} // namespace mold::macho
-573
View File
@@ -1,573 +0,0 @@
#include "mold.h"
#include "../cmdline.h"
#include <optional>
#include <regex>
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unordered_set>
#ifndef _WIN32
# include <unistd.h>
#endif
namespace mold::macho {
static const char helpmsg[] = R"(
Options:
-F<PATH> Add DIR to framework search path
-L<PATH> Add DIR to library search path
-ObjC Load all static archive members that implement
an Objective-C class or category
-U <SYMBOL> Allow a symbol to be undefined
-Z Do not search the standard directories when
searching for libraries and frameworks
-add_ast_path <FILE> Add a N_AST symbol with the given filename
-add_empty_section <SEGNAME> <SECTNAME>
Add an empty section
-adhoc_codesign Add ad-hoc code signature to the output file
-no_adhoc_codesign
-all_load Include all objects from static archives
-noall_load
-application_extension Verify that all dylibs are extension-safe
-no_application_extension
-arch <ARCH_NAME> Specify target architecture
-bundle Produce a mach-o bundle
-bundle_loader <EXECUTABLE> Resolve undefined symbols using the given executable
-compatibility_version <VERSION>
Specifies the compatibility version number of the library
-current_version <VERSION> Specifies the current version number of the library.
-dead_strip Remove unreachable functions and data
-dead_strip_dylibs Remove unreachable dylibs from dependencies
-debug_variant Ignored
-demangle Demangle C++ symbols in log messages (default)
-dependency_info <FILE> Ignored
-dylib Produce a dynamic library
-dylib_compatibility_version <VERSION>
Alias for -compatibility_version
-dylib_current_version <VERSION>
Alias for -current_version
-dylib_install_name Alias for -install_name
-dynamic Link against dylibs (default)
-e <SYMBOL> Specify the entry point of a main executable
-execute Produce an executable (default)
-export_dynamic Preserves all global symbols in main executables during LTO
-exported_symbol <SYMBOL> Export a given symbol
-exported_symbols_list <FILE>
Read a list of exported symbols from a given file
-filelist <FILE>[,<DIR>] Specify the list of input file names
-final_output <NAME>
-force_load <FILE> Include all objects from a given static archive
-framework <NAME>,[,<SUFFIX>]
Search for a given framework
-headerpad <SIZE> Allocate the size of padding after load commands
-headerpad_max_install_names
Allocate MAXPATHLEN byte padding after load commands
-help Report usage information
-hidden-l<LIB>
-ignore_optimization_hints Do not rewrite instructions as optimization (default)
-enable_optimization_hints
-install_name <NAME>
-l<LIB> Search for a given library
-lto_library <FILE> Ignored
-macos_version_min <VERSION>
-map <FILE> Write map file to a given file
-mark_dead_strippable_dylib Mark the output as dead-strippable
-needed-l<LIB> Search for a given library
-needed-framework <NAME>[,<SUFFIX>]
Search for a given framework
-no_deduplicate Ignored
-no_function_starts Do not generate an LC_FUNCTION_STARTS load command
-no_uuid Do not generate an LC_UUID load command
-o <FILE> Set output filename
-objc_abi_version <VERSION> Ignored
-object_path_lto <FILE> Write a LTO temporary file to a given path
-order_file <FILE> Ignored
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
Set platform, platform version and SDK version
-random_uuid Generate a random LC_UUID load command
-reexport-l<LIB> Search for a given library
-rpath <PATH> Add PATH to the runpath search path list
-search_dylibs_first
-search_paths_first
-sectalign <SEGNAME> <SECTNAME> <VALUE>
Set a section's alignment to a given value
-sectcreate <SEGNAME> <SECTNAME> <FILE>
-stack_size <SIZE>
-stats Show statistics info
-syslibroot <DIR> Prepend DIR to library search paths
-t Print out each file the linker loads
-thread_count <NUMBER> Use given number of threads
-u <SYMBOL> Force load a given symbol from archive if necessary
-unexported_symbol <SYMBOL> Export all but a given symbol
-unexported_symbols_list <FILE>
Read a list of unexported symbols from a given file
-v Report version information
-weak_framework <NAME>[,<SUFFIX>]
Search for a given framework
-weak-l<LIB> Search for a given library)";
template <typename E>
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
static std::regex re(R"(\d+)", std::regex_constants::ECMAScript);
if (std::regex_match(arg.begin(), arg.end(), re))
return stoi(std::string(arg));
if (arg == "macos")
return PLATFORM_MACOS;
if (arg == "ios")
return PLATFORM_IOS;
if (arg == "tvos")
return PLATFORM_TVOS;
if (arg == "watchos")
return PLATFORM_WATCHOS;
if (arg == "bridgeos")
return PLATFORM_BRIDGEOS;
if (arg == "mac-catalyst")
return PLATFORM_MACCATALYST;
if (arg == "ios-simulator")
return PLATFORM_IOSSIMULATOR;
if (arg == "tvos-simulator")
return PLATFORM_TVOSSIMULATOR;
if (arg == "watchos-simulator")
return PLATFORM_WATCHOSSIMULATOR;
if (arg == "driverkit")
return PLATFORM_DRIVERKIT;
Fatal(ctx) << "unknown -platform_version name: " << arg;
}
template <typename E>
i64 parse_version(Context<E> &ctx, std::string_view arg) {
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)",
std::regex_constants::ECMAScript);
std::cmatch m;
if (!std::regex_match(arg.data(), arg.data() + arg.size(), m, re))
Fatal(ctx) << "malformed version number: " << arg;
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
return (major << 16) | (minor << 8) | patch;
}
template <typename E>
i64 parse_hex(Context<E> &ctx, std::string_view arg) {
auto flags = std::regex_constants::ECMAScript | std::regex_constants::icase;
static std::regex re(R"((?:0x)?[0-9a-f]+)", flags);
std::cmatch m;
if (!std::regex_match(arg.begin(), arg.end(), re))
Fatal(ctx) << "malformed hexadecimal number: " << arg;
if (arg.starts_with("0x") || arg.starts_with("0X"))
return std::stoll(std::string(arg.substr(2)), nullptr, 16);
return std::stoll(std::string(arg), nullptr, 16);
}
static bool is_directory(std::filesystem::path path) {
std::error_code ec;
return std::filesystem::is_directory(path, ec) && !ec;
}
template <typename E>
static std::vector<std::string>
read_lines(Context<E> &ctx, std::string_view path) {
MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::must_open(ctx, std::string(path));
std::string_view data((char *)mf->data, mf->size);
std::vector<std::string> vec;
while (!data.empty()) {
size_t pos = data.find('\n');
std::string_view line;
if (pos == data.npos) {
line = data;
data = "";
} else {
line = data.substr(0, pos);
data = data.substr(pos + 1);
}
line = string_trim(line);
if (!line.empty() && !line.starts_with('#'))
vec.push_back(std::string(line));
}
return vec;
}
template <typename E>
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
std::vector<std::string_view> &args = ctx.cmdline_args;
std::vector<std::string> remaining;
i64 i = 1;
std::vector<std::string> framework_paths;
std::vector<std::string> library_paths;
bool nostdlib = false;
bool version_shown = false;
std::optional<i64> pagezero_size;
while (i < args.size()) {
std::string_view arg;
std::string_view arg2;
std::string_view arg3;
u64 hex_arg;
auto read_arg = [&](std::string name) {
if (args[i] == name) {
if (args.size() <= i + 1)
Fatal(ctx) << "option -" << name << ": argument missing";
arg = args[i + 1];
i += 2;
return true;
}
return false;
};
auto read_arg2 = [&](std::string name) {
if (args[i] == name) {
if (args.size() <= i + 2)
Fatal(ctx) << "option -" << name << ": argument missing";
arg = args[i + 1];
arg2 = args[i + 2];
i += 3;
return true;
}
return false;
};
auto read_arg3 = [&](std::string name) {
if (args[i] == name) {
if (args.size() <= i + 3)
Fatal(ctx) << "option -" << name << ": argument missing";
arg = args[i + 1];
arg2 = args[i + 2];
arg3 = args[i + 3];
i += 4;
return true;
}
return false;
};
auto read_joined = [&](std::string name) {
if (read_arg(name))
return true;
if (args[i].starts_with(name)) {
arg = args[i].substr(name.size());
i++;
return true;
}
return false;
};
auto read_flag = [&](std::string name) {
if (args[i] == name) {
i++;
return true;
}
return false;
};
auto read_hex = [&](std::string name) {
if (!read_arg(name))
return false;
size_t pos;
hex_arg = std::stol(std::string(arg), &pos, 16);
if (pos != arg.size())
Fatal(ctx) << "malformed " << name << ": " << arg;
return true;
};
if (args[i].starts_with('@')) {
std::vector<std::string_view> vec =
read_response_file(ctx, args[i].substr(1));
args.erase(args.begin() + i);
args.insert(args.begin() + i, vec.begin(), vec.end());
continue;
}
if (read_flag("-help") || read_flag("--help")) {
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
<< " [options] file...\n" << helpmsg;
exit(0);
}
if (read_joined("-F")) {
framework_paths.push_back(std::string(arg));
} else if (read_joined("-L")) {
library_paths.push_back(std::string(arg));
} else if (read_flag("-Z")) {
nostdlib = true;
} else if (read_flag("-ObjC")) {
ctx.arg.ObjC = true;
} else if (read_arg("-U")) {
ctx.arg.U.push_back(std::string(arg));
} else if (read_arg("-add_ast_path")) {
ctx.arg.add_ast_path.push_back(std::string(arg));
} else if (read_arg2("-add_empty_section")) {
ctx.arg.add_empty_section.push_back({arg, arg2});
} else if (read_flag("-adhoc_codesign")) {
ctx.arg.adhoc_codesign = true;
} else if (read_flag("-no_adhoc_codesign")) {
ctx.arg.adhoc_codesign = false;
} else if (read_flag("-all_load")) {
remaining.push_back("-all_load");
} else if (read_flag("-noall_load")) {
remaining.push_back("-noall_load");
} else if (read_flag("-application_extension")) {
ctx.arg.application_extension = true;
} else if (read_flag("-no_application_extension")) {
ctx.arg.application_extension = false;
} else if (read_arg("-arch")) {
if (arg == "x86_64")
ctx.arg.arch = CPU_TYPE_X86_64;
else if (arg == "arm64")
ctx.arg.arch = CPU_TYPE_ARM64;
else
Fatal(ctx) << "unknown -arch: " << arg;
} else if (read_flag("-bundle")) {
ctx.output_type = MH_BUNDLE;
} else if (read_arg("-bundle_loader")) {
ctx.arg.bundle_loader = arg;
} else if (read_arg("-compatibility_version") ||
read_arg("-dylib_compatibility_version")) {
ctx.arg.compatibility_version = parse_version(ctx, arg);
} else if (read_arg("-current_version") ||
read_arg("-dylib_current_version")) {
ctx.arg.current_version = parse_version(ctx, arg);
} else if (read_flag("-color-diagnostics") ||
read_flag("--color-diagnostics")) {
ctx.arg.color_diagnostics = true;
} else if (read_flag("-dead_strip")) {
ctx.arg.dead_strip = true;
} else if (read_flag("-dead_strip_dylibs")) {
ctx.arg.dead_strip_dylibs = true;
} else if (read_flag("-debug_variant")) {
} else if (read_flag("-demangle")) {
ctx.arg.demangle = true;
} else if (read_arg("-dependency_info")) {
ctx.arg.dependency_info = arg;
} else if (read_flag("-dylib")) {
ctx.output_type = MH_DYLIB;
} else if (read_hex("-headerpad")) {
ctx.arg.headerpad = hex_arg;
} else if (read_flag("-headerpad_max_install_names")) {
ctx.arg.headerpad = 1024;
} else if (read_flag("-dynamic")) {
ctx.arg.dynamic = true;
} else if (read_arg("-e")) {
ctx.arg.entry = get_symbol(ctx, arg);
} else if (read_flag("-execute")) {
ctx.output_type = MH_EXECUTE;
} else if (read_flag("-export_dynamic")) {
ctx.arg.export_dynamic = true;
} else if (read_arg("-exported_symbol")) {
if (!ctx.arg.exported_symbols_list.add(arg, 1))
Fatal(ctx) << "-exported_symbol: invalid glob pattern: " << arg;
} else if (read_arg("-exported_symbols_list")) {
for (std::string_view pat : read_lines(ctx, arg))
if (!ctx.arg.exported_symbols_list.add(pat, 1))
Fatal(ctx) << "-exported_symbols_list: " << arg
<< ": invalid glob pattern: " << pat;
} else if (read_arg("-fatal_warnings")) {
} else if (read_arg("-filelist")) {
remaining.push_back("-filelist");
remaining.push_back(std::string(arg));
} else if (read_arg("-final_output")) {
ctx.arg.final_output = arg;
} else if (read_arg("-force_load")) {
remaining.push_back("-force_load");
remaining.push_back(std::string(arg));
} else if (read_arg("-framework")) {
remaining.push_back("-framework");
remaining.push_back(std::string(arg));
} else if (read_arg("-lto_library")) {
ctx.arg.lto_library = arg;
} else if (read_arg("-macos_version_min")) {
ctx.arg.platform = PLATFORM_MACOS;
ctx.arg.platform_min_version = parse_version(ctx, arg);
} else if (read_joined("-hidden-l")) {
remaining.push_back("-hidden-l");
remaining.push_back(std::string(arg));
} else if (read_flag("-ignore_optimization_hints")) {
ctx.arg.ignore_optimization_hints = true;
} else if (read_flag("-enable_optimization_hints")) {
ctx.arg.ignore_optimization_hints = false;
} else if (read_arg("-install_name") || read_arg("-dylib_install_name")) {
ctx.arg.install_name = arg;
} else if (read_joined("-l")) {
remaining.push_back("-l");
remaining.push_back(std::string(arg));
} else if (read_arg("-map")) {
ctx.arg.map = arg;
} else if (read_flag("-mark_dead_strippable_dylib")) {
ctx.arg.mark_dead_strippable_dylib = true;
} else if (read_arg("-mllvm")) {
ctx.arg.mllvm.push_back(std::string(arg));
} else if (read_joined("-needed-l")) {
remaining.push_back("-needed-l");
remaining.push_back(std::string(arg));
} else if (read_arg("-needed_framework")) {
remaining.push_back("-needed_framework");
remaining.push_back(std::string(arg));
} else if (read_flag("-no_deduplicate")) {
} else if (read_flag("-no_function_starts")) {
ctx.arg.function_starts = false;
} else if (read_flag("-no_uuid")) {
ctx.arg.uuid = UUID_NONE;
} else if (read_arg("-o")) {
ctx.arg.output = arg;
} else if (read_arg("-objc_abi_version")) {
} else if (read_arg("-object_path_lto")) {
ctx.arg.object_path_lto = arg;
} else if (read_arg("-order_file")) {
ctx.arg.order_file = read_lines(ctx, arg);
} else if (read_hex("-pagezero_size")) {
pagezero_size = hex_arg;
} else if (read_flag("-perf")) {
ctx.arg.perf = true;
} else if (read_arg3("-platform_version")) {
ctx.arg.platform = parse_platform(ctx, arg);
ctx.arg.platform_min_version = parse_version(ctx, arg2);
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
} else if (read_flag("-quick_exit")) {
ctx.arg.quick_exit = true;
} else if (read_flag("-no_quick_exit")) {
ctx.arg.quick_exit = false;
} else if (read_flag("-random_uuid")) {
ctx.arg.uuid = UUID_RANDOM;
} else if (read_joined("-reexport-l")) {
remaining.push_back("-reexport-l");
remaining.push_back(std::string(arg));
} else if (read_arg("-rpath")) {
ctx.arg.rpath.push_back(std::string(arg));
} else if (read_flag("-search_paths_first")) {
ctx.arg.search_paths_first = true;
} else if (read_flag("-search_dylibs_first")) {
ctx.arg.search_paths_first = false;
} else if (read_arg3("-sectalign")) {
u64 val = parse_hex(ctx, arg3);
std::string key = std::string(arg) + "," + std::string(arg2);
if (!has_single_bit(val))
Fatal(ctx) << "-sectalign: invalid alignment value: " << arg3;
ctx.arg.sectalign.push_back({arg, arg2, (u8)std::countl_zero(val)});
} else if (read_arg3("-sectcreate")) {
ctx.arg.sectcreate.push_back({arg, arg2, arg3});
} else if (read_hex("-stack_size")) {
ctx.arg.stack_size = hex_arg;
} else if (read_flag("-stats")) {
ctx.arg.stats = true;
Counter::enabled = true;
} else if (read_arg("-syslibroot")) {
ctx.arg.syslibroot.push_back(std::string(arg));
} else if (read_flag("-t")) {
ctx.arg.trace = true;
} else if (read_arg("-thread_count")) {
ctx.arg.thread_count = std::stoi(std::string(arg));
} else if (read_arg("-u")) {
ctx.arg.u.push_back(std::string(arg));
} else if (read_arg("-unexported_symbol")) {
if (!ctx.arg.unexported_symbols_list.add(arg, 1))
Fatal(ctx) << "-unexported_symbol: invalid glob pattern: " << arg;
} else if (read_arg("-unexported_symbols_list")) {
for (std::string_view pat : read_lines(ctx, arg))
if (!ctx.arg.unexported_symbols_list.add(pat, 1))
Fatal(ctx) << "-unexported_symbols_list: " << arg
<< ": invalid glob pattern: " << pat;
} else if (read_flag("-v")) {
SyncOut(ctx) << mold_version;
version_shown = true;
} else if (read_arg("-weak_framework")) {
remaining.push_back("-weak_framework");
remaining.push_back(std::string(arg));
} else if (read_joined("-weak-l")) {
remaining.push_back("-weak-l");
remaining.push_back(std::string(arg));
} else {
if (args[i][0] == '-')
Fatal(ctx) << "unknown command line option: " << args[i];
remaining.push_back(std::string(args[i]));
i++;
}
}
if (!ctx.arg.entry)
ctx.arg.entry = get_symbol(ctx, "_main");
if (ctx.arg.thread_count == 0)
ctx.arg.thread_count = get_default_thread_count();
if (!ctx.arg.bundle_loader.empty() && ctx.output_type != MH_BUNDLE)
Fatal(ctx) << "-bundle_loader cannot be specified without -bundle";
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
if (is_directory(path))
vec.push_back(path);
return;
}
bool found = false;
for (std::string &dir : ctx.arg.syslibroot) {
std::string str = path_clean(dir + "/" + path);
if (is_directory(str)) {
vec.push_back(str);
found = true;
}
}
if (!found && is_directory(path))
vec.push_back(path);
};
for (std::string &path : library_paths)
add_search_path(ctx.arg.library_paths, path);
if (!nostdlib) {
add_search_path(ctx.arg.library_paths, "/usr/lib");
add_search_path(ctx.arg.library_paths, "/usr/local/lib");
}
for (std::string &path : framework_paths)
add_search_path(ctx.arg.framework_paths, path);
if (!nostdlib) {
add_search_path(ctx.arg.framework_paths, "/Library/Frameworks");
add_search_path(ctx.arg.framework_paths, "/System/Library/Frameworks");
}
if (pagezero_size.has_value()) {
if (ctx.output_type != MH_EXECUTE)
Fatal(ctx) << "-pagezero_size option can only be used when"
<< " linking a main executable";
ctx.arg.pagezero_size = *pagezero_size;
} else {
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
}
if (ctx.arg.final_output == "") {
if (ctx.arg.install_name != "")
ctx.arg.final_output = ctx.arg.install_name;
else
ctx.arg.final_output = ctx.arg.output;
}
if (ctx.arg.uuid == UUID_RANDOM)
memcpy(ctx.uuid, get_uuid_v4().data(), 16);
if (version_shown && remaining.empty())
exit(0);
return remaining;
}
#define INSTANTIATE(E) \
template i64 parse_version(Context<E> &, std::string_view); \
template std::vector<std::string> parse_nonpositional_args(Context<E> &)
INSTANTIATE_ALL;
} // namespace mold::macho
-147
View File
@@ -1,147 +0,0 @@
#include "mold.h"
#include <tbb/parallel_for_each.h>
namespace mold::macho {
template <typename E>
static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
Timer t(ctx, "collect_root_set");
std::vector<Subsection<E> *> rootset;
auto add = [&](Symbol<E> *sym) {
if (sym->subsec)
rootset.push_back(sym->subsec);
};
auto keep = [&](Symbol<E> *sym) {
if (sym->no_dead_strip)
return true;
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
if (sym->scope == SCOPE_EXTERN || sym->referenced_dynamically)
return true;
return false;
};
for (ObjectFile<E> *file : ctx.objs) {
for (Symbol<E> *sym : file->syms)
if (sym->file == file && keep(sym))
add(sym);
for (Subsection<E> *subsec : file->subsections)
if (const MachSection &hdr = subsec->isec.hdr;
(hdr.attr & S_ATTR_NO_DEAD_STRIP) ||
hdr.type == S_MOD_INIT_FUNC_POINTERS ||
hdr.type == S_MOD_TERM_FUNC_POINTERS)
rootset.push_back(subsec);
}
for (std::string_view name : ctx.arg.u)
if (Symbol<E> *sym = get_symbol(ctx, name); sym->file)
add(sym);
add(ctx.arg.entry);
add(get_symbol(ctx, "dyld_stub_binder"));
return rootset;
}
template <typename E>
static void visit(Context<E> &ctx, Subsection<E> &subsec) {
if (subsec.is_alive.exchange(true))
return;
for (Relocation<E> &rel : subsec.get_rels()) {
if (rel.sym) {
if (rel.sym->subsec)
visit(ctx, *rel.sym->subsec);
} else {
visit(ctx, *rel.subsec);
}
}
for (UnwindRecord<E> &rec : subsec.get_unwind_records()) {
visit(ctx, *rec.subsec);
if (rec.lsda)
visit(ctx, *rec.lsda);
if (rec.personality && rec.personality->subsec)
visit(ctx, *rec.personality->subsec);
}
}
template <typename E>
static bool refers_live_subsection(Subsection<E> &subsec) {
for (Relocation<E> &rel : subsec.get_rels()) {
if (rel.sym) {
if (rel.sym->subsec && rel.sym->subsec->is_alive)
return true;
} else {
if (rel.subsec->is_alive)
return true;
}
}
return false;
}
template <typename E>
static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
Timer t(ctx, "mark");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (Subsection<E> *subsec : file->subsections)
subsec->is_alive.store(false, std::memory_order_relaxed);
});
std::atomic_thread_fence(std::memory_order_seq_cst);
for (Subsection<E> *subsec : rootset)
visit(ctx, *subsec);
bool repeat;
do {
repeat = false;
for (ObjectFile<E> *file : ctx.objs) {
for (Subsection<E> *subsec : file->subsections) {
if ((subsec->isec.hdr.attr & S_ATTR_LIVE_SUPPORT) &&
!subsec->is_alive &&
refers_live_subsection(*subsec)) {
visit(ctx, *subsec);
repeat = true;
}
}
}
} while (repeat);
}
template <typename E>
static void sweep(Context<E> &ctx) {
Timer t(ctx, "sweep");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *&sym : file->syms)
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
sym = nullptr;
});
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
std::erase_if(file->subsections, [](Subsection<E> *subsec) {
return !subsec->is_alive;
});
});
}
template <typename E>
void dead_strip(Context<E> &ctx) {
Timer t(ctx, "dead_strip");
std::vector<Subsection<E> *> rootset = collect_root_set(ctx);
mark(ctx, rootset);
sweep(ctx);
}
#define INSTANTIATE(E) \
template void dead_strip(Context<E> &)
INSTANTIATE_ALL;
} // namespace mold::macho
-983
View File
@@ -1,983 +0,0 @@
#include "mold.h"
#include "../archive-file.h"
namespace mold::macho {
template <typename E>
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
if (file.archive_name.empty())
out << path_clean(file.filename);
else
out << path_clean(file.archive_name) << "(" << path_clean(file.filename) + ")";
return out;
}
template <typename E>
void InputFile<E>::clear_symbols() {
for (Symbol<E> *sym : syms) {
std::scoped_lock lock(sym->mu);
if (sym->file == this) {
sym->file = nullptr;
sym->scope = SCOPE_LOCAL;
sym->is_imported = false;
sym->is_weak = false;
sym->no_dead_strip = false;
sym->subsec = nullptr;
sym->value = 0;
sym->is_common = false;
}
}
}
template <typename E>
ObjectFile<E> *
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
std::string archive_name) {
ObjectFile<E> *obj = new ObjectFile<E>(mf);
obj->archive_name = archive_name;
obj->is_alive = archive_name.empty() || ctx.all_load;
obj->is_hidden = ctx.hidden_l;
ctx.obj_pool.emplace_back(obj);
return obj;
};
template <typename E>
void ObjectFile<E>::parse(Context<E> &ctx) {
if (get_file_type(this->mf) == FileType::LLVM_BITCODE) {
// Open an compiler IR file
load_lto_plugin(ctx);
this->lto_module =
ctx.lto.module_create_from_memory(this->mf->data, this->mf->size);
if (!this->lto_module)
Fatal(ctx) << *this << ": lto_module_create_from_memory failed";
// Read a symbol table
parse_lto_symbols(ctx);
return;
}
parse_sections(ctx);
parse_symbols(ctx);
MachHeader &mach_hdr = *(MachHeader *)this->mf->data;
if (mach_hdr.flags & MH_SUBSECTIONS_VIA_SYMBOLS)
split_subsections_via_symbols(ctx);
else
init_subsections(ctx);
sort(subsections, [](Subsection<E> *a, Subsection<E> *b) {
return a->input_addr < b->input_addr;
});
fix_subsec_members(ctx);
for (std::unique_ptr<InputSection<E>> &isec : sections)
if (isec)
isec->parse_relocations(ctx);
if (unwind_sec)
parse_compact_unwind(ctx, *unwind_sec);
}
template <typename E>
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
SegmentCommand *cmd = (SegmentCommand *)find_load_command(ctx, LC_SEGMENT_64);
if (!cmd)
return;
MachSection *mach_sec = (MachSection *)((u8 *)cmd + sizeof(*cmd));
for (i64 i = 0; i < cmd->nsects; i++) {
MachSection &msec = mach_sec[i];
sections.push_back(nullptr);
if (msec.match("__LD", "__compact_unwind")) {
unwind_sec = &msec;
continue;
}
if (msec.match("__DATA", "__objc_imageinfo") ||
msec.match("__DATA_CONST", "__objc_imageinfo")) {
if (msec.size != sizeof(ObjcImageInfo))
Fatal(ctx) << *this << ": __objc_imageinfo: invalid size";
objc_image_info =
(ObjcImageInfo *)(this->mf->get_contents().data() + msec.offset);
if (objc_image_info->version != 0)
Fatal(ctx) << *this << ": __objc_imageinfo: unknown version: "
<< (u32)objc_image_info->version;
continue;
}
if (msec.attr & S_ATTR_DEBUG)
continue;
sections.back().reset(new InputSection<E>(ctx, *this, msec, i));
}
}
template <typename E>
void ObjectFile<E>::parse_symbols(Context<E> &ctx) {
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
if (!cmd)
return;
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
this->syms.reserve(mach_syms.size());
i64 nlocal = 0;
for (MachSym &msym : mach_syms)
if (!msym.is_extern)
nlocal++;
local_syms.reserve(nlocal);
for (i64 i = 0; i < mach_syms.size(); i++) {
MachSym &msym = mach_syms[i];
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
if (msym.is_extern) {
this->syms.push_back(get_symbol(ctx, name));
} else {
local_syms.emplace_back(name);
Symbol<E> &sym = local_syms.back();
sym.file = this;
sym.subsec = nullptr;
sym.scope = SCOPE_LOCAL;
sym.is_common = false;
sym.is_weak = false;
if (msym.type == N_ABS)
sym.value = msym.value;
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
this->syms.push_back(&sym);
}
}
}
struct SplitRegion {
u32 offset;
u32 size;
u32 symidx;
bool is_alt_entry;
};
template <typename E>
struct SplitInfo {
InputSection<E> *isec;
std::vector<SplitRegion> regions;
};
template <typename E>
static std::vector<SplitInfo<E>>
split_regular_sections(Context<E> &ctx, ObjectFile<E> &file) {
std::vector<SplitInfo<E>> vec(file.sections.size());
for (i64 i = 0; i < file.sections.size(); i++)
if (InputSection<E> *isec = file.sections[i].get())
if (isec->hdr.type != S_CSTRING_LITERALS)
vec[i].isec = isec;
// Find all symbols whose type is N_SECT.
for (i64 i = 0; i < file.mach_syms.size(); i++) {
MachSym &msym = file.mach_syms[i];
if (!msym.stab && msym.type == N_SECT && vec[msym.sect - 1].isec) {
SplitRegion r;
r.offset = msym.value - vec[msym.sect - 1].isec->hdr.addr;
r.symidx = i;
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
vec[msym.sect - 1].regions.push_back(r);
}
}
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
return a.isec->hdr.addr < b.isec->hdr.addr;
});
for (SplitInfo<E> &info : vec) {
sort(info.regions, [](const SplitRegion &a, const SplitRegion &b) {
return a.offset < b.offset;
});
}
// If two symbols point to the same location, we create only one
// subsection.
for (SplitInfo<E> &info : vec) {
i64 last = -1;
for (SplitRegion &r : info.regions) {
if (!r.is_alt_entry) {
if (r.offset == last)
r.is_alt_entry = true;
last = r.offset;
}
}
}
// Fix regions so that they cover the entire section without overlapping.
for (SplitInfo<E> &info : vec) {
std::vector<SplitRegion> &r = info.regions;
if (r.empty()) {
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
continue;
}
if (r[0].offset > 0)
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
for (i64 i = 1; i < r.size(); i++)
if (r[i - 1].offset == r[i].offset)
r[i++].is_alt_entry = true;
i64 last = -1;
for (i64 i = 0; i < r.size(); i++) {
if (!r[i].is_alt_entry) {
if (last != -1)
r[last].size = r[i].offset - r[last].offset;
last = i;
}
}
if (last != -1)
r[last].size = info.isec->hdr.size - r[last].offset;
}
return vec;
}
template <typename E>
void ObjectFile<E>::split_subsections_via_symbols(Context<E> &ctx) {
sym_to_subsec.resize(mach_syms.size());
auto add = [&](InputSection<E> &isec, u32 offset, u32 size, u8 p2align,
bool is_cstring) {
Subsection<E> *subsec = new Subsection<E>{
.isec = isec,
.input_offset = offset,
.input_size = size,
.input_addr = (u32)(isec.hdr.addr + offset),
.p2align = p2align,
.is_cstring = is_cstring,
};
subsec_pool.emplace_back(subsec);
subsections.push_back(subsec);
};
// Split regular sections into subsections.
for (SplitInfo<E> &info : split_regular_sections(ctx, *this)) {
InputSection<E> &isec = *info.isec;
for (SplitRegion &r : info.regions) {
if (!r.is_alt_entry)
add(isec, r.offset, r.size, isec.hdr.p2align, false);
if (r.symidx != -1)
sym_to_subsec[r.symidx] = subsections.back();
}
}
// Split __cstring section.
for (std::unique_ptr<InputSection<E>> &isec : sections) {
if (isec && isec->hdr.type == S_CSTRING_LITERALS) {
std::string_view str = isec->contents;
size_t pos = 0;
while (pos < str.size()) {
size_t end = str.find('\0', pos);
if (end == str.npos)
Fatal(ctx) << *this << " corruupted cstring section: " << *isec;
end = str.find_first_not_of('\0', end);
if (end == str.npos)
end = str.size();
// A constant string in __cstring has no alignment info, so we
// need to infer it.
u8 p2align = std::min<u8>(isec->hdr.p2align, std::countr_zero(pos));
add(*isec, pos, end - pos, p2align, true);
pos = end;
}
}
}
}
template <typename E>
void ObjectFile<E>::init_subsections(Context<E> &ctx) {
subsections.resize(sections.size());
for (i64 i = 0; i < sections.size(); i++) {
if (InputSection<E> *isec = sections[i].get()) {
Subsection<E> *subsec = new Subsection<E>{
.isec = *isec,
.input_offset = 0,
.input_size = (u32)isec->hdr.size,
.input_addr = (u32)isec->hdr.addr,
.p2align = (u8)isec->hdr.p2align,
};
subsec_pool.emplace_back(subsec);
subsections[i] = subsec;
}
}
sym_to_subsec.resize(mach_syms.size());
for (i64 i = 0; i < mach_syms.size(); i++) {
MachSym &msym = mach_syms[i];
if (!msym.stab && msym.type == N_SECT)
sym_to_subsec[i] = subsections[msym.sect - 1];
}
std::erase(subsections, nullptr);
}
// Fix local symbols `subsec` members.
template <typename E>
void ObjectFile<E>::fix_subsec_members(Context<E> &ctx) {
for (i64 i = 0; i < mach_syms.size(); i++) {
MachSym &msym = mach_syms[i];
Symbol<E> &sym = *this->syms[i];
if (!msym.stab && !msym.is_extern && msym.type == N_SECT) {
Subsection<E> *subsec = sym_to_subsec[i];
if (!subsec)
subsec = find_subsection(ctx, msym.sect - 1, msym.value);
if (subsec) {
sym.subsec = subsec;
sym.value = msym.value - subsec->input_addr;
} else {
// Subsec is null if a symbol is in a __compact_unwind.
sym.subsec = nullptr;
sym.value = msym.value;
}
}
}
}
template <typename E>
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE)) {
data_in_code_entries = {
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
cmd->datasize / sizeof(DataInCodeEntry),
};
}
}
template <typename E>
std::vector<std::string> ObjectFile<E>::get_linker_options(Context<E> &ctx) {
if (get_file_type(this->mf) == FileType::LLVM_BITCODE)
return {};
MachHeader &hdr = *(MachHeader *)this->mf->data;
u8 *p = this->mf->data + sizeof(hdr);
std::vector<std::string> vec;
for (i64 i = 0; i < hdr.ncmds; i++) {
LoadCommand &lc = *(LoadCommand *)p;
p += lc.cmdsize;
if (lc.cmd == LC_LINKER_OPTION) {
LinkerOptionCommand *cmd = (LinkerOptionCommand *)&lc;
char *buf = (char *)cmd + sizeof(*cmd);
for (i64 i = 0; i < cmd->count; i++) {
vec.push_back(buf);
buf += vec.back().size() + 1;
}
}
}
return vec;
}
template <typename E>
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
if (!this->mf)
return nullptr;
MachHeader &hdr = *(MachHeader *)this->mf->data;
u8 *p = this->mf->data + sizeof(hdr);
for (i64 i = 0; i < hdr.ncmds; i++) {
LoadCommand &lc = *(LoadCommand *)p;
if (lc.cmd == type)
return &lc;
p += lc.cmdsize;
}
return nullptr;
}
template <typename E>
Subsection<E> *
ObjectFile<E>::find_subsection(Context<E> &ctx, u32 secidx, u32 addr) {
Subsection<E> *ret = nullptr;
for (Subsection<E> *subsec : subsections)
if (subsec->isec.secidx == secidx && subsec->input_addr <= addr)
ret = subsec;
return ret;
}
template <typename E>
Symbol<E> *ObjectFile<E>::find_symbol(Context<E> &ctx, u32 addr) {
for (i64 i = 0; i < mach_syms.size(); i++)
if (MachSym &msym = mach_syms[i]; msym.is_extern && msym.value == addr)
return this->syms[i];
return nullptr;
}
template <typename E>
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
if (hdr.size % sizeof(CompactUnwindEntry))
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
unwind_records.reserve(num_entries);
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
// Read compact unwind entries
for (i64 i = 0; i < num_entries; i++)
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
// Read relocations
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
for (i64 i = 0; i < hdr.nreloc; i++) {
MachRel &r = mach_rels[i];
if (r.offset >= hdr.size)
Fatal(ctx) << *this << ": relocation offset too large: " << i;
i64 idx = r.offset / sizeof(CompactUnwindEntry);
UnwindRecord<E> &dst = unwind_records[idx];
auto error = [&] {
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i
<< " " << *this->syms[r.idx];
};
if (r.is_pcrel || r.p2size != 3 || r.type)
error();
switch (r.offset % sizeof(CompactUnwindEntry)) {
case offsetof(CompactUnwindEntry, code_start): {
Subsection<E> *target;
if (r.is_extern)
target = sym_to_subsec[r.idx];
else
target = find_subsection(ctx, r.idx - 1, src[idx].code_start);
if (!target)
error();
dst.subsec = target;
dst.offset = src[idx].code_start - target->input_addr;
break;
}
case offsetof(CompactUnwindEntry, personality):
if (r.is_extern) {
dst.personality = this->syms[r.idx];
} else {
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
dst.personality = find_symbol(ctx, addr);
}
if (!dst.personality)
Fatal(ctx) << *this << ": __compact_unwind: unsupported "
<< "personality reference: " << i;
break;
case offsetof(CompactUnwindEntry, lsda): {
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
Subsection<E> *target;
if (r.is_extern)
target = sym_to_subsec[r.idx];
else
target = find_subsection(ctx, r.idx - 1, addr);
if (!target)
error();
dst.lsda = target;
dst.lsda_offset = addr - target->input_addr;
break;
}
default:
error();
}
}
for (i64 i = 0; i < num_entries; i++)
if (!unwind_records[i].subsec)
Fatal(ctx) << *this << "_: _compact_unwind: missing relocation at " << i;
// Sort unwind entries by offset
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
return std::tuple(a.subsec->input_addr, a.offset) <
std::tuple(b.subsec->input_addr, b.offset);
});
// Associate unwind entries to subsections
for (i64 i = 0; i < num_entries;) {
Subsection<E> &subsec = *unwind_records[i].subsec;
subsec.unwind_offset = i;
i64 j = i + 1;
while (j < num_entries && unwind_records[j].subsec == &subsec)
j++;
subsec.nunwind = j - i;
i = j;
}
}
// Symbols with higher priorities overwrites symbols with lower priorities.
// Here is the list of priorities, from the highest to the lowest.
//
// 1. Strong defined symbol
// 2. Weak defined symbol
// 3. Strong defined symbol in a DSO/archive
// 4. Weak Defined symbol in a DSO/archive
// 5. Common symbol
// 6. Common symbol in an archive
// 7. Unclaimed (nonexistent) symbol
//
// Ties are broken by file priority.
template <typename E>
static u64 get_rank(InputFile<E> *file, bool is_common, bool is_weak) {
if (is_common) {
assert(!file->is_dylib);
if (!file->is_alive)
return (6 << 24) + file->priority;
return (5 << 24) + file->priority;
}
if (file->is_dylib || !file->is_alive) {
if (is_weak)
return (4 << 24) + file->priority;
return (3 << 24) + file->priority;
}
if (is_weak)
return (2 << 24) + file->priority;
return (1 << 24) + file->priority;
}
template <typename E>
static u64 get_rank(Symbol<E> &sym) {
if (!sym.file)
return 7 << 24;
return get_rank(sym.file, sym.is_common, sym.is_weak);
}
template <typename E>
void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
auto is_private_extern = [&](MachSym &msym) {
return this->is_hidden || msym.is_private_extern ||
((msym.desc & N_WEAK_REF) && (msym.desc & N_WEAK_DEF));
};
auto merge_scope = [&](Symbol<E> &sym, MachSym &msym) {
// If at least one symbol defines it as an EXTERN symbol,
// the result is an EXTERN symbol instead of PRIVATE_EXTERN,
// so that the symbol is exported.
if (sym.scope == SCOPE_EXTERN)
return SCOPE_EXTERN;
return is_private_extern(msym) ? SCOPE_PRIVATE_EXTERN : SCOPE_EXTERN;
};
for (i64 i = 0; i < this->syms.size(); i++) {
MachSym &msym = mach_syms[i];
if (!msym.is_extern || msym.is_undef())
continue;
Symbol<E> &sym = *this->syms[i];
std::scoped_lock lock(sym.mu);
bool is_weak = (msym.desc & N_WEAK_DEF);
sym.scope = merge_scope(sym, msym);
if (get_rank(this, msym.is_common(), is_weak) < get_rank(sym)) {
sym.file = this;
sym.is_imported = false;
sym.is_weak = is_weak;
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
switch (msym.type) {
case N_UNDF:
assert(msym.is_common());
sym.subsec = nullptr;
sym.value = msym.value;
sym.is_common = true;
break;
case N_ABS:
sym.subsec = nullptr;
sym.value = msym.value;
sym.is_common = false;
break;
case N_SECT:
sym.subsec = sym_to_subsec[i];
sym.value = msym.value - sym.subsec->input_addr;
sym.is_common = false;
break;
default:
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
}
}
}
}
template <typename E>
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
for (std::unique_ptr<InputSection<E>> &isec : sections)
if (isec)
if (isec->hdr.match("__DATA", "__objc_catlist") ||
(isec->hdr.get_segname() == "__TEXT" &&
isec->hdr.get_sectname().starts_with("__swift")))
return true;
for (i64 i = 0; i < this->syms.size(); i++)
if (!mach_syms[i].is_undef() && mach_syms[i].is_extern &&
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
return true;
return false;
}
template <typename E>
void
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
std::function<void(ObjectFile<E> *)> feeder) {
assert(this->is_alive);
for (i64 i = 0; i < this->syms.size(); i++) {
MachSym &msym = mach_syms[i];
if (!msym.is_extern)
continue;
Symbol<E> &sym = *this->syms[i];
std::scoped_lock lock(sym.mu);
if (!sym.file)
continue;
if (msym.is_undef() || (msym.is_common() && !sym.is_common))
if (InputFile<E> *file = sym.file)
if (!file->is_alive.exchange(true) && !file->is_dylib)
feeder((ObjectFile<E> *)file);
}
for (Subsection<E> *subsec : subsections)
for (UnwindRecord<E> &rec : subsec->get_unwind_records())
if (Symbol<E> *sym = rec.personality)
if (InputFile<E> *file = sym->file)
if (!file->is_alive.exchange(true) && !file->is_dylib)
feeder((ObjectFile<E> *)file);
}
template <typename E>
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
for (i64 i = 0; i < this->mach_syms.size(); i++) {
Symbol<E> &sym = *this->syms[i];
MachSym &msym = mach_syms[i];
if (sym.file == this && sym.is_common) {
InputSection<E> *isec = get_common_sec(ctx);
Subsection<E> *subsec = new Subsection<E>{
.isec = *isec,
.input_size = (u32)msym.value,
.p2align = (u8)msym.common_p2align,
};
subsections.emplace_back(subsec);
sym.is_imported = false;
sym.is_weak = false;
sym.no_dead_strip = (msym.desc & N_NO_DEAD_STRIP);
sym.subsec = subsec;
sym.value = 0;
sym.is_common = false;
}
}
}
template <typename E>
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
for (i64 i = 0; i < this->mach_syms.size(); i++) {
Symbol<E> *sym = this->syms[i];
MachSym &msym = mach_syms[i];
if (sym && sym->file && sym->file != this && !msym.is_undef() &&
!msym.is_common() && !(msym.desc & N_WEAK_DEF))
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
<< ": " << *sym;
}
}
template <typename E>
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
if (!common_sec) {
MachSection *hdr = new MachSection;
common_hdr.reset(hdr);
memset(hdr, 0, sizeof(*hdr));
hdr->set_segname("__DATA");
hdr->set_sectname("__common");
hdr->type = S_ZEROFILL;
common_sec = new InputSection<E>(ctx, *this, *hdr, sections.size());
sections.emplace_back(common_sec);
}
return common_sec;
}
template <typename E>
void ObjectFile<E>::parse_lto_symbols(Context<E> &ctx) {
i64 nsyms = ctx.lto.module_get_num_symbols(this->lto_module);
this->syms.reserve(nsyms);
this->mach_syms2.reserve(nsyms);
for (i64 i = 0; i < nsyms; i++) {
std::string_view name = ctx.lto.module_get_symbol_name(this->lto_module, i);
this->syms.push_back(get_symbol(ctx, name));
u32 attr = ctx.lto.module_get_symbol_attribute(this->lto_module, i);
MachSym msym = {};
switch (attr & LTO_SYMBOL_DEFINITION_MASK) {
case LTO_SYMBOL_DEFINITION_REGULAR:
case LTO_SYMBOL_DEFINITION_TENTATIVE:
case LTO_SYMBOL_DEFINITION_WEAK:
msym.type = N_ABS;
break;
case LTO_SYMBOL_DEFINITION_UNDEFINED:
case LTO_SYMBOL_DEFINITION_WEAKUNDEF:
msym.type = N_UNDF;
break;
default:
unreachable();
}
switch (attr & LTO_SYMBOL_SCOPE_MASK) {
case 0:
case LTO_SYMBOL_SCOPE_INTERNAL:
case LTO_SYMBOL_SCOPE_HIDDEN:
break;
case LTO_SYMBOL_SCOPE_DEFAULT:
case LTO_SYMBOL_SCOPE_PROTECTED:
case LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN:
msym.is_extern = true;
break;
default:
unreachable();
}
mach_syms2.push_back(msym);
}
mach_syms = mach_syms2;
}
template <typename E>
std::string_view ObjectFile<E>::get_linker_optimization_hints(Context<E> &ctx) {
LinkEditDataCommand *cmd =
(LinkEditDataCommand *)find_load_command(ctx, LC_LINKER_OPTIMIZATION_HINT);
if (cmd)
return {(char *)this->mf->data + cmd->dataoff, cmd->datasize};
return {};
}
template <typename E>
DylibFile<E>::DylibFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
: InputFile<E>(mf) {
this->is_dylib = true;
this->is_alive = (ctx.needed_l || !ctx.arg.dead_strip_dylibs);
this->is_weak = ctx.weak_l;
this->is_reexported = ctx.reexport_l;
ctx.dylib_pool.emplace_back(this);
}
template <typename E>
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
DylibFile<E> *file = new DylibFile<E>(ctx, mf);
file->parse(ctx);
return file;
}
template <typename E>
static MappedFile<Context<E>> *
find_external_lib(Context<E> &ctx, std::string_view parent, std::string path) {
if (!path.starts_with('/'))
return MappedFile<Context<E>>::open(ctx, path);
for (const std::string &root : ctx.arg.syslibroot) {
if (path.ends_with(".tbd")) {
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
return file;
continue;
}
if (path.ends_with(".dylib")) {
std::string stem(path.substr(0, path.size() - 6));
if (auto *file = MappedFile<Context<E>>::open(ctx, root + stem + ".tbd"))
return file;
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
return file;
}
for (std::string extn : {".tbd", ".dylib"})
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path + extn))
return file;
}
return nullptr;
}
template <typename E>
void DylibFile<E>::parse(Context<E> &ctx) {
switch (get_file_type(this->mf)) {
case FileType::TAPI:
parse_tapi(ctx);
break;
case FileType::MACH_DYLIB:
parse_dylib(ctx);
break;
case FileType::MACH_EXE:
parse_dylib(ctx);
dylib_idx = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
break;
default:
Fatal(ctx) << *this << ": is not a dylib";
}
// Read reexported libraries if any
for (std::string_view path : reexported_libs) {
MappedFile<Context<E>> *mf =
find_external_lib(ctx, install_name, std::string(path));
if (!mf)
Fatal(ctx) << install_name << ": cannot open reexported library " << path;
DylibFile<E> *child = DylibFile<E>::create(ctx, mf);
exports.merge(child->exports);
weak_exports.merge(child->weak_exports);
}
// Initialize syms and is_weak_symbols vectors
for (std::string_view s : exports) {
this->syms.push_back(get_symbol(ctx, s));
is_weak_symbol.push_back(false);
}
for (std::string_view s : weak_exports) {
if (!exports.contains(s)) {
this->syms.push_back(get_symbol(ctx, s));
is_weak_symbol.push_back(true);
}
}
}
template <typename E>
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
const std::string &prefix) {
u8 *buf = start + offset;
if (*buf) {
read_uleb(buf); // size
i64 flags = read_uleb(buf) & ~EXPORT_SYMBOL_FLAGS_KIND_MASK;
read_uleb(buf); // addr
std::string_view name = save_string(ctx, prefix);
if (flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION)
weak_exports.insert(name);
else
exports.insert(name);
if (flags & EXPORT_SYMBOL_FLAGS_REEXPORT)
read_uleb(buf); // skip a library ordinal
} else {
buf++;
}
i64 nchild = *buf++;
for (i64 i = 0; i < nchild; i++) {
std::string suffix((char *)buf);
buf += suffix.size() + 1;
i64 off = read_uleb(buf);
read_trie(ctx, start, off, prefix + suffix);
}
}
template <typename E>
void DylibFile<E>::parse_tapi(Context<E> &ctx) {
TextDylib tbd = parse_tbd(ctx, this->mf);
install_name = tbd.install_name;
reexported_libs = std::move(tbd.reexported_libs);
exports = std::move(tbd.exports);
weak_exports = std::move(tbd.weak_exports);
}
template <typename E>
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
MachHeader &hdr = *(MachHeader *)this->mf->data;
u8 *p = this->mf->data + sizeof(hdr);
if (ctx.arg.application_extension && !(hdr.flags & MH_APP_EXTENSION_SAFE))
Warn(ctx) << "linking against a dylib which is not safe for use in "
<< "application extensions: " << *this;
for (i64 i = 0; i < hdr.ncmds; i++) {
LoadCommand &lc = *(LoadCommand *)p;
switch (lc.cmd) {
case LC_ID_DYLIB: {
DylibCommand &cmd = *(DylibCommand *)p;
install_name = (char *)p + cmd.nameoff;
break;
}
case LC_DYLD_INFO_ONLY: {
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
if (cmd.export_off)
read_trie(ctx, this->mf->data + cmd.export_off);
break;
}
case LC_DYLD_EXPORTS_TRIE: {
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
read_trie(ctx, this->mf->data + cmd.dataoff);
break;
}
case LC_REEXPORT_DYLIB:
if (!(hdr.flags & MH_NO_REEXPORTED_DYLIBS)) {
DylibCommand &cmd = *(DylibCommand *)p;
reexported_libs.push_back((char *)p + cmd.nameoff);
}
break;
}
p += lc.cmdsize;
}
}
template <typename E>
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
for (i64 i = 0; i < this->syms.size(); i++) {
Symbol<E> &sym = *this->syms[i];
std::scoped_lock lock(sym.mu);
if (get_rank(this, false, false) < get_rank(sym)) {
sym.file = this;
sym.scope = SCOPE_LOCAL;
sym.is_imported = true;
sym.is_weak = (this->is_weak || is_weak_symbol[i]);
sym.no_dead_strip = false;
sym.subsec = nullptr;
sym.value = 0;
sym.is_common = false;
}
}
}
#define INSTANTIATE(E) \
template class InputFile<E>; \
template class ObjectFile<E>; \
template class DylibFile<E>; \
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
INSTANTIATE_ALL;
} // namespace mold::macho
-72
View File
@@ -1,72 +0,0 @@
#include "mold.h"
namespace mold::macho {
template <typename E>
OutputSection<E> &get_output_section(Context<E> &ctx, const MachSection &hdr) {
static std::unordered_set<std::string_view> set = {
"__got", "__auth_got", "__auth_ptr", "__nl_symbol_ptr", "__const",
"__cfstring", "__mod_init_func", "__mod_term_func", "__objc_classlist",
"__objc_nlclslist", "__objc_catlist", "__objc_nlcatlist", "__objc_protolist",
};
std::string_view seg = hdr.get_segname();
std::string_view sect = hdr.get_sectname();
if (seg == "__DATA" && set.contains(sect)) {
seg = "__DATA_CONST";
} else if (seg == "__TEXT" && sect == "__StaticInit") {
sect = "__text";
}
return *OutputSection<E>::get_instance(ctx, seg, sect);
}
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
const MachSection &hdr, u32 secidx)
: file(file), hdr(hdr), secidx(secidx), osec(get_output_section(ctx, hdr)) {
if (hdr.type != S_ZEROFILL)
contents = file.mf->get_contents().substr(hdr.offset, hdr.size);
}
template <typename E>
void InputSection<E>::parse_relocations(Context<E> &ctx) {
// Parse mach-o relocations to fill `rels` vector
rels = read_relocations(ctx, file, hdr);
// Sort `rels` vector
sort(rels, [](const Relocation<E> &a, const Relocation<E> &b) {
return a.offset < b.offset;
});
// Find subsections this relocation section refers to
auto cmp = [](Subsection<E> *subsec, u32 addr) {
return subsec->input_addr < addr;
};
auto begin = std::lower_bound(file.subsections.begin(),
file.subsections.end(), hdr.addr, cmp);
auto end = std::lower_bound(begin, file.subsections.end(),
hdr.addr + hdr.size, cmp);
// Assign each subsection a group of relocations
i64 i = 0;
for (auto it = begin; it < end; it++) {
Subsection<E> &subsec = **it;
subsec.rel_offset = i;
while (i < rels.size() &&
rels[i].offset < subsec.input_offset + subsec.input_size) {
rels[i].offset -= subsec.input_offset;
i++;
}
subsec.nrels = i - subsec.rel_offset;
}
}
#define INSTANTIATE(E) \
template class InputSection<E>
INSTANTIATE_ALL;
} // namespace mold::macho
-164
View File
@@ -1,164 +0,0 @@
#include "lto.h"
#include "mold.h"
#include <algorithm>
#include <cstdio>
#include <dlfcn.h>
#include <unistd.h>
namespace mold::macho {
LTOPlugin::~LTOPlugin() {
if (dlopen_handle)
dlclose(dlopen_handle);
}
template <typename E>
static void do_load_plugin(Context<E> &ctx) {
void *handle = dlopen(ctx.arg.lto_library.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!handle)
Fatal(ctx) << "could not open plugin file: " << dlerror();
ctx.lto.dlopen_handle = handle;
// I don't want to use a macro, but I couldn't find an easy way to
// write the same thing without a macro.
#define DLSYM(x) ctx.lto.x = (decltype(ctx.lto.x))dlsym(handle, "lto_" #x)
DLSYM(get_version);
DLSYM(get_error_message);
DLSYM(module_is_object_file);
DLSYM(module_is_object_file_for_target);
DLSYM(module_has_objc_category);
DLSYM(module_is_object_file_in_memory);
DLSYM(module_is_object_file_in_memory_for_target);
DLSYM(module_create);
DLSYM(module_create_from_memory);
DLSYM(module_create_from_memory_with_path);
DLSYM(module_create_in_local_context);
DLSYM(module_create_in_codegen_context);
DLSYM(module_create_from_fd);
DLSYM(module_create_from_fd_at_offset);
DLSYM(module_dispose);
DLSYM(module_get_target_triple);
DLSYM(module_set_target_triple);
DLSYM(module_get_num_symbols);
DLSYM(module_get_symbol_name);
DLSYM(module_get_symbol_attribute);
DLSYM(module_get_linkeropts);
DLSYM(module_get_macho_cputype);
DLSYM(module_has_ctor_dtor);
DLSYM(codegen_set_diagnostic_handler);
DLSYM(codegen_create);
DLSYM(codegen_create_in_local_context);
DLSYM(codegen_dispose);
DLSYM(codegen_add_module);
DLSYM(codegen_set_module);
DLSYM(codegen_set_debug_model);
DLSYM(codegen_set_pic_model);
DLSYM(codegen_set_cpu);
DLSYM(codegen_set_assembler_path);
DLSYM(codegen_set_assembler_args);
DLSYM(codegen_add_must_preserve_symbol);
DLSYM(codegen_write_merged_modules);
DLSYM(codegen_compile);
DLSYM(codegen_compile_to_file);
DLSYM(codegen_optimize);
DLSYM(codegen_compile_optimized);
DLSYM(api_version);
DLSYM(set_debug_options);
DLSYM(codegen_debug_options);
DLSYM(codegen_debug_options_array);
DLSYM(initialize_disassembler);
DLSYM(codegen_set_should_internalize);
DLSYM(codegen_set_should_embed_uselists);
#undef DLSYM
}
template <typename E>
void load_lto_plugin(Context<E> &ctx) {
std::call_once(ctx.lto_plugin_loaded, [&]() { do_load_plugin(ctx); });
}
template <typename E>
void do_lto(Context<E> &ctx) {
LTOCodeGen *cg = ctx.lto.codegen_create();
// Add bitcode files to CodeGen.
for (const std::string &opt : ctx.arg.mllvm)
ctx.lto.codegen_debug_options(cg, opt.c_str());
for (ObjectFile<E> *file : ctx.objs)
if (file->lto_module)
ctx.lto.codegen_add_module(cg, file->lto_module);
// Mark symbols that have to be preserved. All symbols that are not
// marked here may be internalized and deleted as an extenrally-
// visible symbol.
if (ctx.output_type == MH_DYLIB || ctx.arg.export_dynamic) {
for (ObjectFile<E> *file : ctx.objs) {
if (!file->lto_module) {
for (i64 i = 0; i < file->mach_syms.size(); i++) {
MachSym &msym = file->mach_syms[i];
Symbol<E> &sym = *file->syms[i];
if (msym.is_undef() && !sym.file->is_dylib &&
((ObjectFile<E> *)sym.file)->lto_module)
ctx.lto.codegen_add_must_preserve_symbol(cg, sym.name.data());
}
}
}
for (ObjectFile<E> *file : ctx.objs)
if (file->lto_module)
for (Symbol<E> *sym : file->syms)
if (sym->file == file && sym->scope != SCOPE_LOCAL)
ctx.lto.codegen_add_must_preserve_symbol(cg, sym->name.data());
}
if (ctx.arg.entry->file)
ctx.lto.codegen_add_must_preserve_symbol(cg, ctx.arg.entry->name.data());
// Run the compiler backend to do LTO.
size_t size;
u8 *data = (u8 *)ctx.lto.codegen_compile(cg, &size);
if (!data)
Fatal(ctx) << "lto_codegen_compile failed: " << ctx.lto.get_error_message();
if (!ctx.arg.object_path_lto.empty()) {
FILE *out = fopen(ctx.arg.object_path_lto.c_str(), "w");
if (!out)
Fatal(ctx) << "-object_path_lto: cannot open " << ctx.arg.object_path_lto
<< ": " << errno_string();
fwrite(data, size, 1, out);
fclose(out);
}
// Remove bitcode object files from ctx.objs.
for (ObjectFile<E> *file : ctx.objs) {
if (file->lto_module) {
file->clear_symbols();
file->is_alive = false;
}
}
// Add a result of LTO as a new object file.
MappedFile<Context<E>> *mf = new MappedFile<Context<E>>;
mf->name = "<LTO>";
mf->data = data;
mf->size = size;
ctx.mf_pool.emplace_back(mf);
ObjectFile<E> *obj = ObjectFile<E>::create(ctx, mf, "");
obj->parse(ctx);
obj->is_alive = true;
obj->priority = 100; // regular file starts with priority 10,000
obj->resolve_symbols(ctx);
ctx.objs.push_back(obj);
}
#define INSTANTIATE(E) \
template void load_lto_plugin(Context<E> &); \
template void do_lto(Context<E> &);
INSTANTIATE_ALL;
} // namespace mold::macho
-26
View File
@@ -1,26 +0,0 @@
#ifdef _WIN32
#include "lto.h"
#include "mold.h"
namespace mold::macho {
LTOPlugin::~LTOPlugin() {}
template <typename E>
void load_lto_plugin(Context<E> &ctx) {
Fatal(ctx) << "LTO is not supported on Windows";
}
template <typename E>
void do_lto(Context<E> &ctx) {}
#define INSTANTIATE(E) \
template void load_lto_plugin(Context<E> &); \
template void do_lto(Context<E> &);
INSTANTIATE_ALL;
} // namespace mold::macho
#endif
-176
View File
@@ -1,176 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <sys/types.h>
namespace mold::macho {
enum LTOSymbolAttributes {
LTO_SYMBOL_ALIGNMENT_MASK = 0x0000001F,
LTO_SYMBOL_PERMISSIONS_MASK = 0x000000E0,
LTO_SYMBOL_PERMISSIONS_CODE = 0x000000A0,
LTO_SYMBOL_PERMISSIONS_DATA = 0x000000C0,
LTO_SYMBOL_PERMISSIONS_RODATA = 0x00000080,
LTO_SYMBOL_DEFINITION_MASK = 0x00000700,
LTO_SYMBOL_DEFINITION_REGULAR = 0x00000100,
LTO_SYMBOL_DEFINITION_TENTATIVE = 0x00000200,
LTO_SYMBOL_DEFINITION_WEAK = 0x00000300,
LTO_SYMBOL_DEFINITION_UNDEFINED = 0x00000400,
LTO_SYMBOL_DEFINITION_WEAKUNDEF = 0x00000500,
LTO_SYMBOL_SCOPE_MASK = 0x00003800,
LTO_SYMBOL_SCOPE_INTERNAL = 0x00000800,
LTO_SYMBOL_SCOPE_HIDDEN = 0x00001000,
LTO_SYMBOL_SCOPE_DEFAULT = 0x00001800,
LTO_SYMBOL_SCOPE_PROTECTED = 0x00002000,
LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN = 0x00002800,
LTO_SYMBOL_COMDAT = 0x00004000,
LTO_SYMBOL_ALIAS = 0x00008000,
};
enum LTODebugModel {
LTO_DEBUG_MODEL_NONE = 0,
LTO_DEBUG_MODEL_DWARF = 1,
};
enum LTOCodegenModel {
LTO_CODEGEN_PIC_MODEL_STATIC = 0,
LTO_CODEGEN_PIC_MODEL_DYNAMIC = 1,
LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC = 2,
LTO_CODEGEN_PIC_MODEL_DEFAULT = 3,
};
enum LTOCodegenDiagnosticSeverity {
LTO_DS_ERROR = 0,
LTO_DS_WARNING = 1,
LTO_DS_REMARK = 3,
LTO_DS_NOTE = 2,
};
struct LTOObjectBuffer {
const char *buffer;
size_t size;
};
using LTOModule = void;
using LTOCodeGen = void;
typedef void LTODiagnosticHandler(LTOCodegenDiagnosticSeverity severity,
const char *diag, void *ctxt);
struct LTOPlugin {
void *dlopen_handle = nullptr;
~LTOPlugin();
char (*get_version)();
char (*get_error_message)();
bool (*module_is_object_file)(const char *path);
bool (*module_is_object_file_for_target)(const char *path,
const char *target_triple_prefix);
bool (*module_has_objc_category)(const void *mem, size_t length);
bool (*module_is_object_file_in_memory)(const void *mem, size_t length);
bool (*module_is_object_file_in_memory_for_target)(
const void *mem, size_t length, const char *target_triple_prefix);
LTOModule *(*module_create)(const char *path);
LTOModule *(*module_create_from_memory)(const void *mem, size_t length);
LTOModule *(*module_create_from_memory_with_path)(const void *mem,
size_t length,
const char *path);
LTOModule *(*module_create_in_local_context)(const void *mem, size_t length,
const char *path);
LTOModule *(*module_create_in_codegen_context)(const void *mem, size_t length,
const char *path,
LTOCodeGen *cg);
LTOModule *(*module_create_from_fd)(int fd, const char *path,
size_t file_size);
LTOModule *(*module_create_from_fd_at_offset)(int fd, const char *path,
size_t file_size,
size_t map_size, uint64_t offset);
void (*module_dispose)(LTOModule *mod);
const char *(*module_get_target_triple)(LTOModule *mod);
void (*module_set_target_triple)(LTOModule *mod, const char *triple);
unsigned (*module_get_num_symbols)(LTOModule *mod);
const char *(*module_get_symbol_name)(LTOModule *mod, unsigned index);
uint32_t (*module_get_symbol_attribute)(LTOModule *mod, unsigned index);
const char *(*module_get_linkeropts)(LTOModule *mod);
bool (*module_get_macho_cputype)(LTOModule *mod, unsigned *out_cputype,
unsigned *out_cpusubtype);
bool (*module_has_ctor_dtor)(LTOModule *mod);
void (*codegen_set_diagnostic_handler)(LTOCodeGen *cg, LTODiagnosticHandler,
void *);
LTOCodeGen *(*codegen_create)();
LTOCodeGen *(*codegen_create_in_local_context)();
void (*codegen_dispose)(LTOCodeGen *cg);
bool (*codegen_add_module)(LTOCodeGen *cg, LTOModule *mod);
void (*codegen_set_module)(LTOCodeGen *cg, LTOModule *mod);
bool (*codegen_set_debug_model)(LTOCodeGen *cg, LTODebugModel);
bool (*codegen_set_pic_model)(LTOCodeGen *cg, LTOCodegenModel);
void (*codegen_set_cpu)(LTOCodeGen *cg, const char *cpu);
void (*codegen_set_assembler_path)(LTOCodeGen *cg, const char *path);
void (*codegen_set_assembler_args)(LTOCodeGen *cg, const char **args,
int nargs);
void (*codegen_add_must_preserve_symbol)(LTOCodeGen *cg, const char *symbol);
bool (*codegen_write_merged_modules)(LTOCodeGen *cg, const char *path);
const void *(*codegen_compile)(LTOCodeGen *cg, size_t *length);
bool (*codegen_compile_to_file)(LTOCodeGen *cg, const char **name);
bool (*codegen_optimize)(LTOCodeGen *cg);
const void *(*codegen_compile_optimized)(LTOCodeGen *cg, size_t *length);
unsigned (*api_version)();
void (*set_debug_options)(const char *const *options, int number);
void (*codegen_debug_options)(LTOCodeGen *cg, const char *);
void (*codegen_debug_options_array)(LTOCodeGen *cg, const char *const *,
int number);
void (*initialize_disassembler)();
void (*codegen_set_should_internalize)(LTOCodeGen *cg,
bool should_internalize);
void (*codegen_set_should_embed_uselists)(LTOCodeGen *cg,
bool should_embed_use_lists);
};
} // namespace mold::macho
-781
View File
@@ -1,781 +0,0 @@
#pragma once
#include "../inttypes.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <string>
#include <string_view>
namespace mold::macho {
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;
struct ARM64;
struct X86_64;
template <typename E>
std::string rel_to_string(u8 r_type);
static constexpr u32 FAT_MAGIC = 0xcafebabe;
static constexpr u32 MH_OBJECT = 0x1;
static constexpr u32 MH_EXECUTE = 0x2;
static constexpr u32 MH_FVMLIB = 0x3;
static constexpr u32 MH_CORE = 0x4;
static constexpr u32 MH_PRELOAD = 0x5;
static constexpr u32 MH_DYLIB = 0x6;
static constexpr u32 MH_DYLINKER = 0x7;
static constexpr u32 MH_BUNDLE = 0x8;
static constexpr u32 MH_DYLIB_STUB = 0x9;
static constexpr u32 MH_DSYM = 0xa;
static constexpr u32 MH_KEXT_BUNDLE = 0xb;
static constexpr u32 MH_NOUNDEFS = 0x1;
static constexpr u32 MH_INCRLINK = 0x2;
static constexpr u32 MH_DYLDLINK = 0x4;
static constexpr u32 MH_BINDATLOAD = 0x8;
static constexpr u32 MH_PREBOUND = 0x10;
static constexpr u32 MH_SPLIT_SEGS = 0x20;
static constexpr u32 MH_LAZY_INIT = 0x40;
static constexpr u32 MH_TWOLEVEL = 0x80;
static constexpr u32 MH_FORCE_FLAT = 0x100;
static constexpr u32 MH_NOMULTIDEFS = 0x200;
static constexpr u32 MH_NOFIXPREBINDING = 0x400;
static constexpr u32 MH_PREBINDABLE = 0x800;
static constexpr u32 MH_ALLMODSBOUND = 0x1000;
static constexpr u32 MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000;
static constexpr u32 MH_CANONICAL = 0x4000;
static constexpr u32 MH_WEAK_DEFINES = 0x8000;
static constexpr u32 MH_BINDS_TO_WEAK = 0x10000;
static constexpr u32 MH_ALLOW_STACK_EXECUTION = 0x20000;
static constexpr u32 MH_ROOT_SAFE = 0x40000;
static constexpr u32 MH_SETUID_SAFE = 0x80000;
static constexpr u32 MH_NO_REEXPORTED_DYLIBS = 0x100000;
static constexpr u32 MH_PIE = 0x200000;
static constexpr u32 MH_DEAD_STRIPPABLE_DYLIB = 0x400000;
static constexpr u32 MH_HAS_TLV_DESCRIPTORS = 0x800000;
static constexpr u32 MH_NO_HEAP_EXECUTION = 0x1000000;
static constexpr u32 MH_APP_EXTENSION_SAFE = 0x02000000;
static constexpr u32 MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000;
static constexpr u32 MH_SIM_SUPPORT = 0x08000000;
static constexpr u32 VM_PROT_READ = 0x1;
static constexpr u32 VM_PROT_WRITE = 0x2;
static constexpr u32 VM_PROT_EXECUTE = 0x4;
static constexpr u32 VM_PROT_NO_CHANGE = 0x8;
static constexpr u32 VM_PROT_COPY = 0x10;
static constexpr u32 VM_PROT_WANTS_COPY = 0x10;
static constexpr u32 LC_REQ_DYLD = 0x80000000;
static constexpr u32 LC_SEGMENT = 0x1;
static constexpr u32 LC_SYMTAB = 0x2;
static constexpr u32 LC_SYMSEG = 0x3;
static constexpr u32 LC_THREAD = 0x4;
static constexpr u32 LC_UNIXTHREAD = 0x5;
static constexpr u32 LC_LOADFVMLIB = 0x6;
static constexpr u32 LC_IDFVMLIB = 0x7;
static constexpr u32 LC_IDENT = 0x8;
static constexpr u32 LC_FVMFILE = 0x9;
static constexpr u32 LC_PREPAGE = 0xa;
static constexpr u32 LC_DYSYMTAB = 0xb;
static constexpr u32 LC_LOAD_DYLIB = 0xc;
static constexpr u32 LC_ID_DYLIB = 0xd;
static constexpr u32 LC_LOAD_DYLINKER = 0xe;
static constexpr u32 LC_ID_DYLINKER = 0xf;
static constexpr u32 LC_PREBOUND_DYLIB = 0x10;
static constexpr u32 LC_ROUTINES = 0x11;
static constexpr u32 LC_SUB_FRAMEWORK = 0x12;
static constexpr u32 LC_SUB_UMBRELLA = 0x13;
static constexpr u32 LC_SUB_CLIENT = 0x14;
static constexpr u32 LC_SUB_LIBRARY = 0x15;
static constexpr u32 LC_TWOLEVEL_HINTS = 0x16;
static constexpr u32 LC_PREBIND_CKSUM = 0x17;
static constexpr u32 LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD);
static constexpr u32 LC_SEGMENT_64 = 0x19;
static constexpr u32 LC_ROUTINES_64 = 0x1a;
static constexpr u32 LC_UUID = 0x1b;
static constexpr u32 LC_RPATH = (0x1c | LC_REQ_DYLD);
static constexpr u32 LC_CODE_SIGNATURE = 0x1d;
static constexpr u32 LC_SEGMENT_SPLIT_INFO = 0x1e;
static constexpr u32 LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD);
static constexpr u32 LC_LAZY_LOAD_DYLIB = 0x20;
static constexpr u32 LC_ENCRYPTION_INFO = 0x21;
static constexpr u32 LC_DYLD_INFO = 0x22;
static constexpr u32 LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD);
static constexpr u32 LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD);
static constexpr u32 LC_VERSION_MIN_MACOSX = 0x24;
static constexpr u32 LC_VERSION_MIN_IPHONEOS = 0x25;
static constexpr u32 LC_FUNCTION_STARTS = 0x26;
static constexpr u32 LC_DYLD_ENVIRONMENT = 0x27;
static constexpr u32 LC_MAIN = (0x28 | LC_REQ_DYLD);
static constexpr u32 LC_DATA_IN_CODE = 0x29;
static constexpr u32 LC_SOURCE_VERSION = 0x2A;
static constexpr u32 LC_DYLIB_CODE_SIGN_DRS = 0x2B;
static constexpr u32 LC_ENCRYPTION_INFO_64 = 0x2C;
static constexpr u32 LC_LINKER_OPTION = 0x2D;
static constexpr u32 LC_LINKER_OPTIMIZATION_HINT = 0x2E;
static constexpr u32 LC_VERSION_MIN_TVOS = 0x2F;
static constexpr u32 LC_VERSION_MIN_WATCHOS = 0x30;
static constexpr u32 LC_NOTE = 0x31;
static constexpr u32 LC_BUILD_VERSION = 0x32;
static constexpr u32 LC_DYLD_EXPORTS_TRIE = (0x33 | LC_REQ_DYLD);
static constexpr u32 LC_DYLD_CHAINED_FIXUPS = (0x34 | LC_REQ_DYLD);
static constexpr u32 SG_HIGHVM = 0x1;
static constexpr u32 SG_FVMLIB = 0x2;
static constexpr u32 SG_NORELOC = 0x4;
static constexpr u32 SG_PROTECTED_VERSION_1 = 0x8;
static constexpr u32 SG_READ_ONLY = 0x10;
static constexpr u32 S_REGULAR = 0x0;
static constexpr u32 S_ZEROFILL = 0x1;
static constexpr u32 S_CSTRING_LITERALS = 0x2;
static constexpr u32 S_4BYTE_LITERALS = 0x3;
static constexpr u32 S_8BYTE_LITERALS = 0x4;
static constexpr u32 S_LITERAL_POINTERS = 0x5;
static constexpr u32 S_NON_LAZY_SYMBOL_POINTERS = 0x6;
static constexpr u32 S_LAZY_SYMBOL_POINTERS = 0x7;
static constexpr u32 S_SYMBOL_STUBS = 0x8;
static constexpr u32 S_MOD_INIT_FUNC_POINTERS = 0x9;
static constexpr u32 S_MOD_TERM_FUNC_POINTERS = 0xa;
static constexpr u32 S_COALESCED = 0xb;
static constexpr u32 S_GB_ZEROFILL = 0xc;
static constexpr u32 S_INTERPOSING = 0xd;
static constexpr u32 S_16BYTE_LITERALS = 0xe;
static constexpr u32 S_DTRACE_DOF = 0xf;
static constexpr u32 S_LAZY_DYLIB_SYMBOL_POINTERS = 0x10;
static constexpr u32 S_THREAD_LOCAL_REGULAR = 0x11;
static constexpr u32 S_THREAD_LOCAL_ZEROFILL = 0x12;
static constexpr u32 S_THREAD_LOCAL_VARIABLES = 0x13;
static constexpr u32 S_THREAD_LOCAL_VARIABLE_POINTERS = 0x14;
static constexpr u32 S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;
static constexpr u32 S_INIT_FUNC_OFFSETS = 0x16;
static constexpr u32 S_ATTR_LOC_RELOC = 0x1;
static constexpr u32 S_ATTR_EXT_RELOC = 0x2;
static constexpr u32 S_ATTR_SOME_INSTRUCTIONS = 0x4;
static constexpr u32 S_ATTR_DEBUG = 0x20000;
static constexpr u32 S_ATTR_SELF_MODIFYING_CODE = 0x40000;
static constexpr u32 S_ATTR_LIVE_SUPPORT = 0x80000;
static constexpr u32 S_ATTR_NO_DEAD_STRIP = 0x100000;
static constexpr u32 S_ATTR_STRIP_STATIC_SYMS = 0x200000;
static constexpr u32 S_ATTR_NO_TOC = 0x400000;
static constexpr u32 S_ATTR_PURE_INSTRUCTIONS = 0x800000;
static constexpr u32 CPU_TYPE_X86_64 = 0x1000007;
static constexpr u32 CPU_TYPE_ARM64 = 0x100000c;
static constexpr u32 CPU_SUBTYPE_X86_64_ALL = 3;
static constexpr u32 CPU_SUBTYPE_ARM64_ALL = 0;
static constexpr u32 REBASE_TYPE_POINTER = 1;
static constexpr u32 REBASE_TYPE_TEXT_ABSOLUTE32 = 2;
static constexpr u32 REBASE_TYPE_TEXT_PCREL32 = 3;
static constexpr u32 REBASE_OPCODE_MASK = 0xf0;
static constexpr u32 REBASE_IMMEDIATE_MASK = 0x0f;
static constexpr u32 REBASE_OPCODE_DONE = 0x00;
static constexpr u32 REBASE_OPCODE_SET_TYPE_IMM = 0x10;
static constexpr u32 REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x20;
static constexpr u32 REBASE_OPCODE_ADD_ADDR_ULEB = 0x30;
static constexpr u32 REBASE_OPCODE_ADD_ADDR_IMM_SCALED = 0x40;
static constexpr u32 REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50;
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES = 0x60;
static constexpr u32 REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = 0x70;
static constexpr u32 REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80;
static constexpr u32 BIND_TYPE_POINTER = 1;
static constexpr u32 BIND_TYPE_TEXT_ABSOLUTE32 = 2;
static constexpr u32 BIND_TYPE_TEXT_PCREL32 = 3;
static constexpr i32 BIND_SPECIAL_DYLIB_SELF = 0;
static constexpr i32 BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1;
static constexpr i32 BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2;
static constexpr i32 BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3;
static constexpr u32 BIND_SYMBOL_FLAGS_WEAK_IMPORT = 0x1;
static constexpr u32 BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION = 0x8;
static constexpr u32 BIND_OPCODE_MASK = 0xF0;
static constexpr u32 BIND_IMMEDIATE_MASK = 0x0F;
static constexpr u32 BIND_OPCODE_DONE = 0x00;
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 0x10;
static constexpr u32 BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 0x20;
static constexpr u32 BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 0x30;
static constexpr u32 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 0x40;
static constexpr u32 BIND_OPCODE_SET_TYPE_IMM = 0x50;
static constexpr u32 BIND_OPCODE_SET_ADDEND_SLEB = 0x60;
static constexpr u32 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x70;
static constexpr u32 BIND_OPCODE_ADD_ADDR_ULEB = 0x80;
static constexpr u32 BIND_OPCODE_DO_BIND = 0x90;
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 0xA0;
static constexpr u32 BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 0xB0;
static constexpr u32 BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 0xC0;
static constexpr u32 BIND_OPCODE_THREADED = 0xD0;
static constexpr u32 BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00;
static constexpr u32 BIND_SUBOPCODE_THREADED_APPLY = 0x01;
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_MASK = 0x03;
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_REGULAR = 0x00;
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL = 0x01;
static constexpr u32 EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE = 0x02;
static constexpr u32 EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION = 0x04;
static constexpr u32 EXPORT_SYMBOL_FLAGS_REEXPORT = 0x08;
static constexpr u32 EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER = 0x10;
static constexpr u32 DICE_KIND_DATA = 1;
static constexpr u32 DICE_KIND_JUMP_TABLE8 = 2;
static constexpr u32 DICE_KIND_JUMP_TABLE16 = 3;
static constexpr u32 DICE_KIND_JUMP_TABLE32 = 4;
static constexpr u32 DICE_KIND_ABS_JUMP_TABLE32 = 5;
static constexpr u32 N_UNDF = 0;
static constexpr u32 N_ABS = 1;
static constexpr u32 N_INDR = 5;
static constexpr u32 N_PBUD = 6;
static constexpr u32 N_SECT = 7;
static constexpr u32 N_GSYM = 0x20;
static constexpr u32 N_FNAME = 0x22;
static constexpr u32 N_FUN = 0x24;
static constexpr u32 N_STSYM = 0x26;
static constexpr u32 N_LCSYM = 0x28;
static constexpr u32 N_BNSYM = 0x2e;
static constexpr u32 N_AST = 0x32;
static constexpr u32 N_OPT = 0x3c;
static constexpr u32 N_RSYM = 0x40;
static constexpr u32 N_SLINE = 0x44;
static constexpr u32 N_ENSYM = 0x4e;
static constexpr u32 N_SSYM = 0x60;
static constexpr u32 N_SO = 0x64;
static constexpr u32 N_OSO = 0x66;
static constexpr u32 N_LSYM = 0x80;
static constexpr u32 N_BINCL = 0x82;
static constexpr u32 N_SOL = 0x84;
static constexpr u32 N_PARAMS = 0x86;
static constexpr u32 N_VERSION = 0x88;
static constexpr u32 N_OLEVEL = 0x8A;
static constexpr u32 N_PSYM = 0xa0;
static constexpr u32 N_EINCL = 0xa2;
static constexpr u32 N_ENTRY = 0xa4;
static constexpr u32 N_LBRAC = 0xc0;
static constexpr u32 N_EXCL = 0xc2;
static constexpr u32 N_RBRAC = 0xe0;
static constexpr u32 N_BCOMM = 0xe2;
static constexpr u32 N_ECOMM = 0xe4;
static constexpr u32 N_ECOML = 0xe8;
static constexpr u32 N_LENG = 0xfe;
static constexpr u32 N_PC = 0x30;
static constexpr u32 REFERENCE_TYPE = 0xf;
static constexpr u32 REFERENCE_FLAG_UNDEFINED_NON_LAZY = 0;
static constexpr u32 REFERENCE_FLAG_UNDEFINED_LAZY = 1;
static constexpr u32 REFERENCE_FLAG_DEFINED = 2;
static constexpr u32 REFERENCE_FLAG_PRIVATE_DEFINED = 3;
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY = 4;
static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5;
static constexpr u32 REFERENCED_DYNAMICALLY = 0x0010;
static constexpr u32 SELF_LIBRARY_ORDINAL = 0x0;
static constexpr u32 MAX_LIBRARY_ORDINAL = 0xfd;
static constexpr u32 DYNAMIC_LOOKUP_ORDINAL = 0xfe;
static constexpr u32 EXECUTABLE_ORDINAL = 0xff;
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
static constexpr u32 N_DESC_DISCARDED = 0x0020;
static constexpr u32 N_WEAK_REF = 0x0040;
static constexpr u32 N_WEAK_DEF = 0x0080;
static constexpr u32 N_REF_TO_WEAK = 0x0080;
static constexpr u32 N_ARM_THUMB_DEF = 0x0008;
static constexpr u32 N_SYMBOL_RESOLVER = 0x0100;
static constexpr u32 N_ALT_ENTRY = 0x0200;
static constexpr u32 PLATFORM_MACOS = 1;
static constexpr u32 PLATFORM_IOS = 2;
static constexpr u32 PLATFORM_TVOS = 3;
static constexpr u32 PLATFORM_WATCHOS = 4;
static constexpr u32 PLATFORM_BRIDGEOS = 5;
static constexpr u32 PLATFORM_MACCATALYST = 6;
static constexpr u32 PLATFORM_IOSSIMULATOR = 7;
static constexpr u32 PLATFORM_TVOSSIMULATOR = 8;
static constexpr u32 PLATFORM_WATCHOSSIMULATOR = 9;
static constexpr u32 PLATFORM_DRIVERKIT = 10;
static constexpr u32 TOOL_CLANG = 1;
static constexpr u32 TOOL_SWIFT = 2;
static constexpr u32 TOOL_LD = 3;
static constexpr u32 TOOL_MOLD = 54321; // Randomly chosen!
static constexpr u32 OBJC_IMAGE_SUPPORTS_GC = 1 << 1;
static constexpr u32 OBJC_IMAGE_REQUIRES_GC = 1 << 2;
static constexpr u32 OBJC_IMAGE_OPTIMIZED_BY_DYLD = 1 << 3;
static constexpr u32 OBJC_IMAGE_SUPPORTS_COMPACTION = 1 << 4;
static constexpr u32 OBJC_IMAGE_IS_SIMULATED = 1 << 5;
static constexpr u32 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES = 1 << 6;
static constexpr u32 LOH_ARM64_ADRP_ADRP = 1;
static constexpr u32 LOH_ARM64_ADRP_LDR = 2;
static constexpr u32 LOH_ARM64_ADRP_ADD_LDR = 3;
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_LDR = 4;
static constexpr u32 LOH_ARM64_ADRP_ADD_STR = 5;
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT_STR = 6;
static constexpr u32 LOH_ARM64_ADRP_ADD = 7;
static constexpr u32 LOH_ARM64_ADRP_LDR_GOT = 8;
static constexpr u32 ARM64_RELOC_UNSIGNED = 0;
static constexpr u32 ARM64_RELOC_SUBTRACTOR = 1;
static constexpr u32 ARM64_RELOC_BRANCH26 = 2;
static constexpr u32 ARM64_RELOC_PAGE21 = 3;
static constexpr u32 ARM64_RELOC_PAGEOFF12 = 4;
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGE21 = 5;
static constexpr u32 ARM64_RELOC_GOT_LOAD_PAGEOFF12 = 6;
static constexpr u32 ARM64_RELOC_POINTER_TO_GOT = 7;
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGE21 = 8;
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 = 9;
static constexpr u32 ARM64_RELOC_ADDEND = 10;
template <>
inline std::string rel_to_string<ARM64>(u8 type) {
switch (type) {
case ARM64_RELOC_UNSIGNED: return "ARM64_RELOC_UNSIGNED";
case ARM64_RELOC_SUBTRACTOR: return "ARM64_RELOC_SUBTRACTOR";
case ARM64_RELOC_BRANCH26: return "ARM64_RELOC_BRANCH26";
case ARM64_RELOC_PAGE21: return "ARM64_RELOC_PAGE21";
case ARM64_RELOC_PAGEOFF12: return "ARM64_RELOC_PAGEOFF12";
case ARM64_RELOC_GOT_LOAD_PAGE21: return "ARM64_RELOC_GOT_LOAD_PAGE21";
case ARM64_RELOC_GOT_LOAD_PAGEOFF12: return "ARM64_RELOC_GOT_LOAD_PAGEOFF12";
case ARM64_RELOC_POINTER_TO_GOT: return "ARM64_RELOC_POINTER_TO_GOT";
case ARM64_RELOC_TLVP_LOAD_PAGE21: return "ARM64_RELOC_TLVP_LOAD_PAGE21";
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: return "ARM64_RELOC_TLVP_LOAD_PAGEOFF12";
case ARM64_RELOC_ADDEND: return "ARM64_RELOC_ADDEND";
}
return "unknown (" + std::to_string(type) + ")";
}
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
static constexpr u32 X86_64_RELOC_SIGNED = 1;
static constexpr u32 X86_64_RELOC_BRANCH = 2;
static constexpr u32 X86_64_RELOC_GOT_LOAD = 3;
static constexpr u32 X86_64_RELOC_GOT = 4;
static constexpr u32 X86_64_RELOC_SUBTRACTOR = 5;
static constexpr u32 X86_64_RELOC_SIGNED_1 = 6;
static constexpr u32 X86_64_RELOC_SIGNED_2 = 7;
static constexpr u32 X86_64_RELOC_SIGNED_4 = 8;
static constexpr u32 X86_64_RELOC_TLV = 9;
template <>
inline std::string rel_to_string<X86_64>(u8 type) {
switch (type) {
case X86_64_RELOC_UNSIGNED: return "X86_64_RELOC_UNSIGNED";
case X86_64_RELOC_SIGNED: return "X86_64_RELOC_SIGNED";
case X86_64_RELOC_BRANCH: return "X86_64_RELOC_BRANCH";
case X86_64_RELOC_GOT_LOAD: return "X86_64_RELOC_GOT_LOAD";
case X86_64_RELOC_GOT: return "X86_64_RELOC_GOT";
case X86_64_RELOC_SUBTRACTOR: return "X86_64_RELOC_SUBTRACTOR";
case X86_64_RELOC_SIGNED_1: return "X86_64_RELOC_SIGNED_1";
case X86_64_RELOC_SIGNED_2: return "X86_64_RELOC_SIGNED_2";
case X86_64_RELOC_SIGNED_4: return "X86_64_RELOC_SIGNED_4";
case X86_64_RELOC_TLV: return "X86_64_RELOC_TLV";
}
return "unknown (" + std::to_string(type) + ")";
}
struct FatHeader {
ub32 magic;
ub32 nfat_arch;
};
struct FatArch {
ub32 cputype;
ub32 cpusubtype;
ub32 offset;
ub32 size;
ub32 align;
};
struct MachHeader {
ul32 magic;
ul32 cputype;
ul32 cpusubtype;
ul32 filetype;
ul32 ncmds;
ul32 sizeofcmds;
ul32 flags;
ul32 reserved;
};
struct LoadCommand {
ul32 cmd;
ul32 cmdsize;
};
struct SegmentCommand {
std::string_view get_segname() const {
return {segname, strnlen(segname, sizeof(segname))};
}
ul32 cmd;
ul32 cmdsize;
char segname[16];
ul64 vmaddr;
ul64 vmsize;
ul64 fileoff;
ul64 filesize;
ul32 maxprot;
ul32 initprot;
ul32 nsects;
ul32 flags;
};
struct MachSection {
void set_segname(std::string_view name) {
assert(name.size() <= sizeof(segname));
memcpy(segname, name.data(), name.size());
}
std::string_view get_segname() const {
return {segname, strnlen(segname, sizeof(segname))};
}
void set_sectname(std::string_view name) {
assert(name.size() <= sizeof(sectname));
memcpy(sectname, name.data(), name.size());
}
std::string_view get_sectname() const {
return {sectname, strnlen(sectname, sizeof(sectname))};
}
bool match(std::string_view segname, std::string_view sectname) const {
return get_segname() == segname && get_sectname() == sectname;
}
char sectname[16];
char segname[16];
ul64 addr;
ul64 size;
ul32 offset;
ul32 p2align;
ul32 reloff;
ul32 nreloc;
u8 type;
ul24 attr;
ul32 reserved1;
ul32 reserved2;
ul32 reserved3;
};
struct DylibCommand {
ul32 cmd;
ul32 cmdsize;
ul32 nameoff;
ul32 timestamp;
ul32 current_version;
ul32 compatibility_version;
};
struct DylinkerCommand {
ul32 cmd;
ul32 cmdsize;
ul32 nameoff;
};
struct SymtabCommand {
ul32 cmd;
ul32 cmdsize;
ul32 symoff;
ul32 nsyms;
ul32 stroff;
ul32 strsize;
};
struct DysymtabCommand {
ul32 cmd;
ul32 cmdsize;
ul32 ilocalsym;
ul32 nlocalsym;
ul32 iextdefsym;
ul32 nextdefsym;
ul32 iundefsym;
ul32 nundefsym;
ul32 tocoff;
ul32 ntoc;
ul32 modtaboff;
ul32 nmodtab;
ul32 extrefsymoff;
ul32 nextrefsyms;
ul32 indirectsymoff;
ul32 nindirectsyms;
ul32 extreloff;
ul32 nextrel;
ul32 locreloff;
ul32 nlocrel;
};
struct VersionMinCommand {
ul32 cmd;
ul32 cmdsize;
ul32 version;
ul32 sdk;
};
struct DyldInfoCommand {
ul32 cmd;
ul32 cmdsize;
ul32 rebase_off;
ul32 rebase_size;
ul32 bind_off;
ul32 bind_size;
ul32 weak_bind_off;
ul32 weak_bind_size;
ul32 lazy_bind_off;
ul32 lazy_bind_size;
ul32 export_off;
ul32 export_size;
};
struct UUIDCommand {
ul32 cmd;
ul32 cmdsize;
u8 uuid[16];
};
struct RpathCommand {
ul32 cmd;
ul32 cmdsize;
ul32 path_off;
};
struct LinkEditDataCommand {
ul32 cmd;
ul32 cmdsize;
ul32 dataoff;
ul32 datasize;
};
struct BuildToolVersion {
ul32 tool;
ul32 version;
};
struct BuildVersionCommand {
ul32 cmd;
ul32 cmdsize;
ul32 platform;
ul32 minos;
ul32 sdk;
ul32 ntools;
};
struct EntryPointCommand {
ul32 cmd;
ul32 cmdsize;
ul64 entryoff;
ul64 stacksize;
};
struct SourceVersionCommand {
ul32 cmd;
ul32 cmdsize;
ul64 version;
};
struct DataInCodeEntry {
ul32 offset;
ul16 length;
ul16 kind;
};
struct LinkerOptionCommand {
ul32 cmd;
ul32 cmdsize;
ul32 count;
};
// This struct is named `n_list` on BSD and macOS.
struct MachSym {
bool is_undef() const {
return type == N_UNDF && !is_common();
}
bool is_common() const {
return type == N_UNDF && is_extern && value;
}
ul32 stroff;
union {
u8 n_type;
struct {
u8 is_extern : 1;
u8 type : 3;
u8 is_private_extern : 1;
u8 stab : 3;
};
};
u8 sect;
union {
ul16 desc;
struct {
u8 padding;
u8 common_p2align : 4;
};
};
ul64 value;
};
// This struct is named `relocation_info` on BSD and macOS.
struct MachRel {
ul32 offset;
ul24 idx;
u8 is_pcrel : 1;
u8 p2size : 2;
u8 is_extern : 1;
u8 type : 4;
};
// __TEXT,__unwind_info section contents
static constexpr u32 UNWIND_SECTION_VERSION = 1;
static constexpr u32 UNWIND_SECOND_LEVEL_REGULAR = 2;
static constexpr u32 UNWIND_SECOND_LEVEL_COMPRESSED = 3;
static constexpr u32 UNWIND_PERSONALITY_MASK = 0x30000000;
struct UnwindSectionHeader {
ul32 version;
ul32 encoding_offset;
ul32 encoding_count;
ul32 personality_offset;
ul32 personality_count;
ul32 page_offset;
ul32 page_count;
};
struct UnwindFirstLevelPage {
ul32 func_addr;
ul32 page_offset;
ul32 lsda_offset;
};
struct UnwindSecondLevelPage {
ul32 kind;
ul16 page_offset;
ul16 page_count;
ul16 encoding_offset;
ul16 encoding_count;
};
struct UnwindLsdaEntry {
ul32 func_addr;
ul32 lsda_addr;
};
struct UnwindPageEntry {
ul24 func_addr;
u8 encoding;
};
// __LD,__compact_unwind section contents
struct CompactUnwindEntry {
ul64 code_start;
ul32 code_len;
ul32 encoding;
ul64 personality;
ul64 lsda;
};
// __LINKEDIT,__code_signature
static constexpr u32 CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0;
static constexpr u32 CS_SUPPORTSEXECSEG = 0x20400;
static constexpr u32 CSMAGIC_CODEDIRECTORY = 0xfade0c02;
static constexpr u32 CSSLOT_CODEDIRECTORY = 0;
static constexpr u32 CS_ADHOC = 0x00000002;
static constexpr u32 CS_LINKER_SIGNED = 0x00020000;
static constexpr u32 CS_EXECSEG_MAIN_BINARY = 1;
static constexpr u32 CS_HASHTYPE_SHA256 = 2;
struct CodeSignatureHeader {
ub32 magic;
ub32 length;
ub32 count;
};
struct CodeSignatureBlobIndex {
ub32 type;
ub32 offset;
ub32 padding;
};
struct CodeSignatureDirectory {
ub32 magic;
ub32 length;
ub32 version;
ub32 flags;
ub32 hash_offset;
ub32 ident_offset;
ub32 n_special_slots;
ub32 n_code_slots;
ub32 code_limit;
u8 hash_size;
u8 hash_type;
u8 platform;
u8 page_size;
ub32 spare2;
ub32 scatter_offset;
ub32 team_offset;
ub32 spare3;
ub64 code_limit64;
ub64 exec_seg_base;
ub64 exec_seg_limit;
ub64 exec_seg_flags;
};
// __DATA,__objc_imageinfo
struct ObjcImageInfo {
ul32 version = 0;
u8 flags = 0;
u8 swift_version = 0;
ul16 swift_lang_version = 0;
};
struct ARM64 {
static constexpr u32 cputype = CPU_TYPE_ARM64;
static constexpr u32 cpusubtype = CPU_SUBTYPE_ARM64_ALL;
static constexpr u32 page_size = 16384;
static constexpr u32 abs_rel = ARM64_RELOC_UNSIGNED;
static constexpr u32 stub_size = 12;
static constexpr u32 stub_helper_hdr_size = 24;
static constexpr u32 stub_helper_size = 12;
};
struct X86_64 {
static constexpr u32 cputype = CPU_TYPE_X86_64;
static constexpr u32 cpusubtype = CPU_SUBTYPE_X86_64_ALL;
static constexpr u32 page_size = 4096;
static constexpr u32 abs_rel = X86_64_RELOC_UNSIGNED;
static constexpr u32 stub_size = 6;
static constexpr u32 stub_helper_hdr_size = 16;
static constexpr u32 stub_helper_size = 10;
};
inline constexpr size_t word_size = 8;
} // namespace mold::macho
-1125
View File
File diff suppressed because it is too large. Load diff
-80
View File
@@ -1,80 +0,0 @@
#include "mold.h"
#include <iomanip>
#include <fstream>
namespace mold::macho {
struct Sym {
u64 addr;
u32 size;
u32 fileidx;
std::string_view name;
};
template <typename E>
void print_map(Context<E> &ctx) {
std::ofstream out(ctx.arg.map.c_str());
if (!out.is_open())
Fatal(ctx) << "cannot open " << ctx.arg.map << ": " << errno_string();
out << "# Path: " << ctx.arg.output << "\n"
<< "# Arch: x86-64\n"
<< "# Object files:\n";
std::vector<Sym> syms;
for (i64 i = 0; i < ctx.objs.size(); i++){
ObjectFile<E> &file = *ctx.objs[i];
if (file.is_alive) {
out << "[" << std::setw(3) << i << "] " << file << "\n";
for (Symbol<E> *sym : file.syms)
if (sym && sym->file == &file)
if (!sym->subsec || sym->subsec->is_alive)
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
}
}
sort(syms, [](const Sym &a, const Sym &b) {
return a.addr < b.addr;
});
out << "# Sections:\n"
<< "# Address Size Segment Section\n";
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments) {
for (Chunk<E> *chunk : seg->chunks) {
if (!chunk->is_hidden) {
out << "0x" << std::hex
<< std::setw(8) << std::setfill('0') << chunk->hdr.addr
<< " 0x"
<< std::setw(8) << std::setfill('0') << chunk->hdr.size
<< " " << std::setw(7) << std::left << std::setfill(' ')
<< chunk->hdr.get_segname()
<< " " << chunk->hdr.get_sectname() << "\n";
}
}
}
out << "# Symbols:\n"
<< "# Address Size File Name\n";
for (Sym &sym : syms) {
out << "0x" << std::hex
<< std::setw(8) << std::setfill('0') << sym.addr
<< " 0x"
<< std::setw(8) << std::setfill('0') << 0
<< " ["
<< std::setw(3) << std::right << std::setfill(' ') << std::dec
<< sym.fileidx
<< "] " << sym.name << "\n";
}
}
#define INSTANTIATE(E) \
template void print_map(Context<E> &)
INSTANTIATE_ALL;
} // namespace mold::macho
-1052
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-294
View File
@@ -1,294 +0,0 @@
// On macOS, you can pass a test file describing a dylib instead of an
// actual dylib file to link against a dynamic library. Such text file
// should be in the YAML format and contains dylib's exported symbols
// as well as the file's various attributes. The extension of the text
// file is `.tbd`.
//
// .tbd files allows users to link against a library without
// distributing the binary of the library file itself.
//
// This file contains functions to parse the .tbd file.
#include "mold.h"
#include <optional>
#include <regex>
#include <unordered_set>
namespace mold::macho {
template <typename T, typename U>
static void merge(std::set<T> &a, U &&b) {
a.insert(b.begin(), b.end());
}
static std::vector<YamlNode>
get_vector(YamlNode &node, std::string_view key) {
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
if (auto it = map->find(key); it != map->end())
if (auto *vec = std::get_if<std::vector<YamlNode>>(&it->second.data))
return *vec;
return {};
}
static std::vector<std::string_view>
get_string_vector(YamlNode &node, std::string_view key) {
std::vector<std::string_view> vec;
for (YamlNode &mem : get_vector(node, key))
if (auto val = std::get_if<std::string_view>(&mem.data))
vec.push_back(*val);
return vec;
}
static std::optional<std::string_view>
get_string(YamlNode &node, std::string_view key) {
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
if (auto it = map->find(key); it != map->end())
if (auto *str = std::get_if<std::string_view>(&it->second.data))
return *str;
return {};
}
static bool contains(const std::vector<YamlNode> &vec, std::string_view key) {
for (const YamlNode &mem : vec)
if (auto val = std::get_if<std::string_view>(&mem.data))
if (*val == key)
return true;
return false;
}
static bool match_arch(const std::vector<YamlNode> &vec, std::string_view arch) {
for (const YamlNode &mem : vec)
if (auto val = std::get_if<std::string_view>(&mem.data))
if (*val == arch || val->starts_with(std::string(arch) + "-"))
return true;
return false;
}
template <typename E>
static std::optional<TextDylib>
to_tbd(Context<E> &ctx, YamlNode &node, std::string_view arch,
std::string_view filename) {
if (!match_arch(get_vector(node, "targets"), arch))
return {};
if (ctx.arg.application_extension &&
contains(get_vector(node, "flags"), "not_app_extension_safe"))
Warn(ctx) << "linking against a dylib which is not safe for use in "
<< "application extensions: " << filename;
TextDylib tbd;
if (auto val = get_string(node, "install-name"))
tbd.install_name = *val;
for (YamlNode &mem : get_vector(node, "reexported-libraries"))
if (match_arch(get_vector(mem, "targets"), arch))
append(tbd.reexported_libs, get_string_vector(mem, "libraries"));
auto concat = [&](const std::string &x, std::string_view y) {
return save_string(ctx, x + std::string(y));
};
for (std::string_view key : {"exports", "reexports"}) {
for (YamlNode &mem : get_vector(node, key)) {
if (match_arch(get_vector(mem, "targets"), arch)) {
merge(tbd.exports, get_string_vector(mem, "symbols"));
merge(tbd.weak_exports, get_string_vector(mem, "weak-symbols"));
for (std::string_view s : get_string_vector(mem, "objc-classes")) {
tbd.exports.insert(concat("_OBJC_CLASS_$_", s));
tbd.exports.insert(concat("_OBJC_METACLASS_$_", s));
}
for (std::string_view s : get_string_vector(mem, "objc-eh-types"))
tbd.exports.insert(concat("_OBJC_EHTYPE_$_", s));
for (std::string_view s : get_string_vector(mem, "objc-ivars"))
tbd.exports.insert(concat("_OBJC_IVAR_$_", s));
}
}
}
return tbd;
}
static i64 parse_version(const std::string &arg) {
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)", flags);
std::smatch m;
bool ok = std::regex_match(arg, m, re);
assert(ok);
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
return (major << 16) | (minor << 8) | patch;
}
// Dylib can contain special symbols whose name starts with "$ld$".
// Such symbols aren't actually symbols but linker directives.
// We interpret such symbols in this function.
template <typename E>
static void interpret_ld_symbols(Context<E> &ctx, TextDylib &tbd) {
std::set<std::string_view> syms;
std::unordered_set<std::string_view> hidden_syms;
auto string_view = [](const std::csub_match &sub) {
return std::string_view{sub.first, (size_t)sub.length()};
};
for (std::string_view s : tbd.exports) {
if (!s.starts_with("$ld$"))
continue;
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
std::cmatch m;
// $ld$previous$ symbol replaces the default install name with a
// specified one if the platform OS version is in a specified range.
static std::regex previous_re(
R"(\$ld\$previous\$([^$]+)\$([\d.]*)\$(\d+)\$([\d.]+)\$([\d.]+)\$(.*)\$)",
flags);
if (std::regex_match(s.data(), s.data() + s.size(), m, previous_re)) {
std::string_view install_name = string_view(m[1]);
i64 platform = std::stoi(m[3].str());
i64 min_version = parse_version(m[4]);
i64 max_version = parse_version(m[5]);
std::string_view symbol_name = string_view(m[6]);
if (!symbol_name.empty()) {
// ld64 source seems to have implemented a feature to give an
// alternative install name for a matching symbol, but it didn't
// work in practice (or I may be using the feature in a wrong way.)
// Ignore such symbol for now.
continue;
}
if (platform == ctx.arg.platform &&
min_version <= ctx.arg.platform_min_version &&
ctx.arg.platform_min_version < max_version) {
tbd.install_name = install_name;
}
continue;
}
// $ld$add$os_version$symbol adds a symbol if the given OS version
// matches.
static std::regex add_re(R"(\$ld\$add\$os([\d.]+)\$(.+))", flags);
if (std::regex_match(s.data(), s.data() + s.size(), m, add_re)) {
if (ctx.arg.platform_min_version == parse_version(m[1]))
syms.insert(string_view(m[2]));
continue;
}
// $ld$hide$os_version$symbol hides a symbol if the given OS version
// matches.
static std::regex hidden_re(R"(\$ld\$hide\$os([\d.]+)\$(.+))", flags);
if (std::regex_match(s.data(), s.data() + s.size(), m, hidden_re)) {
if (ctx.arg.platform_min_version == parse_version(m[1]))
hidden_syms.insert(string_view(m[2]));
continue;
}
// $ld$install_name$os_version$name changes the install name to a
// given name.
static std::regex
install_name_re(R"(\$ld\$install_name\$os([\d.]+)\$(.+))", flags);
if (std::regex_match(s.data(), s.data() + s.size(), m, install_name_re)) {
if (ctx.arg.platform_min_version == parse_version(m[1]))
tbd.install_name = string_view(m[2]);
continue;
}
}
for (std::string_view s : tbd.exports)
if (!s.starts_with("$ld$") && !hidden_syms.contains(s))
syms.insert(s);
tbd.exports = std::move(syms);
}
template <typename E>
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
std::string_view arch);
// A single YAML file may contain multiple text dylibs. The first text
// dylib is the main file followed by optional other text dylibs for
// re-exported libraries.
//
// This fucntion squashes multiple text dylibs into a single text dylib
// by copying symbols of re-exported text dylibs to the main text dylib.
template <typename E>
static TextDylib
squash(Context<E> &ctx, std::span<TextDylib> tbds, std::string_view arch) {
std::unordered_map<std::string_view, TextDylib> map;
std::vector<std::string_view> remainings;
TextDylib main = std::move(tbds[0]);
for (TextDylib &tbd : tbds.subspan(1))
map[tbd.install_name] = std::move(tbd);
std::function<void(TextDylib &)> visit = [&](TextDylib &tbd) {
for (std::string_view lib : tbd.reexported_libs) {
auto it = map.find(lib);
if (it != map.end()) {
TextDylib &child = it->second;
merge(main.exports, child.exports);
merge(main.weak_exports, child.weak_exports);
visit(child);
} else {
remainings.push_back(lib);
}
}
};
visit(main);
main.reexported_libs = remainings;
return main;
}
template <typename E>
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
std::string_view arch) {
std::string_view contents = mf->get_contents();
std::variant<std::vector<YamlNode>, YamlError> res = parse_yaml(contents);
if (YamlError *err = std::get_if<YamlError>(&res)) {
i64 lineno = std::count(contents.begin(), contents.begin() + err->pos, '\n');
Fatal(ctx) << mf->name << ":" << (lineno + 1)
<< ": YAML parse error: " << err->msg;
}
std::vector<YamlNode> &nodes = std::get<std::vector<YamlNode>>(res);
if (nodes.empty())
Fatal(ctx) << mf->name << ": malformed TBD file";
std::vector<TextDylib> vec;
for (YamlNode &node : nodes)
if (std::optional<TextDylib> dylib = to_tbd(ctx, node, arch, mf->name))
vec.push_back(*dylib);
for (TextDylib &tbd : vec)
interpret_ld_symbols(ctx, tbd);
return squash(ctx, vec, arch);
}
template <>
TextDylib parse_tbd(Context<ARM64> &ctx, MappedFile<Context<ARM64>> *mf) {
return parse(ctx, mf, "arm64");
}
template <>
TextDylib parse_tbd(Context<X86_64> &ctx, MappedFile<Context<X86_64>> *mf) {
return parse(ctx, mf, "x86_64");
}
} // namespace mold::macho
-378
View File
@@ -1,378 +0,0 @@
// On macOS, a .tbd file can be used instead of a .dylib to link
// against a dynamic library. A .tbd file is a text file in the YAML
// format, so the linker has to be able to parse a YAML file. This
// file implements a YAML parser.
//
// We implemented a YAML parser ourselves instead of using an external
// library. We didn't want to add an dependency to an external library
// just to read .tdb files.
//
// YAML as a format is very complicated. For example, the official
// YAML specification allows embedding a JSON string into YAML; so it
// is strictly larger than JSON. It has surprisingly many features
// that most users are not aware of. Fortunately, we have to support
// only a small portion of the spec to read a .tbd file.
#include "mold.h"
#include <optional>
namespace mold::macho {
enum { STRING = 1, INDENT, DEDENT, END };
struct Token {
u8 kind = 0;
std::string_view str;
};
class YamlParser {
public:
YamlParser(std::string_view input) : input(input) {}
std::variant<std::vector<YamlNode>, YamlError> parse();
private:
std::optional<YamlError> tokenize();
void tokenize_bare_string(std::string_view &str);
std::optional<YamlError> tokenize_list(std::string_view &str);
std::optional<YamlError> tokenize_string(std::string_view &str, char end);
std::variant<YamlNode, YamlError> parse_element(std::span<Token> &tok);
std::variant<YamlNode, YamlError> parse_list(std::span<Token> &tok);
std::variant<YamlNode, YamlError> parse_map(std::span<Token> &tok);
std::variant<YamlNode, YamlError> parse_flow_element(std::span<Token> &tok);
std::variant<YamlNode, YamlError> parse_flow_list(std::span<Token> &tok);
std::string_view input;
std::vector<Token> tokens;
};
// A tokenizer for YAML. YAML represents blocks by indentation. This
// tokenizer inserts INDENT and DEDENT special tokens before and after
// each indented text block.
std::optional<YamlError> YamlParser::tokenize() {
std::vector<i64> indents = {0};
auto indent = [&](std::string_view str, i64 depth) {
tokens.push_back({INDENT, str});
indents.push_back(depth);
};
auto dedent = [&](std::string_view str) {
assert(indents.size() > 1);
tokens.push_back({DEDENT, str});
indents.pop_back();
};
auto skip_line = [](std::string_view &str) {
size_t pos = str.find('\n');
if (pos == str.npos)
str = str.substr(str.size());
else
str = str.substr(pos + 1);
};
auto tokenize_line = [&](std::string_view &str) -> std::optional<YamlError> {
const char *start = str.data();
if (str.starts_with("---")) {
while (indents.size() > 1)
dedent(str);
tokens.push_back({END, str.substr(0, 3)});
skip_line(str);
return {};
}
if (str.starts_with("...")) {
while (indents.size() > 1)
dedent(str);
tokens.push_back({END, str.substr(0, 3)});
str = str.substr(str.size());
return {};
}
size_t pos = str.find_first_not_of(" \t");
if (pos == str.npos || str[pos] == '#' || str[pos] == '\n') {
skip_line(str);
return {};
}
if (indents.back() != pos) {
if (indents.back() < pos) {
indent(str, pos);
} else {
while (indents.back() != pos) {
if (pos < indents.back())
dedent(str);
else
return YamlError{"bad indentation", start - input.data()};
}
}
}
str = str.substr(pos);
while (!str.empty()) {
if (str[0] == '\n') {
str = str.substr(1);
return {};
}
if (str.starts_with("- ")) {
tokens.push_back({'-', str.substr(0, 1)});
size_t pos = str.find_first_not_of(" \t", 1);
if (pos == str.npos || str[pos] == '\n') {
skip_line(str);
return {};
}
str = str.substr(pos);
indent(str, str.data() - start);
continue;
}
if (str.starts_with('['))
return tokenize_list(str);
if (str.starts_with('\'')) {
if (std::optional<YamlError> err = tokenize_string(str, '\''))
return err;
continue;
}
if (str.starts_with('"')) {
if (std::optional<YamlError> err = tokenize_string(str, '"'))
return err;
continue;
}
if (str.starts_with('#')) {
skip_line(str);
return {};
}
if (str.starts_with(':')) {
tokens.push_back({':', str.substr(0, 1)});
size_t pos = str.find_first_not_of(" \t", 1);
if (pos == str.npos || str[pos] == '\n') {
skip_line(str);
return {};
}
str = str.substr(pos);
continue;
}
tokenize_bare_string(str);
}
return {};
};
std::string_view str = input;
while (!str.empty())
if (std::optional<YamlError> err = tokenize_line(str))
return err;
return {};
}
std::optional<YamlError> YamlParser::tokenize_list(std::string_view &str) {
const char *start = str.data();
tokens.push_back({'[', str.substr(0, 1)});
str = str.substr(1);
while (!str.empty() && str[0] != ']') {
if (size_t pos = str.find_first_not_of(" \t\n"); pos) {
str = str.substr(pos);
continue;
}
if (str.starts_with('\'')) {
if (std::optional<YamlError> err = tokenize_string(str, '\''))
return err;
continue;
}
if (str.starts_with('"')) {
if (std::optional<YamlError> err = tokenize_string(str, '"'))
return err;
continue;
}
if (str.starts_with(',')) {
tokens.push_back({',', str.substr(0, 1)});
str = str.substr(1);
continue;
}
tokenize_bare_string(str);
}
if (str.empty())
return YamlError{"unclosed list", start - input.data()};
const char *bracket = str.data();
tokens.push_back({']', str.substr(0, 1)});
str = str.substr(1);
while (!str.empty() && (str[0] == ' ' || str[0] == '\t'))
str = str.substr(1);
if (str.empty() || str[0] != '\n')
return YamlError{"no newline after ']'", bracket - input.data()};
str = str.substr(1);
return {};
}
std::optional<YamlError>
YamlParser::tokenize_string(std::string_view &str, char end) {
const char *start = str.data();
size_t pos = str.find(end, 1);
if (pos == str.npos)
return YamlError{"unterminated string literal", start - input.data()};
tokens.push_back({STRING, str.substr(1, pos - 1)});
str = str.substr(pos + 1);
return {};
}
void
YamlParser::tokenize_bare_string(std::string_view &str) {
size_t pos = str.find_first_not_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-/.");
if (pos == str.npos)
pos = str.size();
tokens.push_back({STRING, str.substr(0, pos)});
str = str.substr(pos);
}
std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
if (std::optional<YamlError> err = tokenize())
return *err;
std::span<Token> tok(tokens);
std::vector<YamlNode> vec;
while (!tok.empty()) {
if (tok[0].kind == END) {
tok = tok.subspan(1);
continue;
}
std::variant<YamlNode, YamlError> elem = parse_element(tok);
if (YamlError *err = std::get_if<YamlError>(&elem))
return *err;
vec.push_back(std::get<YamlNode>(elem));
if (tok[0].kind != END)
return YamlError{"stray token", tok[0].str.data() - input.data()};
}
return vec;
}
std::variant<YamlNode, YamlError>
YamlParser::parse_element(std::span<Token> &tok) {
if (tok[0].kind == INDENT) {
tok = tok.subspan(1);
std::variant<YamlNode, YamlError> elem = parse_element(tok);
assert(tok[0].kind == DEDENT);
tok = tok.subspan(1);
return elem;
}
if (tok[0].kind == '-')
return parse_list(tok);
if (tok.size() > 2 && tok[0].kind == STRING && tok[1].kind == ':')
return parse_map(tok);
return parse_flow_element(tok);
}
std::variant<YamlNode, YamlError>
YamlParser::parse_list(std::span<Token> &tok) {
std::vector<YamlNode> vec;
while (tok[0].kind != END && tok[0].kind != DEDENT) {
if (tok[0].kind != '-')
return YamlError{"list element expected", tok[0].str.data() - input.data()};
tok = tok.subspan(1);
std::variant<YamlNode, YamlError> elem = parse_element(tok);
if (YamlError *err = std::get_if<YamlError>(&elem))
return *err;
vec.push_back(std::get<YamlNode>(elem));
}
return YamlNode{vec};
}
std::variant<YamlNode, YamlError>
YamlParser::parse_map(std::span<Token> &tok) {
std::map<std::string_view, YamlNode> map;
while (tok[0].kind != END && tok[0].kind != DEDENT) {
if (tok.size() < 2 || tok[0].kind != STRING || tok[1].kind != ':')
return YamlError{"map key expected", tok[0].str.data() - input.data()};
std::string_view key = tok[0].str;
tok = tok.subspan(2);
std::variant<YamlNode, YamlError> elem = parse_element(tok);
if (YamlError *err = std::get_if<YamlError>(&elem))
return *err;
map[key] = std::get<YamlNode>(elem);
}
return YamlNode{map};
}
std::variant<YamlNode, YamlError>
YamlParser::parse_flow_element(std::span<Token> &tok) {
if (tok[0].kind == '[') {
tok = tok.subspan(1);
return parse_flow_list(tok);
}
if (tok[0].kind != STRING)
return YamlError{"scalar expected", tok[0].str.data() - input.data()};
std::string_view val = tok[0].str;
tok = tok.subspan(1);
return YamlNode{val};
}
std::variant<YamlNode, YamlError>
YamlParser::parse_flow_list(std::span<Token> &tok) {
std::vector<YamlNode> vec;
const char *start = tok[0].str.data();
while (tok[0].kind != ']' && tok[0].kind != END) {
std::variant<YamlNode, YamlError> elem = parse_flow_element(tok);
if (YamlError *err = std::get_if<YamlError>(&elem))
return *err;
vec.push_back(std::get<YamlNode>(elem));
if (tok[0].kind == ']')
break;
if (tok[0].kind != ',')
return YamlError{"comma expected", tok[0].str.data() - input.data()};
tok = tok.subspan(1);
}
if (tok[0].kind == END)
return YamlError{"unterminated flow list", start - input.data()};
tok = tok.subspan(1);
return YamlNode{vec};
}
std::variant<std::vector<YamlNode>, YamlError>
parse_yaml(std::string_view str) {
return YamlParser(str).parse();
}
} // namespace mold::macho
+45 -4
View File
@@ -1,6 +1,8 @@
#include "mold.h"
#include "config.h"
#include <cstring>
#include <filesystem>
#include <signal.h>
#include <tbb/global_control.h>
@@ -8,8 +10,15 @@
#include <mimalloc-new-delete.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
# include <unistd.h>
#endif
namespace mold {
std::string mold_version_string = MOLD_VERSION;
namespace elf {
int main(int argc, char **argv);
}
@@ -19,16 +28,45 @@ int main(int argc, char **argv);
}
static std::string get_mold_version() {
std::string name = MOLD_IS_SOLD ? "mold (sold) " : "mold ";
if (mold_git_hash.empty())
return "mold " MOLD_VERSION " (compatible with GNU ld)";
return "mold " MOLD_VERSION " (" + mold_git_hash + "; compatible with GNU ld)";
return name + MOLD_VERSION + " (compatible with GNU ld)";
return name + MOLD_VERSION + " (" + mold_git_hash + "; compatible with GNU ld)";
}
void cleanup() {
if (output_tmpfile)
unlink(output_tmpfile);
if (socket_tmpfile)
unlink(socket_tmpfile);
}
std::string errno_string() {
// strerror is not thread-safe, so guard it with a lock.
static std::mutex mu;
std::scoped_lock lock(mu);
return strerror(errno);
}
// Returns the path of the mold executable itself
std::string get_self_path() {
#ifdef __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
}
// mold mmap's an output file, and the mmap succeeds even if there's
@@ -108,8 +146,11 @@ i64 get_default_thread_count() {
int main(int argc, char **argv) {
mold::mold_version = mold::get_mold_version();
#if MOLD_IS_SOLD
std::string cmd = mold::filepath(argv[0]).filename().string();
if (cmd == "ld64" || cmd == "ld64.mold")
return mold::macho::main(argc, argv);
#endif
return mold::elf::main(argc, argv);
}
+50 -37
View File
@@ -1,6 +1,6 @@
#pragma once
#include "inttypes.h"
#include "integers.h"
#include <array>
#include <atomic>
@@ -60,33 +60,28 @@ namespace mold {
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
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 C> class OutputFile;
inline char *output_tmpfile;
inline char *socket_tmpfile;
inline thread_local bool opt_demangle;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
inline std::string mold_version;
extern std::string mold_version_string;
extern std::string mold_git_hash;
std::string_view errno_string();
std::string errno_string();
std::string get_self_path();
void cleanup();
void install_signal_handler();
i64 get_default_thread_count();
static u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// Error output
//
@@ -199,7 +194,7 @@ inline bool has_single_bit(u64 val) {
inline u64 bit_ceil(u64 val) {
if (has_single_bit(val))
return val;
return (u64)1 << (64 - std::countl_zero(val));
return 1LL << (64 - std::countl_zero(val));
}
inline u64 align_to(u64 val, u64 align) {
@@ -220,7 +215,7 @@ inline u64 bit(u64 val, i64 pos) {
// Returns [hi:lo] bits of val.
inline u64 bits(u64 val, u64 hi, u64 lo) {
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
}
inline i64 sign_extend(u64 val, i64 size) {
@@ -229,16 +224,29 @@ inline i64 sign_extend(u64 val, i64 size) {
template <typename T, typename Compare = std::less<T>>
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic;
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(new_val, old_val) &&
!atomic.compare_exchange_weak(old_val, new_val));
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
template <typename T, typename Compare = std::less<T>>
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic;
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(old_val, new_val) &&
!atomic.compare_exchange_weak(old_val, new_val));
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
// Returns true if `visited` was false and updated to true.
inline bool fast_mark(std::atomic<bool> &visited) {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return !visited.load(std::memory_order_relaxed) &&
!visited.exchange(true, std::memory_order_relaxed);
}
template <typename T, typename U>
@@ -343,7 +351,7 @@ inline i64 uleb_size(u64 val) {
#pragma GCC unroll 8
#endif
for (int i = 1; i < 9; i++)
if (val < ((u64)1 << (7 * i)))
if (val < (1LL << (7 * i)))
return i;
return 9;
}
@@ -411,18 +419,19 @@ public:
i64 retry = 0;
while (retry < MAX_RETRY) {
const char *ptr = keys[idx];
const char *ptr = keys[idx].load(std::memory_order_acquire);
if (ptr == marker) {
pause();
continue;
}
if (ptr == nullptr) {
if (!keys[idx].compare_exchange_weak(ptr, marker))
if (!keys[idx].compare_exchange_weak(ptr, marker,
std::memory_order_acq_rel))
continue;
new (values + idx) T(val);
key_sizes[idx] = key.size();
keys[idx] = key.data();
keys[idx].store(key.data(), std::memory_order_release);
return {values + idx, true};
}
@@ -563,7 +572,6 @@ private:
std::vector<std::string> strings;
std::unique_ptr<TrieNode> root;
std::vector<std::pair<Glob, u32>> globs;
std::vector<u32> values;
std::once_flag once;
bool is_compiled = false;
};
@@ -598,27 +606,30 @@ std::optional<std::string_view> cpp_demangle(std::string_view name);
// compress.cc
//
class ZlibCompressor {
class Compressor {
public:
virtual void write_to(u8 *buf) = 0;
virtual ~Compressor() {}
i64 compressed_size = 0;
};
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size);
void write_to(u8 *buf);
i64 size() const;
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
u64 checksum = 0;
};
class GzipCompressor {
class ZstdCompressor : public Compressor {
public:
GzipCompressor(std::string_view input);
void write_to(u8 *buf);
i64 size() const;
ZstdCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
u32 checksum = 0;
u32 uncompressed_size = 0;
};
//
@@ -738,7 +749,8 @@ public:
static MappedFile *open(C &ctx, std::string path);
static MappedFile *must_open(C &ctx, std::string path);
~MappedFile();
~MappedFile() { unmap(); }
void unmap();
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
@@ -785,7 +797,6 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
i64 fd;
#ifdef _WIN32
fd = ::_open(path.c_str(), O_RDONLY);
@@ -858,8 +869,8 @@ MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
}
template <typename C>
MappedFile<C>::~MappedFile() {
if (size == 0 || parent)
void MappedFile<C>::unmap() {
if (size == 0 || parent || !data)
return;
#ifdef _WIN32
@@ -869,6 +880,8 @@ MappedFile<C>::~MappedFile() {
#else
munmap(data, size);
#endif
data = nullptr;
}
} // namespace mold
+7 -9
View File
@@ -26,7 +26,7 @@ namespace mold {
std::optional<u32> MultiGlob::find(std::string_view str) {
std::call_once(once, [&] { compile(); });
u32 idx = UINT32_MAX;
u32 val = UINT32_MAX;
if (root) {
// Match against simple glob patterns
@@ -36,7 +36,7 @@ std::optional<u32> MultiGlob::find(std::string_view str) {
for (;;) {
if (node->children[c]) {
node = node->children[c].get();
idx = std::min(idx, node->value);
val = std::min(val, node->value);
return;
}
@@ -55,11 +55,11 @@ std::optional<u32> MultiGlob::find(std::string_view str) {
// Match against complex glob patterns
for (std::pair<Glob, u32> &glob : globs)
if (glob.first.match(str))
idx = std::min(idx, glob.second);
val = std::min(val, glob.second);
if (idx == UINT32_MAX)
if (val == UINT32_MAX)
return {};
return values[idx];
return val;
}
static bool is_simple_pattern(std::string_view pat) {
@@ -86,14 +86,12 @@ bool MultiGlob::add(std::string_view pat, u32 val) {
assert(!is_compiled);
assert(!pat.empty());
u32 idx = strings.size();
strings.push_back(std::string(pat));
values.push_back(val);
// Complex glob pattern
if (!is_simple_pattern(pat)) {
if (std::optional<Glob> glob = Glob::compile(pat)) {
globs.push_back({std::move(*glob), idx});
globs.push_back({std::move(*glob), val});
return true;
}
return false;
@@ -110,7 +108,7 @@ bool MultiGlob::add(std::string_view pat, u32 val) {
node = node->children[c].get();
}
node->value = std::min(node->value, idx);
node->value = std::min(node->value, val);
return true;
}
+28
View File
@@ -65,16 +65,31 @@ public:
mold::output_buffer_end = this->buf + filesize;
}
~MemoryMappedOutputFile() {
if (fd2 != -1)
::close(fd2);
}
void close(C &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
// 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 C>
@@ -133,6 +148,19 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
else
file = new MemoryMappedOutputFile<C>(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<C>>(file);
+1 -1
View File
@@ -16,7 +16,7 @@ inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
static std::once_flag once;
static BCRYPT_ALG_HANDLE alg;
std::call_once(once, [&]() {
std::call_once(once, [&] {
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
});
return alg;
-24
View File
@@ -1,24 +0,0 @@
// GNU's strerror_r is different from POSIX's strerror_r.
// In this file, we explicitly undefine _GNU_SOURCE to always
// use the POSIX version.
#define _POSIX_C_SOURCE 200809L
#undef _GNU_SOURCE
#include <cstring>
#include <errno.h>
#include <string_view>
namespace mold {
std::string_view errno_string() {
static thread_local char buf[200];
#ifdef _WIN32
strerror_s(buf, errno);
#else
strerror_r(errno, buf, sizeof(buf));
#endif
return buf;
}
} // namespace mold
+4 -4
View File
@@ -24,8 +24,8 @@ struct UstarHeader {
int sum = 0;
for (i64 i = 0; i < sizeof(*this); i++)
sum += ((u8 *)this)[i];
assert(sum < 1000000);
sprintf(checksum, "%06o", sum);
assert(sum < 01000000);
snprintf(checksum, sizeof(checksum), "%06o", sum);
}
char name[100];
@@ -77,7 +77,7 @@ void TarWriter::append(std::string path, std::string_view data) {
UstarHeader pax;
std::string attr = encode_path(basedir, path);
sprintf(pax.size, "%011zo", attr.size());
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
pax.typeflag[0] = 'x';
pax.finalize();
fwrite(&pax, sizeof(pax), 1, out);
@@ -89,7 +89,7 @@ void TarWriter::append(std::string path, std::string_view data) {
// Write Ustar header
UstarHeader ustar;
memcpy(ustar.mode, "0000664", 8);
sprintf(ustar.size, "%011zo", data.size());
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
ustar.finalize();
fwrite(&ustar, sizeof(ustar), 1, out);
+56 -35
View File
@@ -1,43 +1,64 @@
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests on RV32" OFF)
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
file(GLOB TESTS
RELATIVE ${CMAKE_CURRENT_LIST_DIR}
CONFIGURE_DEPENDS
"*.sh")
function(add_target TRIPLE)
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
if(${HOST} MATCHES "arm.*")
set(HOST arm)
endif()
function(add_qemu_test TEST TRIPLE MACHINE)
if(NOT ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL ${MACHINE})
add_test(NAME "${MACHINE}-${TEST}"
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
WORKING_DIRECTORY ${mold_BINARY_DIR})
if(${TRIPLE} MATCHES "${HOST}-.*")
set(IS_NATIVE 1)
endif()
set(ENV
TEST_CC=${TRIPLE}-gcc
TEST_CXX=${TRIPLE}-g++
TEST_GCC=${TRIPLE}-gcc
TEST_GXX=${TRIPLE}-g++
OBJDUMP=${TRIPLE}-objdump
MACHINE=${MACHINE}
"QEMU=qemu-${MACHINE} -L /usr/${TRIPLE}")
set_tests_properties("${MACHINE}-${TEST}" PROPERTIES ENVIRONMENT "${ENV}")
if(${TRIPLE} MATCHES "([^-]+)-.")
set(MACHINE ${CMAKE_MATCH_1})
endif()
if(IS_NATIVE OR MOLD_ENABLE_QEMU_TESTS)
file(GLOB ALL_TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
"*.sh")
list(FILTER ALL_TESTS EXCLUDE REGEX "_")
file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_LIST_DIR} CONFIGURE_DEPENDS
"${MACHINE}_*.sh")
list(APPEND TESTS ${ALL_TESTS})
foreach(TEST IN LISTS TESTS)
string(REGEX REPLACE "\\.sh$" "" TESTNAME "${MACHINE}-${TEST}")
add_test(NAME ${TESTNAME}
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
WORKING_DIRECTORY ${mold_BINARY_DIR})
if(IS_NATIVE)
set_tests_properties(${TESTNAME} PROPERTIES
SKIP_REGULAR_EXPRESSION "skipped")
else()
set_tests_properties(${TESTNAME} PROPERTIES
ENVIRONMENT "TRIPLE=${TRIPLE}")
endif()
endforeach()
endif()
endfunction()
foreach(TEST IN LISTS TESTS)
add_test(NAME "${CMAKE_HOST_SYSTEM_PROCESSOR}-${TEST}"
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
WORKING_DIRECTORY ${mold_BINARY_DIR})
add_target(x86_64-linux-gnu)
add_target(i686-linux-gnu)
add_target(aarch64-linux-gnu)
add_target(arm-linux-gnueabihf)
add_target(riscv64-linux-gnu)
add_target(powerpc-linux-gnu)
add_target(powerpc64-linux-gnu)
add_target(powerpc64le-linux-gnu)
add_target(sparc64-linux-gnu)
add_target(s390x-linux-gnu)
add_target(m68k-linux-gnu)
add_target(sh4-linux-gnu)
add_target(alpha-linux-gnu)
add_target(hppa-linux-gnu)
if(MOLD_ENABLE_QEMU_TESTS)
add_qemu_test(${TEST} x86_64-linux-gnu x86_64)
add_qemu_test(${TEST} i686-linux-gnu i386)
add_qemu_test(${TEST} aarch64-linux-gnu aarch64)
add_qemu_test(${TEST} arm-linux-gnueabihf arm)
add_qemu_test(${TEST} riscv64-linux-gnu riscv64)
endif()
if (MOLD_ENABLE_QEMU_TESTS_RV32)
add_qemu_test(${TEST} riscv32-linux-gnu riscv32)
endif()
endforeach()
if(MOLD_ENABLE_QEMU_TESTS_RV32)
add_target(riscv32-linux-gnu)
endif()
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] || skip
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
#include <stdio.h>
void fn1();
void fn2();
__attribute__((section(".low"))) void fn1() { fn2(); }
__attribute__((section(".high"))) void fn2() { fn1(); }
int main() {
fn1();
}
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>:'
+6 -15
View File
@@ -1,18 +1,11 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] && { echo skipped; exit; }
[ $MACHINE = aarch64 ] && skip
[ $MACHINE = ppc64 ] && skip
[ $MACHINE = ppc64le ] && skip
[ $MACHINE = s390x ] && skip
[ $MACHINE = alpha ] && skip
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
.globl foo
@@ -27,5 +20,3 @@ 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
echo OK
+10 -21
View File
@@ -1,22 +1,14 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
# This test crashes only on qemu-sparc64 running on GitHub Actions,
# even though it works on a local x86-64 machine and on an actual
# SPARC machine.
[ $MACHINE = sparc64 ] && skip
cat <<EOF | $CC -o $t/a.o -c -x assembler -
.globl foo
foo = 0x800000
foo = 0x800008
EOF
cat <<EOF | $CC -o $t/b.o -c -fno-PIC -xc -
@@ -27,12 +19,11 @@ cat <<EOF | $CC -o $t/b.o -c -fno-PIC -xc -
#include <ucontext.h>
void handler(int signum, siginfo_t *info, void *ptr) {
ucontext_t *u = (ucontext_t *)ptr;
printf("ip=0x%llx\n", u->uc_mcontext.gregs[REG_RIP]);
printf("ip=%p\n", info->si_addr);
exit(0);
}
void foo();
extern volatile int foo;
int main() {
struct sigaction act;
@@ -40,11 +31,9 @@ int main() {
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
sigaction(SIGSEGV, &act, 0);
foo();
foo = 5;
}
EOF
$CC -B. -o $t/exe -no-pie $t/a.o $t/b.o
$QEMU $t/exe | grep -q '^ip=0x800000$'
echo OK
$QEMU $t/exe | grep -q '^ip=0x80000.$'
+1 -14
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
echo 'int main() { return 0; }' | $CC -c -o $t/a.o -xc -
echo 'int main() { return 1; }' | $CC -c -o $t/b.o -xc -
@@ -18,5 +7,3 @@ 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
$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
echo OK
+1 -14
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int two() { return 2; }
@@ -37,5 +26,3 @@ rm -f $t/d.a
ar rcs $t/d.a $t/a.o $t/b.o
$CC -B. -o $t/exe $t/c.o $t/d.a
echo OK
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
. $(dirname $0)/common.inc
[ $MACHINE = arm ] || skip
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
#include <stdio.h>
void fn1();
void fn2();
__attribute__((section(".low"))) void fn1() { fn2(); }
__attribute__((section(".high"))) void fn2() { fn1(); }
int main() {
fn1();
}
EOF
$CC -B. -o $t/exe $t/a.o \
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
$OBJDUMP -dr $t/exe | grep -F -A7 '<fn1$thunk>:' > $t/log
grep -Eq 'mov\s+ip, pc' $t/log
grep -Eq 'bx\s+ip' $t/log
grep -Eq 'add\s+ip, ip, pc' $t/log
@@ -1,21 +1,10 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
[[ $MACHINE = arm* ]] || { echo skipped; exit; }
[ $MACHINE = arm ] || skip
echo 'int main() {}' | $CC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|| { echo skipped; exit; }
|| skip
cat <<EOF > $t/a.c
#include <stdio.h>
@@ -56,5 +45,3 @@ $CC -c -o $t/f.o $t/b.c -O2 -marm
$CC -B. -o $t/exe $t/e.o $t/f.o \
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
$QEMU $t/exe | grep -q 'main fn1 fn3 fn2 fn4'
echo OK
@@ -1,20 +1,9 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
[[ $MACHINE == arm* ]] || { echo skipped; exit; }
[[ $MACHINE == arm* ]] || skip
echo 'int foo() { return 0; }' | $CC -o /dev/null -c -xc - -mthumb 2> /dev/null || { echo skipped; exit; }
echo 'int foo() { return 0; }' | $CC -o /dev/null -c -xc - -mthumb 2> /dev/null || skip
cat <<EOF | $CC -o $t/a.o -c -xc - -mthumb
#include <stdio.h>
@@ -41,5 +30,3 @@ EOF
$CC -B. -o $t/exe $t/a.o $t/b.o
$QEMU $t/exe | grep -q 'main foo bar'
echo OK
@@ -1,21 +1,10 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
[[ $MACHINE = arm* ]] || { echo skipped; exit; }
[ $MACHINE = arm ] || skip
echo 'int main() {}' | $GCC -c -o /dev/null -xc - -O0 -mthumb >& /dev/null \
|| { echo skipped; exit; }
|| skip
cat <<EOF > $t/a.c
extern _Thread_local int foo;
@@ -74,5 +63,3 @@ $QEMU $t/exe5 | grep -q '42 5'
$CC -B. -o $t/exe6 $t/e.o $t/f.o -Wl,-no-relax \
-Wl,--section-start=.low=0x10000000,--section-start=.high=0x20000000
$QEMU $t/exe6 | grep -q '42 5'
echo OK
+5 -18
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
__attribute__((weak)) int fn1();
@@ -31,13 +20,11 @@ EOF
$CC -B. -o $t/exe $t/a.o -Wl,-no-as-needed $t/b.so $t/c.so
readelf --dynamic $t/exe > $t/readelf
fgrep -q 'Shared library: [libfoo.so]' $t/readelf
fgrep -q 'Shared library: [libbar.so]' $t/readelf
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
grep -Fq 'Shared library: [libbar.so]' $t/readelf
$CC -B. -o $t/exe $t/a.o -Wl,-as-needed $t/b.so $t/c.so
readelf --dynamic $t/exe > $t/readelf
! fgrep -q 'Shared library: [libfoo.so]' $t/readelf || false
! fgrep -q 'Shared library: [libbar.so]' $t/readelf || false
echo OK
! grep -Fq 'Shared library: [libfoo.so]' $t/readelf || false
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
+5 -18
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
void fn1();
@@ -30,13 +19,11 @@ EOF
$CC -B. -o $t/exe $t/a.o -Wl,--no-as-needed $t/b.so $t/c.so
readelf --dynamic $t/exe > $t/readelf
fgrep -q 'Shared library: [libfoo.so]' $t/readelf
fgrep -q 'Shared library: [libbar.so]' $t/readelf
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
grep -Fq 'Shared library: [libbar.so]' $t/readelf
$CC -B. -o $t/exe $t/a.o -Wl,--as-needed $t/b.so $t/c.so
readelf --dynamic $t/exe > $t/readelf
fgrep -q 'Shared library: [libfoo.so]' $t/readelf
! fgrep -q 'Shared library: [libbar.so]' $t/readelf || false
echo OK
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
+8 -19
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
int foo() { return 3; }
@@ -20,11 +9,13 @@ cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
int bar() { return 3; }
EOF
cat <<EOF | $CC -shared -fPIC -o $t/c.so -xc -
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
int foo();
int baz() { return foo(); }
EOF
$CC -shared -o $t/c.so $t/c.o $t/a.so
cat <<EOF | $CC -c -o $t/d.o -xc -
#include <stdio.h>
int baz();
@@ -33,12 +24,10 @@ int main() {
}
EOF
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed \
$t/c.so $t/b.so $t/a.so
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed $t/c.so $t/b.so $t/a.so
$QEMU $t/exe | grep -q '^3$'
readelf --dynamic $t/exe > $t/log
grep -q /a.so $t/log
grep -q /c.so $t/log
! grep -q /a.so $t/log || false
grep -q /c.so $t/log || false
! grep -q /b.so $t/log || false
echo OK
+3 -16
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -x assembler -
.text
@@ -22,7 +11,5 @@ EOF
./mold -o $t/b.so $t/a.o -auxiliary foo -f bar -shared
readelf --dynamic $t/b.so > $t/log
fgrep -q 'Auxiliary library: [foo]' $t/log
fgrep -q 'Auxiliary library: [bar]' $t/log
echo OK
grep -Fq 'Auxiliary library: [foo]' $t/log
grep -Fq 'Auxiliary library: [bar]' $t/log
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
[ $MACHINE = x86_64 ] || { echo skipped; exit; }
echo '.globl _start; _start: jmp loop' | $CC -o $t/a.o -c -x assembler -
echo '.globl loop; loop: jmp loop' | $CC -o $t/b.o -c -x assembler -
./mold -static -o $t/exe $t/a.o $t/b.o
$OBJDUMP -d $t/exe > /dev/null
file $t/exe | grep -q ELF
echo OK
+13 -15
View File
@@ -1,16 +1,16 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
# GCC produces buggy code for this test case on s390x.
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
# This test does not pass even with GNU ld, which means function pointer
# equality is not guaranteed on HP/PA. I'm not sure if this is an bug in
# QEMU or this test really fails on a real HP/PA machine, but it's quite
# surprising. Many programs could silently break if function pointer
# equality is not guaranteed.
[ $MACHINE = hppa ] && skip
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
int foo = 4;
@@ -26,7 +26,7 @@ EOF
$CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic -Wl,-Bno-symbolic
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
cat <<EOF | $CC -B. -c -o $t/c.o -xc - -fno-PIE
#include <stdio.h>
extern int foo;
@@ -41,5 +41,3 @@ EOF
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
$QEMU $t/exe | grep -q '3 3 1'
echo OK
+6 -24
View File
@@ -1,27 +1,11 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
int foo = 4;
int get_foo() {
return foo;
}
void *bar() {
return bar;
}
int get_foo() { return foo; }
void *bar() { return bar; }
EOF
$CC -B. -shared -o $t/b.so $t/a.o -Wl,-Bsymbolic-functions
@@ -29,17 +13,15 @@ $CC -B. -shared -o $t/b.so $t/a.o -Wl,-Bsymbolic-functions
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
#include <stdio.h>
extern int foo;
int foo = 3;
int x = 5;
int get_foo();
void *bar();
void *bar() { return &x; }
int main() {
foo = 3;
printf("%d %d %d\n", foo, get_foo(), bar == bar());
}
EOF
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
$QEMU $t/exe | grep -q '3 3 0'
echo OK
+5 -27
View File
@@ -1,27 +1,9 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
int foo = 4;
int get_foo() {
return foo;
}
void *bar() {
return bar;
}
int get_foo() { return foo; }
EOF
$CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic
@@ -29,17 +11,13 @@ $CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
#include <stdio.h>
extern int foo;
int foo = 3;
int get_foo();
void *bar();
int main() {
foo = 3;
printf("%d %d %d\n", foo, get_foo(), bar == bar());
printf("%d %d\n", foo, get_foo());
}
EOF
$CC -B. -no-pie -o $t/exe $t/c.o $t/b.so
$QEMU $t/exe | grep -q '3 4 0'
echo OK
$QEMU $t/exe | grep -q '3 4'
+1 -14
View File
@@ -1,16 +1,5 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${TEST_CC:-cc}"
CXX="${TEST_CXX:-c++}"
GCC="${TEST_GCC:-gcc}"
GXX="${TEST_GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
t=out/test/elf/$MACHINE/$testname
mkdir -p $t
. $(dirname $0)/common.inc
# Verify that mold does not crash if no object file is included
# in the output. The resulting executable doesn't contain any
@@ -25,5 +14,3 @@ rm -f $t/a.a
ar rcs $t/a.a $t/a.o
./mold -o $t/exe $t/a.a
echo OK
Loaded 100 of 1487 files, more files were not shown because too many files have changed in this diff. Show more