mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e95e7a88a | ||
|
|
7e68606f78 | ||
|
|
ffc170652f | ||
|
|
8366016955 | ||
|
|
ebd2865fea | ||
|
|
cce86d402a | ||
|
|
187583f489 | ||
|
|
9ffcd8453f | ||
|
|
ba77281c46 | ||
|
|
53cbf48020 | ||
|
|
5f87604373 | ||
|
|
d49fd79114 | ||
|
|
2376604619 | ||
|
|
73770696cd | ||
|
|
abf35a55c0 | ||
|
|
103348b4a5 | ||
|
|
5d2c0306f0 | ||
|
|
748f35f4f2 | ||
|
|
d7df43a38e | ||
|
|
85c115e1be | ||
|
|
d0aa95abe9 | ||
|
|
eb273d1c1c | ||
|
|
3e3687e64e | ||
|
|
8f598a0f96 | ||
|
|
6a2f747bf9 | ||
|
|
9c24d197c1 | ||
|
|
5beb02714c | ||
|
|
4afb95ac5b | ||
|
|
2d45a8eab7 | ||
|
|
a0c46e95d3 | ||
|
|
2a90660aa0 | ||
|
|
f147d78a56 | ||
|
|
aa52f90d69 | ||
|
|
7a66f71796 | ||
|
|
2708ba6201 | ||
|
|
400dfad97c | ||
|
|
6841e77230 | ||
|
|
e9c3d1629f | ||
|
|
b91f3363e1 | ||
|
|
899655b2c1 | ||
|
|
17e705efaf | ||
|
|
ad4a683ff6 | ||
|
|
d557daa268 | ||
|
|
4f08de1147 | ||
|
|
bdb00bc558 | ||
|
|
abcbd451b4 | ||
|
|
f1b4be87f6 | ||
|
|
441f231dbf | ||
|
|
1f44c6c6df | ||
|
|
154e99b2ff | ||
|
|
048b7b2dfd | ||
|
|
8b5eeb84fa | ||
|
|
3c018ca56f | ||
|
|
4a9e30defd | ||
|
|
5f272c4184 | ||
|
|
c32946dfab | ||
|
|
e77e9d52b8 | ||
|
|
a2e670e2a4 | ||
|
|
019fb43e8c | ||
|
|
61933bfd79 | ||
|
|
b4bb6a856d | ||
|
|
b7bbf9c15c | ||
|
|
a3affe8789 | ||
|
|
87b2889254 | ||
|
|
70ca8ffa53 | ||
|
|
10b37a81fb | ||
|
|
45970e661d | ||
|
|
155a8106c9 | ||
|
|
51b9683ce7 | ||
|
|
e2d0c1a93b | ||
|
|
6b0ecea236 | ||
|
|
6b8293bf8c | ||
|
|
82b5ed48bf | ||
|
|
4d313a269c | ||
|
|
384527fd29 | ||
|
|
d947b1a6d3 | ||
|
|
9e32fce011 | ||
|
|
b7102d26ca | ||
|
|
59957fce3b | ||
|
|
c36c5a64fc | ||
|
|
e71cef7448 | ||
|
|
6695f13dce | ||
|
|
ab704cf476 | ||
|
|
c028765bb7 | ||
|
|
04209593c1 | ||
|
|
56fc57198a | ||
|
|
84c411b9a2 | ||
|
|
6f8d62e21c | ||
|
|
1b2306a71b | ||
|
|
a91381b1b1 | ||
|
|
5b8c7dc023 | ||
|
|
6e40a87aad |
No files matched your search
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: win-x86_64
|
||||
path: dist/mold-*.*
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
|
||||
+16
-12
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@staging
|
||||
- run: sudo ./install-build-deps.sh
|
||||
- name: build
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: redhat-actions/podman-login@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
sudo ./install-build-deps.sh
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-multi-archs
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
container: ${{ matrix.distro }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- run: ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
@@ -98,11 +98,15 @@ jobs:
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: sanitize distro name
|
||||
if: failure()
|
||||
id: sanitize
|
||||
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ matrix.distro }}
|
||||
name: test-results-${{ steps.sanitize.outputs.name }}
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
@@ -111,7 +115,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
@@ -123,7 +127,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
@@ -135,7 +139,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@@ -154,7 +158,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build and test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: win-x86_64
|
||||
path: dist/mold-*.*
|
||||
@@ -74,9 +74,9 @@ jobs:
|
||||
needs: [build-linux, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: dist
|
||||
pattern: '*'
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install ronn
|
||||
run: sudo apt-get update && sudo apt-get install -y ronn
|
||||
|
||||
+14
-5
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "December 2025" ""
|
||||
.TH "MOLD" "1" "May 2026" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -282,8 +282,8 @@ Synonym for \fB\-\-build\-id=sha256\fR\.
|
||||
\fB\-\-no\-build\-id\fR
|
||||
Synonym for \fB\-\-build\-id=none\fR\.
|
||||
.TP
|
||||
\fB\-\-compress\-debug\-sections\fR=[ \fBzlib\fR | \fBzlib\-gabi\fR | \fBzstd\fR | \fBnone\fR ]
|
||||
Compress DWARF debug info (\fB\.debug_*\fR sections) using the zlib or zstd compression algorithm\. \fBzlib\-gabi\fR is an alias for \fBzlib\fR\.
|
||||
\fB\-\-compress\-debug\-sections\fR=[ \fBzlib\fR | \fBzlib\-gabi\fR | \fBzlib:\fR\fIN\fR | \fBzstd\fR | \fBzstd:\fR\fIN\fR | \fBnone\fR ]
|
||||
Compress DWARF debug info (\fB\.debug_*\fR sections) using the zlib or zstd compression algorithm\. \fBzlib\-gabi\fR is an alias for \fBzlib\fR\. \fBzlib:\fR\fIN\fR selects the zlib compression level, where \fIN\fR is between 0 and 9 (0 means no compression), and \fBzstd:\fR\fIN\fR selects the zstd compression level, where \fIN\fR is between 1 and 22\. Higher levels achieve better compression ratios but are slower\. \fBzlib\fR is equivalent to \fBzlib:1\fR and \fBzstd\fR is equivalent to \fBzstd:3\fR\.
|
||||
.TP
|
||||
\fB\-\-defsym\fR=\fIsymbol\fR=\fIvalue\fR
|
||||
Define \fIsymbol\fR as an alias for \fIvalue\fR\.
|
||||
@@ -371,10 +371,16 @@ Embed a specified string into the \fB\.note\.package\fR section\. This option is
|
||||
.IP
|
||||
An argument to this option is treated as percent\-encoded and decoded before being inserted into the section, allowing you to avoid the use of the comma (\fB,\fR) character in the argument\. This is useful because the compiler replaces all occurrences of commas in \fB\-Wl,\fR with spaces before forwarding them to the linker\. Note that \fBmold\fR always interprets the argument as percent\-encoded, so you also need to escape all occurrences of \fB%\fR as \fB%25\fR\.
|
||||
.TP
|
||||
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBnone\fR ]
|
||||
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBandroid\fR | \fBandroid+relr\fR | \fBnone\fR ]
|
||||
Compress dynamic relocations in the output file\.
|
||||
.IP
|
||||
If \fBrelr\fR is specified, all \fBR_*_RELATIVE\fR relocations are put into \fB\.relr\.dyn\fR section instead of \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section\. Since \fB\.relr\.dyn\fR section uses a space\-efficient encoding scheme, specifying this flag can reduce the size of the output\. This is typically most effective for position\-independent executable\.
|
||||
.IP
|
||||
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2022, only ChromeOS, Android and Fuchsia support it\.
|
||||
If \fBandroid\fR is specified, all dynamic relocations are encoded in Android's packed relocation format (APS2) and stored in the \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section, whose section type is changed to \fBSHT_ANDROID_REL\fR or \fBSHT_ANDROID_RELA\fR\. The dynamic table is updated to use \fBDT_ANDROID_REL\fR / \fBDT_ANDROID_RELA\fR and their \fBSZ\fR variants in place of the standard tags\.
|
||||
.IP
|
||||
\fBandroid+relr\fR combines both: \fBR_*_RELATIVE\fR relocations go to \fB\.relr\.dyn\fR and the rest are APS2\-encoded\. This is the configuration used by most Android system binaries\.
|
||||
.IP
|
||||
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2026, glibc (since 2\.36), musl (since 1\.2\.4), Android, ChromeOS, and Fuchsia all support it\. The APS2 format, on the other hand, is only handled by Android's bionic loader; the encoder itself is target\-agnostic, but the produced binary can only be loaded on platforms where bionic (or a compatible loader) handles \fBDT_ANDROID_REL(A)\fR\.
|
||||
.TP
|
||||
\fB\-\-pie\fR, \fB\-\-pic\-executable\fR, \fB\-\-no\-pie\fR, \fB\-\-no\-pic\-executable\fR
|
||||
Create a position\-independent executable\.
|
||||
@@ -440,6 +446,9 @@ How to handle undefined symbols\.
|
||||
\fB\-\-version\-script\fR=\fIfile\fR
|
||||
Read version script from \fIfile\fR\. If \fIfile\fR does not exist in the current directory, it is searched from library search paths for the sake of compatibility with GNU ld\.
|
||||
.TP
|
||||
\fB\-w\fR, \fB\-\-no\-warnings\fR
|
||||
Suppress warnings and cancel \fB\-\-fatal\-warnings\fR\.
|
||||
.TP
|
||||
\fB\-\-warn\-common\fR, \fB\-\-no\-warn\-common\fR
|
||||
Warn about common symbols\.
|
||||
.TP
|
||||
|
||||
+29
-5
@@ -506,9 +506,14 @@ but as `-o magic`.
|
||||
* `--no-build-id`:
|
||||
Synonym for `--build-id=none`.
|
||||
|
||||
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `none` ]:
|
||||
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zlib:`_N_ | `zstd` | `zstd:`_N_ | `none` ]:
|
||||
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
|
||||
compression algorithm. `zlib-gabi` is an alias for `zlib`.
|
||||
compression algorithm. `zlib-gabi` is an alias for `zlib`. `zlib:`_N_
|
||||
selects the zlib compression level, where _N_ is between 0 and 9 (0
|
||||
means no compression), and `zstd:`_N_ selects the zstd compression
|
||||
level, where _N_ is between 1 and 22. Higher levels achieve better
|
||||
compression ratios but are slower. `zlib` is equivalent to `zlib:1`
|
||||
and `zstd` is equivalent to `zstd:3`.
|
||||
|
||||
* `--defsym`=_symbol_=_value_:
|
||||
Define _symbol_ as an alias for _value_.
|
||||
@@ -658,16 +663,32 @@ but as `-o magic`.
|
||||
argument as percent-encoded, so you also need to escape all occurrences
|
||||
of `%` as `%25`.
|
||||
|
||||
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
|
||||
* `--pack-dyn-relocs`=[ `relr` | `android` | `android+relr` | `none` ]:
|
||||
Compress dynamic relocations in the output file.
|
||||
|
||||
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
|
||||
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
|
||||
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
|
||||
flag can reduce the size of the output. This is typically most effective for
|
||||
position-independent executable.
|
||||
|
||||
If `android` is specified, all dynamic relocations are encoded in Android's
|
||||
packed relocation format (APS2) and stored in the `.rel.dyn` or `.rela.dyn`
|
||||
section, whose section type is changed to `SHT_ANDROID_REL` or
|
||||
`SHT_ANDROID_RELA`. The dynamic table is updated to use `DT_ANDROID_REL`
|
||||
/ `DT_ANDROID_RELA` and their `SZ` variants in place of the standard tags.
|
||||
|
||||
`android+relr` combines both: `R_*_RELATIVE` relocations go to `.relr.dyn`
|
||||
and the rest are APS2-encoded. This is the configuration used by most
|
||||
Android system binaries.
|
||||
|
||||
Note that a runtime loader has to support `.relr.dyn` to run executables or
|
||||
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2022, only
|
||||
ChromeOS, Android and Fuchsia support it.
|
||||
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2026, glibc
|
||||
(since 2.36), musl (since 1.2.4), Android, ChromeOS, and Fuchsia all
|
||||
support it. The APS2 format, on the other hand, is only handled by
|
||||
Android's bionic loader; the encoder itself is target-agnostic, but the
|
||||
produced binary can only be loaded on platforms where bionic (or a
|
||||
compatible loader) handles `DT_ANDROID_REL(A)`.
|
||||
|
||||
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
|
||||
Create a position-independent executable.
|
||||
@@ -757,6 +778,9 @@ but as `-o magic`.
|
||||
directory, it is searched from library search paths for the sake of
|
||||
compatibility with GNU ld.
|
||||
|
||||
* `-w`, `--no-warnings`:
|
||||
Suppress warnings and cancel `--fatal-warnings`.
|
||||
|
||||
* `--warn-common`, `--no-warn-common`:
|
||||
Warn about common symbols.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ rocky | ol)
|
||||
dnf install -y gcc-c++ cmake diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
|
||||
+8
-10
@@ -45,12 +45,11 @@ static std::vector<std::span<u8>> split(std::span<u8> input) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::span<u8> zlib_compress(std::span<u8> input) {
|
||||
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
// pretty well with lower compression levels, the default level is 1.
|
||||
z_stream strm = {};
|
||||
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
@@ -94,7 +93,7 @@ static std::span<u8> zlib_compress(std::span<u8> input) {
|
||||
return {buf, (size_t)(bufsize - strm.avail_out)};
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
std::vector<u32> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
@@ -103,7 +102,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
std::span<u8> in = inputs[i];
|
||||
adlers[i] = adler32(1, in.data(), in.size());
|
||||
shards[i] = zlib_compress(in);
|
||||
shards[i] = zlib_compress(in, level);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
@@ -139,22 +138,21 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
static std::span<u8> zstd_compress(std::span<u8> input) {
|
||||
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
|
||||
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
int level = 3; // compression level; must be between 1 to 22
|
||||
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
return {buf, sz};
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
shards[i] = zstd_compress(inputs[i], level);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
|
||||
+6
-2
@@ -95,11 +95,15 @@ static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
// [!a-z]: Any character except lowercase letters
|
||||
//
|
||||
// Both `!` and `^` are accepted as negation markers. `!` is the
|
||||
// POSIX/shell convention used by other linkers. `^` was mold's
|
||||
// original syntax and is kept for backward compatibility.
|
||||
bool negate = false;
|
||||
bool closed = false;
|
||||
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
|
||||
negate = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
@@ -395,11 +395,15 @@ public:
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
i64 bufsize = sizeof(Entry) * this->nbuckets;
|
||||
|
||||
// Allocate a zero-initialized buffer. We use mmap() if available
|
||||
// because it's faster than malloc() and memset().
|
||||
// Allocate a zero-initialized buffer. mmap() is faster than
|
||||
// malloc()+memset(), and MAP_POPULATE pre-faults pages here to
|
||||
// avoid CoW faults and TLB shootdowns under concurrent inserts.
|
||||
#ifdef _WIN32
|
||||
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
|
||||
memset((void *)entries, 0, bufsize);
|
||||
#elif MAP_POPULATE
|
||||
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0);
|
||||
#else
|
||||
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
@@ -665,7 +669,7 @@ protected:
|
||||
|
||||
class ZlibCompressor : public Compressor {
|
||||
public:
|
||||
ZlibCompressor(u8 *buf, i64 size);
|
||||
ZlibCompressor(u8 *buf, i64 size, i64 level);
|
||||
void write_to(u8 *buf) override;
|
||||
|
||||
private:
|
||||
@@ -674,7 +678,7 @@ private:
|
||||
|
||||
class ZstdCompressor : public Compressor {
|
||||
public:
|
||||
ZstdCompressor(u8 *buf, i64 size);
|
||||
ZstdCompressor(u8 *buf, i64 size, i64 level);
|
||||
void write_to(u8 *buf) override;
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -5,7 +5,9 @@
|
||||
#include <ios>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
@@ -86,6 +86,9 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
UstarHeader pax = {};
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
|
||||
if (attr.size() >= 0100'000'000'000)
|
||||
unreachable();
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.name[0] = '/';
|
||||
pax.typeflag[0] = 'x';
|
||||
@@ -99,6 +102,8 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write Ustar header
|
||||
UstarHeader ustar = {};
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
if (data.size() >= 0100'000'000'000)
|
||||
unreachable();
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
finalize(ustar);
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
@@ -208,6 +208,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G0:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, S + A - P);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G1:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, (i64)(S + A - P) >> 16);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G2:
|
||||
check(S + A - P, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, (i64)(S + A - P) >> 32);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G3:
|
||||
*(ul32 *)loc |= bits(S + A - P, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
if (sym.has_got(ctx)) {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
@@ -573,6 +597,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_PREL_G0:
|
||||
case R_AARCH64_MOVW_PREL_G0_NC:
|
||||
case R_AARCH64_MOVW_PREL_G1:
|
||||
case R_AARCH64_MOVW_PREL_G1_NC:
|
||||
case R_AARCH64_MOVW_PREL_G2:
|
||||
case R_AARCH64_MOVW_PREL_G2_NC:
|
||||
case R_AARCH64_MOVW_PREL_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
|
||||
+3
-3
@@ -46,10 +46,10 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *loc;
|
||||
return *(i8 *)loc;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
return *(ul16 *)loc;
|
||||
return *(il16 *)loc;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
@@ -65,7 +65,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(ul32 *)loc;
|
||||
return *(il32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
+82
-1
@@ -981,7 +981,7 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
// pcaddi $t0, <offset>
|
||||
if (is_relaxable_got_load(ctx, isec, i))
|
||||
if (i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
is_int(dist, 22))
|
||||
is_int(dist, 22) && (dist & 3) == 0)
|
||||
remove(4);
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_HI20:
|
||||
@@ -1010,6 +1010,87 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
isec.sh_size -= r_delta;
|
||||
}
|
||||
|
||||
// With --emit-relocs, rewrite the emitted relocations to match the
|
||||
// instructions that relaxation produced. The logic parallels apply_reloc_alloc.
|
||||
template <>
|
||||
void rewrite_reloc_types(Context<E> &ctx, InputSection<E> &isec, ElfRel<E> *buf) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::span<RelocDelta> deltas = isec.extra.r_deltas;
|
||||
i64 k = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
|
||||
i64 removed_bytes = 0;
|
||||
if (!deltas.empty()) {
|
||||
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
|
||||
k++;
|
||||
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
|
||||
removed_bytes = get_removed_bytes(deltas, k);
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_TLS_LE_HI20_R:
|
||||
case R_LARCH_TLS_LE_ADD_R:
|
||||
// lu12i.w + add.d + addi.d => addi.d
|
||||
if (removed_bytes)
|
||||
buf[i].r_type = R_LARCH_NONE;
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
// pcalau12i + addi.d/ld.d => pcaddi. The high part vanishes and the low
|
||||
// part becomes the pcaddi's PC-relative relocation.
|
||||
if (removed_bytes) {
|
||||
buf[i].r_type = R_LARCH_NONE;
|
||||
buf[i + 2].r_type = R_LARCH_PCREL20_S2;
|
||||
i += 3;
|
||||
}
|
||||
break;
|
||||
case R_LARCH_CALL36:
|
||||
// pcaddu18i + jirl => b or bl
|
||||
if (removed_bytes)
|
||||
buf[i].r_type = R_LARCH_B26;
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_HI20:
|
||||
// pcalau12i + addi.d => pcaddi when TLSDESC is kept; both deleted when it
|
||||
// is relaxed to IE/LE.
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
buf[i].r_type = R_LARCH_NONE;
|
||||
} else if (removed_bytes) {
|
||||
buf[i].r_type = R_LARCH_NONE;
|
||||
buf[i + 2].r_type = R_LARCH_TLS_DESC_PCREL20_S2;
|
||||
}
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_LO12:
|
||||
if (!sym.has_tlsdesc(ctx))
|
||||
buf[i].r_type = R_LARCH_NONE;
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_LD:
|
||||
// TLSDESC is relaxed to IE or LE. The ld.d slot holds the first
|
||||
// instruction of the resulting sequence.
|
||||
if (sym.has_tlsdesc(ctx))
|
||||
break;
|
||||
if (sym.has_gottp(ctx))
|
||||
buf[i].r_type = R_LARCH_TLS_IE_PC_HI20; // pcalau12i
|
||||
else if (removed_bytes)
|
||||
buf[i].r_type = R_LARCH_NONE; // small TP offset (deleted)
|
||||
else
|
||||
buf[i].r_type = R_LARCH_TLS_LE_HI20; // large TP offset (lu12i.w)
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_CALL:
|
||||
// The jirl slot holds the second instruction of the IE or LE sequence.
|
||||
if (sym.has_tlsdesc(ctx))
|
||||
break;
|
||||
if (sym.has_gottp(ctx))
|
||||
buf[i].r_type = R_LARCH_TLS_IE_PC_LO12; // ld.d
|
||||
else
|
||||
buf[i].r_type = R_LARCH_TLS_LE_LO12; // ori or addi.w
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
+13
-15
@@ -486,7 +486,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
if (isec && isec->name() == ".opd")
|
||||
if (isec && isec->name == ".opd")
|
||||
return isec.get();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -643,30 +643,28 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
this->shdr.sh_size += ENTRY_SIZE;
|
||||
}
|
||||
|
||||
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
if (ctx.arg.pic)
|
||||
return symbols.size() * 2;
|
||||
return 0;
|
||||
std::vector<ElfRel<E>> PPC64OpdSection::collect_dynrels(Context<E> &ctx) const {
|
||||
if (!ctx.arg.pic)
|
||||
return {};
|
||||
|
||||
std::vector<ElfRel<E>> rels;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 loc = sym->get_opd_addr(ctx);
|
||||
rels.emplace_back(loc, E::R_RELATIVE, 0, sym->get_addr(ctx, NO_PLT | NO_OPD));
|
||||
rels.emplace_back(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
return rels;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
ElfRel<E> *rel = nullptr;
|
||||
if (ctx.arg.pic)
|
||||
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = addr;
|
||||
*buf++ = ctx.extra.TOC->value;
|
||||
*buf++ = 0;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
u64 loc = sym->get_opd_addr(ctx);
|
||||
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
|
||||
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1000,6 +1000,72 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
isec.sh_size -= r_delta;
|
||||
}
|
||||
|
||||
// With --emit-relocs, rewrite the emitted relocations to match the
|
||||
// instructions that relaxation produced. The logic parallels apply_reloc_alloc.
|
||||
template <>
|
||||
void rewrite_reloc_types(Context<E> &ctx, InputSection<E> &isec, ElfRel<E> *buf) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::span<RelocDelta> deltas = isec.extra.r_deltas;
|
||||
i64 k = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
|
||||
i64 removed_bytes = 0;
|
||||
if (!deltas.empty()) {
|
||||
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
|
||||
k++;
|
||||
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
|
||||
removed_bytes = get_removed_bytes(deltas, k);
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT:
|
||||
// auipc + jalr => jal, or c.j/c.jal
|
||||
if (removed_bytes == 4)
|
||||
buf[i].r_type = R_RISCV_JAL;
|
||||
else if (removed_bytes == 6)
|
||||
buf[i].r_type = R_RISCV_RVC_JUMP;
|
||||
break;
|
||||
case R_RISCV_GOT_HI20:
|
||||
// auipc + l[dw] GOT load => the value is materialized directly, so both
|
||||
// this and the paired PCREL_LO12 (the load) no longer need a relocation.
|
||||
if (removed_bytes) {
|
||||
buf[i].r_type = R_RISCV_NONE;
|
||||
buf[i + 2].r_type = R_RISCV_NONE;
|
||||
i += 3;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
// lui (+ addi) => an instruction holding a link-time constant.
|
||||
if (removed_bytes)
|
||||
buf[i].r_type = R_RISCV_NONE;
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
case R_RISCV_TPREL_ADD:
|
||||
// lui + add => deleted; the variable is accessed relative to tp directly.
|
||||
if (removed_bytes)
|
||||
buf[i].r_type = R_RISCV_NONE;
|
||||
break;
|
||||
case R_RISCV_TLSDESC_HI20:
|
||||
if (!sym.has_tlsdesc(ctx))
|
||||
buf[i].r_type = R_RISCV_NONE;
|
||||
break;
|
||||
case R_RISCV_TLSDESC_LOAD_LO12:
|
||||
case R_RISCV_TLSDESC_ADD_LO12:
|
||||
case R_RISCV_TLSDESC_CALL: {
|
||||
Symbol<E> &sym2 =
|
||||
*isec.file.symbols[find_paired_reloc(ctx, isec, rels, sym, i).r_sym];
|
||||
if (!sym2.has_tlsdesc(ctx))
|
||||
buf[i].r_type = R_RISCV_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ISA name handlers
|
||||
//
|
||||
// An example of ISA name is "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0".
|
||||
|
||||
+67
-16
@@ -78,26 +78,77 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
memset(buf, 0, E::plt_hdr_size);
|
||||
}
|
||||
|
||||
// Returns the byte offset within .plt of the data pointer for a large SPARC
|
||||
// PLT entry. See write_plt_entry below for the block layout this assumes.
|
||||
i64 sparc_plt_ptr_offset(Context<E> &ctx, i64 pltidx) {
|
||||
i64 i = pltidx - sparc_num_small_plt;
|
||||
i64 block = i / 160;
|
||||
i64 nlarge = (i64)ctx.plt->symbols.size() - sparc_num_small_plt;
|
||||
i64 nstub = std::min<i64>(160, nlarge - block * 160);
|
||||
return 0x100000 + block * 5120 + nstub * 24 + (i % 160) * 8;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
// SPARC uses two PLT entry formats. A "small" entry branches directly to
|
||||
// the resolver stub (.PLT1) with a BPcc instruction whose reach is only
|
||||
// ±1 MiB. Once the PLT grows past that (0x100000 bytes), we switch to a
|
||||
// "large" format: rather than branching to the resolver, a large entry
|
||||
// loads a 64-bit value from a nearby data pointer and jumps to (that value
|
||||
// + its own address).
|
||||
//
|
||||
// Large entries are grouped into blocks of 160: each block is 160 code
|
||||
// stubs followed by 160 8-byte data pointers, one per stub. The stubs must
|
||||
// sit on a fixed grid, 24 bytes apart, because that is how the loader finds
|
||||
// them: on a lazy bind the stub jumps to the resolver (.PLT0) with only its
|
||||
// own address, and the loader derives which symbol to resolve from that
|
||||
// address. Nothing may sit between two stubs, so each stub's pointer lives
|
||||
// in the block's pointer region, which it reaches with a signed 13-bit ldx
|
||||
// offset (see to_plt_offset). This layout is dictated by the loader; we
|
||||
// cannot rearrange or simplify it.
|
||||
i64 idx = sym.get_plt_idx(ctx);
|
||||
|
||||
u64 plt0 = ctx.plt->shdr.sh_addr;
|
||||
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
|
||||
u64 entry = sym.get_plt_addr(ctx);
|
||||
if (idx < sparc_num_small_plt) {
|
||||
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
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
|
||||
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
|
||||
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);
|
||||
} else {
|
||||
static ub32 insn[] = {
|
||||
0x8a10'000f, // mov %o7, %g5
|
||||
0x4000'0002, // call . + 8
|
||||
0x0100'0000, // nop
|
||||
0xc25b'e000, // ldx [ %o7 + .ptr ], %g1
|
||||
0x83c3'c001, // jmpl %o7 + %g1, %g1
|
||||
0x9e10'0005, // mov %g5, %o7
|
||||
};
|
||||
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
u64 call = sym.get_plt_addr(ctx) + 4;
|
||||
i64 ptroff = sparc_plt_ptr_offset(ctx, idx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 12) |= bits(plt + ptroff - call, 12, 0);
|
||||
|
||||
// The data pointer initially holds (.PLT0 - call) so that the first call
|
||||
// jumps to .PLT0, where the loader's lazy resolver lives. The resolver
|
||||
// later overwrites it with (target - call).
|
||||
*(ub64 *)(ctx.buf + ctx.plt->shdr.sh_offset + ptroff) = plt - call;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+57
-85
@@ -18,7 +18,6 @@
|
||||
#else
|
||||
# include <direct.h>
|
||||
# define isatty _isatty
|
||||
# define chdir _chdir
|
||||
# define STDERR_FILENO (_fileno(stderr))
|
||||
#endif
|
||||
|
||||
@@ -62,6 +61,7 @@ Options:
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
-w, --no-warnings Suppress warnings
|
||||
-y SYMBOL, --trace-symbol SYMBOL
|
||||
Trace references to SYMBOL
|
||||
--Bdynamic, --dy Link against shared libraries (default)
|
||||
@@ -89,7 +89,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,zstd]
|
||||
--compress-debug-sections=[none,zlib,zlib:0,...,zlib:9,zstd,zstd:1,...,zstd:22]
|
||||
Compress .debug_* sections
|
||||
--dc Ignored
|
||||
--dependency-file=FILE Write Makefile-style dependency rules to FILE
|
||||
@@ -131,7 +131,7 @@ Options:
|
||||
--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
|
||||
--pack-dyn-relocs=[relr,none]
|
||||
--pack-dyn-relocs=[relr,android,android+relr,none]
|
||||
Pack dynamic relocations
|
||||
--package-metadata=PERCENT_ENCODED_STRING
|
||||
Set a given string to .note.package
|
||||
@@ -507,63 +507,6 @@ static bool is_file(const std::filesystem::path& path) {
|
||||
return !std::filesystem::is_directory(path, error) && !error;
|
||||
}
|
||||
|
||||
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"(TEXT|DATA|RODATA|BSS)", flags);
|
||||
static std::regex re2(R"([a-zA-Z0-9_.]\S*|EHDR|PHDR)", flags);
|
||||
static std::regex re3(R"(=(0x[0-9a-f]+|\d+))", flags);
|
||||
static std::regex re4(R"(%(0x[0-9a-f]+|\d+))", flags);
|
||||
static std::regex re5(R"(!(\S+))", flags);
|
||||
|
||||
std::vector<SectionOrder> vec;
|
||||
|
||||
for (std::string_view tok : split_string(arg, " \t")) {
|
||||
if (tok.empty())
|
||||
continue;
|
||||
|
||||
vec.push_back(SectionOrder{ .token = tok });
|
||||
SectionOrder &ord = vec.back();
|
||||
std::cmatch m;
|
||||
|
||||
if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re1)) {
|
||||
ord.type = SectionOrder::GROUP;
|
||||
ord.name = m[0].str();
|
||||
} else if (std::regex_match(tok.data(), tok.data() + tok.size(), m, re2)) {
|
||||
ord.type = SectionOrder::SECTION;
|
||||
ord.name = m[0].str();
|
||||
} else if (std::regex_match(tok.data(), tok.data() + tok.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_match(tok.data(), tok.data() + tok.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_match(tok.data(), tok.data() + tok.size(), m, re5)) {
|
||||
ord.type = SectionOrder::SYMBOL;
|
||||
ord.name = m[1].str();
|
||||
} else {
|
||||
Fatal(ctx) << "--section-order: parse error: " << arg;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -893,6 +836,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.detach = true;
|
||||
} else if (read_flag("no-detach")) {
|
||||
ctx.arg.detach = false;
|
||||
} else if (read_arg("orphan-handling")) {
|
||||
if (arg != "place" && arg != "warn" && arg != "error")
|
||||
Fatal(ctx) << "--orphan-handling: unsupported mode: " << arg;
|
||||
ctx.arg.orphan_handling = arg;
|
||||
} else if (read_flag("warn-rwx-segments") ||
|
||||
read_flag("no-warn-rwx-segments")) {
|
||||
// mold doesn't warn about RWX segments
|
||||
} else if (read_flag("default-symver")) {
|
||||
ctx.arg.default_symver = true;
|
||||
} else if (read_flag("noinhibit-exec")) {
|
||||
@@ -1001,9 +951,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("pack-dyn-relocs=relr") ||
|
||||
read_z_flag("pack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
ctx.arg.pack_dyn_relocs_android = false;
|
||||
} else if (read_flag("pack-dyn-relocs=android")) {
|
||||
ctx.arg.pack_dyn_relocs_android = true;
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_flag("pack-dyn-relocs=android+relr")) {
|
||||
ctx.arg.pack_dyn_relocs_android = true;
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_flag("pack-dyn-relocs=none") ||
|
||||
read_z_flag("nopack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
ctx.arg.pack_dyn_relocs_android = false;
|
||||
} else if (read_flag("use-android-relr-tags")) {
|
||||
ctx.arg.use_android_relr_tags = true;
|
||||
} else if (read_flag("no-use-android-relr-tags")) {
|
||||
ctx.arg.use_android_relr_tags = false;
|
||||
} else if (read_arg("package-metadata")) {
|
||||
ctx.arg.package_metadata = parse_package_metadata(ctx, arg);
|
||||
} else if (read_flag("stats")) {
|
||||
@@ -1039,14 +1001,31 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("zero-to-bss")) {
|
||||
ctx.arg.zero_to_bss = true;
|
||||
} else if (read_arg("compress-debug-sections")) {
|
||||
if (arg == "zlib" || arg == "zlib-gabi")
|
||||
if (arg == "zlib" || arg == "zlib-gabi") {
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
|
||||
else if (arg == "zstd")
|
||||
ctx.arg.compress_debug_sections_level = 1;
|
||||
} else if (arg.starts_with("zlib:")) {
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
|
||||
i64 level = parse_number(ctx, "compress-debug-sections", arg.substr(5));
|
||||
if (level < 0 || 9 < level)
|
||||
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg
|
||||
<< " (zlib level must be between 0 and 9)";
|
||||
ctx.arg.compress_debug_sections_level = level;
|
||||
} else if (arg == "zstd") {
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
|
||||
else if (arg == "none")
|
||||
ctx.arg.compress_debug_sections_level = 3;
|
||||
} else if (arg.starts_with("zstd:")) {
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
|
||||
i64 level = parse_number(ctx, "compress-debug-sections", arg.substr(5));
|
||||
if (level < 1 || 22 < level)
|
||||
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg
|
||||
<< " (zstd level must be between 1 and 22)";
|
||||
ctx.arg.compress_debug_sections_level = level;
|
||||
} else if (arg == "none") {
|
||||
ctx.arg.compress_debug_sections = ELFCOMPRESS_NONE;
|
||||
else
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
|
||||
}
|
||||
} else if (read_arg("wrap")) {
|
||||
ctx.arg.wrap.insert(arg);
|
||||
} else if (read_flag("omagic") || read_flag("N")) {
|
||||
@@ -1074,8 +1053,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
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")) {
|
||||
@@ -1191,6 +1168,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.fatal_warnings = true;
|
||||
} else if (read_flag("no-fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = false;
|
||||
} else if (read_flag("w") || read_flag("no-warnings")) {
|
||||
ctx.arg.suppress_warnings = true;
|
||||
} else if (read_flag("fork")) {
|
||||
ctx.arg.fork = true;
|
||||
} else if (read_flag("no-fork")) {
|
||||
@@ -1226,8 +1205,6 @@ 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 = "-";
|
||||
} else if (read_eq("print-icf-sections")) {
|
||||
@@ -1471,10 +1448,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.dependency_file = ctx.arg.chroot + "/" + ctx.arg.dependency_file;
|
||||
}
|
||||
|
||||
if (!ctx.arg.directory.empty())
|
||||
if (chdir(ctx.arg.directory.c_str()) == -1)
|
||||
if (!ctx.arg.directory.empty()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::current_path(ctx.arg.directory, ec);
|
||||
if (ec)
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
<< ": " << ec.message();
|
||||
}
|
||||
|
||||
if (!ctx.arg.sysroot.empty()) {
|
||||
for (std::string &path : ctx.arg.library_paths) {
|
||||
@@ -1496,6 +1476,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
// A shared library is loaded by the dynamic linker, so it doesn't need
|
||||
// PT_INTERP. (-pie makes the output an executable even with -shared.)
|
||||
if (ctx.arg.shared && !ctx.arg.pie)
|
||||
ctx.arg.dynamic_linker = "";
|
||||
|
||||
if (allow_shlib_undefined)
|
||||
ctx.arg.allow_shlib_undefined = *allow_shlib_undefined;
|
||||
else
|
||||
@@ -1526,11 +1511,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
((u64)std::random_device()() << 32) | std::random_device()();
|
||||
}
|
||||
|
||||
// --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;
|
||||
|
||||
// `-z dynamic-undefined-weak` is enabled by default for DSOs.
|
||||
if (z_dynamic_undefined_weak)
|
||||
@@ -1538,11 +1520,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
else
|
||||
ctx.arg.z_dynamic_undefined_weak = ctx.arg.shared;
|
||||
|
||||
// --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.nmagic || ctx.arg.omagic)
|
||||
ctx.arg.z_relro = false;
|
||||
@@ -1565,9 +1544,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (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";
|
||||
|
||||
@@ -1595,8 +1571,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Fatal(ctx) << "--version-script: file not found: " << path;
|
||||
};
|
||||
|
||||
ReaderContext rctx;
|
||||
Script(ctx, rctx, open()).parse_version_script();
|
||||
Script(ctx, open()).parse_version_script();
|
||||
}
|
||||
|
||||
if (separate_debug_file) {
|
||||
@@ -1613,8 +1588,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.stats || ctx.arg.perf)
|
||||
ctx.arg.detach = false;
|
||||
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
std::variant<Symbol<E> *, u64> &val = ctx.arg.defsyms[i].second;
|
||||
if (Symbol<E> **sym = std::get_if<Symbol<E> *>(&val))
|
||||
@@ -1644,7 +1617,6 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
sym->gc_root = true;
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
sym->gc_root = true;
|
||||
ctx.arg.entry->gc_root = true;
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
|
||||
@@ -81,7 +81,11 @@ enum : u32 {
|
||||
SHT_RELR = 19,
|
||||
SHT_CREL = 0x40000014,
|
||||
SHT_LOOS = 0x60000000,
|
||||
SHT_ANDROID_REL = 0x60000001,
|
||||
SHT_ANDROID_RELA = 0x60000002,
|
||||
SHT_LLVM_ADDRSIG = 0x6fff4c03,
|
||||
SHT_ANDROID_RELR = 0x6fffff00,
|
||||
SHT_GNU_SFRAME = 0x6ffffff4,
|
||||
SHT_GNU_HASH = 0x6ffffff6,
|
||||
SHT_GNU_VERDEF = 0x6ffffffd,
|
||||
SHT_GNU_VERNEED = 0x6ffffffe,
|
||||
@@ -191,6 +195,7 @@ enum : u32 {
|
||||
PT_GNU_STACK = 0x6474e551,
|
||||
PT_GNU_RELRO = 0x6474e552,
|
||||
PT_GNU_PROPERTY = 0x6474e553,
|
||||
PT_GNU_SFRAME = 0x6474e554,
|
||||
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
|
||||
PT_ARM_EXIDX = 0x70000001,
|
||||
PT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
@@ -285,6 +290,13 @@ enum : u32 {
|
||||
DT_RELRSZ = 35,
|
||||
DT_RELR = 36,
|
||||
DT_RELRENT = 37,
|
||||
DT_ANDROID_REL = 0x6000000f,
|
||||
DT_ANDROID_RELSZ = 0x60000010,
|
||||
DT_ANDROID_RELA = 0x60000011,
|
||||
DT_ANDROID_RELASZ = 0x60000012,
|
||||
DT_ANDROID_RELR = 0x6fffe000,
|
||||
DT_ANDROID_RELRSZ = 0x6fffe001,
|
||||
DT_ANDROID_RELRENT = 0x6fffe003,
|
||||
DT_GNU_HASH = 0x6ffffef5,
|
||||
DT_DEPAUDIT = 0x6ffffefb,
|
||||
DT_AUDIT = 0x6ffffefc,
|
||||
@@ -1493,6 +1505,52 @@ template <typename E> using U64 = std::conditional_t<E::is_le, ul64, ub64>;
|
||||
template <typename E> using Word = std::conditional_t<E::is_64, U64<E>, U32<E>>;
|
||||
template <typename E> using SWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
|
||||
|
||||
//
|
||||
// SFrame is a simple unwind information format used as a lightweight
|
||||
// alternative to .eh_frame. A .sframe section consists of a header, an
|
||||
// array of Function Descriptor Entries (FDEs) sorted by PC, and a blob
|
||||
// of Frame Row Entries (FREs). mold understands SFrame Version 3.
|
||||
//
|
||||
// https://sourceware.org/binutils/docs/sframe-spec.html
|
||||
//
|
||||
|
||||
static constexpr u16 SFRAME_MAGIC = 0xdee2;
|
||||
|
||||
static constexpr u8 SFRAME_F_FDE_SORTED = 0x1;
|
||||
static constexpr u8 SFRAME_F_FRAME_POINTER = 0x2;
|
||||
static constexpr u8 SFRAME_F_FDE_FUNC_START_PCREL = 0x4;
|
||||
|
||||
static constexpr u8 SFRAME_ABI_AARCH64_ENDIAN_BIG = 1;
|
||||
static constexpr u8 SFRAME_ABI_AARCH64_ENDIAN_LITTLE = 2;
|
||||
static constexpr u8 SFRAME_ABI_AMD64_ENDIAN_LITTLE = 3;
|
||||
static constexpr u8 SFRAME_ABI_S390X_ENDIAN_BIG = 4;
|
||||
|
||||
template <typename E>
|
||||
struct SFrameHeader {
|
||||
U16<E> magic;
|
||||
u8 version;
|
||||
u8 flags;
|
||||
u8 abi_arch;
|
||||
i8 cfa_fixed_fp_offset;
|
||||
i8 cfa_fixed_ra_offset;
|
||||
u8 auxhdr_len;
|
||||
U32<E> num_fdes;
|
||||
U32<E> num_fres;
|
||||
U32<E> fre_len;
|
||||
U32<E> fdeoff;
|
||||
U32<E> freoff;
|
||||
};
|
||||
|
||||
// The index part of an SFrame Version 3 FDE. The func_start_offset field
|
||||
// is PC-relative (relative to its own address) when the section flag
|
||||
// SFRAME_F_FDE_FUNC_START_PCREL is set, which is how mold always emits it.
|
||||
template <typename E>
|
||||
struct SFrameFdeIdx {
|
||||
I64<E> func_start_offset;
|
||||
U32<E> func_size;
|
||||
U32<E> func_start_fre_off;
|
||||
};
|
||||
|
||||
template <typename E> requires E::is_64
|
||||
struct ElfSym<E> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
@@ -1809,6 +1867,7 @@ struct ElfRel<SH4BE> {
|
||||
|
||||
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
|
||||
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
template <typename E> concept supports_sframe = requires { E::sframe_abi; };
|
||||
template <typename E> concept needs_thunk = requires { E::thunk_size; };
|
||||
|
||||
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
|
||||
@@ -1852,6 +1911,7 @@ struct X86_64 {
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u8 sframe_abi = SFRAME_ABI_AMD64_ENDIAN_LITTLE;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
@@ -1867,6 +1927,7 @@ struct X86_64 {
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
static constexpr u32 R_SFRAME = R_X86_64_PC64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
|
||||
};
|
||||
|
||||
@@ -1902,6 +1963,7 @@ struct ARM64LE {
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_AARCH64;
|
||||
static constexpr u8 sframe_abi = SFRAME_ABI_AARCH64_ENDIAN_LITTLE;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
@@ -1919,12 +1981,14 @@ struct ARM64LE {
|
||||
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
|
||||
static constexpr u32 R_SFRAME = R_AARCH64_PREL64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
|
||||
};
|
||||
|
||||
struct ARM64BE : ARM64LE {
|
||||
static constexpr std::string_view name = "arm64be";
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr u8 sframe_abi = SFRAME_ABI_AARCH64_ENDIAN_BIG;
|
||||
};
|
||||
|
||||
struct ARM32LE {
|
||||
@@ -2097,6 +2161,7 @@ struct S390X {
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_S390X;
|
||||
static constexpr u8 sframe_abi = SFRAME_ABI_S390X_ENDIAN_BIG;
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
@@ -2111,6 +2176,7 @@ struct S390X {
|
||||
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
|
||||
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
|
||||
static constexpr u32 R_SFRAME = R_390_PC64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
return Script(ctx, rctx, mf).get_script_output_type();
|
||||
return Script(ctx, mf, &rctx).get_script_output_type();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
+67
-19
@@ -5,31 +5,57 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
static bool should_keep(const InputSection<E> &isec) {
|
||||
static bool should_keep(Context<E> &ctx, const InputSection<E> &isec) {
|
||||
u32 type = isec.shdr().sh_type;
|
||||
u32 flags = isec.shdr().sh_flags;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
if (isec.name == ".got2")
|
||||
return true;
|
||||
|
||||
// KEEP'ed by a linker script
|
||||
if (!isec.file.script_match.empty()) {
|
||||
i32 rank = isec.file.script_match[isec.shndx];
|
||||
if (rank != -1 && ctx.script_ranks[rank].keep)
|
||||
return true;
|
||||
}
|
||||
|
||||
return (flags & SHF_GNU_RETAIN) ||
|
||||
type == SHT_NOTE ||
|
||||
type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
name.starts_with(".fini") ||
|
||||
is_c_identifier(name);
|
||||
isec.name.starts_with(".ctors") ||
|
||||
isec.name.starts_with(".dtors") ||
|
||||
isec.name.starts_with(".init") ||
|
||||
isec.name.starts_with(".fini");
|
||||
}
|
||||
|
||||
// Sections whose names are valid C identifiers can be referenced via
|
||||
// __start_<name>/__stop_<name> symbols, which the linker synthesizes.
|
||||
// Such sections must be kept alive only if such a marker symbol is
|
||||
// referenced from a live section. This map lets us find all sections
|
||||
// of a given name when we encounter such a reference during marking.
|
||||
template <typename E>
|
||||
using StartStopMap =
|
||||
tbb::concurrent_unordered_multimap<std::string_view, InputSection<E> *>;
|
||||
|
||||
template <typename E>
|
||||
static StartStopMap<E> build_start_stop_map(Context<E> &ctx) {
|
||||
StartStopMap<E> map;
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC) &&
|
||||
is_c_identifier(isec->name))
|
||||
map.insert({isec->name, isec.get()});
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -72,7 +98,7 @@ collect_root_set(Context<E> &ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (should_keep(*isec))
|
||||
if (should_keep(ctx, *isec))
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -96,9 +122,18 @@ collect_root_set(Context<E> &ctx) {
|
||||
return rootset;
|
||||
}
|
||||
|
||||
static std::string_view start_stop_name(std::string_view sym) {
|
||||
if (sym.starts_with("__start_"))
|
||||
return sym.substr(8);
|
||||
if (sym.starts_with("__stop_"))
|
||||
return sym.substr(7);
|
||||
return "";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
static void visit_section(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth,
|
||||
const StartStopMap<E> &start_stop_map) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
@@ -106,7 +141,7 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
auto mark = [&](InputSection<E> *sec) {
|
||||
if (mark_section(sec)) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sec, feeder, depth + 1);
|
||||
visit_section(ctx, sec, feeder, depth + 1, start_stop_map);
|
||||
else
|
||||
feeder.add(sec);
|
||||
}
|
||||
@@ -123,10 +158,21 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
// Symbol can refer to either a section fragment or an input section.
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
if (SectionFragment<E> *frag = sym.get_frag())
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive = true;
|
||||
else
|
||||
mark(sym.get_input_section());
|
||||
continue;
|
||||
}
|
||||
|
||||
mark(sym.get_input_section());
|
||||
|
||||
// A reference to __start_<name> or __stop_<name> keeps every section
|
||||
// named <name> alive, mirroring how those symbols are defined.
|
||||
if (std::string_view sec = start_stop_name(sym.name());
|
||||
!sec.empty()) {
|
||||
auto [i, end] = start_stop_map.equal_range(sec);
|
||||
for (; i != end; ++i)
|
||||
mark(i->second);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
@@ -136,12 +182,13 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// Mark all reachable sections
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset,
|
||||
const StartStopMap<E> &start_stop_map) {
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
visit(ctx, isec, feeder, 0);
|
||||
visit_section(ctx, isec, feeder, 0, start_stop_map);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,7 +237,8 @@ template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
StartStopMap<E> start_stop_map = build_start_stop_map(ctx);
|
||||
mark(ctx, rootset, start_stop_map);
|
||||
sweep(ctx);
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -456,11 +456,8 @@ read_address_ranges(Context<E> &ctx, const Compunit &cu) {
|
||||
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + rnglists_base;
|
||||
i64 num_offsets = *(U32<E> *)(base - 4);
|
||||
Offset *offsets = (Offset *)base;
|
||||
|
||||
for (i64 i = 0; i < num_offsets; i++)
|
||||
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
|
||||
read_rnglist_range<E>(vec, base + offsets[ranges.value], addrx, low_pc.value);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -783,8 +780,12 @@ void write_gdb_index(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// Write the final counts into the buffer.
|
||||
for (Entry *ent : entries)
|
||||
*(ul32 *)(base + ent->value.type_offset) = ent->value.count;
|
||||
tbb::parallel_for_each(entries, [&](Entry *ent) {
|
||||
ul32 *p = (ul32 *)(base + ent->value.type_offset);
|
||||
p[0] = ent->value.count;
|
||||
// Sort entries for deterministic output
|
||||
std::sort(p + 1, p + 1 + ent->value.count);
|
||||
});
|
||||
|
||||
// Write names
|
||||
tbb::parallel_for_each(entries, [&](Entry *ent) {
|
||||
|
||||
+19
-7
@@ -123,24 +123,24 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
|
||||
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(isec.name))
|
||||
return false;
|
||||
|
||||
if (shdr.sh_flags & SHF_EXECINSTR)
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
name != ".init" && name != ".fini";
|
||||
isec.name != ".init" && isec.name != ".fini";
|
||||
|
||||
// .gcc_except_table contains a compiler-generated table. Pointer
|
||||
// equality for the section is not significant because only the C++
|
||||
// exception handling code will use the table at runtime.
|
||||
if (name == ".gcc_except_table" || name.starts_with(".gcc_except_table."))
|
||||
if (isec.name == ".gcc_except_table" ||
|
||||
isec.name.starts_with(".gcc_except_table."))
|
||||
return true;
|
||||
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.name().starts_with(".data.rel.ro");
|
||||
bool is_relro = isec.name.starts_with(".data.rel.ro");
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
@@ -255,7 +255,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
InputSection<E> *isec = sym.get_input_section();
|
||||
|
||||
if (!sym.file) {
|
||||
if (!sym.file || sym.is_imported) {
|
||||
hash('1');
|
||||
hash((u64)&sym);
|
||||
} else if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
@@ -614,6 +614,18 @@ void icf_sections(Context<E> &ctx) {
|
||||
if (!ctx.arg.print_icf_sections.empty())
|
||||
print_icf_sections(ctx);
|
||||
|
||||
// Update alignment of leaders.
|
||||
{
|
||||
Timer t(ctx, "update_alignment");
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->leader && isec->leader != isec.get()) {
|
||||
update_maximum(isec->leader->p2align, isec->p2align);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Eliminate duplicate sections.
|
||||
// Symbols pointing to eliminated sections will be redirected on the fly when
|
||||
// exporting to the symtab.
|
||||
|
||||
+188
-36
@@ -93,6 +93,15 @@ InputFile<E>::InputFile(Context<E> &ctx, MappedFile *mf)
|
||||
shstrtab = this->get_string(ctx, shstrtab_idx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputFile<E>::populate_symbol_names() {
|
||||
symbol_names.reserve(elf_syms.size());
|
||||
for (const ElfSym<E> &esym : elf_syms) {
|
||||
const char *p = symbol_strtab.data() + esym.st_name;
|
||||
symbol_names.emplace_back(p, strlen(p));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
for (ElfShdr<E> &sec : elf_sections)
|
||||
@@ -329,7 +338,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
signature = this->shstrtab.data() +
|
||||
this->elf_sections[get_shndx(esym)].sh_name;
|
||||
} else {
|
||||
signature = this->symbol_strtab.data() + esym.st_name;
|
||||
signature = this->symbol_names[shdr.sh_info];
|
||||
}
|
||||
|
||||
// Ignore a broken comdat group GCC emits for .debug_macros.
|
||||
@@ -449,6 +458,10 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_sections.push_back(isec);
|
||||
|
||||
if constexpr (supports_sframe<E>)
|
||||
if (name == ".sframe")
|
||||
sframe_sections.push_back(isec);
|
||||
|
||||
if (name == ".debug_info" && !(shdr.sh_flags & SHF_ALLOC))
|
||||
debug_info = isec;
|
||||
|
||||
@@ -624,6 +637,96 @@ void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the byte length of the SFrame FRE block at offset `offset`:
|
||||
// a 5-byte attribute header followed by a series of frame row
|
||||
// entries, each of which is a start address (whose width is given by
|
||||
// the attribute header), a one-byte info field and a number of
|
||||
// variable-width data words encoded in that info field.
|
||||
template <typename E>
|
||||
static i64 sframe_fre_block_size(std::string_view data, i64 offset) {
|
||||
const u8 *attr = (const u8 *)data.data() + offset;
|
||||
i64 num_fres = *(U16<E> *)attr;
|
||||
i64 addr_size = 1 << bits(attr[2], 3, 0);
|
||||
|
||||
i64 p = offset + 5;
|
||||
for (i64 i = 0; i < num_fres; i++) {
|
||||
u8 info = data[p + addr_size];
|
||||
i64 num_words = bits(info, 4, 1);
|
||||
i64 word_size = 1 << bits(info, 6, 5);
|
||||
p += addr_size + 1 + num_words * word_size;
|
||||
}
|
||||
return p - offset;
|
||||
}
|
||||
|
||||
// .sframe is a compact stack-unwinding format. Just like .eh_frame, the
|
||||
// linker has to understand its contents: an output .sframe section is a
|
||||
// single header followed by a PC-sorted index of function descriptor
|
||||
// entries (FDEs) and a blob of frame row entries (FREs). This function
|
||||
// parses an input .sframe section into individual FDEs so that we can
|
||||
// later drop the dead ones, sort the survivors and rewrite the header.
|
||||
//
|
||||
// Unlike .eh_frame, the variable-length FRE data carries no relocations,
|
||||
// so we copy it to the output verbatim. The only relocated field is each
|
||||
// FDE's func_start, which points to the function the FDE describes; we
|
||||
// use it both to find the function (for garbage collection and to obtain
|
||||
// its output address) and to re-emit a PC-relative offset.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sframe(Context<E> &ctx) requires supports_sframe<E> {
|
||||
for (InputSection<E> *isec : sframe_sections) {
|
||||
// The input section is consumed by the linker; it's not copied to the
|
||||
// output as-is but reconstructed into ctx.sframe.
|
||||
isec->is_alive = false;
|
||||
|
||||
std::string_view data = this->get_string(ctx, isec->shdr());
|
||||
const SFrameHeader<E> &hdr = *(const SFrameHeader<E> *)data.data();
|
||||
|
||||
if (hdr.magic != SFRAME_MAGIC)
|
||||
Fatal(ctx) << *isec << ": corrupted .sframe section";
|
||||
if (hdr.abi_arch != E::sframe_abi)
|
||||
Fatal(ctx) << *isec << ": .sframe is incompatible with " << E::name;
|
||||
|
||||
// We support only SFrame Version 3. A section written in an older
|
||||
// version (e.g. by an old assembler) isn't an error; we just ignore
|
||||
// it, so the functions it covers won't have SFrame info in the output.
|
||||
if (hdr.version != 3)
|
||||
continue;
|
||||
|
||||
i64 hdr_len = sizeof(SFrameHeader<E>) + hdr.auxhdr_len;
|
||||
i64 num_fdes = hdr.num_fdes;
|
||||
i64 fde_off = hdr_len + hdr.fdeoff;
|
||||
i64 fre_off = hdr_len + hdr.freoff;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
i64 rel_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < num_fdes; i++) {
|
||||
i64 idx_off = fde_off + i * sizeof(SFrameFdeIdx<E>);
|
||||
const SFrameFdeIdx<E> &ent =
|
||||
*(const SFrameFdeIdx<E> *)(data.data() + idx_off);
|
||||
|
||||
// Find the relocation for this FDE's func_start field. An FDE without
|
||||
// one isn't tied to any function (`ld -r` can emit such dead FDEs),
|
||||
// so we drop it.
|
||||
while (rel_idx < rels.size() && rels[rel_idx].r_offset < idx_off)
|
||||
rel_idx++;
|
||||
if (rel_idx == rels.size() || rels[rel_idx].r_offset != idx_off)
|
||||
continue;
|
||||
|
||||
const ElfRel<E> &rel = rels[rel_idx];
|
||||
i64 off = fre_off + ent.func_start_fre_off;
|
||||
|
||||
SFrameFde<E> fde;
|
||||
fde.isec = get_section(this->elf_syms[rel.r_sym]);
|
||||
fde.sym = this->symbols[rel.r_sym];
|
||||
fde.addend = rel.r_addend;
|
||||
fde.fre = data.substr(off, sframe_fre_block_size<E>(data, off));
|
||||
fde.func_size = ent.func_size;
|
||||
fde.num_fres = *(U16<E> *)(data.data() + off);
|
||||
sframe_fdes.push_back(fde);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
if (this->elf_syms.empty())
|
||||
@@ -646,7 +749,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
if (esym.st_type == STT_SECTION)
|
||||
name = this->shstrtab.data() + this->elf_sections[get_shndx(esym)].sh_name;
|
||||
else
|
||||
name = this->symbol_strtab.data() + esym.st_name;
|
||||
name = this->symbol_names[i];
|
||||
|
||||
Symbol<E> &sym = this->local_syms[i];
|
||||
sym.set_name(name);
|
||||
@@ -674,7 +777,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
has_common_symbol = true;
|
||||
|
||||
// Get a symbol name
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
std::string_view key = this->symbol_names[i];
|
||||
std::string_view name = key;
|
||||
|
||||
// Parse symbol version after atsign
|
||||
@@ -734,11 +837,21 @@ void ObjectFile<E>::convert_mergeable_sections(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
const ElfShdr<E> &shdr = isec->shdr();
|
||||
if (!(shdr.sh_flags & SHF_MERGE))
|
||||
if (!(shdr.sh_flags & SHF_MERGE) || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
// Under a SECTIONS-driven layout, sections matched by different
|
||||
// output section descriptions merge separately
|
||||
i64 cmd = -1;
|
||||
i32 rank = INT32_MAX;
|
||||
if (!this->script_match.empty())
|
||||
if (i32 r = this->script_match[i]; r != -1) {
|
||||
cmd = ctx.script_ranks[r].cmd;
|
||||
rank = r;
|
||||
}
|
||||
|
||||
MergedSection<E> *parent =
|
||||
MergedSection<E>::get_instance(ctx, isec->name(), shdr);
|
||||
MergedSection<E>::get_instance(ctx, isec->name, shdr, cmd, rank);
|
||||
|
||||
if (parent) {
|
||||
this->mergeable_sections[i] =
|
||||
@@ -889,6 +1002,7 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
this->first_global = symtab_sec->sh_info;
|
||||
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
this->populate_symbol_names();
|
||||
|
||||
if (ElfShdr<E> *shdr = this->find_section(SHT_SYMTAB_SHNDX))
|
||||
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, *shdr);
|
||||
@@ -1260,7 +1374,7 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
soname = get_soname(ctx);
|
||||
version_strings = read_verdef(ctx);
|
||||
version_strings = read_version_strings(ctx);
|
||||
|
||||
// Read a symbol table.
|
||||
std::span<ElfSym<E>> esyms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
|
||||
@@ -1270,20 +1384,28 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
vers = this->template get_data<U16<E>>(ctx, *sec);
|
||||
|
||||
for (i64 i = symtab_sec->sh_info; i < esyms.size(); i++) {
|
||||
u16 ver;
|
||||
if (vers.empty() || esyms[i].is_undef())
|
||||
ver = VER_NDX_GLOBAL;
|
||||
else
|
||||
ver = vers[i] & ~VERSYM_HIDDEN;
|
||||
u16 ver = vers.empty() ? VER_NDX_GLOBAL : (vers[i] & ~VERSYM_HIDDEN);
|
||||
|
||||
if (ver == VER_NDX_LOCAL)
|
||||
// A defined symbol with VER_NDX_LOCAL is bound locally and isn't
|
||||
// really exposed for dynamic linking; skip it. We never skip
|
||||
// undefined references — the dynamic loader doesn't distinguish
|
||||
// local vs global on the undef side, and dropping them would hide
|
||||
// the DSO's reference from us.
|
||||
if (ver == VER_NDX_LOCAL && !esyms[i].is_undef())
|
||||
continue;
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
this->versyms.push_back(ver);
|
||||
|
||||
// resolve_symbols only consults versyms[] for defined symbols
|
||||
// (see SharedFile::resolve_symbols), so VER_NDX_GLOBAL is fine
|
||||
// for undefined entries.
|
||||
this->versyms.push_back(esyms[i].is_undef() ? (u16)VER_NDX_GLOBAL : ver);
|
||||
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
|
||||
bool has_version = ver != VER_NDX_GLOBAL && ver < version_strings.size() &&
|
||||
!version_strings[ver].empty();
|
||||
|
||||
auto get_versioned_sym = [&] {
|
||||
std::string_view key = save_string(
|
||||
ctx, std::string(name) + "@" + std::string(version_strings[ver]));
|
||||
@@ -1304,12 +1426,13 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
// and `foo@VERSION` as usual, but with information to forward
|
||||
// references to `foo@VERSION` to `foo`. After name resolution, we
|
||||
// visit all symbol references to redirect `foo@VERSION` to `foo`.
|
||||
if (vers.empty() || ver == VER_NDX_GLOBAL) {
|
||||
if (!has_version) {
|
||||
// Unversioned symbol
|
||||
this->symbols.push_back(get_symbol(ctx, name));
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else if (vers[i] & VERSYM_HIDDEN) {
|
||||
// Versioned non-default symbol
|
||||
} else if (esyms[i].is_undef() || (vers[i] & VERSYM_HIDDEN)) {
|
||||
// Versioned non-default symbol, or undefined reference whose
|
||||
// version comes from .gnu.version_r.
|
||||
this->symbols.push_back(get_versioned_sym());
|
||||
this->symbols2.push_back(nullptr);
|
||||
} else {
|
||||
@@ -1375,32 +1498,61 @@ std::string_view SharedFile<E>::get_dt_audit(Context<E> &ctx) {
|
||||
// If an undefiend symbol `foo` is resolved to a symbol defined by the
|
||||
// shared object, it's marked so that it'll be resolved to (`foo`, the
|
||||
// default version of the library) at load-time.
|
||||
//
|
||||
// Reads .gnu.version_d and .gnu.version_r and returns a vector of
|
||||
// version names indexed by versym value. The versym index space is
|
||||
// shared between the two sections (vd_ndx for defined symbols and
|
||||
// vna_other for undefined ones), so a single vector covers both.
|
||||
template <typename E>
|
||||
std::vector<std::string_view> SharedFile<E>::read_verdef(Context<E> &ctx) {
|
||||
ElfShdr<E> *verdef_sec = this->find_section(SHT_GNU_VERDEF);
|
||||
if (!verdef_sec)
|
||||
return {};
|
||||
|
||||
std::string_view verdef = this->get_string(ctx, *verdef_sec);
|
||||
std::string_view strtab = this->get_string(ctx, verdef_sec->sh_link);
|
||||
|
||||
std::vector<std::string_view>
|
||||
SharedFile<E>::read_version_strings(Context<E> &ctx) {
|
||||
std::vector<std::string_view> vec;
|
||||
u8 *ptr = (u8 *)verdef.data();
|
||||
|
||||
for (;;) {
|
||||
ElfVerdef<E> *ver = (ElfVerdef<E> *)ptr;
|
||||
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
|
||||
Fatal(ctx) << *this << ": symbol version too large";
|
||||
if (ElfShdr<E> *sec = this->find_section(SHT_GNU_VERDEF)) {
|
||||
std::string_view verdef = this->get_string(ctx, *sec);
|
||||
std::string_view strtab = this->get_string(ctx, sec->sh_link);
|
||||
u8 *ptr = (u8 *)verdef.data();
|
||||
|
||||
if (vec.size() <= ver->vd_ndx)
|
||||
vec.resize(ver->vd_ndx + 1);
|
||||
for (;;) {
|
||||
ElfVerdef<E> *ver = (ElfVerdef<E> *)ptr;
|
||||
if (ver->vd_ndx == VER_NDX_UNSPECIFIED)
|
||||
Fatal(ctx) << *this << ": symbol version too large";
|
||||
|
||||
ElfVerdaux<E> *aux = (ElfVerdaux<E> *)(ptr + ver->vd_aux);
|
||||
vec[ver->vd_ndx] = strtab.data() + aux->vda_name;
|
||||
if (!ver->vd_next)
|
||||
break;
|
||||
ptr += ver->vd_next;
|
||||
if (vec.size() <= ver->vd_ndx)
|
||||
vec.resize(ver->vd_ndx + 1);
|
||||
|
||||
ElfVerdaux<E> *aux = (ElfVerdaux<E> *)(ptr + ver->vd_aux);
|
||||
vec[ver->vd_ndx] = strtab.data() + aux->vda_name;
|
||||
if (!ver->vd_next)
|
||||
break;
|
||||
ptr += ver->vd_next;
|
||||
}
|
||||
}
|
||||
|
||||
if (ElfShdr<E> *sec = this->find_section(SHT_GNU_VERNEED)) {
|
||||
std::string_view verneed = this->get_string(ctx, *sec);
|
||||
std::string_view strtab = this->get_string(ctx, sec->sh_link);
|
||||
u8 *ptr = (u8 *)verneed.data();
|
||||
|
||||
for (;;) {
|
||||
ElfVerneed<E> *vn = (ElfVerneed<E> *)ptr;
|
||||
u8 *aux_ptr = ptr + vn->vn_aux;
|
||||
for (i64 i = 0; i < vn->vn_cnt; i++) {
|
||||
ElfVernaux<E> *aux = (ElfVernaux<E> *)aux_ptr;
|
||||
u16 idx = aux->vna_other & ~VERSYM_HIDDEN;
|
||||
if (vec.size() <= idx)
|
||||
vec.resize(idx + 1);
|
||||
vec[idx] = strtab.data() + aux->vna_name;
|
||||
if (!aux->vna_next)
|
||||
break;
|
||||
aux_ptr += aux->vna_next;
|
||||
}
|
||||
if (!vn->vn_next)
|
||||
break;
|
||||
ptr += vn->vn_next;
|
||||
}
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,12 @@ bool cie_equals(const CieRecord<E> &a, const CieRecord<E> &b) {
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
if (shndx < file.elf_sections.size()) {
|
||||
name = file.shstrtab.data() + file.elf_sections[shndx].sh_name;
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
} else {
|
||||
name = (shdr().sh_flags & SHF_TLS) ? ".tls_common" : ".common";
|
||||
}
|
||||
|
||||
if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
|
||||
+2593
-178
File diff suppressed because it is too large.
Load diff
@@ -675,6 +675,7 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
|
||||
obj->symbol_strtab = save_string(ctx, strtab);
|
||||
obj->elf_syms = obj->lto_elf_syms;
|
||||
obj->populate_symbol_names();
|
||||
obj->initialize_symbols(ctx);
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
|
||||
+50
-11
@@ -2,6 +2,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -32,6 +33,7 @@ static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
assert(rctx.tg);
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
Out(ctx) << "trace: " << *file;
|
||||
@@ -71,6 +73,7 @@ new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
file->priority = ctx.file_priority++;
|
||||
file->as_needed = rctx.as_needed;
|
||||
|
||||
assert(rctx.tg);
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
Out(ctx) << "trace: " << *file;
|
||||
@@ -108,7 +111,7 @@ void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
}
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
Script(ctx, rctx, mf).parse_linker_script();
|
||||
Script(ctx, mf, &rctx).parse_linker_script();
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
@@ -146,7 +149,7 @@ detect_machine_type(Context<E> &ctx, std::vector<std::string> args) {
|
||||
if (MappedFile *mf = open_file(ctx, arg))
|
||||
if (get_file_type(ctx, mf) == FileType::TEXT)
|
||||
if (std::string_view target =
|
||||
Script(ctx, rctx, mf).get_script_output_type();
|
||||
Script(ctx, mf, &rctx).get_script_output_type();
|
||||
!target.empty())
|
||||
return target;
|
||||
}
|
||||
@@ -282,6 +285,12 @@ int mold_main(int argc, char **argv) {
|
||||
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv))
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
|
||||
// parse_nonpositional_args() may chdir(2) for -C. If we end up
|
||||
// restarting in redo_main(), we need to re-enter from the original
|
||||
// directory so relative paths (e.g. response files) still resolve.
|
||||
std::error_code ec;
|
||||
std::filesystem::path orig_cwd = std::filesystem::current_path(ec);
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
@@ -291,8 +300,11 @@ int mold_main(int argc, char **argv) {
|
||||
ctx.arg.emulation = detect_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m does not match with our speculation.
|
||||
if (ctx.arg.emulation != E::name)
|
||||
if (ctx.arg.emulation != E::name) {
|
||||
if (!ec)
|
||||
std::filesystem::current_path(orig_cwd, ec);
|
||||
return redo_main<E>(ctx.arg.emulation, argc, argv);
|
||||
}
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
@@ -322,6 +334,13 @@ int mold_main(int argc, char **argv) {
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
// The entry point is given by -e, by an ENTRY command in a linker
|
||||
// script, or defaults to _start
|
||||
if (!ctx.arg.entry)
|
||||
ctx.arg.entry = get_symbol(ctx, "_start");
|
||||
ctx.arg.entry->gc_root = true;
|
||||
ctx.arg.undefined.push_back(ctx.arg.entry);
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
@@ -358,9 +377,19 @@ int mold_main(int argc, char **argv) {
|
||||
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
std::erase_if(ctx.dsos, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
|
||||
// Remove input sections that match a /DISCARD/ pattern and
|
||||
// compute, for each input section, the output section description
|
||||
// it matches. This runs before mergeable sections are split
|
||||
// because the patterns match the original input sections.
|
||||
apply_script_discards(ctx);
|
||||
match_script_sections(ctx);
|
||||
|
||||
// Parse .eh_frame section contents.
|
||||
parse_eh_frame_sections(ctx);
|
||||
|
||||
// Parse .sframe section contents.
|
||||
parse_sframe_sections(ctx);
|
||||
|
||||
// Split mergeable section contents into section pieces.
|
||||
create_merged_sections(ctx);
|
||||
|
||||
@@ -375,6 +404,9 @@ int mold_main(int argc, char **argv) {
|
||||
// Create .bss sections for common symbols.
|
||||
convert_common_symbols(ctx);
|
||||
|
||||
// Define symbols PROVIDE'd by linker scripts.
|
||||
add_provided_symbols(ctx);
|
||||
|
||||
// Apply version scripts.
|
||||
apply_version_script(ctx);
|
||||
|
||||
@@ -507,12 +539,6 @@ int mold_main(int argc, char **argv) {
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// If --packed_dyn_relocs=relr was given, base relocations are stored
|
||||
// to a .relr.dyn section in a compressed form. Construct a compressed
|
||||
// relocations now so that we can fix section sizes and file layout.
|
||||
if (ctx.arg.pack_dyn_relocs_relr)
|
||||
construct_relr(ctx);
|
||||
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
@@ -539,6 +565,11 @@ int mold_main(int argc, char **argv) {
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// .sframe is likewise parsed and reconstructed by the linker. Build
|
||||
// the merged, PC-sorted output .sframe.
|
||||
if constexpr (supports_sframe<E>)
|
||||
ctx.sframe->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
@@ -593,12 +624,20 @@ int mold_main(int argc, char **argv) {
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// At this point, both memory and file layouts are fixed.
|
||||
|
||||
// Gather thunk symbols and attach them to themselves.
|
||||
if constexpr (needs_thunk<E>)
|
||||
gather_thunk_addresses(ctx);
|
||||
|
||||
// Re-finalize layout. fix_synthetic_symbols above may have changed
|
||||
// addends for dynamic relocations referencing synthetic symbols, which
|
||||
// can shift the encoded size of .rela.dyn under --pack-dyn-relocs=android.
|
||||
// For other modes this is a cheap no-op convergence.
|
||||
filesize = set_osec_offsets(ctx);
|
||||
|
||||
// At this point, both memory and file layouts are fixed.
|
||||
|
||||
ctx.reldyn->update_shdr(ctx);
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
|
||||
+443
-73
@@ -25,7 +25,9 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -469,6 +471,21 @@ struct FdeRecord {
|
||||
Atomic<bool> is_alive = true;
|
||||
};
|
||||
|
||||
// Represents a single function descriptor entry (FDE) read from an input
|
||||
// .sframe section. Unlike .eh_frame, the variable-length Frame Row Entry
|
||||
// (FRE) data carries no relocations, so it can be copied to the output
|
||||
// verbatim. Only the FDE's func_start field is relocated, which we
|
||||
// resolve here and re-emit as a PC-relative offset.
|
||||
template <typename E>
|
||||
struct SFrameFde {
|
||||
InputSection<E> *isec = nullptr; // the function's input section (liveness)
|
||||
Symbol<E> *sym = nullptr; // symbol the func_start reloc points to
|
||||
i64 addend = 0; // addend of the func_start reloc
|
||||
std::string_view fre; // the FRE block (attr + FREs), copied as-is
|
||||
u32 func_size = 0;
|
||||
u32 num_fres = 0;
|
||||
};
|
||||
|
||||
// A struct to hold target-dependent input section members.
|
||||
template <typename E>
|
||||
struct InputSectionExtras {};
|
||||
@@ -512,7 +529,6 @@ public:
|
||||
void apply_reloc_nonalloc(Context<E> &ctx, u8 *base);
|
||||
void kill();
|
||||
|
||||
std::string_view name() const;
|
||||
i64 get_priority() const;
|
||||
u64 get_addr() const;
|
||||
ElfShdr<E> &shdr() const;
|
||||
@@ -531,6 +547,7 @@ public:
|
||||
OutputSection<E> *output_section = nullptr;
|
||||
i64 sh_size = -1;
|
||||
|
||||
std::string_view name;
|
||||
std::string_view contents;
|
||||
|
||||
i32 fde_begin = -1;
|
||||
@@ -539,13 +556,12 @@ public:
|
||||
i64 offset = -1;
|
||||
i32 shndx = -1;
|
||||
i32 relsec_idx = -1;
|
||||
i32 reldyn_offset = 0;
|
||||
|
||||
bool uncompressed = false;
|
||||
|
||||
// For COMDAT de-duplication and garbage collection
|
||||
Atomic<bool> is_alive = true;
|
||||
u8 p2align = 0;
|
||||
Atomic<u8> p2align = 0;
|
||||
|
||||
// For ICF
|
||||
Atomic<bool> address_taken = false;
|
||||
@@ -647,12 +663,13 @@ public:
|
||||
virtual bool is_header() { return false; }
|
||||
virtual OutputSection<E> *to_osec() { return nullptr; }
|
||||
virtual void compute_section_size(Context<E> &ctx) {}
|
||||
virtual i64 get_reldyn_size(Context<E> &ctx) const { return 0; }
|
||||
virtual void construct_relr(Context<E> &ctx) {}
|
||||
virtual void copy_buf(Context<E> &ctx) {}
|
||||
virtual void write_to(Context<E> &ctx, u8 *buf) { unreachable(); }
|
||||
virtual void update_shdr(Context<E> &ctx) {}
|
||||
|
||||
virtual std::vector<ElfRel<E>>
|
||||
collect_dynrels(Context<E> &ctx) const { return {}; }
|
||||
|
||||
std::string_view name;
|
||||
ElfShdr<E> shdr = { .sh_addralign = 1 };
|
||||
i64 shndx = 0;
|
||||
@@ -673,14 +690,16 @@ public:
|
||||
i64 strtab_size = 0;
|
||||
i64 strtab_offset = 0;
|
||||
|
||||
// Offset in .rel.dyn
|
||||
i64 reldyn_offset = 0;
|
||||
|
||||
// For --section-order
|
||||
// The chunk's position in a SECTIONS-driven layout: 2n for the
|
||||
// chunk described by the n'th script command, 2n+1 for an orphan
|
||||
// placed after it
|
||||
i64 sect_order = 0;
|
||||
|
||||
// For --pack-dyn-relocs=relr
|
||||
std::vector<u64> relr;
|
||||
// For linker scripts: the index of the output section description
|
||||
// in Context<E>::script_sections this chunk was created for, and
|
||||
// the load address (LMA) assigned by the script.
|
||||
i32 script_cmd = -1;
|
||||
u64 lma = 0;
|
||||
};
|
||||
|
||||
// ELF header which is at the beginning of each ELF file.
|
||||
@@ -756,7 +775,6 @@ public:
|
||||
enum AbsRelKind {
|
||||
ABS_REL_NONE,
|
||||
ABS_REL_BASEREL,
|
||||
ABS_REL_RELR,
|
||||
ABS_REL_IFUNC,
|
||||
ABS_REL_DYNREL,
|
||||
};
|
||||
@@ -773,6 +791,14 @@ struct AbsRel {
|
||||
|
||||
// OutputSection represents the usual output section that contains input
|
||||
// sections read from object files.
|
||||
// A linker script data command's (e.g. `LONG(0xdeadbeef)`)
|
||||
// contribution to an output section
|
||||
struct ScriptData {
|
||||
u64 offset = 0;
|
||||
i64 size = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSection : public Chunk<E> {
|
||||
public:
|
||||
@@ -783,8 +809,7 @@ public:
|
||||
|
||||
OutputSection<E> *to_osec() override { return this; }
|
||||
void compute_section_size(Context<E> &ctx) override;
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override;
|
||||
void construct_relr(Context<E> &ctx) override;
|
||||
std::vector<ElfRel<E>> collect_dynrels(Context<E> &ctx) const override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
void write_to(Context<E> &ctx, u8 *buf) override;
|
||||
|
||||
@@ -795,11 +820,20 @@ public:
|
||||
void create_range_extension_thunks(Context<E> &ctx);
|
||||
|
||||
std::vector<InputSection<E> *> members;
|
||||
std::vector<MergedSection<E> *> merged_members;
|
||||
std::vector<std::unique_ptr<Thunk<E>>> thunks;
|
||||
std::unique_ptr<RelocSection<E>> reloc_sec;
|
||||
std::vector<AbsRel<E>> abs_rels;
|
||||
Atomic<u32> sh_flags;
|
||||
|
||||
// The fill pattern for gaps between members, e.g. `=0xcccccccc` in
|
||||
// a linker script. If absent, executable sections are filled with
|
||||
// trap instructions and other sections with zeros.
|
||||
std::optional<u32> fill;
|
||||
|
||||
// Data inserted by linker script BYTE/SHORT/LONG/QUAD commands
|
||||
std::vector<ScriptData> script_data;
|
||||
|
||||
// Used only by create_output_sections()
|
||||
std::vector<std::vector<InputSection<E> *>> members_vec;
|
||||
};
|
||||
@@ -831,10 +865,9 @@ public:
|
||||
|
||||
u64 get_tlsld_addr(Context<E> &ctx) const;
|
||||
bool has_tlsld(Context<E> &ctx) const { return tlsld_idx != -1; }
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override;
|
||||
std::vector<ElfRel<E>> collect_dynrels(Context<E> &ctx) const override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
void construct_relr(Context<E> &ctx) override;
|
||||
void compute_symtab_size(Context<E> &ctx) override;
|
||||
void populate_symtab(Context<E> &ctx) override;
|
||||
|
||||
@@ -938,15 +971,27 @@ public:
|
||||
template <typename E>
|
||||
class RelDynSection : public Chunk<E> {
|
||||
public:
|
||||
RelDynSection() {
|
||||
RelDynSection(Context<E> &ctx) {
|
||||
this->name = E::is_rela ? ".rela.dyn" : ".rel.dyn";
|
||||
this->shdr.sh_type = E::is_rela ? SHT_RELA : SHT_REL;
|
||||
this->shdr.sh_flags = SHF_ALLOC;
|
||||
this->shdr.sh_entsize = sizeof(ElfRel<E>);
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
|
||||
if (ctx.arg.pack_dyn_relocs_android) {
|
||||
this->shdr.sh_type = E::is_rela ? SHT_ANDROID_RELA : SHT_ANDROID_REL;
|
||||
this->shdr.sh_entsize = 0;
|
||||
this->shdr.sh_addralign = 1;
|
||||
} else {
|
||||
this->shdr.sh_type = E::is_rela ? SHT_RELA : SHT_REL;
|
||||
this->shdr.sh_entsize = sizeof(ElfRel<E>);
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
}
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<ElfRel<E>> relocs;
|
||||
std::vector<u8> android_encoded;
|
||||
bool keep_android_size = false;
|
||||
};
|
||||
|
||||
// .relr.dyn is a relatively new section to contain base relocation
|
||||
@@ -985,16 +1030,17 @@ public:
|
||||
template <typename E>
|
||||
class RelrDynSection : public Chunk<E> {
|
||||
public:
|
||||
RelrDynSection() {
|
||||
RelrDynSection(Context<E> &ctx) {
|
||||
this->name = ".relr.dyn";
|
||||
this->shdr.sh_type = SHT_RELR;
|
||||
this->shdr.sh_type = ctx.arg.use_android_relr_tags ? SHT_ANDROID_RELR : SHT_RELR;
|
||||
this->shdr.sh_flags = SHF_ALLOC;
|
||||
this->shdr.sh_entsize = sizeof(Word<E>);
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<u64> relocs;
|
||||
};
|
||||
|
||||
// .strtab is referenced by .strtab and contains symbol names. Note that
|
||||
@@ -1200,7 +1246,8 @@ template <typename E>
|
||||
class MergedSection : public Chunk<E> {
|
||||
public:
|
||||
static MergedSection<E> *
|
||||
get_instance(Context<E> &ctx, std::string_view name, const ElfShdr<E> &shdr);
|
||||
get_instance(Context<E> &ctx, std::string_view name, const ElfShdr<E> &shdr,
|
||||
i64 script_cmd = -1, i32 script_rank = INT32_MAX);
|
||||
|
||||
SectionFragment<E> *insert(Context<E> &ctx, std::string_view data,
|
||||
u64 hash, i64 p2align);
|
||||
@@ -1214,6 +1261,12 @@ public:
|
||||
std::vector<MergeableSection<E> *> members;
|
||||
std::mutex mu;
|
||||
|
||||
// Under a SECTIONS-driven layout, a merged section may belong to an
|
||||
// output section described by the script; it is then laid out and
|
||||
// written as part of it.
|
||||
OutputSection<E> *script_owner = nullptr;
|
||||
std::atomic_int32_t script_rank = INT32_MAX;
|
||||
|
||||
ConcurrentMap<SectionFragment<E>> map;
|
||||
HyperLogLog estimator;
|
||||
bool resolved = false;
|
||||
@@ -1285,6 +1338,48 @@ public:
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
// .sframe is a compact stack-unwinding format. Like .eh_frame, the linker
|
||||
// has to parse and reconstruct it: the output section is a single sorted
|
||||
// index of FDEs (one per live function) followed by their FREs, so we
|
||||
// gather the live FDEs from all input files, drop dead ones, concatenate
|
||||
// their FREs, sort the index by PC and rewrite the header. mold reads and
|
||||
// writes SFrame Version 3.
|
||||
template <typename E>
|
||||
class SFrameSection : public Chunk<E> {
|
||||
public:
|
||||
SFrameSection() {
|
||||
this->name = ".sframe";
|
||||
this->shdr.sh_type = SHT_GNU_SFRAME;
|
||||
this->shdr.sh_flags = SHF_ALLOC;
|
||||
this->shdr.sh_addralign = 8;
|
||||
}
|
||||
|
||||
void construct(Context<E> &ctx) requires supports_sframe<E>;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
SFrameHeader<E> hdr = {};
|
||||
std::vector<SFrameFde<E> *> fdes;
|
||||
};
|
||||
|
||||
// SFrameRelocSection holds the relocations for .sframe. We use it only for
|
||||
// relocatable outputs, where function addresses aren't known yet and each
|
||||
// FDE's func_start has to remain a relocation for the final link to
|
||||
// resolve. It is the .sframe counterpart of EhFrameRelocSection.
|
||||
template <typename E>
|
||||
class SFrameRelocSection : public Chunk<E> {
|
||||
public:
|
||||
SFrameRelocSection() {
|
||||
this->name = ".rela.sframe";
|
||||
this->shdr.sh_type = SHT_RELA;
|
||||
this->shdr.sh_flags = SHF_INFO_LINK;
|
||||
this->shdr.sh_addralign = sizeof(Word<E>);
|
||||
this->shdr.sh_entsize = sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
void update_shdr(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
// .copyrel and .copyrel.rel.ro represent memory regions to which the
|
||||
// runtime copies symbols from other ELF files for copy relocations.
|
||||
template <typename E>
|
||||
@@ -1298,8 +1393,7 @@ public:
|
||||
}
|
||||
|
||||
void add_symbol(Context<E> &ctx, Symbol<E> *sym);
|
||||
i64 get_reldyn_size(Context<E> &ctx) const override { return symbols.size(); }
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
std::vector<ElfRel<E>> collect_dynrels(Context<E> &ctx) const override;
|
||||
|
||||
std::vector<Symbol<E> *> symbols;
|
||||
};
|
||||
@@ -1709,6 +1803,11 @@ public:
|
||||
std::string_view shstrtab;
|
||||
std::string_view symbol_strtab;
|
||||
|
||||
// Parallel to elf_syms; cached to avoid per-call strlen.
|
||||
std::vector<std::string_view> symbol_names;
|
||||
|
||||
void populate_symbol_names();
|
||||
|
||||
bool as_needed = false;
|
||||
bool has_init_array = false;
|
||||
bool has_ctors = false;
|
||||
@@ -1756,6 +1855,7 @@ public:
|
||||
void parse(Context<E> &ctx);
|
||||
void initialize_symbols(Context<E> &ctx);
|
||||
void parse_ehframe(Context<E> &ctx);
|
||||
void parse_sframe(Context<E> &ctx) requires supports_sframe<E>;
|
||||
void convert_mergeable_sections(Context<E> &ctx);
|
||||
void reattach_section_pieces(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx) override;
|
||||
@@ -1772,6 +1872,12 @@ public:
|
||||
|
||||
std::string archive_name;
|
||||
std::vector<std::unique_ptr<InputSection<E>>> sections;
|
||||
|
||||
// Per-section linker script match ranks, indexed by section index.
|
||||
// -1 means the section is not matched by any SECTIONS pattern.
|
||||
// Empty unless a linker script has a SECTIONS command.
|
||||
std::vector<i32> script_match;
|
||||
|
||||
std::vector<std::unique_ptr<MergeableSection<E>>> mergeable_sections;
|
||||
std::vector<ElfShdr<E>> elf_sections2;
|
||||
std::vector<CieRecord<E>> cies;
|
||||
@@ -1779,6 +1885,8 @@ public:
|
||||
std::vector<bool> has_symver;
|
||||
std::vector<ComdatGroupRef<E>> comdat_groups;
|
||||
std::vector<InputSection<E> *> eh_frame_sections;
|
||||
std::vector<InputSection<E> *> sframe_sections;
|
||||
std::vector<SFrameFde<E>> sframe_fdes;
|
||||
std::vector<std::vector<ElfRel<E>>> decoded_crel;
|
||||
bool exclude_libs = false;
|
||||
std::map<u32, u32> gnu_properties;
|
||||
@@ -1852,7 +1960,7 @@ private:
|
||||
std::string get_soname(Context<E> &ctx);
|
||||
void maybe_override_symbol(Symbol<E> &sym, const ElfSym<E> &esym);
|
||||
std::vector<std::string_view> read_dt_needed(Context<E> &ctx);
|
||||
std::vector<std::string_view> read_verdef(Context<E> &ctx);
|
||||
std::vector<std::string_view> read_version_strings(Context<E> &ctx);
|
||||
|
||||
std::vector<u16> versyms;
|
||||
const ElfShdr<E> *symtab_sec;
|
||||
@@ -1880,11 +1988,169 @@ struct DynamicPattern {
|
||||
bool is_cpp = false;
|
||||
};
|
||||
|
||||
// ScriptExpr represents an expression in the linker script language.
|
||||
// For simplicity, we use a single type for all kinds of expressions
|
||||
// instead of a class hierarchy.
|
||||
struct ScriptExpr {
|
||||
enum Kind { INT, NAME, DOT, UNARY, BINARY, TERNARY, FUNC };
|
||||
Kind kind = INT;
|
||||
u64 value = 0; // integer literal value
|
||||
std::string_view str; // name, operator or function name
|
||||
std::vector<ScriptExpr> args; // operands
|
||||
};
|
||||
|
||||
// A sort specifier for input section patterns. REVERSE composes with
|
||||
// the others, so a pattern holds a list of these, outermost first.
|
||||
enum class ScriptSort : u8 { NONE, NAME, ALIGNMENT, INIT_PRIORITY, REVERSE };
|
||||
|
||||
// A pattern group in an input section description. For example,
|
||||
// `*(SORT_BY_NAME(.ctors.*))` contains one group whose `sorts` is
|
||||
// {NAME} and whose `pats` is {".ctors.*"}.
|
||||
struct ScriptPattern {
|
||||
std::vector<ScriptSort> sorts;
|
||||
std::vector<std::string_view> exclude_files; // EXCLUDE_FILE(...)
|
||||
std::vector<std::string_view> pats;
|
||||
};
|
||||
|
||||
// A memory region defined in a MEMORY { ... } command
|
||||
struct ScriptRegion {
|
||||
std::string_view loc; // command's first token for error reporting
|
||||
std::string_view name;
|
||||
std::string_view attrs; // e.g. "rwx" in `ram (rwx) : ...`
|
||||
ScriptExpr origin;
|
||||
ScriptExpr length;
|
||||
i64 alias_of = -1; // for REGION_ALIAS
|
||||
};
|
||||
|
||||
// A program header defined in a PHDRS { ... } command
|
||||
struct ScriptPhdr {
|
||||
std::string_view loc;
|
||||
std::string_view name;
|
||||
std::string_view type; // e.g. "PT_LOAD"
|
||||
bool filehdr = false;
|
||||
bool phdrs = false;
|
||||
std::optional<ScriptExpr> at;
|
||||
std::optional<ScriptExpr> flags;
|
||||
};
|
||||
|
||||
// ScriptCmd represents a single linker script command. Commands form
|
||||
// a tree; SECTIONS, OVERLAY and output section descriptions contain
|
||||
// subcommands in `cmds`. As with ScriptExpr, we use a single type for
|
||||
// all kinds of commands. Each kind uses only a subset of the fields.
|
||||
struct ScriptCmd {
|
||||
enum Kind {
|
||||
SECTIONS, // SECTIONS { <cmds> }
|
||||
OUTPUT_SECTION, // <name> <addr> (<type>) : { <cmds> } <region> ...
|
||||
OVERLAY, // OVERLAY <addr> : { <cmds> } <region> ...
|
||||
INPUT_SECTION, // KEEP(<file_pat>(<pats>))
|
||||
ASSIGNMENT, // <name> <op> <value>;
|
||||
DATA, // BYTE(<value>), SHORT, LONG, QUAD or SQUAD
|
||||
FILL, // FILL(<value>)
|
||||
ASCIZ, // ASCIZ "<msg>"
|
||||
CONSTRUCTORS, // CONSTRUCTORS
|
||||
ASSERT, // ASSERT(<value>, "<msg>")
|
||||
ENTRY, // ENTRY(<name>)
|
||||
MEMORY, // MEMORY { <memory> }
|
||||
PHDRS, // PHDRS { <phdrs> }
|
||||
INSERT, // INSERT BEFORE|AFTER <name>
|
||||
REGION_ALIAS, // REGION_ALIAS(<name2>, <name>)
|
||||
OTHER, // an unsupported command such as NOCROSSREFS
|
||||
};
|
||||
|
||||
Kind kind;
|
||||
std::string_view loc; // command's first token for error reporting
|
||||
std::string_view name;
|
||||
std::vector<ScriptCmd> cmds;
|
||||
|
||||
// For ASSIGNMENT, an index into Context<E>::script_assignments.
|
||||
// For INPUT_SECTION, the first match rank (see ScriptMatchRank).
|
||||
i32 aux = -1;
|
||||
|
||||
// For ASSIGNMENT
|
||||
std::string_view op; // "=", "+=", etc.
|
||||
bool provide = false;
|
||||
bool hidden = false;
|
||||
|
||||
std::optional<ScriptExpr> value; // assignment RHS, data value or condition
|
||||
std::string_view msg; // ASSERT or ASCIZ string
|
||||
i64 data_size = 0; // 1, 2, 4 or 8 for DATA
|
||||
|
||||
// For INPUT_SECTION
|
||||
bool keep = false; // KEEP(...)
|
||||
std::string_view file_pat;
|
||||
std::vector<ScriptSort> file_sorts;
|
||||
std::vector<ScriptPattern> pats;
|
||||
std::vector<std::string_view> exclude_files; // EXCLUDE_FILE outside parens
|
||||
std::vector<std::string_view> names; // INPUT_SECTION_FLAGS or OTHER args
|
||||
|
||||
// For OUTPUT_SECTION and OVERLAY
|
||||
std::optional<ScriptExpr> addr;
|
||||
std::string_view type; // e.g. "NOLOAD"
|
||||
std::optional<ScriptExpr> at; // AT(...)
|
||||
std::optional<ScriptExpr> align; // ALIGN(...)
|
||||
bool align_with_input = false;
|
||||
std::optional<ScriptExpr> subalign; // SUBALIGN(...)
|
||||
std::string_view constraint; // ONLY_IF_RO or ONLY_IF_RW
|
||||
std::string_view region; // >region
|
||||
std::string_view lma_region; // AT>region
|
||||
std::vector<std::string_view> phdr_refs; // :phdr
|
||||
std::optional<ScriptExpr> fill; // =<fill>
|
||||
|
||||
// For INSERT and REGION_ALIAS
|
||||
bool insert_before = false;
|
||||
std::string_view name2;
|
||||
|
||||
// For MEMORY and PHDRS
|
||||
std::vector<ScriptRegion> memory;
|
||||
std::vector<ScriptPhdr> phdrs;
|
||||
};
|
||||
|
||||
// A linker script symbol assignment such as `end = ALIGN(16);` whose
|
||||
// value can be computed only after the output file layout is fixed.
|
||||
// Until then, it is kept in Context<E>::script_assignments.
|
||||
template <typename E>
|
||||
struct ScriptAssignment {
|
||||
Symbol<E> *sym = nullptr;
|
||||
ScriptExpr value;
|
||||
bool provide = false;
|
||||
bool hidden = false;
|
||||
bool defined = false; // true if we are the symbol's definer
|
||||
MappedFile *mf = nullptr; // for error reporting
|
||||
std::string_view loc;
|
||||
|
||||
// True if the assignment appeared within SECTIONS. Such assignments
|
||||
// are executed by the layout pass, where the location counter is
|
||||
// live; the others are evaluated after the layout is fixed.
|
||||
bool in_sections = false;
|
||||
};
|
||||
|
||||
// The result of matching an input section against the section
|
||||
// patterns in SECTIONS. Each pattern group has a rank; an output
|
||||
// section's members are ordered by the rank of the pattern that
|
||||
// matched them, which mirrors the order patterns appear in the script.
|
||||
struct ScriptMatchRank {
|
||||
i32 cmd = -1; // index into Context<E>::script_sections
|
||||
bool keep = false;
|
||||
std::vector<ScriptSort> sorts;
|
||||
};
|
||||
|
||||
|
||||
// A linker script ASSERT command, evaluated after layout
|
||||
struct ScriptAssert {
|
||||
ScriptExpr cond;
|
||||
std::string_view msg;
|
||||
MappedFile *mf = nullptr;
|
||||
std::string_view loc;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class Script {
|
||||
public:
|
||||
Script(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf)
|
||||
: ctx(ctx), rctx(rctx), mf(mf) {}
|
||||
// rctx is the command line reader's state, which is needed only to
|
||||
// execute commands that read other input files, such as GROUP.
|
||||
// Version scripts and dynamic lists are parsed without one.
|
||||
Script(Context<E> &ctx, MappedFile *mf, ReaderContext *rctx = nullptr)
|
||||
: ctx(ctx), rctx(rctx), mf(mf), input(mf->get_contents()) {}
|
||||
|
||||
std::string_view get_script_output_type();
|
||||
void parse_linker_script();
|
||||
@@ -1892,40 +2158,111 @@ public:
|
||||
std::vector<DynamicPattern> parse_dynamic_list();
|
||||
|
||||
private:
|
||||
// The language is context-dependent; e.g. `*` is a wildcard in a
|
||||
// glob pattern but a multiplication operator in an expression, so
|
||||
// the parser switches the lexer's behavior with these modes.
|
||||
enum LexMode : u8 { LEX_PATH, LEX_GLOB, LEX_EXPR };
|
||||
|
||||
[[noreturn]] void error(std::string_view pos, std::string msg);
|
||||
|
||||
void tokenize();
|
||||
std::string_view lex_one();
|
||||
void set_lex_mode(LexMode mode);
|
||||
|
||||
std::span<std::string_view>
|
||||
skip(std::span<std::string_view> tok, std::string_view str);
|
||||
std::string_view peek(i64 n = 0);
|
||||
std::string_view next();
|
||||
bool at_eof();
|
||||
bool consume(std::string_view str);
|
||||
void skip(std::string_view str);
|
||||
bool consume_label(std::string label);
|
||||
bool consume_colon();
|
||||
|
||||
std::span<std::string_view> read_output_format(std::span<std::string_view> tok);
|
||||
std::span<std::string_view> read_group(std::span<std::string_view> tok);
|
||||
void read_command();
|
||||
void read_output_format();
|
||||
void read_group();
|
||||
void read_include();
|
||||
|
||||
std::span<std::string_view>
|
||||
read_version_script_commands(std::span<std::string_view> tok,
|
||||
std::string_view ver_str, u16 ver_idx,
|
||||
bool is_global, bool is_cpp);
|
||||
ScriptExpr read_expr();
|
||||
ScriptExpr read_expr1(ScriptExpr lhs, i64 min_prec);
|
||||
ScriptExpr read_primary();
|
||||
|
||||
std::span<std::string_view> read_version_script(std::span<std::string_view> tok);
|
||||
ScriptCmd read_assignment();
|
||||
ScriptCmd read_assert();
|
||||
ScriptCmd read_sections();
|
||||
ScriptCmd read_output_section();
|
||||
void read_output_section_body(ScriptCmd &cmd);
|
||||
void read_output_section_tail(ScriptCmd &cmd);
|
||||
ScriptCmd read_input_section();
|
||||
ScriptPattern read_pattern();
|
||||
ScriptCmd read_overlay();
|
||||
ScriptCmd read_memory();
|
||||
ScriptCmd read_phdrs();
|
||||
|
||||
void evaluate();
|
||||
void evaluate_command(ScriptCmd &cmd);
|
||||
void evaluate_sections(ScriptCmd &cmd);
|
||||
void register_assignment(ScriptCmd &cmd);
|
||||
|
||||
MappedFile *file_of(std::string_view pos);
|
||||
|
||||
void read_version_script();
|
||||
void read_version_script_commands(std::string_view ver_str, u16 ver_idx,
|
||||
bool is_global, bool is_cpp);
|
||||
|
||||
MappedFile *resolve_path(std::string_view tok, bool check_target);
|
||||
|
||||
std::span<std::string_view>
|
||||
read_dynamic_list_commands(std::span<std::string_view> tok,
|
||||
std::vector<DynamicPattern> &result, bool is_cpp);
|
||||
void read_dynamic_list_commands(std::vector<DynamicPattern> &result,
|
||||
bool is_cpp);
|
||||
|
||||
ReaderContext &reader();
|
||||
|
||||
Context<E> &ctx;
|
||||
ReaderContext &rctx;
|
||||
MappedFile *mf = mf;
|
||||
std::once_flag once;
|
||||
ReaderContext *rctx;
|
||||
MappedFile *mf;
|
||||
|
||||
// The unprocessed part of the script being parsed. Tokens are lexed
|
||||
// on demand because how text should be tokenized depends on context.
|
||||
std::string_view input;
|
||||
|
||||
// Tokens lexed so far. tokens[pos] is the next token to be read.
|
||||
std::vector<std::string_view> tokens;
|
||||
i64 pos = 0;
|
||||
LexMode lex_mode = LEX_PATH;
|
||||
|
||||
// Parsed commands
|
||||
std::vector<ScriptCmd> cmds;
|
||||
|
||||
// Files suspended by INCLUDE and all files read so far. The latter
|
||||
// is kept so that an error message for a token can be attributed to
|
||||
// the file the token came from.
|
||||
std::vector<std::pair<MappedFile *, std::string_view>> include_stack;
|
||||
std::vector<MappedFile *> files = {mf};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path);
|
||||
|
||||
template <typename E>
|
||||
void eval_script_commands(Context<E> &ctx);
|
||||
|
||||
template <typename E>
|
||||
void apply_script_discards(Context<E> &ctx);
|
||||
|
||||
template <typename E>
|
||||
void match_script_sections(Context<E> &ctx);
|
||||
|
||||
template <typename E>
|
||||
void match_synthetic_sections(Context<E> &ctx);
|
||||
|
||||
template <typename E>
|
||||
void script_finalize_section(Context<E> &ctx, OutputSection<E> &osec);
|
||||
|
||||
template <typename E>
|
||||
void set_virtual_addresses_by_script(Context<E> &ctx);
|
||||
|
||||
template <typename E>
|
||||
std::vector<ElfPhdr<E>> create_script_phdrs(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// archive-file.cc
|
||||
//
|
||||
@@ -1978,6 +2315,13 @@ template <typename E>
|
||||
i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel);
|
||||
|
||||
// With --emit-relocs, rewrites the relocation types in `buf` (the output
|
||||
// relocations for isec, one per input relocation) to match the instructions
|
||||
// that instruction relaxation produced. Defined only for targets that support
|
||||
// relaxation.
|
||||
template <typename E> requires is_riscv<E> || is_loongarch<E>
|
||||
void rewrite_reloc_types(Context<E> &ctx, InputSection<E> &isec, ElfRel<E> *buf);
|
||||
|
||||
//
|
||||
// gc-sections.cc
|
||||
//
|
||||
@@ -2042,9 +2386,11 @@ template <typename E> void set_file_priority(Context<E> &);
|
||||
template <typename E> void resolve_symbols(Context<E> &);
|
||||
template <typename E> void do_lto(Context<E> &);
|
||||
template <typename E> void parse_eh_frame_sections(Context<E> &);
|
||||
template <typename E> void parse_sframe_sections(Context<E> &);
|
||||
template <typename E> void create_merged_sections(Context<E> &);
|
||||
template <typename E> void convert_common_symbols(Context<E> &);
|
||||
template <typename E> void create_output_sections(Context<E> &);
|
||||
template <typename E> void add_provided_symbols(Context<E> &);
|
||||
template <typename E> void add_synthetic_symbols(Context<E> &);
|
||||
template <typename E> void apply_section_align(Context<E> &);
|
||||
template <typename E> void check_cet_errors(Context<E> &);
|
||||
@@ -2064,7 +2410,6 @@ template <typename E> void sort_output_sections(Context<E> &);
|
||||
template <typename E> void claim_unresolved_symbols(Context<E> &);
|
||||
template <typename E> void scan_relocations(Context<E> &);
|
||||
template <typename E> void compute_imported_symbol_weakness(Context<E> &);
|
||||
template <typename E> void construct_relr(Context<E> &);
|
||||
template <typename E> void sort_dynsyms(Context<E> &);
|
||||
template <typename E> void sort_debug_info_sections(Context<E> &);
|
||||
template <typename E> void create_output_symtab(Context<E> &);
|
||||
@@ -2164,7 +2509,7 @@ public:
|
||||
}
|
||||
|
||||
void add_symbol(Context<PPC64V1> &ctx, Symbol<PPC64V1> *sym);
|
||||
i64 get_reldyn_size(Context<PPC64V1> &ctx) const override;
|
||||
std::vector<ElfRel<PPC64V1>> collect_dynrels(Context<PPC64V1> &ctx) const override;
|
||||
void copy_buf(Context<PPC64V1> &ctx) override;
|
||||
|
||||
static constexpr i64 ENTRY_SIZE = sizeof(Word<PPC64V1>) * 3;
|
||||
@@ -2194,6 +2539,13 @@ public:
|
||||
|
||||
template <> u64 get_eflags(Context<PPC64V2> &ctx);
|
||||
|
||||
|
||||
//
|
||||
// arch-sparc64.cc
|
||||
//
|
||||
|
||||
i64 sparc_plt_ptr_offset(Context<SPARC64> &ctx, i64 pltidx);
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
@@ -2257,13 +2609,6 @@ struct VersionPattern {
|
||||
bool is_cpp = false;
|
||||
};
|
||||
|
||||
struct SectionOrder {
|
||||
enum { NONE, SECTION, GROUP, ADDR, ALIGN, SYMBOL } type = NONE;
|
||||
std::string name;
|
||||
u64 value = 0;
|
||||
std::string_view token; // for error reporting
|
||||
};
|
||||
|
||||
// Target-specific context members
|
||||
template <typename E>
|
||||
struct ContextExtras {};
|
||||
@@ -2313,7 +2658,6 @@ struct ContextExtras<SPARC64> {
|
||||
template <typename E>
|
||||
struct Context {
|
||||
Context() {
|
||||
arg.entry = get_symbol(*this, "_start");
|
||||
arg.fini = get_symbol(*this, "_fini");
|
||||
arg.init = get_symbol(*this, "_init");
|
||||
|
||||
@@ -2353,6 +2697,7 @@ struct Context {
|
||||
bool detach = true;
|
||||
bool discard_all = false;
|
||||
bool discard_locals = false;
|
||||
std::string_view orphan_handling = "place";
|
||||
bool dynamic_list_data = false;
|
||||
bool eh_frame_hdr = true;
|
||||
bool emit_relocs = false;
|
||||
@@ -2373,6 +2718,7 @@ struct Context {
|
||||
bool noinhibit_exec = false;
|
||||
bool oformat_binary = false;
|
||||
bool omagic = false;
|
||||
bool pack_dyn_relocs_android = false;
|
||||
bool pack_dyn_relocs_relr = false;
|
||||
bool perf = false;
|
||||
bool pic = false;
|
||||
@@ -2394,6 +2740,7 @@ struct Context {
|
||||
bool suppress_warnings = false;
|
||||
bool trace = false;
|
||||
bool undefined_version = false;
|
||||
bool use_android_relr_tags = false;
|
||||
bool warn_common = false;
|
||||
bool warn_once = false;
|
||||
bool warn_textrel = false;
|
||||
@@ -2420,13 +2767,13 @@ struct Context {
|
||||
bool z_text = false;
|
||||
bool zero_to_bss = false;
|
||||
i64 compress_debug_sections = ELFCOMPRESS_NONE;
|
||||
i64 compress_debug_sections_level = 0;
|
||||
i64 filler = -1;
|
||||
i64 spare_dynamic_tags = 5;
|
||||
i64 spare_program_headers = 0;
|
||||
i64 z_stack_size = 0;
|
||||
std::optional<i64> thread_count;
|
||||
std::optional<std::vector<Symbol<E> *>> retain_symbols_file;
|
||||
std::optional<u64> physical_image_base;
|
||||
std::string Map;
|
||||
std::string audit;
|
||||
std::string chroot;
|
||||
@@ -2452,7 +2799,6 @@ struct Context {
|
||||
std::unordered_set<std::string_view> exclude_libs;
|
||||
std::unordered_set<std::string_view> ignore_ir_file;
|
||||
std::unordered_set<std::string_view> wrap;
|
||||
std::vector<SectionOrder> section_order;
|
||||
std::vector<Symbol<E> *> require_defined;
|
||||
std::vector<Symbol<E> *> undefined;
|
||||
std::vector<std::pair<Symbol<E> *, std::variant<Symbol<E> *, u64>>> defsyms;
|
||||
@@ -2470,6 +2816,13 @@ struct Context {
|
||||
std::optional<tbb::global_control> global_limit;
|
||||
std::vector<VersionPattern> version_patterns;
|
||||
std::vector<DynamicPattern> dynamic_list_patterns;
|
||||
std::vector<ScriptAssignment<E>> script_assignments;
|
||||
std::vector<ScriptAssert> script_asserts;
|
||||
std::vector<ScriptCmd> script_discards;
|
||||
std::vector<ScriptCmd> script_sections;
|
||||
std::vector<ScriptPhdr> script_phdrs;
|
||||
std::vector<ScriptRegion> script_regions;
|
||||
std::vector<ScriptMatchRank> script_ranks;
|
||||
i64 default_version = VER_NDX_UNSPECIFIED;
|
||||
i64 page_size = E::page_size;
|
||||
bool has_error = false;
|
||||
@@ -2545,6 +2898,8 @@ struct Context {
|
||||
EhFrameSection<E> *eh_frame = nullptr;
|
||||
EhFrameHdrSection<E> *eh_frame_hdr = nullptr;
|
||||
EhFrameRelocSection<E> *eh_frame_reloc = nullptr;
|
||||
SFrameSection<E> *sframe = nullptr;
|
||||
SFrameRelocSection<E> *sframe_reloc = nullptr;
|
||||
CopyrelSection<E> *copyrel = nullptr;
|
||||
CopyrelSection<E> *copyrel_relro = nullptr;
|
||||
VersymSection<E> *versym = nullptr;
|
||||
@@ -2922,7 +3277,7 @@ std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
|
||||
template <typename E>
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream &out, const InputSection<E> &isec) {
|
||||
out << isec.file << ":(" << isec.name() << ")";
|
||||
out << isec.file << ":(" << isec.name << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -2938,13 +3293,6 @@ inline u64 InputSection<E>::get_addr() const {
|
||||
return output_section->shdr.sh_addr + offset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::string_view InputSection<E>::name() const {
|
||||
if (file.elf_sections.size() <= shndx)
|
||||
return (shdr().sh_flags & SHF_TLS) ? ".tls_common" : ".common";
|
||||
return file.shstrtab.data() + file.elf_sections[shndx].sh_name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline i64 InputSection<E>::get_priority() const {
|
||||
return ((i64)file.priority << 32) | shndx;
|
||||
@@ -3038,20 +3386,19 @@ InputSection<E>::get_tombstone(Symbol<E> &sym, SectionFragment<E> *frag) {
|
||||
if (!isec || isec->is_alive)
|
||||
return {};
|
||||
|
||||
std::string_view str = name();
|
||||
if (!str.starts_with(".debug_"))
|
||||
if (!name.starts_with(".debug_"))
|
||||
return {};
|
||||
|
||||
// If the section was dead due to ICF, we don't want to emit debug
|
||||
// info for that section but want to set real values to .debug_line so
|
||||
// that users can set a breakpoint inside a merged section.
|
||||
if (isec->icf_removed() && str == ".debug_line")
|
||||
if (isec->icf_removed() && name == ".debug_line")
|
||||
return {};
|
||||
|
||||
// 0 is an invalid value in most debug info sections, so we use it
|
||||
// as a tombstone value. .debug_loc and .debug_ranges reserve 0 as
|
||||
// the terminator marker, so we use 1 if that'str the case.
|
||||
return (str == ".debug_loc" || str == ".debug_ranges") ? 1 : 0;
|
||||
// the terminator marker, so we use 1 if that's the case.
|
||||
return (name == ".debug_loc" || name == ".debug_ranges") ? 1 : 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -3186,7 +3533,7 @@ u64 Symbol<E>::get_addr(Context<E> &ctx, i64 flags) const {
|
||||
if (isec->icf_removed())
|
||||
return isec->leader->get_addr() + value;
|
||||
|
||||
if (isec->name() == ".eh_frame") {
|
||||
if (isec->name == ".eh_frame") {
|
||||
// .eh_frame contents are parsed and reconstructed by the linker,
|
||||
// so pointing to a specific location in a source .eh_frame
|
||||
// section doesn't make much sense. However, CRT files contain
|
||||
@@ -3255,6 +3602,12 @@ inline u64 Symbol<E>::get_tlsdesc_addr(Context<E> &ctx) const {
|
||||
return ctx.got->shdr.sh_addr + get_tlsdesc_idx(ctx) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
// On SPARC, .plt uses 32-byte "small" entries until it grows past 0x100000
|
||||
// bytes (the reach of a small entry's branch to the resolver), after which
|
||||
// it switches to a "large" entry format. This is how many small entries fit.
|
||||
constexpr i64 sparc_num_small_plt =
|
||||
(0x100000 - SPARC64::plt_hdr_size) / SPARC64::plt_size;
|
||||
|
||||
template <typename E>
|
||||
inline u64 to_plt_offset(i32 pltidx) {
|
||||
if constexpr (is_ppc64v1<E>) {
|
||||
@@ -3265,6 +3618,15 @@ inline u64 to_plt_offset(i32 pltidx) {
|
||||
if (pltidx < 0x8000)
|
||||
return E::plt_hdr_size + pltidx * 8;
|
||||
return E::plt_hdr_size + 0x8000 * 8 + (pltidx - 0x8000) * 12;
|
||||
} else if constexpr (is_sparc<E>) {
|
||||
// SPARC large PLT entries are grouped into blocks of 160, each holding
|
||||
// 160 24-byte code stubs followed by 160 8-byte data pointers (so a
|
||||
// stub's `ldx` reaches its pointer within a signed 13-bit offset). This
|
||||
// returns the offset of pltidx's code stub.
|
||||
if (pltidx < sparc_num_small_plt)
|
||||
return E::plt_hdr_size + pltidx * E::plt_size;
|
||||
i64 i = pltidx - sparc_num_small_plt;
|
||||
return 0x100000 + (i / 160) * 5120 + (i % 160) * 24;
|
||||
} else {
|
||||
return E::plt_hdr_size + pltidx * E::plt_size;
|
||||
}
|
||||
@@ -3451,7 +3813,15 @@ template <typename E>
|
||||
inline bool Symbol<E>::is_local(Context<E> &ctx) const {
|
||||
if (ctx.arg.relocatable)
|
||||
return esym().st_bind == STB_LOCAL;
|
||||
return !is_imported && !is_exported;
|
||||
|
||||
// A global symbol keeps its binding in the output symbol table as
|
||||
// in GNU ld and LLD, except that hidden visibility or a version
|
||||
// script's `local:` directive reduces it to local. Tools do depend on the binding; for example, the Linux
|
||||
// kernel's build extracts addresses of global symbols from the nm
|
||||
// output of a previous link.
|
||||
return esym().st_bind == STB_LOCAL || visibility == STV_HIDDEN ||
|
||||
visibility == STV_INTERNAL ||
|
||||
(!is_imported && ver_idx == VER_NDX_LOCAL);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
|
||||
+535
-207
File diff suppressed because it is too large.
Load diff
+427
-266
@@ -113,22 +113,16 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
if (!ctx.arg.oformat_binary) {
|
||||
auto find = [&](std::string_view name) {
|
||||
for (SectionOrder &ord : ctx.arg.section_order)
|
||||
if (ord.type == SectionOrder::SECTION && ord.name == name)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
// With an explicit PHDRS command, the ELF and program headers are
|
||||
// mapped to memory only if a segment asks for them with FILEHDR or
|
||||
// PHDRS. Without one, the layout pass maps them if they fit in
|
||||
// front of the first section, so leave them allocated for now.
|
||||
bool hdrs = ctx.script_phdrs.empty();
|
||||
for (ScriptPhdr &p : ctx.script_phdrs)
|
||||
hdrs |= (p.filehdr || p.phdrs);
|
||||
|
||||
if (ctx.arg.section_order.empty() || find("EHDR"))
|
||||
ctx.ehdr = push(new OutputEhdr<E>(SHF_ALLOC));
|
||||
else
|
||||
ctx.ehdr = push(new OutputEhdr<E>(0));
|
||||
|
||||
if (ctx.arg.section_order.empty() || find("PHDR"))
|
||||
ctx.phdr = push(new OutputPhdr<E>(SHF_ALLOC));
|
||||
else
|
||||
ctx.phdr = push(new OutputPhdr<E>(0));
|
||||
ctx.ehdr = push(new OutputEhdr<E>(hdrs ? (u64)SHF_ALLOC : 0));
|
||||
ctx.phdr = push(new OutputPhdr<E>(hdrs ? (u64)SHF_ALLOC : 0));
|
||||
|
||||
if (ctx.arg.z_sectionheader)
|
||||
ctx.shdr = push(new OutputShdr<E>);
|
||||
@@ -139,11 +133,11 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
if constexpr (!is_sparc<E>)
|
||||
ctx.gotplt = push(new GotPltSection<E>(ctx));
|
||||
|
||||
ctx.reldyn = push(new RelDynSection<E>);
|
||||
ctx.reldyn = push(new RelDynSection<E>(ctx));
|
||||
ctx.relplt = push(new RelPltSection<E>);
|
||||
|
||||
if (ctx.arg.pack_dyn_relocs_relr)
|
||||
ctx.relrdyn = push(new RelrDynSection<E>);
|
||||
ctx.relrdyn = push(new RelrDynSection<E>(ctx));
|
||||
|
||||
ctx.strtab = push(new StrtabSection<E>);
|
||||
ctx.plt = push(new PltSection<E>);
|
||||
@@ -152,6 +146,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.dynsym = push(new DynsymSection<E>);
|
||||
ctx.dynstr = push(new DynstrSection<E>);
|
||||
ctx.eh_frame = push(new EhFrameSection<E>);
|
||||
ctx.sframe = push(new SFrameSection<E>);
|
||||
ctx.copyrel = push(new CopyrelSection<E>(false));
|
||||
ctx.copyrel_relro = push(new CopyrelSection<E>(true));
|
||||
|
||||
@@ -166,7 +161,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.eh_frame_hdr = push(new EhFrameHdrSection<E>);
|
||||
if (ctx.arg.gdb_index && has_debug_info_section(ctx))
|
||||
ctx.gdb_index = push(new GdbIndexSection<E>);
|
||||
if (ctx.arg.z_relro && ctx.arg.section_order.empty())
|
||||
if (ctx.arg.z_relro && ctx.script_sections.empty())
|
||||
ctx.relro_padding = push(new RelroPaddingSection<E>);
|
||||
if (ctx.arg.hash_style_sysv)
|
||||
ctx.hash = push(new HashSection<E>);
|
||||
@@ -374,12 +369,11 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
//
|
||||
// Therefore, only let IR files claim ownership for COMDAT keys that have
|
||||
// no reachable regular-object owner.
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->is_reachable)
|
||||
for (ComdatGroup *g : file->lto_comdat_groups)
|
||||
if (g && g->owner == (u32)-1)
|
||||
update_minimum(g->owner, file->priority);
|
||||
});
|
||||
g->owner = file->priority;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (file->is_reachable)
|
||||
@@ -446,6 +440,16 @@ void parse_eh_frame_sections(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_sframe_sections(Context<E> &ctx) {
|
||||
if constexpr (supports_sframe<E>) {
|
||||
Timer t(ctx, "parse_sframe_sections");
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->parse_sframe(ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_merged_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_merged_sections");
|
||||
@@ -509,6 +513,7 @@ struct OutputSectionKey {
|
||||
bool operator==(const OutputSectionKey &) const = default;
|
||||
std::string_view name;
|
||||
u64 type;
|
||||
i32 script_cmd = -1;
|
||||
|
||||
struct Hash {
|
||||
size_t operator()(const OutputSectionKey &k) const {
|
||||
@@ -567,6 +572,16 @@ template <typename E>
|
||||
static OutputSectionKey
|
||||
get_output_section_key(Context<E> &ctx, InputSection<E> &isec,
|
||||
bool ctors_in_init_array) {
|
||||
// A section matched by a linker script goes to the output section
|
||||
// the script says, period.
|
||||
if (!ctx.script_sections.empty()) {
|
||||
i32 rank = isec.file.script_match[isec.shndx];
|
||||
if (rank != -1) {
|
||||
i32 cmd = ctx.script_ranks[rank].cmd;
|
||||
return {ctx.script_sections[cmd].name, SHT_PROGBITS, cmd};
|
||||
}
|
||||
}
|
||||
|
||||
// If .init_array/.fini_array exist, .ctors/.dtors must be merged
|
||||
// with them.
|
||||
//
|
||||
@@ -576,15 +591,14 @@ get_output_section_key(Context<E> &ctx, InputSection<E> &isec,
|
||||
// We do not place them into .init_array/.fini_array because such
|
||||
// invalid pointer values would simply make the program to crash.
|
||||
if (ctors_in_init_array && !isec.get_rels(ctx).empty()) {
|
||||
std::string_view name = isec.name();
|
||||
if (name == ".ctors" || name.starts_with(".ctors."))
|
||||
if (isec.name == ".ctors" || isec.name.starts_with(".ctors."))
|
||||
return {".init_array", SHT_INIT_ARRAY};
|
||||
if (name == ".dtors" || name.starts_with(".dtors."))
|
||||
if (isec.name == ".dtors" || isec.name.starts_with(".dtors."))
|
||||
return {".fini_array", SHT_FINI_ARRAY};
|
||||
}
|
||||
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = get_output_name(ctx, isec.name(), shdr.sh_flags);
|
||||
std::string_view name = get_output_name(ctx, isec.name, shdr.sh_flags);
|
||||
u64 type = canonicalize_type<E>(name, shdr.sh_type);
|
||||
return {name, type};
|
||||
}
|
||||
@@ -645,7 +659,7 @@ void create_output_sections(Context<E> &ctx) {
|
||||
~SHF_COMPRESSED & ~SHF_GNU_RETAIN;
|
||||
|
||||
if (ctx.arg.relocatable && (sh_flags & SHF_GROUP)) {
|
||||
OutputSection<E> *osec = new OutputSection<E>(isec->name(), shdr.sh_type);
|
||||
OutputSection<E> *osec = new OutputSection<E>(isec->name, shdr.sh_type);
|
||||
osec->sh_flags = sh_flags;
|
||||
isec->output_section = osec;
|
||||
ctx.osec_pool.emplace_back(osec);
|
||||
@@ -669,6 +683,7 @@ void create_output_sections(Context<E> &ctx) {
|
||||
|
||||
std::unique_ptr<OutputSection<E>> osec =
|
||||
std::make_unique<OutputSection<E>>(key.name, key.type);
|
||||
osec->script_cmd = key.script_cmd;
|
||||
|
||||
std::unique_lock lock(mu);
|
||||
auto [it, inserted] = map.insert({key, osec.get()});
|
||||
@@ -715,6 +730,72 @@ void create_output_sections(Context<E> &ctx) {
|
||||
osec->members = flatten(osec->members_vec);
|
||||
osec->members_vec.clear();
|
||||
osec->members_vec.shrink_to_fit();
|
||||
|
||||
if (osec->script_cmd != -1)
|
||||
script_finalize_section(ctx, *osec);
|
||||
}
|
||||
|
||||
// An output section description that inserts data with a command
|
||||
// like BYTE must materialize even if no input section is placed in
|
||||
// it, e.g. the .signature section of the Linux kernel's setup.ld.
|
||||
if (!ctx.script_sections.empty()) {
|
||||
std::vector<bool> taken(ctx.script_sections.size());
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
|
||||
if (osec->script_cmd != -1)
|
||||
taken[osec->script_cmd] = true;
|
||||
|
||||
for (i64 i = 0; i < ctx.script_sections.size(); i++) {
|
||||
ScriptCmd &cmd = ctx.script_sections[i];
|
||||
if (cmd.kind != ScriptCmd::OUTPUT_SECTION || taken[i])
|
||||
continue;
|
||||
|
||||
bool need = false;
|
||||
for (ScriptCmd &b : cmd.cmds)
|
||||
need |= (b.kind == ScriptCmd::DATA);
|
||||
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
|
||||
need |= (ms->script_cmd == i);
|
||||
if (!need)
|
||||
continue;
|
||||
|
||||
// Data commands are loadable content; a section created only
|
||||
// to hold merged sections takes its flags from them when they
|
||||
// are attached below
|
||||
bool has_data = false;
|
||||
for (ScriptCmd &b : cmd.cmds)
|
||||
has_data |= (b.kind == ScriptCmd::DATA);
|
||||
|
||||
OutputSection<E> *osec = new OutputSection<E>(cmd.name, SHT_PROGBITS);
|
||||
osec->script_cmd = i;
|
||||
osec->sh_flags = has_data ? SHF_ALLOC : 0;
|
||||
osec->shdr.sh_flags = osec->sh_flags;
|
||||
osec->shdr.sh_addralign = 1;
|
||||
script_finalize_section(ctx, *osec);
|
||||
ctx.osec_pool.emplace_back(osec);
|
||||
}
|
||||
|
||||
// A merged section matched by an output section description is
|
||||
// laid out and written as part of the section created for it
|
||||
std::unordered_map<i64, OutputSection<E> *> osec_of;
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
|
||||
if (osec->script_cmd != -1)
|
||||
osec_of[osec->script_cmd] = osec.get();
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections) {
|
||||
if (ms->script_cmd != -1) {
|
||||
OutputSection<E> *osec = osec_of[ms->script_cmd];
|
||||
ms->script_owner = osec;
|
||||
osec->merged_members.push_back(ms.get());
|
||||
osec->sh_flags |= ms->shdr.sh_flags & ~(u64)SHF_MERGE &
|
||||
~(u64)SHF_STRINGS;
|
||||
osec->shdr.sh_flags = osec->sh_flags;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
|
||||
ranges::stable_sort(osec->merged_members, {},
|
||||
[](MergedSection<E> *ms) {
|
||||
return ms->script_rank.load();
|
||||
});
|
||||
}
|
||||
|
||||
// Add output sections and mergeable sections to ctx.chunks
|
||||
@@ -722,7 +803,8 @@ void create_output_sections(Context<E> &ctx) {
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.osec_pool)
|
||||
chunks.push_back(osec.get());
|
||||
for (std::unique_ptr<MergedSection<E>> &osec : ctx.merged_sections)
|
||||
chunks.push_back(osec.get());
|
||||
if (!osec->script_owner)
|
||||
chunks.push_back(osec.get());
|
||||
|
||||
// Sections are added to the section lists in an arbitrary order
|
||||
// because they are created in parallel. Sort them to to make the
|
||||
@@ -774,14 +856,112 @@ void create_internal_file(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++)
|
||||
add(ctx.arg.defsyms[i].first);
|
||||
|
||||
// Add --section-order symbols
|
||||
for (SectionOrder &ord : ctx.arg.section_order)
|
||||
if (ord.type == SectionOrder::SYMBOL)
|
||||
add(get_symbol(ctx, ord.name));
|
||||
// Add symbols defined by linker scripts. PROVIDE'd symbols are not
|
||||
// added here because they must yield to definitions in input files;
|
||||
// they are handled in add_synthetic_symbols() instead.
|
||||
for (ScriptAssignment<E> &a : ctx.script_assignments) {
|
||||
if (!a.provide) {
|
||||
add(a.sym);
|
||||
a.defined = true;
|
||||
}
|
||||
}
|
||||
|
||||
obj->elf_syms = ctx.internal_esyms;
|
||||
}
|
||||
|
||||
// Define symbols PROVIDE'd by linker scripts. A PROVIDE'd symbol is
|
||||
// defined only if no input file defines it, so this pass must run
|
||||
// after symbol resolution. It must also run before
|
||||
// compute_import_export() so that PROVIDE'd symbols are exported just
|
||||
// like any other defined symbol.
|
||||
template <typename E>
|
||||
void add_provided_symbols(Context<E> &ctx) {
|
||||
if (!ctx.internal_obj)
|
||||
return;
|
||||
|
||||
ObjectFile<E> &obj = *ctx.internal_obj;
|
||||
bool changed = false;
|
||||
|
||||
// A PROVIDE'd symbol is defined only if it is actually referenced,
|
||||
// either by an input file or by another linker script expression
|
||||
// that is evaluated. Note that if it is not, its value expression
|
||||
// is not even evaluated; an expression referring to a nonexistent
|
||||
// symbol or section is not an error in that case.
|
||||
std::unordered_set<Symbol<E> *> candidates;
|
||||
for (ScriptAssignment<E> &a : ctx.script_assignments)
|
||||
if (a.provide && !a.sym->file)
|
||||
candidates.insert(a.sym);
|
||||
|
||||
std::mutex mu;
|
||||
std::unordered_set<Symbol<E> *> referenced;
|
||||
|
||||
if (!candidates.empty()) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file == ctx.internal_obj)
|
||||
return;
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
if (file->elf_syms[i].is_undef()) {
|
||||
Symbol<E> *sym = file->symbols[i];
|
||||
if (candidates.contains(sym)) {
|
||||
std::scoped_lock lock(mu);
|
||||
referenced.insert(sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// References from expressions we will evaluate, propagated
|
||||
// transitively through PROVIDEs that become defined
|
||||
std::function<void(const ScriptExpr &)> mark = [&](const ScriptExpr &e) {
|
||||
if (e.kind == ScriptExpr::NAME)
|
||||
referenced.insert(get_symbol(ctx, e.str));
|
||||
for (const ScriptExpr &arg : e.args)
|
||||
mark(arg);
|
||||
};
|
||||
|
||||
for (ScriptAssignment<E> &a : ctx.script_assignments)
|
||||
if (!a.provide)
|
||||
mark(a.value);
|
||||
for (ScriptAssert &a : ctx.script_asserts)
|
||||
mark(a.cond);
|
||||
|
||||
for (bool changed = true; changed;) {
|
||||
changed = false;
|
||||
for (ScriptAssignment<E> &a : ctx.script_assignments) {
|
||||
if (a.provide && referenced.contains(a.sym)) {
|
||||
i64 size = referenced.size();
|
||||
mark(a.value);
|
||||
changed |= (referenced.size() != size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ScriptAssignment<E> &a : ctx.script_assignments) {
|
||||
if (a.provide && !a.sym->file && referenced.contains(a.sym)) {
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
esym.st_visibility = a.hidden ? STV_HIDDEN : STV_DEFAULT;
|
||||
ctx.internal_esyms.push_back(esym);
|
||||
|
||||
a.sym->value = 0xdeadbeef; // unique dummy value
|
||||
if (a.hidden)
|
||||
a.sym->visibility = STV_HIDDEN;
|
||||
obj.symbols.push_back(a.sym);
|
||||
a.defined = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
obj.elf_syms = ctx.internal_esyms;
|
||||
obj.resolve_symbols(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::optional<std::string>
|
||||
get_start_stop_name(Context<E> &ctx, Chunk<E> &chunk) {
|
||||
@@ -838,10 +1018,15 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
ctx._DYNAMIC = add("_DYNAMIC");
|
||||
ctx._GLOBAL_OFFSET_TABLE_ = add("_GLOBAL_OFFSET_TABLE_");
|
||||
ctx._PROCEDURE_LINKAGE_TABLE_ = add("_PROCEDURE_LINKAGE_TABLE_");
|
||||
ctx.__bss_start = add("__bss_start");
|
||||
ctx._end = add("_end");
|
||||
ctx._etext = add("_etext");
|
||||
ctx._edata = add("_edata");
|
||||
// A linker script may define these; in that case the script wins
|
||||
if (!get_symbol(ctx, "__bss_start")->file)
|
||||
ctx.__bss_start = add("__bss_start");
|
||||
if (!get_symbol(ctx, "_end")->file)
|
||||
ctx._end = add("_end");
|
||||
if (!get_symbol(ctx, "_etext")->file)
|
||||
ctx._etext = add("_etext");
|
||||
if (!get_symbol(ctx, "_edata")->file)
|
||||
ctx._edata = add("_edata");
|
||||
ctx.__executable_start = add("__executable_start");
|
||||
|
||||
ctx.__rel_iplt_start =
|
||||
@@ -891,11 +1076,6 @@ void add_synthetic_symbols(Context<E> &ctx) {
|
||||
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
|
||||
add_start_stop("__start_" + *name);
|
||||
add_start_stop("__stop_" + *name);
|
||||
|
||||
if (ctx.arg.physical_image_base) {
|
||||
add_start_stop("__phys_start_" + *name);
|
||||
add_start_stop("__phys_stop_" + *name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1298,7 +1478,7 @@ void check_symbol_types(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
static i64 get_init_fini_priority(InputSection<E> *isec) {
|
||||
static std::regex re(R"(\.(\d+)$)", std::regex_constants::optimize);
|
||||
std::string_view name = isec->name();
|
||||
std::string_view name = isec->name;
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re))
|
||||
return std::stoi(m[1]);
|
||||
@@ -1315,14 +1495,14 @@ static i64 get_ctor_dtor_priority(InputSection<E> *isec) {
|
||||
// crtbegin.o and crtend.o contain marker symbols such as
|
||||
// __CTOR_LIST__ or __DTOR_LIST__. So they have to be at the
|
||||
// beginning or end of the section.
|
||||
std::smatch m;
|
||||
if (std::regex_search(isec->file.filename, m, re1))
|
||||
if (std::regex_search(isec->file.filename, re1))
|
||||
return -2;
|
||||
if (std::regex_search(isec->file.filename, m, re2))
|
||||
if (std::regex_search(isec->file.filename, re2))
|
||||
return 65536;
|
||||
|
||||
std::string name(isec->name());
|
||||
if (std::regex_search(name, m, re3))
|
||||
std::string_view name = isec->name;
|
||||
std::cmatch m;
|
||||
if (std::regex_search(name.data(), name.data() + name.size(), m, re3))
|
||||
return std::stoi(m[1]);
|
||||
return -1;
|
||||
}
|
||||
@@ -1346,8 +1526,8 @@ void sort_init_fini(Context<E> &ctx) {
|
||||
std::vector<Entry> vec;
|
||||
|
||||
for (InputSection<E> *isec : osec->members) {
|
||||
std::string_view name = isec->name();
|
||||
if (name.starts_with(".ctors") || name.starts_with(".dtors"))
|
||||
if (isec->name.starts_with(".ctors") ||
|
||||
isec->name.starts_with(".dtors"))
|
||||
vec.push_back({isec, 65535 - get_ctor_dtor_priority(isec)});
|
||||
else
|
||||
vec.push_back({isec, get_init_fini_priority(isec)});
|
||||
@@ -1555,13 +1735,13 @@ void fixup_ctors_in_init_array(Context<E> &ctx) {
|
||||
if (Chunk<E> *chunk = find_chunk(ctx, ".init_array"))
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
if (isec->name().starts_with(".ctors"))
|
||||
if (isec->name.starts_with(".ctors"))
|
||||
reverse_contents(*isec);
|
||||
|
||||
if (Chunk<E> *chunk = find_chunk(ctx, ".fini_array"))
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
if (isec->name().starts_with(".dtors"))
|
||||
if (isec->name.starts_with(".dtors"))
|
||||
reverse_contents(*isec);
|
||||
}
|
||||
|
||||
@@ -1664,9 +1844,13 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
if constexpr (needs_thunk<E>) {
|
||||
std::vector<Chunk<E> *> vec = ctx.chunks;
|
||||
|
||||
// A section described by a linker script is laid out by the
|
||||
// script engine, which doesn't support range extension thunks
|
||||
// yet; a branch that cannot reach its target is reported as an
|
||||
// out-of-range relocation.
|
||||
auto tail = ranges::partition(vec, [&](Chunk<E> *chunk) {
|
||||
return chunk->to_osec() && (chunk->shdr.sh_flags & SHF_EXECINSTR) &&
|
||||
!ctx.arg.relocatable;
|
||||
!ctx.arg.relocatable && chunk->script_cmd == -1;
|
||||
});
|
||||
|
||||
// create_range_extension_thunks is not thread-safe
|
||||
@@ -1681,6 +1865,13 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
chunk->compute_section_size(ctx);
|
||||
});
|
||||
}
|
||||
// Merged sections owned by a script-described output section are
|
||||
// not in ctx.chunks; their content layout happens here, their
|
||||
// placement in the address walk.
|
||||
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
|
||||
if (ms->script_owner)
|
||||
ms->compute_section_size(ctx);
|
||||
|
||||
}
|
||||
|
||||
// Find all unresolved symbols and attach them to the most appropriate files.
|
||||
@@ -1964,6 +2155,12 @@ void copy_chunks(Context<E> &ctx) {
|
||||
copy(*chunk);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.merged_sections,
|
||||
[&](std::unique_ptr<MergedSection<E>> &ms) {
|
||||
if (ms->script_owner)
|
||||
copy(*ms);
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (is_rel(*chunk))
|
||||
copy(*chunk);
|
||||
@@ -1992,15 +2189,6 @@ void copy_chunks(Context<E> &ctx) {
|
||||
zero(chunks.back(), ctx.output_file->filesize);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void construct_relr(Context<E> &ctx) {
|
||||
Timer t(ctx, "construct_relr");
|
||||
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
chunk->construct_relr(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// The hash function for .gnu.hash.
|
||||
static u32 djb_hash(std::string_view name) {
|
||||
u32 h = 5381;
|
||||
@@ -2187,8 +2375,8 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
if (sym->file != file)
|
||||
continue;
|
||||
|
||||
const char *name = file->symbol_strtab.data() + file->elf_syms[i].st_name;
|
||||
std::string_view ver = strchr(name, '@') + 1;
|
||||
std::string_view name = file->symbol_names[i];
|
||||
std::string_view ver = name.substr(name.find('@') + 1);
|
||||
|
||||
bool is_default = false;
|
||||
if (ver.starts_with('@')) {
|
||||
@@ -2511,130 +2699,156 @@ void compute_address_significance(Context<E> &ctx) {
|
||||
// Other file layouts are possible, but this layout is chosen to keep
|
||||
// the number of segments as few as possible.
|
||||
template <typename E>
|
||||
void sort_output_sections_regular(Context<E> &ctx) {
|
||||
auto get_rank1 = [&](Chunk<E> *chunk) {
|
||||
u64 type = chunk->shdr.sh_type;
|
||||
u64 flags = chunk->shdr.sh_flags;
|
||||
static i64 get_regular_rank1(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
u64 type = chunk->shdr.sh_type;
|
||||
u64 flags = chunk->shdr.sh_flags;
|
||||
|
||||
if (chunk == ctx.ehdr)
|
||||
return 0;
|
||||
if (chunk == ctx.phdr)
|
||||
return 1;
|
||||
if (chunk == ctx.interp)
|
||||
return 2;
|
||||
if (type == SHT_NOTE && (flags & SHF_ALLOC))
|
||||
return 3;
|
||||
if (chunk == ctx.hash)
|
||||
return 4;
|
||||
if (chunk == ctx.gnu_hash)
|
||||
return 5;
|
||||
if (chunk == ctx.dynsym)
|
||||
return 6;
|
||||
if (chunk == ctx.dynstr)
|
||||
return 7;
|
||||
if (chunk == ctx.versym)
|
||||
return 8;
|
||||
if (chunk == ctx.verneed)
|
||||
return 9;
|
||||
if (chunk == ctx.reldyn)
|
||||
return 10;
|
||||
if (chunk == ctx.relplt)
|
||||
return 11;
|
||||
if (chunk == ctx.shdr)
|
||||
return INT32_MAX - 1;
|
||||
if (chunk == ctx.gdb_index)
|
||||
return INT32_MAX;
|
||||
|
||||
bool alloc = (flags & SHF_ALLOC);
|
||||
bool writable = (flags & SHF_WRITE);
|
||||
bool exec = (flags & SHF_EXECINSTR);
|
||||
bool tls = (flags & SHF_TLS);
|
||||
bool relro = chunk->is_relro;
|
||||
bool is_bss = (type == SHT_NOBITS);
|
||||
|
||||
return (1 << 10) | (!alloc << 9) | (writable << 8) | (exec << 7) |
|
||||
(!tls << 6) | (!relro << 5) | (is_bss << 4);
|
||||
};
|
||||
|
||||
// Ties are broken by additional rules
|
||||
auto get_rank2 = [&](Chunk<E> *chunk) -> i64 {
|
||||
ElfShdr<E> &shdr = chunk->shdr;
|
||||
if (shdr.sh_type == SHT_NOTE)
|
||||
return -shdr.sh_addralign;
|
||||
|
||||
if (chunk == ctx.got)
|
||||
return 2;
|
||||
if (chunk->name == ".toc")
|
||||
return 3;
|
||||
if (chunk == ctx.relro_padding)
|
||||
return INT64_MAX;
|
||||
if (chunk == ctx.ehdr)
|
||||
return 0;
|
||||
if (chunk == ctx.phdr)
|
||||
return 1;
|
||||
if (chunk == ctx.interp)
|
||||
return 2;
|
||||
if (type == SHT_NOTE && (flags & SHF_ALLOC))
|
||||
return 3;
|
||||
if (chunk == ctx.hash)
|
||||
return 4;
|
||||
if (chunk == ctx.gnu_hash)
|
||||
return 5;
|
||||
if (chunk == ctx.dynsym)
|
||||
return 6;
|
||||
if (chunk == ctx.dynstr)
|
||||
return 7;
|
||||
if (chunk == ctx.versym)
|
||||
return 8;
|
||||
if (chunk == ctx.verneed)
|
||||
return 9;
|
||||
if (chunk == ctx.reldyn)
|
||||
return 10;
|
||||
if (chunk == ctx.relplt)
|
||||
return 11;
|
||||
if (chunk == ctx.shdr)
|
||||
return INT32_MAX - 1;
|
||||
if (chunk == ctx.gdb_index)
|
||||
return INT32_MAX;
|
||||
|
||||
bool alloc = (flags & SHF_ALLOC);
|
||||
bool writable = (flags & SHF_WRITE);
|
||||
bool exec = (flags & SHF_EXECINSTR);
|
||||
bool tls = (flags & SHF_TLS);
|
||||
bool relro = chunk->is_relro;
|
||||
bool is_bss = (type == SHT_NOBITS);
|
||||
|
||||
return (1 << 10) | (!alloc << 9) | (writable << 8) | (exec << 7) |
|
||||
(!tls << 6) | (!relro << 5) | (is_bss << 4);
|
||||
}
|
||||
|
||||
// Ties are broken by additional rules
|
||||
template <typename E>
|
||||
static i64 get_regular_rank2(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
ElfShdr<E> &shdr = chunk->shdr;
|
||||
if (shdr.sh_type == SHT_NOTE)
|
||||
return -shdr.sh_addralign;
|
||||
|
||||
if (chunk == ctx.got)
|
||||
return 2;
|
||||
if (chunk->name == ".toc")
|
||||
return 3;
|
||||
if (chunk == ctx.relro_padding)
|
||||
return INT64_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void sort_output_sections_regular(Context<E> &ctx) {
|
||||
ranges::stable_sort(ctx.chunks, {}, [&](Chunk<E> *x) {
|
||||
return std::tuple{get_regular_rank1(ctx, x), get_regular_rank2(ctx, x),
|
||||
x->name};
|
||||
});
|
||||
}
|
||||
|
||||
// Sort output sections according to a linker script. Sections the
|
||||
// script describes appear in script order; orphans are inserted after
|
||||
// the most similar script section.
|
||||
template <typename E>
|
||||
static void sort_output_sections_by_script(Context<E> &ctx) {
|
||||
match_synthetic_sections(ctx);
|
||||
|
||||
// Find the insertion point for an orphan: the position the default
|
||||
// section order would give it, i.e. after the last script section
|
||||
// that the default order ranks no higher. This keeps related
|
||||
// orphans, such as .tdata and .tbss, together.
|
||||
i64 first_osec = 0;
|
||||
for (i64 i = 0; i < ctx.script_sections.size(); i++) {
|
||||
if (ctx.script_sections[i].kind == ScriptCmd::OUTPUT_SECTION) {
|
||||
first_osec = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto get_anchor = [&](Chunk<E> *chunk) -> i64 {
|
||||
i64 rank = get_regular_rank1(ctx, chunk);
|
||||
|
||||
// An orphan that orders before everything the script describes
|
||||
// is placed right after the first section; placing it in front
|
||||
// would displace the address the script gives the section.
|
||||
i64 anchor = first_osec;
|
||||
i64 equal = -1;
|
||||
|
||||
for (Chunk<E> *sc : ctx.chunks) {
|
||||
if (sc->script_cmd == -1 || !(sc->shdr.sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
i64 r = get_regular_rank1(ctx, sc);
|
||||
if (r <= rank)
|
||||
anchor = std::max<i64>(anchor, sc->script_cmd);
|
||||
if (r == rank)
|
||||
equal = std::max<i64>(equal, sc->script_cmd);
|
||||
}
|
||||
|
||||
// Prefer a section of exactly the same class; e.g. exec orphans
|
||||
// such as .plt go with .text rather than after some read-only
|
||||
// data, which would create an executable island in a page shared
|
||||
// with data.
|
||||
return (equal != -1) ? equal : anchor;
|
||||
};
|
||||
|
||||
auto get_rank = [&](Chunk<E> *chunk) -> i64 {
|
||||
if (chunk == ctx.ehdr)
|
||||
return -2;
|
||||
if (chunk == ctx.phdr)
|
||||
return -1;
|
||||
if (chunk == ctx.shdr)
|
||||
return INT64_MAX;
|
||||
if (chunk->script_cmd != -1)
|
||||
return 2 * (i64)chunk->script_cmd;
|
||||
if (!(chunk->shdr.sh_flags & SHF_ALLOC))
|
||||
return INT64_MAX - 1;
|
||||
|
||||
if (ctx.arg.orphan_handling == "warn")
|
||||
Warn(ctx) << "placing orphan section " << chunk->name;
|
||||
else if (ctx.arg.orphan_handling == "error")
|
||||
Error(ctx) << "orphan section " << chunk->name;
|
||||
return 2 * get_anchor(chunk) + 1;
|
||||
};
|
||||
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->sect_order = get_rank(chunk);
|
||||
|
||||
// Orphans that share an insertion point are ordered as the default
|
||||
// layout would order them, which keeps pairs like .tdata/.tbss in
|
||||
// the order other passes depend on.
|
||||
ranges::stable_sort(ctx.chunks, {}, [&](Chunk<E> *x) {
|
||||
return std::tuple{get_rank1(x), get_rank2(x), x->name};
|
||||
return std::tuple{x->sect_order, get_regular_rank1(ctx, x),
|
||||
get_regular_rank2(ctx, x), x->name};
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::string_view get_section_order_group(Chunk<E> &chunk) {
|
||||
if (chunk.shdr.sh_type == SHT_NOBITS)
|
||||
return "BSS";
|
||||
if (chunk.shdr.sh_flags & SHF_EXECINSTR)
|
||||
return "TEXT";
|
||||
if (chunk.shdr.sh_flags & SHF_WRITE)
|
||||
return "DATA";
|
||||
return "RODATA";
|
||||
};
|
||||
|
||||
// Sort sections according to a --section-order argument.
|
||||
template <typename E>
|
||||
void sort_output_sections_by_order(Context<E> &ctx) {
|
||||
auto get_rank = [&](Chunk<E> *chunk) -> i64 {
|
||||
u64 flags = chunk->shdr.sh_flags;
|
||||
|
||||
if (chunk == ctx.ehdr && !(chunk->shdr.sh_flags & SHF_ALLOC))
|
||||
return -2;
|
||||
if (chunk == ctx.phdr && !(chunk->shdr.sh_flags & SHF_ALLOC))
|
||||
return -1;
|
||||
|
||||
if (chunk == ctx.shdr)
|
||||
return INT32_MAX;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
return INT32_MAX - 1;
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.section_order.size(); i++)
|
||||
if (SectionOrder &arg = ctx.arg.section_order[i];
|
||||
arg.type == SectionOrder::SECTION && arg.name == chunk->name)
|
||||
return i;
|
||||
|
||||
std::string_view group = get_section_order_group(*chunk);
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.section_order.size(); i++)
|
||||
if (SectionOrder &arg = ctx.arg.section_order[i];
|
||||
arg.type == SectionOrder::GROUP && arg.name == group)
|
||||
return i;
|
||||
|
||||
Error(ctx) << "--section-order: missing section specification for "
|
||||
<< chunk->name;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// It is an error if a section order cannot be determined by a given
|
||||
// section order list.
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
chunk->sect_order = get_rank(chunk);
|
||||
|
||||
// Sort output sections by --section-order
|
||||
ranges::stable_sort(ctx.chunks, {}, &Chunk<E>::sect_order);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void sort_output_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.section_order.empty())
|
||||
if (ctx.script_sections.empty())
|
||||
sort_output_sections_regular(ctx);
|
||||
else
|
||||
sort_output_sections_by_order(ctx);
|
||||
sort_output_sections_by_script(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -2787,69 +3001,6 @@ static void set_virtual_addresses_regular(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void set_virtual_addresses_by_order(Context<E> &ctx) {
|
||||
std::vector<Chunk<E> *> vec;
|
||||
for (Chunk<E> *c : ctx.chunks)
|
||||
if (c->shdr.sh_flags & SHF_ALLOC)
|
||||
vec.push_back(c);
|
||||
|
||||
u64 addr = ctx.arg.image_base;
|
||||
i64 i = 0;
|
||||
|
||||
for (i64 j = 0; j < ctx.arg.section_order.size(); j++) {
|
||||
SectionOrder &ord = ctx.arg.section_order[j];
|
||||
|
||||
switch (ord.type) {
|
||||
case SectionOrder::SECTION:
|
||||
case SectionOrder::GROUP:
|
||||
for (; i < vec.size() && vec[i]->sect_order == j; i++) {
|
||||
// Memory protection works on page size granularity. We need to
|
||||
// put sections with different memory attributes into different
|
||||
// pages. We do it by inserting a padding.
|
||||
if (i != 0) {
|
||||
i64 flags1 = to_phdr_flags(ctx, vec[i - 1]);
|
||||
i64 flags2 = to_phdr_flags(ctx, vec[i]);
|
||||
if (flags1 != flags2) {
|
||||
switch (ctx.arg.z_separate_code) {
|
||||
case SEPARATE_LOADABLE_SEGMENTS:
|
||||
addr = align_to(addr, ctx.page_size);
|
||||
break;
|
||||
case SEPARATE_CODE:
|
||||
if ((flags1 & PF_X) != (flags2 & PF_X))
|
||||
addr = align_to(addr, ctx.page_size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addr = align_to(addr, vec[i]->shdr.sh_addralign);
|
||||
vec[i]->shdr.sh_addr = addr;
|
||||
addr += vec[i]->shdr.sh_size;
|
||||
}
|
||||
break;
|
||||
case SectionOrder::ADDR:
|
||||
if (addr != ctx.arg.image_base && ord.value < addr)
|
||||
Error(ctx) << "--section-order: address goes backward: requested "
|
||||
<< std::hex << std::showbase
|
||||
<< ord.value << " < current " << addr
|
||||
<< " (at token '" << ord.token << "')";
|
||||
addr = ord.value;
|
||||
break;
|
||||
case SectionOrder::ALIGN:
|
||||
addr = align_to(addr, ord.value);
|
||||
break;
|
||||
case SectionOrder::SYMBOL:
|
||||
get_symbol(ctx, ord.name)->value = addr;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the smallest integer N that satisfies N >= val and
|
||||
// N % align == skew % align.
|
||||
//
|
||||
@@ -2905,6 +3056,12 @@ static i64 set_file_offsets(Context<E> &ctx) {
|
||||
if (chunks[i]->shdr.sh_addr < first.shdr.sh_addr)
|
||||
break;
|
||||
|
||||
// This section requires larger alignment, we need to adjust the
|
||||
// offset to ensure offset % align == vaddr % align.
|
||||
if (chunks[i]->shdr.sh_addralign > ctx.page_size &&
|
||||
chunks[i]->shdr.sh_addralign > chunks[i - 1]->shdr.sh_addralign)
|
||||
break;
|
||||
|
||||
i64 gap_size = chunks[i]->shdr.sh_addr - chunks[i - 1]->shdr.sh_addr -
|
||||
chunks[i - 1]->shdr.sh_size;
|
||||
|
||||
@@ -2963,6 +3120,12 @@ void compute_section_headers(Context<E> &ctx) {
|
||||
if (!chunk->is_header())
|
||||
chunk->shndx = shndx++;
|
||||
|
||||
// Symbols in a merged section within a script-described output
|
||||
// section belong to that section
|
||||
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
|
||||
if (ms->script_owner)
|
||||
ms->shndx = ms->script_owner->shndx;
|
||||
|
||||
if (ctx.symtab && SHN_LORESERVE <= shndx) {
|
||||
SymtabShndxSection<E> *sec = new SymtabShndxSection<E>;
|
||||
sec->shndx = shndx++;
|
||||
@@ -2992,16 +3155,32 @@ i64 set_osec_offsets(Context<E> &ctx) {
|
||||
Timer t(ctx, "set_osec_offsets");
|
||||
|
||||
for (;;) {
|
||||
if (ctx.arg.section_order.empty())
|
||||
set_virtual_addresses_regular(ctx);
|
||||
if (!ctx.script_sections.empty())
|
||||
set_virtual_addresses_by_script(ctx);
|
||||
else
|
||||
set_virtual_addresses_by_order(ctx);
|
||||
set_virtual_addresses_regular(ctx);
|
||||
|
||||
if (ctx.arg.pack_dyn_relocs_relr || ctx.arg.pack_dyn_relocs_android) {
|
||||
i64 x = ctx.reldyn->shdr.sh_size;
|
||||
i64 y = ctx.relrdyn ? (i64)ctx.relrdyn->shdr.sh_size : 0;
|
||||
ctx.reldyn->update_shdr(ctx);
|
||||
|
||||
if (x != (i64)ctx.reldyn->shdr.sh_size ||
|
||||
y != (ctx.relrdyn ? (i64)ctx.relrdyn->shdr.sh_size : 0))
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
|
||||
// Assigning new offsets may change the contents and the length
|
||||
// of the program header, so repeat it until converge.
|
||||
i64 fileoff = set_file_offsets(ctx);
|
||||
|
||||
for (std::unique_ptr<MergedSection<E>> &ms : ctx.merged_sections)
|
||||
if (Chunk<E> *p = ms->script_owner)
|
||||
ms->shdr.sh_offset =
|
||||
p->shdr.sh_offset + ms->shdr.sh_addr - p->shdr.sh_addr;
|
||||
|
||||
if (ctx.phdr) {
|
||||
i64 sz = ctx.phdr->shdr.sh_size;
|
||||
ctx.phdr->update_shdr(ctx);
|
||||
@@ -3022,15 +3201,6 @@ static i64 get_num_irelative_relocs(Context<E> &ctx) {
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 to_paddr(Context<E> &ctx, u64 vaddr) {
|
||||
for (ElfPhdr<E> &phdr : ctx.phdr->phdrs)
|
||||
if (phdr.p_type == PT_LOAD)
|
||||
if (phdr.p_vaddr <= vaddr && vaddr < phdr.p_vaddr + phdr.p_memsz)
|
||||
return phdr.p_paddr + (vaddr - phdr.p_vaddr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
auto start = [](Symbol<E> *sym, auto &chunk, i64 bias = 0) {
|
||||
@@ -3197,18 +3367,6 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
if (std::optional<std::string> name = get_start_stop_name(ctx, *chunk)) {
|
||||
start(get_symbol(ctx, save_string(ctx, "__start_" + *name)), chunk);
|
||||
stop(get_symbol(ctx, save_string(ctx, "__stop_" + *name)), chunk);
|
||||
|
||||
if (ctx.arg.physical_image_base) {
|
||||
u64 paddr = to_paddr(ctx, chunk->shdr.sh_addr);
|
||||
|
||||
Symbol<E> *x = get_symbol(ctx, save_string(ctx, "__phys_start_" + *name));
|
||||
x->set_output_section(chunk);
|
||||
x->value = paddr;
|
||||
|
||||
Symbol<E> *y = get_symbol(ctx, save_string(ctx, "__phys_stop_" + *name));
|
||||
y->set_output_section(chunk);
|
||||
y->value = paddr + chunk->shdr.sh_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3228,10 +3386,8 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// --section-order symbols
|
||||
for (SectionOrder &ord : ctx.arg.section_order)
|
||||
if (ord.type == SectionOrder::SYMBOL)
|
||||
get_symbol(ctx, ord.name)->set_output_section(sections[0]);
|
||||
// Linker script symbol assignments and ASSERT commands
|
||||
eval_script_commands(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -3304,6 +3460,10 @@ template <typename E>
|
||||
void sort_reldyn(Context<E> &ctx) {
|
||||
Timer t(ctx, "sort_reldyn");
|
||||
|
||||
// .rela.dyn contains APS2-encoded bytes, not ElfRel entries.
|
||||
if (ctx.arg.pack_dyn_relocs_android)
|
||||
return;
|
||||
|
||||
ElfRel<E> *begin = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset);
|
||||
ElfRel<E> *end = begin + ctx.reldyn->shdr.sh_size / sizeof(ElfRel<E>);
|
||||
|
||||
@@ -3641,9 +3801,11 @@ template void create_synthetic_sections(Context<E> &);
|
||||
template void resolve_symbols(Context<E> &);
|
||||
template void do_lto(Context<E> &);
|
||||
template void parse_eh_frame_sections(Context<E> &);
|
||||
template void parse_sframe_sections(Context<E> &);
|
||||
template void create_merged_sections(Context<E> &);
|
||||
template void convert_common_symbols(Context<E> &);
|
||||
template void create_output_sections(Context<E> &);
|
||||
template void add_provided_symbols(Context<E> &);
|
||||
template void add_synthetic_symbols(Context<E> &);
|
||||
template void check_cet_errors(Context<E> &);
|
||||
template void apply_section_align(Context<E> &);
|
||||
@@ -3666,7 +3828,6 @@ template void scan_relocations(Context<E> &);
|
||||
template void report_undef_errors(Context<E> &);
|
||||
template void create_reloc_sections(Context<E> &);
|
||||
template void copy_chunks(Context<E> &);
|
||||
template void construct_relr(Context<E> &);
|
||||
template void sort_dynsyms(Context<E> &);
|
||||
template void sort_debug_info_sections(Context<E> &);
|
||||
template void create_output_symtab(Context<E> &);
|
||||
|
||||
@@ -50,6 +50,8 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.shdr = push(new OutputShdr<E>);
|
||||
ctx.eh_frame = push(new EhFrameSection<E>);
|
||||
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
|
||||
ctx.sframe = push(new SFrameSection<E>);
|
||||
ctx.sframe_reloc = push(new SFrameRelocSection<E>);
|
||||
ctx.strtab = push(new StrtabSection<E>);
|
||||
ctx.symtab = push(new SymtabSection<E>);
|
||||
ctx.shstrtab = push(new ShstrtabSection<E>);
|
||||
@@ -161,6 +163,9 @@ void combine_objects(Context<E> &ctx) {
|
||||
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
if constexpr (supports_sframe<E>)
|
||||
ctx.sframe->construct(ctx);
|
||||
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
create_comdat_group_sections(ctx);
|
||||
|
||||
+6
-4
@@ -32,12 +32,14 @@ namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
// We create thunks for each 25.6/3.2/6.4 MiB code block for
|
||||
// We create thunks for each 32/8/16 MiB code block for
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
static constexpr i64 batch_size = branch_distance<E> / 5;
|
||||
static constexpr i64 batch_size =
|
||||
(is_arm64<E> ? 32 : is_arm32<E> ? 8 : 16) * 1024 * 1024;
|
||||
|
||||
// We assume that a single thunk group is smaller than 1 MiB.
|
||||
static constexpr i64 max_thunk_size = 1024 * 1024;
|
||||
// We assume that a single thunk group is smaller than 16/4/8 MiB
|
||||
// for ARM64/ARM32/PPC, respectively.
|
||||
static constexpr i64 max_thunk_size = batch_size / 2;
|
||||
|
||||
// We align thunks to 16 byte boundaries because many processor vendors
|
||||
// recommend we align branch targets to 16 byte boundaries for performance
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
command -v timeout >/dev/null || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
|
||||
.section .custom_rodata_a, "a", %progbits
|
||||
.hidden A
|
||||
.global A
|
||||
A:
|
||||
.space 8
|
||||
|
||||
.section .custom_rodata_b_huge, "a", %progbits
|
||||
.balign 16384
|
||||
.hidden B
|
||||
.global B
|
||||
B:
|
||||
.space 8
|
||||
|
||||
.section .data, "aw", %progbits
|
||||
.rept 7439
|
||||
.quad A
|
||||
.endr
|
||||
|
||||
.global P_1
|
||||
P_1:
|
||||
.quad A
|
||||
|
||||
.global P_2
|
||||
P_2:
|
||||
.quad B
|
||||
EOF
|
||||
|
||||
timeout 5s ./mold -m aarch64elf -shared -o $t/b.so $t/a.o \
|
||||
--pack-dyn-relocs=android -z max-page-size=16384
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# In the REL relocation format, an addend is read from the relocated
|
||||
# place itself, and its value is signed. The Linux kernel's real-mode
|
||||
# setup code, for example, presets fields relocated with R_386_8 and
|
||||
# R_386_16 with negative values such as -1 and -512. Make sure we
|
||||
# sign-extend such in-place addends instead of zero-extending them.
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/a.o -xassembler -
|
||||
.data
|
||||
.globl x8, x16, xpc8, xpc16
|
||||
x8: .byte abs8 - 1
|
||||
x16: .short abs16 - 512
|
||||
xpc8: .byte target - . - 1
|
||||
xpc16: .short target - . - 512
|
||||
|
||||
.section .nonalloc
|
||||
.byte abs8 - 1
|
||||
.short abs16 - 512
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/b.o -xassembler -
|
||||
.globl abs8, abs16
|
||||
abs8 = 100
|
||||
abs16 = 1000
|
||||
|
||||
.data
|
||||
.globl target
|
||||
target:
|
||||
.byte 0
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -c -o $t/c.o -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
extern char target;
|
||||
extern unsigned char x8, xpc8;
|
||||
extern unsigned short x16, xpc16;
|
||||
|
||||
int main() {
|
||||
printf("%d %d %d %d\n", x8, x16,
|
||||
(signed char)xpc8 == (char *)&target - (char *)&xpc8 - 1,
|
||||
(short)xpc16 == (char *)&target - (char *)&xpc16 - 512);
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe $t/a.o $t/b.o $t/c.o
|
||||
$QEMU $t/exe | grep '^99 488 1 1$'
|
||||
|
||||
$OBJDUMP -s -j .nonalloc $t/exe | grep -w 63e801
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# When mold relaxes an instruction sequence, the relocations attached to the
|
||||
# original instructions no longer match the relaxed code. With --emit-relocs,
|
||||
# mold must rewrite them so that the emitted relocations describe the relaxed
|
||||
# instructions, both in type and in offset.
|
||||
|
||||
cat <<'EOF' | $CC -o $t/a.o -c -xassembler -
|
||||
.text
|
||||
.globl _start
|
||||
_start:
|
||||
# pcaddu18i + jirl => bl (R_LARCH_CALL36 => R_LARCH_B26)
|
||||
.reloc ., R_LARCH_RELAX
|
||||
pcaddu18i $ra, %call36(get_zero)
|
||||
jirl $ra, $ra, 0
|
||||
|
||||
# pcalau12i + addi.d => pcaddi (R_LARCH_PCALA_* => R_LARCH_PCREL20_S2)
|
||||
.reloc ., R_LARCH_RELAX
|
||||
pcalau12i $t0, %pc_hi20(v)
|
||||
.reloc ., R_LARCH_RELAX
|
||||
addi.d $t0, $t0, %pc_lo12(v)
|
||||
ld.w $t0, $t0, 0
|
||||
or $a0, $a0, $t0
|
||||
|
||||
# pcalau12i + ld.d => pcaddi (R_LARCH_GOT_PC_* => R_LARCH_PCREL20_S2)
|
||||
.reloc ., R_LARCH_RELAX
|
||||
pcalau12i $t1, %got_pc_hi20(v)
|
||||
.reloc ., R_LARCH_RELAX
|
||||
ld.d $t1, $t1, %got_pc_lo12(v)
|
||||
ld.w $t1, $t1, 0
|
||||
or $a0, $a0, $t1
|
||||
|
||||
li.w $a7, 93 # __NR_exit
|
||||
syscall 0
|
||||
|
||||
get_zero:
|
||||
li.w $a0, 0
|
||||
ret
|
||||
|
||||
.section .rodata,"a"
|
||||
.p2align 2
|
||||
.hidden v
|
||||
v:
|
||||
.word 0
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o --static -nostdlib -Wl,-e,_start,--emit-relocs,--relax
|
||||
$QEMU $t/exe
|
||||
|
||||
$OBJDUMP -dr $t/exe > $t/exe.objdump
|
||||
|
||||
# Relaxation took place.
|
||||
grep -Fw pcaddi $t/exe.objdump
|
||||
grep -Fw bl $t/exe.objdump
|
||||
|
||||
# The relocations were rewritten to match the relaxed instructions. The dead
|
||||
# high-part relocations are turned into R_LARCH_NONE.
|
||||
grep -Fw R_LARCH_PCREL20_S2 $t/exe.objdump
|
||||
grep -Fw R_LARCH_B26 $t/exe.objdump
|
||||
grep -Fw R_LARCH_NONE $t/exe.objdump
|
||||
|
||||
# The stale pre-relaxation relocations are gone.
|
||||
not grep -Fw R_LARCH_PCALA_HI20 $t/exe.objdump
|
||||
not grep -Fw R_LARCH_PCALA_LO12 $t/exe.objdump
|
||||
not grep -Fw R_LARCH_GOT_PC_HI20 $t/exe.objdump
|
||||
not grep -Fw R_LARCH_GOT_PC_LO12 $t/exe.objdump
|
||||
not grep -Fw R_LARCH_CALL36 $t/exe.objdump
|
||||
|
||||
# Each rewritten relocation must sit at the address of the relaxed instruction
|
||||
# it describes, not at its original pre-relaxation offset. This checks that the
|
||||
# relocation offsets were adjusted: R_LARCH_PCREL20_S2 must land on a pcaddi
|
||||
# and R_LARCH_B26 on a b/bl.
|
||||
awk '
|
||||
$3 == "pcaddi" { insn[$1] = $3 }
|
||||
$3 == "b" || $3 == "bl" { insn[$1] = $3 }
|
||||
$2 == "R_LARCH_PCREL20_S2" { if (insn[$1] != "pcaddi") { bad=1; print "bad offset: " $0 } }
|
||||
$2 == "R_LARCH_B26" { if (insn[$1] !~ /^bl?$/) { bad=1; print "bad offset: " $0 } }
|
||||
END { exit bad+0 }
|
||||
' $t/exe.objdump
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Depending on the symbol, mold relaxes a TLSDESC sequence into a local-exec
|
||||
# (LE) or initial-exec (IE) sequence, or keeps it as TLSDESC but folds its
|
||||
# first two instructions into a single pcaddi. With --emit-relocs, the emitted
|
||||
# relocations must be rewritten to match whichever relaxation was applied.
|
||||
|
||||
cat <<'EOF' | $CC -o $t/a.o -c -xc - -fPIC
|
||||
_Thread_local char foo[4] = "foo";
|
||||
_Thread_local char padding[100000] = "pad";
|
||||
_Thread_local char bar[4] = "bar";
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -o $t/b.o -c -xc - -fPIC -mtls-dialect=desc -O2
|
||||
extern _Thread_local char foo[4];
|
||||
extern _Thread_local char bar[4];
|
||||
char *get_foo() { return foo; }
|
||||
char *get_bar() { return bar; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc - -mtls-dialect=desc
|
||||
#include <stdio.h>
|
||||
char *get_foo();
|
||||
char *get_bar();
|
||||
int main() { printf("%s %s\n", get_foo(), get_bar()); }
|
||||
EOF
|
||||
|
||||
#
|
||||
# Local TLS in an executable: TLSDESC => LE
|
||||
#
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -Wl,--emit-relocs,--relax
|
||||
{ $QEMU $t/exe1; true; } |& grep -q 'unexpected reloc type' && skip
|
||||
$QEMU $t/exe1 | grep -F 'foo bar'
|
||||
|
||||
$OBJDUMP -dr $t/exe1 > $t/exe1.objdump
|
||||
|
||||
# foo has a small TP offset, so its sequence relaxes to a single instruction
|
||||
# with only a low-part relocation. bar has a large offset, so it keeps both
|
||||
# the high- and low-part relocations.
|
||||
grep -E $'R_LARCH_TLS_LE_LO12[ \t]+foo' $t/exe1.objdump
|
||||
not grep -E $'R_LARCH_TLS_LE_HI20[ \t]+foo' $t/exe1.objdump
|
||||
grep -E $'R_LARCH_TLS_LE_HI20[ \t]+bar' $t/exe1.objdump
|
||||
grep -E $'R_LARCH_TLS_LE_LO12[ \t]+bar' $t/exe1.objdump
|
||||
|
||||
# No TLSDESC relocation survives.
|
||||
not grep -Fw R_LARCH_TLS_DESC_PC_HI20 $t/exe1.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_PC_LO12 $t/exe1.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_LD $t/exe1.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_CALL $t/exe1.objdump
|
||||
|
||||
#
|
||||
# TLS defined in a shared library: TLSDESC => IE
|
||||
#
|
||||
$CC -B. -shared -o $t/d.so $t/a.o -Wl,--relax
|
||||
$CC -B. -o $t/exe2 $t/b.o $t/c.o $t/d.so -Wl,--emit-relocs,--relax -Wl,-rpath=$t
|
||||
$QEMU $t/exe2 | grep -F 'foo bar'
|
||||
|
||||
$OBJDUMP -dr $t/exe2 > $t/exe2.objdump
|
||||
grep -Fw R_LARCH_TLS_IE_PC_HI20 $t/exe2.objdump
|
||||
grep -Fw R_LARCH_TLS_IE_PC_LO12 $t/exe2.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_LD $t/exe2.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_CALL $t/exe2.objdump
|
||||
|
||||
#
|
||||
# Shared library output: TLSDESC is kept, but pcalau12i+addi.d => pcaddi, so
|
||||
# the low-part relocation becomes the pcaddi's TLS_DESC_PCREL20_S2 while the
|
||||
# ld.d/jirl relocations are left unchanged.
|
||||
#
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o -Wl,--emit-relocs,--relax
|
||||
$OBJDUMP -dr $t/e.so > $t/e.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_PCREL20_S2 $t/e.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_LD $t/e.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_CALL $t/e.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_PC_HI20 $t/e.objdump
|
||||
not grep -Fw R_LARCH_TLS_DESC_PC_LO12 $t/e.objdump
|
||||
|
||||
#
|
||||
# Relocatable output: no relaxation happens and TLSDESC is not downgraded, so
|
||||
# every relocation must be passed through unchanged rather than rewritten as
|
||||
# if it had been relaxed to LE/IE.
|
||||
#
|
||||
$CC -B. -o $t/reloc.o $t/b.o -r
|
||||
$OBJDUMP -r $t/reloc.o > $t/reloc.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_PC_HI20 $t/reloc.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_PC_LO12 $t/reloc.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_LD $t/reloc.objdump
|
||||
grep -Fw R_LARCH_TLS_DESC_CALL $t/reloc.objdump
|
||||
not grep -Fw R_LARCH_TLS_LE_HI20 $t/reloc.objdump
|
||||
not grep -Fw R_LARCH_TLS_LE_LO12 $t/reloc.objdump
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# pcaddi takes a 20-bit immediate that is shifted left by 2 to form
|
||||
# a PC-relative offset. Therefore, relaxing a GOT load (pcalau12i +
|
||||
# ld.d) to pcaddi is valid only when the symbol's address is 4-byte
|
||||
# aligned. Here, `foo` is placed at an odd address, so the relaxation
|
||||
# must not happen.
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xassembler -
|
||||
.data
|
||||
.byte 0
|
||||
.globl foo
|
||||
.type foo, @object
|
||||
foo:
|
||||
.byte 0x42
|
||||
.size foo, 1
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc - -fPIC -O
|
||||
extern char foo;
|
||||
int get_foo() { return foo; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int get_foo();
|
||||
int main() { printf("%d\n", get_foo()); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o $t/c.o -pie -Wl,--relax
|
||||
$QEMU $t/exe | grep '^66$'
|
||||
$OBJDUMP -d $t/exe | grep -A2 '<get_foo>:' | grep -Fw pcalau12i
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# When mold relaxes instructions, the relocations attached to the relaxed code
|
||||
# must be rewritten for --emit-relocs. A shortened call still references its
|
||||
# symbol, so R_RISCV_CALL_PLT becomes R_RISCV_JAL or R_RISCV_RVC_JUMP. A
|
||||
# relaxation that folds an address into the instruction as a link-time
|
||||
# constant (lui/addi or an AUIPC+LD GOT load) leaves the instruction without a
|
||||
# relocation, so we emit R_RISCV_NONE.
|
||||
|
||||
cat <<'EOF' | $CC -o $t/a.o -c -xassembler -
|
||||
.text
|
||||
.globl _start
|
||||
_start:
|
||||
# CALL_PLT => jal (near target)
|
||||
.reloc ., R_RISCV_RELAX
|
||||
call func
|
||||
|
||||
# lui + addi of a small absolute symbol => lui is deleted (HI20 => NONE),
|
||||
# while the low-part relocation stays.
|
||||
lui t0, %hi(small)
|
||||
.reloc ., R_RISCV_RELAX
|
||||
addi t0, t0, %lo(small)
|
||||
|
||||
# GOT load of an absolute symbol => the value is materialized directly
|
||||
# (GOT_HI20 and its paired PCREL_LO12_I both => NONE).
|
||||
got_anchor:
|
||||
.reloc ., R_RISCV_GOT_HI20, small
|
||||
.reloc ., R_RISCV_RELAX
|
||||
auipc t1, 0
|
||||
.reloc ., R_RISCV_PCREL_LO12_I, got_anchor
|
||||
.reloc ., R_RISCV_RELAX
|
||||
ld t1, 0(t1)
|
||||
|
||||
# CALL with rd=zero (tail call) to a near target => c.j (=> RVC_JUMP)
|
||||
.reloc ., R_RISCV_RELAX
|
||||
tail do_exit
|
||||
|
||||
func:
|
||||
ret
|
||||
|
||||
do_exit:
|
||||
li a0, 0
|
||||
li a7, 93 # __NR_exit
|
||||
ecall
|
||||
EOF
|
||||
|
||||
cat <<'EOF' | $CC -o $t/b.o -c -xassembler -
|
||||
.globl small
|
||||
small = 0xba
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o --static -nostdlib -Wl,-e,_start,--emit-relocs,--relax
|
||||
$QEMU $t/exe
|
||||
|
||||
$OBJDUMP -dr $t/exe > $t/exe.objdump
|
||||
|
||||
# A shortened call keeps a relocation that names the symbol.
|
||||
grep -Fw R_RISCV_JAL $t/exe.objdump
|
||||
grep -Fw R_RISCV_RVC_JUMP $t/exe.objdump
|
||||
|
||||
# Address-materializing relaxations drop their relocations.
|
||||
grep -Fw R_RISCV_NONE $t/exe.objdump
|
||||
|
||||
# The stale pre-relaxation relocations are gone.
|
||||
not grep -Fw R_RISCV_CALL_PLT $t/exe.objdump
|
||||
not grep -Fw R_RISCV_GOT_HI20 $t/exe.objdump
|
||||
not grep -Fw R_RISCV_HI20 $t/exe.objdump
|
||||
|
||||
# The low-part relocation of the lui/addi pair survives (the addi is kept).
|
||||
grep -Fw R_RISCV_LO12_I $t/exe.objdump
|
||||
|
||||
# Each rewritten call relocation must sit on the relaxed instruction it
|
||||
# describes: R_RISCV_JAL on a jal and R_RISCV_RVC_JUMP on a c.j. This checks
|
||||
# that the relocation offsets were adjusted for the deleted bytes.
|
||||
awk '
|
||||
$3 == "jal" { insn[$1] = "jal" }
|
||||
$3 == "j" || $3 == "c.j" { insn[$1] = "cj" }
|
||||
$2 == "R_RISCV_JAL" { if (insn[$1] != "jal") { bad=1; print "bad offset: " $0 } }
|
||||
$2 == "R_RISCV_RVC_JUMP" { if (insn[$1] != "cj") { bad=1; print "bad offset: " $0 } }
|
||||
END { exit bad+0 }
|
||||
' $t/exe.objdump
|
||||
|
||||
# TLS local-exec: `lui + add` that materializes TP + %tprel_hi are deleted for
|
||||
# a variable within 2 KiB of TP (TPREL_HI20 and TPREL_ADD => NONE), while the
|
||||
# access relative to TP keeps its low-part relocation.
|
||||
cat <<'EOF' | $CC -o $t/tls.o -c -xc - -O2 -fno-section-anchors
|
||||
__thread int tv;
|
||||
int main(void) { return tv; } // tv is zero-initialized, so exits 0
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/tls $t/tls.o -static -Wl,--emit-relocs,--relax
|
||||
$QEMU $t/tls
|
||||
|
||||
$OBJDUMP -dr $t/tls > $t/tls.objdump
|
||||
grep -E $'R_RISCV_TPREL_LO12_[IS][ \t]+tv' $t/tls.objdump
|
||||
not grep -E $'R_RISCV_TPREL_HI20[ \t]+tv' $t/tls.objdump
|
||||
not grep -E $'R_RISCV_TPREL_ADD[ \t]+tv' $t/tls.objdump
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Verify that the .sframe section mold produces is not just well-formed but
|
||||
# actually usable for stack unwinding. We link a program that, using only
|
||||
# its own merged .sframe (read via libsframe), walks a known call chain and
|
||||
# checks that each recovered frame maps to the expected function.
|
||||
|
||||
# Skip unless the assembler emits SFrame Version 3.
|
||||
cat <<EOF | $CC -Wa,--gsframe -o $t/probe.o -c -xc - 2>/dev/null || skip
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
readelf --sframe=.sframe $t/probe.o 2>/dev/null | grep -q SFRAME_VERSION_3 || skip
|
||||
|
||||
# Skip unless libsframe and its header are available.
|
||||
echo 'int main(){return 0;}' | \
|
||||
$CC -o $t/probe -xc - -lsframe -include sframe-api.h 2>/dev/null || skip
|
||||
|
||||
cat <<'EOF' | $CC -O0 -g -fno-omit-frame-pointer -Wa,--gsframe -B. -o $t/exe -xc - -lsframe
|
||||
#define _GNU_SOURCE
|
||||
#include <sframe-api.h>
|
||||
#include <link.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef PT_GNU_SFRAME
|
||||
#define PT_GNU_SFRAME 0x6474e554
|
||||
#endif
|
||||
|
||||
static uintptr_t sec_addr;
|
||||
static size_t sec_size;
|
||||
|
||||
static int find_cb(struct dl_phdr_info *info, size_t sz, void *u) {
|
||||
(void)sz; (void)u;
|
||||
for (int i = 0; i < info->dlpi_phnum; i++) {
|
||||
const ElfW(Phdr) *p = &info->dlpi_phdr[i];
|
||||
if (p->p_type == PT_GNU_SFRAME) {
|
||||
sec_addr = info->dlpi_addr + p->p_vaddr;
|
||||
sec_size = p->p_memsz;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static sframe_decoder_ctx *dctx;
|
||||
static int32_t fixed_ra;
|
||||
|
||||
#define MAXF 4096
|
||||
static uintptr_t fstart[MAXF], fend[MAXF];
|
||||
static int nfde;
|
||||
|
||||
static int find_fde(uintptr_t pc) {
|
||||
for (int i = 0; i < nfde; i++)
|
||||
if (fstart[i] <= pc && pc < fend[i])
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// One SFrame unwind step per loop iteration; records the start address of
|
||||
// the function each frame belongs to.
|
||||
static int unwind(uintptr_t pc, uintptr_t sp, uintptr_t fp,
|
||||
uintptr_t *out, int max) {
|
||||
int n = 0;
|
||||
while (n < max) {
|
||||
int idx = find_fde(pc);
|
||||
if (idx < 0)
|
||||
break;
|
||||
out[n++] = fstart[idx];
|
||||
|
||||
uint32_t num_fres, func_size;
|
||||
int64_t spo;
|
||||
unsigned char fi, fi2;
|
||||
uint8_t rep;
|
||||
if (sframe_decoder_get_funcdesc_v3(dctx, idx, &num_fres, &func_size,
|
||||
&spo, &fi, &fi2, &rep))
|
||||
break;
|
||||
uint32_t fde_type = SFRAME_V3_FDE_TYPE(fi2);
|
||||
|
||||
int64_t off = pc - fstart[idx];
|
||||
sframe_frame_row_entry fre, cur;
|
||||
int have = 0;
|
||||
for (uint32_t k = 0; k < num_fres; k++) {
|
||||
if (sframe_decoder_get_fre(dctx, idx, k, &cur))
|
||||
break;
|
||||
if ((int64_t)cur.fre_start_addr <= off) { fre = cur; have = 1; }
|
||||
else break;
|
||||
}
|
||||
if (!have)
|
||||
break;
|
||||
|
||||
int e = 0;
|
||||
int base = sframe_fre_get_base_reg_id(&fre, &e);
|
||||
int32_t cfa_off = sframe_fre_get_cfa_offset(dctx, &fre, fde_type, &e);
|
||||
uintptr_t cfa = (base == SFRAME_BASE_REG_SP ? sp : fp) + cfa_off;
|
||||
uintptr_t ra = *(uintptr_t *)(cfa + fixed_ra);
|
||||
|
||||
e = 0;
|
||||
int32_t fp_off = sframe_fre_get_fp_offset(dctx, &fre, fde_type, &e);
|
||||
uintptr_t next_fp = (e == 0) ? *(uintptr_t *)(cfa + fp_off) : fp;
|
||||
|
||||
if (!ra)
|
||||
break;
|
||||
sp = cfa;
|
||||
fp = next_fp;
|
||||
pc = ra;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static uintptr_t chain[16];
|
||||
static int chain_n;
|
||||
|
||||
__attribute__((noinline)) static int level3(int x) {
|
||||
uintptr_t pc, sp, fp;
|
||||
asm volatile("leaq 1f(%%rip), %0\n1:\n\t"
|
||||
"movq %%rsp, %1\n\t"
|
||||
"movq %%rbp, %2"
|
||||
: "=r"(pc), "=r"(sp), "=r"(fp));
|
||||
chain_n = unwind(pc, sp, fp, chain, 16);
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
__attribute__((noinline)) static int level2(int x) { return level3(x) + x; }
|
||||
__attribute__((noinline)) static int level1(int x) { return level2(x) + x; }
|
||||
|
||||
int main(void) {
|
||||
if (!dl_iterate_phdr(find_cb, NULL) || !sec_addr)
|
||||
return 1;
|
||||
|
||||
int err = 0;
|
||||
dctx = sframe_decode((const char *)sec_addr, sec_size, &err);
|
||||
if (!dctx)
|
||||
return 1;
|
||||
fixed_ra = sframe_decoder_get_fixed_ra_offset(dctx);
|
||||
|
||||
uint32_t num = sframe_decoder_get_num_fidx(dctx);
|
||||
for (uint32_t i = 0; i < num && nfde < MAXF; i++) {
|
||||
uint32_t num_fres, func_size;
|
||||
int64_t spo;
|
||||
unsigned char fi, fi2;
|
||||
uint8_t rep;
|
||||
if (sframe_decoder_get_funcdesc_v3(dctx, i, &num_fres, &func_size,
|
||||
&spo, &fi, &fi2, &rep))
|
||||
continue;
|
||||
int e = 0;
|
||||
uint32_t fo = sframe_decoder_get_offsetof_fde_start_addr(dctx, i, &e);
|
||||
fstart[nfde] = sec_addr + fo + spo; // PCREL: relative to the field
|
||||
fend[nfde] = fstart[nfde] + func_size;
|
||||
nfde++;
|
||||
}
|
||||
|
||||
level1(0);
|
||||
|
||||
uintptr_t want[] = { (uintptr_t)&level3, (uintptr_t)&level2,
|
||||
(uintptr_t)&level1, (uintptr_t)&main };
|
||||
int n = sizeof(want) / sizeof(want[0]);
|
||||
if (chain_n < n)
|
||||
return 1;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (chain[i] != want[i])
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
$QEMU $t/exe
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -g -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
int main() { printf("Hello world\n"); }
|
||||
EOF
|
||||
|
||||
# Level 0 stores data uncompressed; level 9 compresses hardest.
|
||||
# The level-0 executable must be larger than the level-9 one.
|
||||
$CC -B. -o $t/exe0 $t/a.o -Wl,--compress-debug-sections=zlib:0
|
||||
$CC -B. -o $t/exe9 $t/a.o -Wl,--compress-debug-sections=zlib:9
|
||||
readelf -WS $t/exe0 | grep '\.debug_info .* [Cx] '
|
||||
readelf -WS $t/exe9 | grep '\.debug_info .* [Cx] '
|
||||
[ $(wc -c < $t/exe0) -gt $(wc -c < $t/exe9) ]
|
||||
|
||||
# Out-of-range level should fail with a descriptive error
|
||||
not $CC -B. -o $t/exe-1 $t/a.o -Wl,--compress-debug-sections=zlib:-1 |&
|
||||
grep 'zlib level must be between 0 and 9'
|
||||
not $CC -B. -o $t/exe10 $t/a.o -Wl,--compress-debug-sections=zlib:10 |&
|
||||
grep 'zlib level must be between 0 and 9'
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -g -o $t/a.o -xc -
|
||||
#include <stdio.h>
|
||||
int main() { printf("Hello world\n"); }
|
||||
EOF
|
||||
|
||||
# Test zstd:1 (lowest level)
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,--compress-debug-sections=zstd:1
|
||||
readelf -WS $t/exe1 | grep '\.debug_info .* [Cx] '
|
||||
|
||||
# Test zstd:22 (highest level)
|
||||
$CC -B. -o $t/exe22 $t/a.o -Wl,--compress-debug-sections=zstd:22
|
||||
readelf -WS $t/exe22 | grep '\.debug_info .* [Cx] '
|
||||
|
||||
# Out-of-range level should fail with a descriptive error
|
||||
not $CC -B. -o $t/exe0 $t/a.o -Wl,--compress-debug-sections=zstd:0 |&
|
||||
grep 'zstd level must be between 1 and 22'
|
||||
not $CC -B. -o $t/exe23 $t/a.o -Wl,--compress-debug-sections=zstd:23 |&
|
||||
grep 'zstd level must be between 1 and 22'
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# A section whose name is a valid C identifier should be GC'd if
|
||||
# neither its contents nor its __start_/__stop_ marker symbols are
|
||||
# referenced from a live section.
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
__attribute__((section("foo"))) int dead_data = 42;
|
||||
__attribute__((section("bar"))) int live_data = 99;
|
||||
|
||||
extern int __start_bar[];
|
||||
extern int __stop_bar[];
|
||||
|
||||
int main() {
|
||||
return __stop_bar - __start_bar + live_data;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-gc-sections
|
||||
readelf --symbols $t/exe > $t/log
|
||||
|
||||
not grep dead_data $t/log
|
||||
grep live_data $t/log
|
||||
grep __start_bar $t/log
|
||||
grep __stop_bar $t/log
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
on_qemu && skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
test_cflags -gdwarf-5 -g || skip
|
||||
|
||||
# Generate many translation units that all reference common types and
|
||||
# names so the per-name CU index list contains entries from multiple
|
||||
# threads. This stresses the parallel writer in --gdb-index that fills
|
||||
# in (type, cu) tuples for each name, which must produce a deterministic
|
||||
# output regardless of thread scheduling.
|
||||
objs=
|
||||
for i in $(seq 1 24); do
|
||||
cat <<EOF > $t/u$i.c
|
||||
#include <stdio.h>
|
||||
struct s$i { int x; };
|
||||
int fn$i(struct s$i *p) { return printf("%d\n", p->x); }
|
||||
EOF
|
||||
$CC -c -o $t/u$i.o $t/u$i.c -fPIC -g -ggnu-pubnames
|
||||
objs="$objs $t/u$i.o"
|
||||
done
|
||||
|
||||
$CC -B. -shared -o $t/out1.so -Wl,--gdb-index $objs
|
||||
$CC -B. -shared -o $t/out2.so -Wl,--gdb-index $objs
|
||||
$CC -B. -shared -o $t/out3.so -Wl,--gdb-index $objs
|
||||
$CC -B. -shared -o $t/out4.so -Wl,--gdb-index $objs
|
||||
$CC -B. -shared -o $t/out5.so -Wl,--gdb-index $objs
|
||||
|
||||
cmp $t/out1.so $t/out2.so
|
||||
cmp $t/out1.so $t/out3.so
|
||||
cmp $t/out1.so $t/out4.so
|
||||
cmp $t/out1.so $t/out5.so
|
||||
@@ -63,8 +63,8 @@ $CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -ffunction-sections
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
|
||||
readelf -WS $t/e.so 2> /dev/null | grep -F .gdb_index
|
||||
readelf --debug=gdb_index $t/e.so 2> /dev/null | grep 'fn1: .* \[global, function\]'
|
||||
readelf --debug=gdb_index $t/e.so 2> /dev/null | grep 'char: .* \[static, type\]'
|
||||
readelf --debug-dump=gdb_index $t/e.so 2> /dev/null | grep 'fn1: .* \[global, function\]'
|
||||
readelf --debug-dump=gdb_index $t/e.so 2> /dev/null | grep 'char: .* \[static, type\]'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/f.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
|
||||
void fn1();
|
||||
@@ -76,7 +76,7 @@ EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/e.so $t/f.o -Wl,--gdb-index
|
||||
readelf -WS $t/exe 2> /dev/null | grep -F .gdb_index
|
||||
readelf --debug=gdb_index $t/exe 2> /dev/null | grep 'main: .* \[global, function\]'
|
||||
readelf --debug-dump=gdb_index $t/exe 2> /dev/null | grep 'main: .* \[global, function\]'
|
||||
|
||||
$QEMU $t/exe | grep 'Hello world'
|
||||
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Regression test for https://github.com/rui314/mold/issues/1578.
|
||||
# When DWARF 5 .debug_rnglists' offset table contains entries for both
|
||||
# the CU root and child DIEs, mold must read only the entry indexed by
|
||||
# the CU's DW_FORM_rnglistx. Otherwise child DIE ranges leak into the
|
||||
# .gdb_index address table, overlapping the CU's enclosing range.
|
||||
|
||||
[ $MACHINE = $(uname -m) ] || skip
|
||||
echo 'int main() {}' | clang++ -gdwarf-5 -g -o /dev/null -xc++ - 2> /dev/null || skip
|
||||
|
||||
cat <<EOF > $t/a.cc
|
||||
void may_throw(int x) {
|
||||
if (x < 0)
|
||||
throw x;
|
||||
}
|
||||
|
||||
struct Guard {
|
||||
int v;
|
||||
~Guard() { may_throw(v); }
|
||||
};
|
||||
|
||||
int main() {
|
||||
{
|
||||
Guard g{1};
|
||||
may_throw(2);
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
clang++ -B. -O0 -ggdb3 -ggnu-pubnames -gdwarf-5 \
|
||||
-Wl,--gdb-index $t/a.cc -o $t/exe
|
||||
|
||||
readelf --debug-dump=gdb_index $t/exe |
|
||||
sed -n '/^Address table:/,/^$/p' |
|
||||
grep -E '^[0-9a-f]+ [0-9a-f]+' |
|
||||
sort |
|
||||
awk 'NR > 1 && $1 < prev { exit 1 } { prev = $2 }'
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Verify that mold can link object files compiled with Clang's HWASAN
|
||||
# instrumentation. On aarch64 such objects contain relocations like
|
||||
# R_AARCH64_MOVW_PREL_G3 that mold previously rejected
|
||||
# (https://github.com/rui314/mold/issues/1592).
|
||||
[ "$CC" = cc ] || skip
|
||||
|
||||
# Disable HWASAN's global-variable tagging. It is orthogonal to the
|
||||
# relocations exercised here, and old Clang implements it by baking a tag
|
||||
# into the high bits of a 32-bit relocation's addend, producing objects
|
||||
# that no linker (mold, GNU ld or lld) can resolve.
|
||||
cat <<EOF | clang -fsanitize=hwaddress -mllvm -hwasan-globals=0 -c -o $t/a.o -xc - 2> /dev/null || skip
|
||||
#include <stdio.h>
|
||||
int main() { printf("Hello world\n"); }
|
||||
EOF
|
||||
|
||||
# Skip unless this toolchain can produce a HWASAN executable at all (e.g.
|
||||
# the sanitizer runtime may not be installed). We probe with Clang's
|
||||
# default linker so that a mold-specific failure below is still reported
|
||||
# as a test failure rather than silently skipped.
|
||||
clang -fsanitize=hwaddress -o $t/exe $t/a.o 2> /dev/null || skip
|
||||
|
||||
clang -fsanitize=hwaddress -B. -o $t/exe $t/a.o
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# This test is only applicable to x86 and x86_64 because they use the lowest
|
||||
# bit of member function pointers for virtuality.
|
||||
case $MACHINE in
|
||||
x86_64 | i686) ;;
|
||||
*) skip ;;
|
||||
esac
|
||||
|
||||
# We use assembly to define functions to have full control over alignment.
|
||||
# We swap the order so that regular_func (alignment 1) comes first and
|
||||
# is more likely to be chosen as the leader by ICF.
|
||||
# We make pre_func size 2 to try to make regular_func start at an odd address.
|
||||
cat <<EOF | $CC -c -o $t/fns.o -x assembler -
|
||||
.text
|
||||
|
||||
.section .text.pre_func, "ax", @progbits
|
||||
.globl pre_func
|
||||
.p2align 0
|
||||
pre_func:
|
||||
.byte 0x90, 0x90 # two nops (size 2)
|
||||
|
||||
.section .text.regular_func, "ax", @progbits
|
||||
.globl regular_func
|
||||
.p2align 0 # 1-byte alignment
|
||||
regular_func:
|
||||
ret
|
||||
|
||||
.section .text.member_func, "ax", @progbits
|
||||
.globl _ZN3Foo11member_funcEv
|
||||
.p2align 2 # 4-byte alignment
|
||||
_ZN3Foo11member_funcEv:
|
||||
ret
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/main.o -xc++ -
|
||||
#include <iostream>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Foo {
|
||||
void member_func();
|
||||
};
|
||||
|
||||
extern "C" void regular_func();
|
||||
|
||||
int main() {
|
||||
void (Foo::*ptr)() = &Foo::member_func;
|
||||
union {
|
||||
void (Foo::*mem_ptr)();
|
||||
struct {
|
||||
void* ptr;
|
||||
ptrdiff_t adj;
|
||||
} parts;
|
||||
} u;
|
||||
u.mem_ptr = ptr;
|
||||
|
||||
std::cout << "member_func address: " << u.parts.ptr << std::endl;
|
||||
std::cout << "regular_func address: " << (void*)®ular_func << std::endl;
|
||||
|
||||
if (u.parts.ptr != (void*)®ular_func) {
|
||||
std::cerr << "FAIL: functions were not folded!" << std::endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (reinterpret_cast<uintptr_t>(u.parts.ptr) & 1) {
|
||||
std::cerr << "FAIL: member function pointer has odd address (treated as virtual)" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe $t/main.o $t/fns.o -Wl,--icf=all
|
||||
$QEMU $t/exe
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# On PPC64V1, function pointers refer function descriptors in .opd
|
||||
# instead of directly referring .text section.
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -ffunction-sections -fPIC -xc -
|
||||
void local_fn(void) {}
|
||||
__attribute__((visibility("hidden"))) void local_fn_hidden(void) {
|
||||
local_fn();
|
||||
}
|
||||
|
||||
void global_fn(void) {}
|
||||
|
||||
void caller_local(void) {
|
||||
local_fn_hidden();
|
||||
}
|
||||
|
||||
void caller_global(void) {
|
||||
global_fn();
|
||||
}
|
||||
EOF
|
||||
|
||||
# Link as shared library with --icf=all
|
||||
$CC -B. -shared -o $t/libtest.so $t/a.o -Wl,--icf=all -Wl,--print-icf-sections > $t/out 2>&1
|
||||
|
||||
# Verify that caller_local and caller_global were NOT folded.
|
||||
# If they were folded, one of them would be removed.
|
||||
not grep -q "removing identical section.*caller_local" $t/out
|
||||
not grep -q "removing identical section.*caller_global" $t/out
|
||||
|
||||
# Also check with nm to be sure they have different addresses
|
||||
nm $t/libtest.so | grep -E 'caller_local|caller_global' | awk '{print $1}' | uniq | wc -l | grep -q 2
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
echo 'ASSERT(SIZEOF(.text) > 0, "text must not be empty")' > $t/good
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/good
|
||||
|
||||
echo 'ASSERT(SIZEOF(.text) == 0, "this should fail")' > $t/bad
|
||||
not $CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/bad |& grep 'this should fail'
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# `. = ASSERT(expr, msg);` after SECTIONS is a common idiom for a
|
||||
# standalone assertion, e.g. the Linux kernel's KERNEL_IMAGE_SIZE
|
||||
# check
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.text : { *(.text .text.*) }
|
||||
_end = .;
|
||||
}
|
||||
. = ASSERT(_end - 0x10000 < 0x100000, "image too big");
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
|
||||
cat <<'EOF' > $t/script2
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.text : { *(.text .text.*) }
|
||||
_end = .;
|
||||
}
|
||||
. = ASSERT(_end - 0x10000 > 0x100000, "this should fail");
|
||||
EOF
|
||||
|
||||
not ./mold -o $t/exe2 $t/a.o -T $t/script2 |& grep 'this should fail'
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
text_end = ADDR(.text) + SIZEOF(.text);
|
||||
big = MAX(0x100, 0x200);
|
||||
aligned = ALIGN(0x1234, 0x1000);
|
||||
cond = DEFINED(no_such_symbol) ? no_such_symbol : 7;
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script
|
||||
readelf -sW $t/exe > $t/log
|
||||
|
||||
# text_end must be relative to the .text output section, not absolute
|
||||
text=$(readelf -SW $t/exe | grep -F ' .text' | head -1 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
|
||||
grep -E "text_end" $t/log | grep -vw ABS | grep -w $text
|
||||
grep -E '200 .* ABS big' $t/log
|
||||
grep -E '2000 .* ABS aligned' $t/log
|
||||
grep -E '7 .* ABS cond' $t/log
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".mydata"))) int d = 3;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# .mydata runs at a high address but is stored (loaded) right after
|
||||
# .text, like a kernel or firmware image
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x100000;
|
||||
.text : { *(.text .text.*) }
|
||||
. = 0x800000;
|
||||
.mydata : AT(ADDR(.mydata) - 0x600000) { *(.mydata) }
|
||||
data_lma = LOADADDR(.mydata);
|
||||
data_vma = ADDR(.mydata);
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -lW $t/exe > $t/log
|
||||
|
||||
# The load segment for .mydata must have PhysAddr = VirtAddr - 0x600000
|
||||
grep -E 'LOAD .*0x0*800000 0x0*200000' $t/log
|
||||
|
||||
readelf -sW $t/exe > $t/log2
|
||||
grep -E '200000 .* data_lma' $t/log2
|
||||
grep -E '800000 .* data_vma' $t/log2
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".aa"))) int a = 1;
|
||||
__attribute__((section(".bb"))) int b = 2;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# end_aa lies exactly on the boundary of .aa and .bb. It must be
|
||||
# associated with the preceding section, as in GNU ld; tools strip
|
||||
# sections (e.g. objcopy --remove-section) together with the symbols
|
||||
# they contain.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.aa : { *(.aa) }
|
||||
end_aa = .;
|
||||
.bb : { *(.bb) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
idx=$(readelf -SW $t/exe | grep -F " .aa" | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
|
||||
readelf -sW $t/exe | grep -w end_aa | grep -w $idx
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# A section made only of data commands, padded with a fill pattern.
|
||||
# The script's own ASSERTs check the layout.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.text : { *(.text .text.*) }
|
||||
.blob : ALIGN(16) {
|
||||
blob_start = .;
|
||||
BYTE(0x12)
|
||||
SHORT(0x3456)
|
||||
LONG(0x789abcde)
|
||||
QUAD(0x1122334455667788)
|
||||
. = ALIGN(16);
|
||||
blob_end = .;
|
||||
} =0xaaaaaaaa
|
||||
ASSERT(blob_end - blob_start == 16, "bad blob size")
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
|
||||
off=$(readelf -SW $t/exe | grep -F .blob | awk '{print strtonum("0x" $6)}')
|
||||
test "$(dd if=$t/exe bs=1 skip=$off count=1 2>/dev/null | od -An -tx1 | tr -d ' ')" = 12
|
||||
# The padding byte at offset 15 comes from the fill pattern, which
|
||||
# starts anew at each gap
|
||||
test "$(dd if=$t/exe bs=1 skip=$((off + 15)) count=1 2>/dev/null | od -An -tx1 | tr -d ' ')" = aa
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
# A symbol assignment to an integer literal behaves like --defsym
|
||||
cat <<'EOF' > $t/script
|
||||
foo = 0x1234;
|
||||
bar = 256;
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script
|
||||
readelf -sW $t/exe > $t/log
|
||||
grep -E '1234 .* ABS foo' $t/log
|
||||
grep -E '100 .* ABS bar' $t/log
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".foo"))) int foo_var = 1;
|
||||
__attribute__((section(".bar.x"))) int bar_var = 2;
|
||||
__attribute__((section(".keep"))) int keep_var = 3;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
/DISCARD/ : { *(.foo) *(.bar.*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,-T,$t/script
|
||||
readelf -SW $t/exe1 > $t/log1
|
||||
not grep -F .foo $t/log1
|
||||
not grep -F .bar $t/log1
|
||||
grep -F .keep $t/log1
|
||||
|
||||
# EXCLUDE_FILE and per-file patterns restrict what is discarded
|
||||
cat <<'EOF' > $t/script2
|
||||
SECTIONS {
|
||||
/DISCARD/ : { *(EXCLUDE_FILE(*a.o) .foo) no-such-file.o(.bar.*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/script2
|
||||
readelf -SW $t/exe2 > $t/log2
|
||||
grep -F .foo $t/log2
|
||||
grep -F .bar.x $t/log2
|
||||
|
||||
# A /DISCARD/ with output section attributes is not a plain discard
|
||||
# and must be rejected
|
||||
cat <<'EOF' > $t/script3
|
||||
SECTIONS {
|
||||
/DISCARD/ 0x1000 : { *(.foo) }
|
||||
}
|
||||
EOF
|
||||
|
||||
not $CC -B. -o $t/exe3 $t/a.o -Wl,-T,$t/script3 |& grep 'cannot have output section attributes'
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".dat"), aligned(4))) int d = 7;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# Location counter arithmetic in an output section body. The script's
|
||||
# own assertions verify the layout, including a symbol defined from an
|
||||
# earlier body symbol.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.text : { *(.text .text.*) }
|
||||
.dat : ALIGN(32) {
|
||||
dat_start = .;
|
||||
. += 16;
|
||||
reserved_end = .;
|
||||
*(.dat)
|
||||
. = dat_start + 64;
|
||||
dat_end = .;
|
||||
}
|
||||
ASSERT(reserved_end - dat_start == 16, "bad reservation")
|
||||
ASSERT(dat_end - dat_start == 64, "bad section size")
|
||||
ASSERT(SIZEOF(.dat) == 64, "bad SIZEOF")
|
||||
ASSERT(ADDR(.dat) % 32 == 0, "bad alignment")
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -SW $t/exe | grep -E "\.dat .* 000040"
|
||||
|
||||
# The input section was placed after the 16-byte reservation
|
||||
readelf -sW $t/exe > $t/log
|
||||
dat=$(grep -w dat_start $t/log | awk '{print strtonum("0x" $2)}')
|
||||
d=$(grep -E " d$" $t/log | grep OBJECT | awk '{print strtonum("0x" $2)}')
|
||||
test $((d - dat)) = 16
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# A vDSO-shaped link: a shared library whose script places the
|
||||
# linker-synthesized dynamic sections and defines the program headers
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
|
||||
int foo() { return 42; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
PHDRS {
|
||||
text PT_LOAD FLAGS(5) FILEHDR PHDRS;
|
||||
dyn PT_DYNAMIC FLAGS(4);
|
||||
}
|
||||
SECTIONS {
|
||||
. = SIZEOF_HEADERS;
|
||||
.dynsym : { *(.dynsym) } :text
|
||||
.dynstr : { *(.dynstr) }
|
||||
.text : { *(.text .text.*) }
|
||||
.dynamic : { *(.dynamic) } :text :dyn
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/b.so -shared $t/a.o -T $t/script
|
||||
readelf -lW $t/b.so > $t/log
|
||||
|
||||
# The script's segments, with FILEHDR extending :text back to offset 0
|
||||
grep -E 'LOAD .*0x000000 .* R E' $t/log
|
||||
grep -E 'DYNAMIC ' $t/log
|
||||
grep -E '01 .*\.dynamic' $t/log
|
||||
|
||||
# Synthesized sections are placed where the script says: .dynsym
|
||||
# before .text, .dynamic after
|
||||
readelf -SW $t/b.so > $t/log2
|
||||
dynsym=$(grep -F ' .dynsym' $t/log2 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
|
||||
text=$(grep -wF ' .text' $t/log2 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
|
||||
dynamic=$(grep -F ' .dynamic' $t/log2 | sed 's/.*\[ *\([0-9]*\)\].*/\1/')
|
||||
test "$dynsym" -lt "$text"
|
||||
test "$text" -lt "$dynamic"
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# Sections that never materialize: their body symbols still get
|
||||
# addresses, the location counter still moves, and SIZEOF/ADDR of
|
||||
# such sections read as zero so assertions about them work.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.text : { *(.text .text.*) }
|
||||
. = 0x20000;
|
||||
.empty1 : { empty1_start = .; KEEP(*(.no_such_input)) empty1_end = .; }
|
||||
.reserve : { . += 0x100; }
|
||||
after_reserve = .;
|
||||
ASSERT(SIZEOF(.empty1) == 0, "SIZEOF of an empty section must be 0")
|
||||
ASSERT(after_reserve == 0x20100, "the reservation must move the dot")
|
||||
.data : { *(.data .data.*) }
|
||||
.bss : { *(.bss .bss.*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -sW $t/exe > $t/log
|
||||
grep -E '20000 .* empty1_start' $t/log
|
||||
grep -E '20000 .* empty1_end' $t/log
|
||||
grep -E '20100 .* after_reserve' $t/log
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
echo 'ENTRY(foo)' > $t/script
|
||||
|
||||
# ENTRY(foo) sets the entry point
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,-T,$t/script
|
||||
sym=$(readelf -sW $t/exe1 | grep -w foo | awk '{print $2}' | sed 's/^0*//')
|
||||
readelf -h $t/exe1 | grep "Entry point address:.*0x$sym"
|
||||
|
||||
# The -e option takes precedence over ENTRY()
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/script -Wl,-e,main
|
||||
sym=$(readelf -sW $t/exe2 | grep -w main | awk '{print $2}' | sed 's/^0*//')
|
||||
readelf -h $t/exe2 | grep "Entry point address:.*0x$sym"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int foo() { return 3; }
|
||||
EOF
|
||||
|
||||
rm -f $t/libfoo.a
|
||||
ar rcs $t/libfoo.a $t/a.o
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
# Without EXTERN, the archive member is not pulled out
|
||||
$CC -B. -o $t/exe1 $t/b.o $t/libfoo.a
|
||||
not grep -qw foo <(readelf -sW $t/exe1)
|
||||
|
||||
# EXTERN(foo) is equivalent to -u foo
|
||||
echo 'EXTERN(foo)' > $t/script
|
||||
$CC -B. -o $t/exe2 $t/b.o $t/libfoo.a -Wl,-T,$t/script
|
||||
readelf -sW $t/exe2 | grep -w foo
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".dat"))) char a = 1;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
__attribute__((section(".dat"))) char b = 2;
|
||||
EOF
|
||||
|
||||
# Input sections can be selected by the file they come from
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.first : { *b.o(.dat) }
|
||||
.rest : { *(.dat) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o $t/b.o -T $t/script
|
||||
readelf -SW $t/exe > $t/log
|
||||
grep -F .first $t/log
|
||||
grep -F .rest $t/log
|
||||
|
||||
off=$(readelf -SW $t/exe | grep -F .first | awk '{print strtonum("0x" $6)}')
|
||||
test "$(dd if=$t/exe bs=1 skip=$off count=1 2>/dev/null | od -An -tx1 | tr -d ' ')" = 02
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# The gap created by moving the location counter is written with the
|
||||
# fill pattern
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.pad : {
|
||||
BYTE(0x11)
|
||||
. += 15;
|
||||
BYTE(0x22)
|
||||
} =0xcafe01aa
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
|
||||
off=$(readelf -SW $t/exe | grep -F " .pad" | awk '{print strtonum("0x" $6)}')
|
||||
bytes=$(dd if=$t/exe bs=1 skip=$off count=17 2>/dev/null | od -An -tx1 | tr -d ' \n')
|
||||
# member byte, then 15 fill bytes (the big-endian pattern restarts at
|
||||
# each gap), then the second byte
|
||||
test "$bytes" = 11cafe01aacafe01aacafe01aacafe0122
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
echo 'INCLUDE inner.script' > $t/outer.script
|
||||
echo 'foo = 0xbeef;' > $t/inner.script
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/outer.script
|
||||
readelf -sW $t/exe | grep -E 'beef .* ABS foo'
|
||||
|
||||
# An error in an included file should be reported with the name of
|
||||
# the included file.
|
||||
echo 'INCLUDE bad.script' > $t/outer2.script
|
||||
echo 'NO_SUCH_COMMAND' > $t/bad.script
|
||||
|
||||
not $CC -B. -o $t/exe2 $t/a.o -Wl,-T,$t/outer2.script |& grep 'bad.script:1:'
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".tbl.20"))) char t20 = 20;
|
||||
__attribute__((section(".tbl.3"))) char t3 = 3;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# The Linux kernel idiom: KEEP'ed, sorted table sections
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.tbl : {
|
||||
tbl_start = .;
|
||||
KEEP(*(SORT_BY_INIT_PRIORITY(.tbl.*)))
|
||||
tbl_end = .;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script --gc-sections -e main
|
||||
readelf -sW $t/exe > $t/log
|
||||
|
||||
# Both survive gc, in numeric order
|
||||
a3=$(grep -E " t3$" $t/log | awk '{print strtonum("0x" $2)}')
|
||||
a20=$(grep -E " t20$" $t/log | awk '{print strtonum("0x" $2)}')
|
||||
test $a3 -lt $a20
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc - -ffunction-sections
|
||||
__attribute__((section(".keepme"))) int keep_var = 1;
|
||||
__attribute__((section(".dropme"))) int drop_var = 2;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.keepme : { KEEP(*(.keepme)) }
|
||||
.dropme : { *(.dropme) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script --gc-sections -e main
|
||||
readelf -SW $t/exe > $t/log
|
||||
|
||||
# KEEP saves an otherwise unreferenced section from --gc-sections
|
||||
grep -F .keepme $t/log
|
||||
not grep -F .dropme $t/log
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".text.main"))) int main() { return 0; }
|
||||
__attribute__((section(".rodata.str"))) const char ro[16] = "read-only";
|
||||
__attribute__((section(".data.var"))) int var = 7;
|
||||
__attribute__((section(".bss.var"))) int zero;
|
||||
EOF
|
||||
|
||||
# The classic microcontroller layout: code and constants in flash,
|
||||
# data in RAM with its initialization image stacked in flash
|
||||
cat <<'EOF' > $t/script
|
||||
MEMORY {
|
||||
flash (rx) : ORIGIN = 0x8000000, LENGTH = 128K
|
||||
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||
}
|
||||
REGION_ALIAS("rom", flash);
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) } >rom
|
||||
.rodata : { *(.rodata .rodata.*) } >flash
|
||||
.data : { *(.data .data.*) } >ram AT>flash
|
||||
.bss : { *(.bss .bss.*) } >ram
|
||||
data_lma = LOADADDR(.data);
|
||||
flash_end = ORIGIN(flash) + LENGTH(flash);
|
||||
ASSERT(data_lma + SIZEOF(.data) <= flash_end, "flash overflow")
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
|
||||
addr_of() {
|
||||
readelf -SW $1 | grep -F " $2 " | sed 's/.*BITS//' | \
|
||||
awk '{print strtonum("0x" $1)}'
|
||||
}
|
||||
|
||||
in_range() {
|
||||
test $1 -ge $(($2)) && test $1 -lt $(($3))
|
||||
}
|
||||
|
||||
# Code and constants are in flash, data and bss in RAM
|
||||
test $(addr_of $t/exe .text) = $((0x8000000))
|
||||
in_range $(addr_of $t/exe .rodata) 0x8000000 0x8020000
|
||||
in_range $(addr_of $t/exe .data) 0x20000000 0x20010000
|
||||
in_range $(addr_of $t/exe .bss) 0x20000000 0x20010000
|
||||
|
||||
# .data's load image is in flash, after the read-only sections
|
||||
readelf -sW $t/exe > $t/log2
|
||||
rodata_end=$(readelf -SW $t/exe | grep -F " .rodata " | sed 's/.*PROGBITS//' | \
|
||||
awk '{print strtonum("0x" $1) + strtonum("0x" $3)}')
|
||||
lma=$(grep -w data_lma $t/log2 | awk '{print strtonum("0x" $2)}')
|
||||
test $lma -ge $rodata_end
|
||||
test $lma -lt $((0x8020000))
|
||||
|
||||
readelf -lW $t/exe | grep -E 'LOAD .*0x0*2000[0-9a-f]{4} 0x0*8[0-9a-f]{6} .* RW'
|
||||
|
||||
# Overflowing a region must be reported
|
||||
cat <<'EOF' > $t/script2
|
||||
MEMORY {
|
||||
flash (rx) : ORIGIN = 0x8000000, LENGTH = 4
|
||||
ram (rw) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||
}
|
||||
SECTIONS { .text : { *(.text .text.*) } >flash }
|
||||
EOF
|
||||
|
||||
not ./mold -o $t/exe2 $t/a.o -T $t/script2 |& \
|
||||
grep 'overflows memory region flash'
|
||||
|
||||
# A section that names no region is placed by region attributes
|
||||
cat <<'EOF' > $t/script3
|
||||
MEMORY {
|
||||
flash (rx) : ORIGIN = 0x8000000, LENGTH = 128K
|
||||
ram (rw) : ORIGIN = 0x20000000, LENGTH = 64K
|
||||
}
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.data : { *(.data .data.*) }
|
||||
.bss : { *(.bss .bss.*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe3 $t/a.o -T $t/script3
|
||||
test $(addr_of $t/exe3 .text) = $((0x8000000))
|
||||
in_range $(addr_of $t/exe3 .data) 0x20000000 0x20010000
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -O2 -o $t/a.o -c -xc -
|
||||
const char *f() { return "hello mold script"; }
|
||||
EOF
|
||||
cat <<EOF | $CC -O2 -o $t/b.o -c -xc -
|
||||
const char *g() { return "hello mold script"; }
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# Identical string literals from different files must be merged into
|
||||
# a single copy, placed inside the output section the script names
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.text : { *(.text .text.*) }
|
||||
.rodata : { ro_start = .; *(.rodata .rodata.*) ro_end = .; }
|
||||
ASSERT(ro_end - ro_start > 0, "rodata must not be empty")
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o $t/b.o -T $t/script
|
||||
test $(readelf -p .rodata $t/exe | grep -c 'hello mold script') = 1
|
||||
|
||||
# The merged strings lie within the script's section bounds
|
||||
readelf -sW $t/exe > $t/log
|
||||
ro_start=$(grep -w ro_start $t/log | awk '{print strtonum("0x" $2)}')
|
||||
ro_end=$(grep -w ro_end $t/log | awk '{print strtonum("0x" $2)}')
|
||||
size=$(readelf -SW $t/exe | grep -F " .rodata " | sed 's/.*PROGBITS//' | \
|
||||
awk '{print strtonum("0x" $3)}')
|
||||
test $((ro_end - ro_start)) = $size
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".reserve"))) int r = 1;
|
||||
__attribute__((section(".meta"))) int m = 2;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.reserve (NOLOAD) : { *(.reserve) }
|
||||
.meta (INFO) : { *(.meta) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -SW $t/exe > $t/log
|
||||
|
||||
# A NOLOAD section becomes NOBITS; an INFO section loses SHF_ALLOC
|
||||
grep -F .reserve $t/log | grep -w NOBITS
|
||||
grep -F .meta $t/log | grep -vw A | grep -w PROGBITS
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".orphan"))) int o = 1;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.data : { *(.data .data.*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
# An orphan section is placed even though the script doesn't mention it
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -SW $t/exe | grep -F .orphan
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script --orphan-handling=warn |& grep 'placing orphan section .orphan'
|
||||
not ./mold -o $t/exe2 $t/a.o -T $t/script --orphan-handling=error |& grep 'orphan section .orphan'
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".lo"))) int lo = 1;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# Sections may be given addresses in any order
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text 0x20000 : { *(.text .text.*) }
|
||||
.lo 0x10000 : { *(.lo) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -SW $t/exe > $t/log
|
||||
grep -E '\.text .* 0*20000 ' $t/log
|
||||
grep -E '\.lo .* 0*10000 ' $t/log
|
||||
|
||||
# Loadable segments must still be sorted by address
|
||||
readelf -lW $t/exe | grep LOAD | head -1 | grep -E '0x0*10000 '
|
||||
|
||||
# Overlapping sections are an error
|
||||
cat <<'EOF' > $t/script2
|
||||
SECTIONS {
|
||||
.text 0x20000 : { *(.text .text.*) }
|
||||
.lo 0x20004 : { *(.lo) }
|
||||
}
|
||||
EOF
|
||||
|
||||
not ./mold -o $t/exe2 $t/a.o -T $t/script2 |& grep 'overlaps with .text'
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".foo"))) char foo[8] = {1};
|
||||
__attribute__((section(".mydata"))) int d = 1;
|
||||
__attribute__((section(".mydata2"))) int d2 = 2;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
PHDRS {
|
||||
text PT_LOAD FLAGS(5);
|
||||
data PT_LOAD FLAGS(6);
|
||||
note PT_NOTE FLAGS(4);
|
||||
}
|
||||
SECTIONS {
|
||||
. = 0x200000;
|
||||
.text : { *(.text .text.*) } :text
|
||||
.foo : { *(.foo) } :text :note
|
||||
. = 0x400000;
|
||||
.mydata : { *(.mydata) } :data
|
||||
.mydata2 : { *(.mydata2) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -lW $t/exe > $t/log
|
||||
|
||||
# Exactly the segments the script asks for, with the given flags
|
||||
grep -E 'LOAD .* R E' $t/log
|
||||
grep -E 'LOAD .* RW ' $t/log
|
||||
grep -E 'NOTE ' $t/log
|
||||
test $(grep -c LOAD $t/log) = 2
|
||||
|
||||
# .foo is in both :text and :note; .mydata2 inherits :data from the
|
||||
# previous section
|
||||
grep -E '00 .*\.text .*\.foo' $t/log
|
||||
grep -E '01 .*\.mydata \.mydata2' $t/log
|
||||
grep -E '02 .*\.foo' $t/log
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
|
||||
int foo = 3;
|
||||
extern char provided;
|
||||
extern char hidden_sym;
|
||||
void *use1() { return &provided; }
|
||||
void *use2() { return &hidden_sym; }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
PROVIDE(foo = 0x999);
|
||||
PROVIDE(provided = 0x42);
|
||||
PROVIDE_HIDDEN(hidden_sym = 0x43);
|
||||
PROVIDE(unreferenced = no_such_symbol);
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-T,$t/script
|
||||
readelf -sW $t/b.so > $t/log
|
||||
|
||||
# A PROVIDE'd symbol must yield to a definition in an input file
|
||||
grep -w foo $t/log | grep -v 999
|
||||
|
||||
# A referenced PROVIDE'd symbol is defined with the given value
|
||||
grep -E '42 .* ABS provided' $t/log
|
||||
grep -E '43 .* ABS hidden_sym' $t/log
|
||||
|
||||
# An unreferenced one is not defined, and its value expression is
|
||||
# not even evaluated
|
||||
not grep -w unreferenced $t/log
|
||||
|
||||
# A PROVIDE'd symbol is exported from a shared library while a
|
||||
# PROVIDE_HIDDEN'd one is not
|
||||
readelf --dyn-syms -W $t/b.so > $t/log2
|
||||
grep -w provided $t/log2
|
||||
not grep -w hidden_sym $t/log2
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fPIC -
|
||||
extern char chain_a;
|
||||
void *use() { return &chain_a; }
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# chain_a is referenced by the program; chain_b and chain_c only by
|
||||
# other PROVIDE expressions. All three must be defined transitively,
|
||||
# while unused (and its bogus expression) must not be evaluated.
|
||||
cat <<'EOF' > $t/script
|
||||
PROVIDE(chain_a = chain_b + 1);
|
||||
PROVIDE(chain_b = chain_c + 1);
|
||||
PROVIDE(chain_c = 0x1000);
|
||||
PROVIDE(unused = no_such_symbol);
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -sW $t/exe > $t/log
|
||||
grep -E '1002 .* chain_a' $t/log
|
||||
grep -E '1001 .* chain_b' $t/log
|
||||
grep -E '1000 .* chain_c' $t/log
|
||||
not grep -w unused $t/log
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Range extension thunks are not supported in script layout yet
|
||||
[[ $MACHINE = ppc* ]] && skip
|
||||
|
||||
# An executable laid out by a linker script must actually run. The
|
||||
# script places the standard sections; everything else (dynamic
|
||||
# sections, TLS, etc.) is placed as orphans.
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
__attribute__((section(".greeting"))) char msg[6] = "Hello";
|
||||
int main() { printf("%s world\n", msg); }
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x400000 + SIZEOF_HEADERS;
|
||||
.text : { *(.text .text.*) }
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
.rodata : { *(.rodata .rodata.*) }
|
||||
.greeting : { KEEP(*(.greeting)) }
|
||||
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||
.data : { *(.data .data.*) }
|
||||
.bss : { *(.bss .bss.* COMMON) }
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script -no-pie
|
||||
$QEMU $t/exe | grep 'Hello world'
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
mkdir -p $t/dir
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int foo() { return 3; }
|
||||
EOF
|
||||
|
||||
rm -f $t/dir/libfoo.a
|
||||
ar rcs $t/dir/libfoo.a $t/a.o
|
||||
|
||||
cat <<EOF | $CC -o $t/b.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int foo();
|
||||
int main() { printf("%d\n", foo()); }
|
||||
EOF
|
||||
|
||||
# SEARCH_DIR(dir) is equivalent to -L
|
||||
cat <<EOF > $t/script
|
||||
SEARCH_DIR($t/dir)
|
||||
GROUP(-lfoo)
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/b.o -Wl,-T,$t/script
|
||||
$QEMU $t/exe | grep 3
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".foo..bar"))) int a = 1;
|
||||
__attribute__((section(".foo.x"))) int b = 2;
|
||||
__attribute__((section(".foo"))) int c = 3;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# The first matching pattern in the script wins: .foo..bar must go to
|
||||
# the first output section even though *(.foo.*) also matches it.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
. = 0x10000;
|
||||
.foo..bar : { *(.foo..bar) }
|
||||
.foo : { *(.foo) *(.foo.*) }
|
||||
.text : { *(.text .text.*) }
|
||||
.data : { *(.data .data.*) }
|
||||
.bss : { *(.bss .bss.*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-T,$t/script -no-pie -nostdlib
|
||||
readelf -SW $t/exe > $t/log
|
||||
|
||||
grep -F .foo..bar $t/log
|
||||
test $(readelf -SW $t/exe | grep -F " .foo" | wc -l) = 2
|
||||
grep -E "\.foo\.\.bar.*000004" $t/log
|
||||
grep -E " \.foo .*000008" $t/log
|
||||
|
||||
# Sections appear in script order at script addresses
|
||||
adr1=$(readelf -SW $t/exe | grep -F ".foo..bar" | awk '{print $5}' | sed 's/^0*//')
|
||||
test "$adr1" = 10000
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC -
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# SIZEOF/ADDR of a section the script declares but that doesn't
|
||||
# materialize evaluate to zero. Our always-created .got placeholder
|
||||
# must not be claimed by the script's .got description, so the Linux
|
||||
# kernel's GOT assertions hold in a static link.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.got : { *(.got) }
|
||||
got_size = SIZEOF(.got);
|
||||
nowhere = ADDR(.got);
|
||||
ASSERT(SIZEOF(.got) == 0, "expected no GOT input sections")
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe $t/a.o -T $t/script
|
||||
readelf -sW $t/exe > $t/log
|
||||
grep -E ' 0 .* ABS got_size' $t/log || \
|
||||
grep -E '^.* 0{8,16} .* got_size' $t/log
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
__attribute__((section(".x.10"))) char c10 = 10;
|
||||
__attribute__((section(".x.2"))) char c2 = 2;
|
||||
__attribute__((section(".x.1"))) char c1 = 1;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# SORT_BY_NAME is lexicographic: .x.1 .x.10 .x.2
|
||||
cat <<'EOF' > $t/script1
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.x : { *(SORT_BY_NAME(.x.*)) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe1 $t/a.o -T $t/script1
|
||||
readelf -sW $t/exe1 > $t/log1
|
||||
a1=$(grep -E " c1$" $t/log1 | awk '{print strtonum("0x" $2)}')
|
||||
a2=$(grep -E " c2$" $t/log1 | awk '{print strtonum("0x" $2)}')
|
||||
a10=$(grep -E " c10$" $t/log1 | awk '{print strtonum("0x" $2)}')
|
||||
test $a1 -lt $a10 && test $a10 -lt $a2
|
||||
|
||||
# SORT_BY_INIT_PRIORITY is numeric: .x.1 .x.2 .x.10
|
||||
cat <<'EOF' > $t/script2
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.x : { *(SORT_BY_INIT_PRIORITY(.x.*)) }
|
||||
}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe2 $t/a.o -T $t/script2
|
||||
readelf -sW $t/exe2 > $t/log2
|
||||
a1=$(grep -E " c1$" $t/log2 | awk '{print strtonum("0x" $2)}')
|
||||
a2=$(grep -E " c2$" $t/log2 | awk '{print strtonum("0x" $2)}')
|
||||
a10=$(grep -E " c10$" $t/log2 | awk '{print strtonum("0x" $2)}')
|
||||
test $a1 -lt $a2 && test $a2 -lt $a10
|
||||
|
||||
# SORT_NONE keeps the input order
|
||||
cat <<'EOF2' > $t/script3
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) }
|
||||
.x : { *(SORT_NONE(.x.*)) }
|
||||
}
|
||||
EOF2
|
||||
|
||||
./mold -o $t/exe3 $t/a.o -T $t/script3
|
||||
readelf -sW $t/exe3 > $t/log3
|
||||
a2=$(grep -E " c2$" $t/log3 | awk '{print strtonum("0x" $2)}')
|
||||
a10=$(grep -E " c10$" $t/log3 | awk '{print strtonum("0x" $2)}')
|
||||
test $a10 -lt $a2
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
# Commands that mold cannot faithfully execute yet must be rejected
|
||||
# rather than silently ignored.
|
||||
cat <<'EOF' > $t/script
|
||||
SECTIONS {
|
||||
OVERLAY 0x1000 : { .o1 { *(.o1) } }
|
||||
}
|
||||
EOF
|
||||
|
||||
not ./mold $t/a.o -T $t/script -o $t/exe |& grep 'not supported yet'
|
||||
|
||||
echo 'OUTPUT(foo.exe)' > $t/script2
|
||||
not ./mold $t/a.o -T $t/script2 -o $t/exe |& grep 'not supported yet'
|
||||
|
||||
cat <<'EOF' > $t/script3
|
||||
SECTIONS {
|
||||
.text : SUBALIGN(8) { *(.text*) }
|
||||
}
|
||||
EOF
|
||||
|
||||
not ./mold $t/a.o -T $t/script3 -o $t/exe |& grep 'not supported yet'
|
||||
|
||||
# A memory region must be declared before use
|
||||
cat <<'EOF' > $t/script5
|
||||
SECTIONS {
|
||||
.text : { *(.text*) } >ram
|
||||
}
|
||||
EOF
|
||||
|
||||
not ./mold $t/a.o -T $t/script5 -o $t/exe |& grep 'no such memory region'
|
||||
|
||||
echo 'SECTIONS { .text : { *(.text*) } }' > $t/script4
|
||||
not ./mold $t/a.o -T $t/script4 -r -o $t/r.o |& grep 'not supported in relocatable'
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ "$(uname)" = FreeBSD ] && skip
|
||||
|
||||
# libfoo.so defines foo@VER_1.0 as a non-default version. Referencing
|
||||
# the bare name `foo` does NOT bind to this symbol.
|
||||
cat <<EOF > $t/libfoo.ver
|
||||
VER_1.0 {};
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libfoo.so -xc - \
|
||||
-Wl,--version-script=$t/libfoo.ver
|
||||
__asm__(".symver foo_impl,foo@VER_1.0");
|
||||
int foo_impl(void) { return 42; }
|
||||
EOF
|
||||
|
||||
# libbar.so references foo@VER_1.0 via .symver. Its dynsym holds an
|
||||
# undefined symbol with name "foo" whose versym indexes into the
|
||||
# verneed (.gnu.version_r) entry naming "VER_1.0".
|
||||
cat <<EOF | $CC -B. -shared -fPIC -o $t/libbar.so -xc - -L$t -lfoo
|
||||
__asm__(".symver foo_v1,foo@VER_1.0");
|
||||
int foo_v1(void);
|
||||
int call_foo_v1(void) { return foo_v1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -xc -
|
||||
int call_foo_v1(void);
|
||||
int main(void) { return call_foo_v1(); }
|
||||
EOF
|
||||
|
||||
# Both libs are present; libbar.so's undef foo@VER_1.0 is satisfied by
|
||||
# libfoo.so. --no-allow-shlib-undefined must accept the link.
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,--no-allow-shlib-undefined -L$t -lbar -lfoo
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Object with an entry point: used for PIE / runnable-.so cases
|
||||
cat <<EOF | $CC -fPIC -xc -c -o $t/a.o -
|
||||
void _start(void) {}
|
||||
EOF
|
||||
|
||||
# Object without an entry point: used for the regular shared case
|
||||
cat <<EOF | $CC -fPIC -xc -c -o $t/b.o -
|
||||
int foo(void) { return 42; }
|
||||
EOF
|
||||
|
||||
DL=/lib/ld.so
|
||||
|
||||
# Control: bare -shared (no --dynamic-linker) must NOT emit PT_INTERP or .interp
|
||||
./mold -shared -o $t/control.so $t/b.o
|
||||
readelf -l $t/control.so | not grep INTERP
|
||||
readelf -S $t/control.so | not grep '\.interp'
|
||||
|
||||
# Bug case: -shared with --dynamic-linker must NOT emit PT_INTERP or .interp
|
||||
./mold -shared --dynamic-linker=$DL -o $t/regular.so $t/b.o
|
||||
readelf -l $t/regular.so | not grep INTERP
|
||||
readelf -S $t/regular.so | not grep '\.interp'
|
||||
|
||||
# Regression: -pie executable still gets PT_INTERP and .interp
|
||||
./mold -pie --dynamic-linker=$DL -e _start -o $t/pie-exe $t/a.o
|
||||
readelf -l $t/pie-exe | grep INTERP
|
||||
readelf -S $t/pie-exe | grep '\.interp'
|
||||
|
||||
# Regression: -shared -pie (runnable .so) still gets PT_INTERP and .interp -- BFD parity
|
||||
./mold -shared -pie --dynamic-linker=$DL -e _start -o $t/runnable.so $t/a.o
|
||||
readelf -l $t/runnable.so | grep INTERP
|
||||
readelf -S $t/runnable.so | grep '\.interp'
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -fcommon -xc -c -o $t/a.o -
|
||||
int foo;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fcommon -xc -c -o $t/b.o -
|
||||
int foo;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# Control: -warn-common emits a warning by default
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common 2> $t/log
|
||||
grep -q "multiple common symbols" $t/log
|
||||
|
||||
# -w suppresses the warning
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,-w 2> $t/log
|
||||
not grep -q "multiple common symbols" $t/log
|
||||
|
||||
# --no-warnings does the same
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,--no-warnings 2> $t/log
|
||||
not grep -q "multiple common symbols" $t/log
|
||||
|
||||
# Sanity: -fatal-warnings without -w would have failed
|
||||
not $CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,-fatal-warnings 2> /dev/null
|
||||
|
||||
# -w cancels -fatal-warnings (lld parity)
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-warn-common -Wl,-fatal-warnings -Wl,-w
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# Skip if llvm-readelf isn't available; we use it to decode APS2.
|
||||
command -v llvm-readelf >/dev/null || skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -fPIC -c -xc -
|
||||
extern int x, y, z;
|
||||
void *ptrs[] = { &x, &y, &z, &x, &y, &z };
|
||||
int x = 1, y = 2, z = 3;
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
|
||||
# Probe whether the host toolchain can produce a working PIE for this $CC.
|
||||
$CC -o $t/probe $t/a.o -pie 2> /dev/null || skip
|
||||
|
||||
# --pack-dyn-relocs=android: all dynrels packed into APS2 .rela.dyn.
|
||||
$CC -B. -o $t/exe1 $t/a.o -pie -Wl,--pack-dyn-relocs=android
|
||||
llvm-readelf -SW $t/exe1 | grep -Ew '\.rela?\.dyn' | grep -E 'ANDROID_RELA?'
|
||||
llvm-readelf --dynamic $t/exe1 | grep -E 'ANDROID_RELA?\b'
|
||||
llvm-readelf --dynamic $t/exe1 | grep -E 'ANDROID_RELA?SZ'
|
||||
# llvm-readelf decodes APS2 back to individual relocations. Check the
|
||||
# section was decoded with a non-zero count (we don't grep for a specific
|
||||
# type name because llvm-readelf prints "Unknown" for relocation types
|
||||
# of arches it doesn't recognize, e.g. SH4/M68K).
|
||||
llvm-readelf -r $t/exe1 | grep -E "'\.rela?\.dyn'.*contains [1-9]"
|
||||
|
||||
# --pack-dyn-relocs=android+relr: R_RELATIVE -> .relr.dyn, others -> APS2.
|
||||
$CC -B. -o $t/exe2 $t/a.o -pie -Wl,--pack-dyn-relocs=android+relr
|
||||
llvm-readelf -SW $t/exe2 | grep -Ew '\.rela?\.dyn' | grep -E 'ANDROID_RELA?'
|
||||
llvm-readelf -SW $t/exe2 | grep -F .relr.dyn
|
||||
llvm-readelf --dynamic $t/exe2 | grep -E 'ANDROID_RELA?\b'
|
||||
llvm-readelf --dynamic $t/exe2 | grep -Ew 'RELR'
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
# Test if grep supports backreferences
|
||||
echo abab | grep -E '(ab)\1' || skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((section("foo"))) int bar;
|
||||
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -o $t/exe1 $t/a.o -Wl,--image-base=0x200000 \
|
||||
-Wl,--physical-image-base=0x800000
|
||||
|
||||
$QEMU $t/exe1 | grep 'Hello world'
|
||||
|
||||
readelf -W --segments $t/exe1 | grep -E 'LOAD\s+0x000000 0x0*200000 0x0*800000'
|
||||
readelf -Ws $t/exe1 | grep __phys_start_foo
|
||||
|
||||
|
||||
$CC -B. -no-pie -o $t/exe2 $t/a.o -Wl,--physical-image-base=0x800000 \
|
||||
-Wl,--section-order='=0x800000 TEXT RODATA =0x900000 DATA BSS'
|
||||
|
||||
readelf -W --segments $t/exe2 | grep -E 'LOAD\s+\S+\s+(\S+)\s\1.*R E 0'
|
||||
readelf -W --segments $t/exe2 | grep -E 'LOAD\s+\S+\s+(\S+)\s\1.*R 0'
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC -
|
||||
__attribute__((section(".sum"))) __attribute__((aligned(8192))) void sum() {};
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o
|
||||
lines=$(readelf -Wl $t/exe1 | grep "LOAD")
|
||||
|
||||
while IFS= read -ra line; do
|
||||
read -ra row <<< "$line"
|
||||
offset=${row[1]}
|
||||
vaddr=${row[2]}
|
||||
align=${row[-1]}
|
||||
if (( offset % align != vaddr % align )); then
|
||||
false
|
||||
fi
|
||||
done <<< "$lines"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
# Regression test for https://github.com/rui314/mold/issues/1538.
|
||||
#
|
||||
# On targets that use range extension thunks (ARM and PPC), mold's first
|
||||
# thunk-creation pass pessimistically routes every PLT-resolved call
|
||||
# through a thunk. If a single batch of input sections references a huge
|
||||
# number of distinct imported functions, that first-pass thunk group can
|
||||
# grow past max_thunk_size and trip an assertion in
|
||||
# create_range_extension_thunks(). 50,000 imports exceed the historical
|
||||
# 1 MiB cap on AArch64 (32 bytes per thunk entry), so this exercises the
|
||||
# large-thunk-group path and checks that every call reaches its target.
|
||||
n=50000
|
||||
|
||||
seq 1 $n | sed 's/.*/int func&(void) { return &; }/' > $t/a.c
|
||||
$CC -B. -o $t/b.so -shared $t/a.c
|
||||
|
||||
echo '#include <stdio.h>' > $t/b.c
|
||||
seq 1 $n | sed 's/.*/int func&(void);/' >> $t/b.c
|
||||
echo 'int main() {' >> $t/b.c
|
||||
echo ' int sum = 0;' >> $t/b.c
|
||||
seq 1 $n | sed 's/.*/ sum += func&();/' >> $t/b.c
|
||||
echo ' printf("%d\n", sum);' >> $t/b.c
|
||||
echo '}' >> $t/b.c
|
||||
|
||||
$CC -c -o $t/c.o $t/b.c
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
|
||||
sum=$((n * (n + 1) / 2))
|
||||
$QEMU $t/exe | grep -E "^$sum\$"
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIC $flags -
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((section(".fn1"))) void fn1() { printf(" fn1"); }
|
||||
__attribute__((section(".fn2"))) void fn2() { printf(" fn2"); }
|
||||
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe1 $t/a.o -no-pie \
|
||||
-Wl,--section-order='=0x100000 EHDR PHDR =0x200000 .fn2 TEXT =0x300000 .fn1 DATA BSS RODATA'
|
||||
$QEMU $t/exe1 | grep Hello
|
||||
|
||||
readelf -SW $t/exe1 | grep '\.fn2 .*00200000'
|
||||
readelf -SW $t/exe1 | grep '\.fn1 .*00300000'
|
||||
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -no-pie \
|
||||
-Wl,--section-order='=0x200000 EHDR RODATA =0x300000 PHDR =0x400000 .fn2 TEXT DATA BSS'
|
||||
|
||||
readelf -SW $t/exe2 | grep '\.fn2 .*00400000'
|
||||
readelf -sW $t/exe2 | grep -E ': 0+200000\s.*\s__ehdr_start$'
|
||||
readelf -W --segments $t/exe2 | grep -E 'PHDR\s.*0x0+300000\s'
|
||||
|
||||
|
||||
$CC -B. -o $t/exe3 $t/a.o -no-pie \
|
||||
-Wl,--section-order='=0x200000 !ehdr_start EHDR %0x20 !rodata_start RODATA =0x300000 !phdr_start PHDR %4096 !phdr_end =0x400000 !text_start TEXT DATA BSS'
|
||||
|
||||
readelf -sW $t/exe3 > $t/log3
|
||||
grep -E '\b0+200000 .* ehdr_start$' $t/log3
|
||||
grep -E '\b0+200040 .* rodata_start$' $t/log3
|
||||
grep -E '\b0+300000 .* phdr_start$' $t/log3
|
||||
grep -E '\b0+301000 .* phdr_end$' $t/log3
|
||||
grep -E '\b0+400000 .* text_start$' $t/log3
|
||||
|
||||
not $CC -B. -o $t/exe4 $t/a.o -no-pie \
|
||||
-Wl,--section-order='=0x800000 EHDR PHDR =0x200000 .fn2 TEXT =0x300000 .fn1 DATA BSS RODATA' |&
|
||||
grep 'address goes backward'
|
||||
@@ -36,7 +36,7 @@ gdb $t/exe1 -ex 'list main' -ex 'quit' | grep -F return1
|
||||
# See issue #1535 for more details
|
||||
readelf -W --sections $t/exe1.dbg | not grep -E '[EPS]HDR'
|
||||
readelf -W --symbols $t/exe1.dbg | not grep -E '<corrupt>'
|
||||
readelf -W --symbols $t/exe1.dbg | not grep -E '^ *[0-9][0-9]:.*UND *$'
|
||||
readelf -W --symbols $t/exe1.dbg | grep -vw REGISTER | not grep -E '^ *[0-9][0-9]:.*UND *$'
|
||||
|
||||
|
||||
$CC -c -o $t/a.o $t/a.c -g -I$t
|
||||
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# .sframe is a compact stack-unwinding format. The linker parses the
|
||||
# .sframe sections in the input files, drops the entries for dead
|
||||
# functions, concatenates the rest, sorts the resulting index by PC and
|
||||
# rewrites the header. mold handles SFrame Version 3.
|
||||
|
||||
# Skip if the assembler cannot emit SFrame Version 3.
|
||||
cat <<EOF | $CC -Wa,--gsframe -o $t/probe.o -c -xc - 2>/dev/null || skip
|
||||
int main() { return 0; }
|
||||
EOF
|
||||
readelf --sframe=.sframe $t/probe.o 2>/dev/null | grep -q SFRAME_VERSION_3 || skip
|
||||
|
||||
# Compile two translation units, each with several functions, so that the
|
||||
# linker has to merge and sort more than one .sframe section.
|
||||
cat <<EOF | $CC -O1 -Wa,--gsframe -o $t/a.o -c -xc -
|
||||
int foo(int x) { return x + 1; }
|
||||
int bar(int x) { return foo(x) * 2; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -O1 -Wa,--gsframe -o $t/b.o -c -xc -
|
||||
int foo(int);
|
||||
int bar(int);
|
||||
int baz(int x) { return foo(x) - bar(x); }
|
||||
int main(void) { return baz(7) + 8; }
|
||||
EOF
|
||||
|
||||
$CC -B. -Wa,--gsframe -o $t/exe $t/a.o $t/b.o
|
||||
$QEMU $t/exe
|
||||
|
||||
# The output is a single Version 3 section whose index is sorted by PC.
|
||||
readelf --sframe=.sframe $t/exe > $t/log
|
||||
grep -q SFRAME_VERSION_3 $t/log
|
||||
grep -q SFRAME_F_FDE_SORTED $t/log
|
||||
|
||||
# A PT_GNU_SFRAME segment must point at the section.
|
||||
readelf -Wl $t/exe | grep -q GNU_SFRAME
|
||||
|
||||
# Verify that the FDEs really are sorted by function address.
|
||||
prev=0
|
||||
for pc in $(grep -oE 'pc = 0x[0-9a-f]+' $t/log | grep -oE '0x[0-9a-f]+'); do
|
||||
[ $((pc)) -ge $prev ]
|
||||
prev=$((pc))
|
||||
done
|
||||
|
||||
# --gc-sections must drop the FDEs of collected functions.
|
||||
cat <<EOF | $CC -O0 -ffunction-sections -Wa,--gsframe -o $t/c.o -c -xc -
|
||||
int used(int x) { return x; }
|
||||
int unused(int x) { return x + 1; }
|
||||
int main(void) { return used(0); }
|
||||
EOF
|
||||
|
||||
$CC -B. -Wa,--gsframe -ffunction-sections -o $t/exe2 $t/c.o
|
||||
$CC -B. -Wa,--gsframe -ffunction-sections -Wl,--gc-sections -o $t/exe3 $t/c.o
|
||||
$QEMU $t/exe3
|
||||
|
||||
n1=$(readelf --sframe=.sframe $t/exe2 | grep -c 'func idx')
|
||||
n2=$(readelf --sframe=.sframe $t/exe3 | grep -c 'func idx')
|
||||
[ "$n2" -lt "$n1" ]
|
||||
|
||||
# A relocatable link (-r) must merge the inputs into a single .sframe and
|
||||
# keep each func_start as a relocation for the final link to resolve.
|
||||
./mold --relocatable -o $t/r.o $t/a.o $t/b.o
|
||||
readelf --sframe=.sframe $t/r.o > $t/rlog
|
||||
grep -q SFRAME_VERSION_3 $t/rlog
|
||||
[ "$(readelf -SW $t/r.o | grep -cw GNU_SFRAME)" = 1 ]
|
||||
readelf -SW $t/r.o | grep -q '\.rela\.sframe'
|
||||
|
||||
# The relocatable object must link into a working executable that still
|
||||
# describes every function from the original inputs.
|
||||
$CC -B. -Wa,--gsframe -o $t/exe4 $t/r.o
|
||||
$QEMU $t/exe4
|
||||
[ "$(grep -c 'func idx' $t/rlog)" -le "$(readelf --sframe=.sframe $t/exe4 | grep -c 'func idx')" ]
|
||||
+2
-2
@@ -16,13 +16,13 @@ int main() {
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -o $t/exe1 $t/a.o -pie
|
||||
$CC -o $t/exe1 $t/a.o -pie -Wl,-z,notext
|
||||
$QEMU $t/exe1 || skip
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -pie
|
||||
$QEMU $t/exe2 | grep 'Hello world'
|
||||
|
||||
$CC -o $t/exe3 $t/a.o -pie -Wl,-z,pack-relative-relocs 2> /dev/null || skip
|
||||
$CC -o $t/exe3 $t/a.o -pie -Wl,-z,notext -Wl,-z,pack-relative-relocs 2> /dev/null || skip
|
||||
readelf -WS $t/exe3 | grep -F .relr.dyn || skip
|
||||
$QEMU $t/exe3 2> /dev/null | grep 'Hello world' || skip
|
||||
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -fPIC -c -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
printf("Hello world\n");
|
||||
}
|
||||
EOF
|
||||
|
||||
# Probe whether the host toolchain can link a PIE; skip if not.
|
||||
$CC -o $t/probe $t/a.o -pie 2> /dev/null || skip
|
||||
|
||||
# Default: standard DT_RELR tags (35/36/37).
|
||||
$CC -B. -o $t/exe1 $t/a.o -pie -Wl,--pack-dyn-relocs=relr
|
||||
readelf -SW $t/exe1 | grep -F .relr.dyn
|
||||
readelf --dynamic $t/exe1 > $t/log1
|
||||
grep -Ew 'RELR|<unknown>: 24' $t/log1
|
||||
grep -Ew 'RELRSZ|<unknown>: 23' $t/log1
|
||||
grep -Ew 'RELRENT|<unknown>: 25' $t/log1
|
||||
|
||||
# --use-android-relr-tags: legacy DT_ANDROID_RELR* tags (in the
|
||||
# OS-specific range), for compatibility with pre-Android-12 loaders
|
||||
# that don't recognize the standard tags. Match by raw hex value because
|
||||
# older readelf doesn't know the symbolic names.
|
||||
$CC -B. -o $t/exe2 $t/a.o -pie \
|
||||
-Wl,--pack-dyn-relocs=relr -Wl,--use-android-relr-tags
|
||||
readelf -SW $t/exe2 | grep -F .relr.dyn
|
||||
readelf --dynamic $t/exe2 > $t/log2
|
||||
grep -E '0x0*6fffe000\b' $t/log2
|
||||
grep -E '0x0*6fffe001\b' $t/log2
|
||||
grep -E '0x0*6fffe003\b' $t/log2
|
||||
# Make sure the standard tags are NOT emitted in this mode.
|
||||
not grep -Ew 'RELR|<unknown>: 24' $t/log2
|
||||
not grep -Ew 'RELRSZ|<unknown>: 23' $t/log2
|
||||
not grep -Ew 'RELRENT|<unknown>: 25' $t/log2
|
||||
|
||||
# --no-use-android-relr-tags reverts to the standard tags.
|
||||
$CC -B. -o $t/exe3 $t/a.o -pie \
|
||||
-Wl,--pack-dyn-relocs=relr \
|
||||
-Wl,--use-android-relr-tags -Wl,--no-use-android-relr-tags
|
||||
readelf --dynamic $t/exe3 > $t/log3
|
||||
grep -Ew 'RELR|<unknown>: 24' $t/log3
|
||||
@@ -7,6 +7,7 @@ local:
|
||||
*;
|
||||
global:
|
||||
[abc][^abc][^\]a-zABC];
|
||||
[abc][!abc][!\]a-zABC]_;
|
||||
};
|
||||
EOF
|
||||
|
||||
@@ -15,12 +16,20 @@ void azZ() {}
|
||||
void czZ() {}
|
||||
void azC() {}
|
||||
void aaZ() {}
|
||||
void azZ_() {}
|
||||
void czZ_() {}
|
||||
void azC_() {}
|
||||
void aaZ_() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/c.so > $t/log
|
||||
grep ' azZ' $t/log
|
||||
grep ' czZ' $t/log
|
||||
not grep ' azC' $t/log
|
||||
not grep ' aaZ' $t/log
|
||||
grep ' azZ$' $t/log
|
||||
grep ' czZ$' $t/log
|
||||
not grep ' azC$' $t/log
|
||||
not grep ' aaZ$' $t/log
|
||||
grep ' azZ_$' $t/log
|
||||
grep ' czZ_$' $t/log
|
||||
not grep ' azC_$' $t/log
|
||||
not grep ' aaZ_$' $t/log
|
||||
Reference in new issue
Block a user