mirror of
https://github.com/rui314/mold.git
synced 2026-11-12 02:01:25 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9a8205be2 | ||
|
|
0bbc1808cc | ||
|
|
cd3d82c89e | ||
|
|
52c8e09ddb | ||
|
|
c214bfc96b | ||
|
|
7b8d7a6417 | ||
|
|
cfb7c268f5 | ||
|
|
285ea7ba26 | ||
|
|
603a831f02 | ||
|
|
edfe9f30f2 | ||
|
|
5228fa2d78 | ||
|
|
e4e267cc8b | ||
|
|
75ac73809b | ||
|
|
1b89d52504 | ||
|
|
b05a671789 | ||
|
|
30222dfb3f | ||
|
|
91aca3b7cf | ||
|
|
610e839e9e | ||
|
|
dcbc85b412 | ||
|
|
7133f8a547 | ||
|
|
792773be20 | ||
|
|
13816a14e1 | ||
|
|
8f02509ba6 | ||
|
|
87163c667a | ||
|
|
e45bebd1c7 | ||
|
|
d0cc19735d | ||
|
|
2c9e97c768 | ||
|
|
114cb8e9c8 | ||
|
|
2af30acbb2 | ||
|
|
6f9eedf98a | ||
|
|
e60bf30513 | ||
|
|
610ce3c307 | ||
|
|
f3e1f728d3 | ||
|
|
6f04ef9cf2 | ||
|
|
880204ebe5 | ||
|
|
406d8786c9 | ||
|
|
e11e68d508 | ||
|
|
3af44d08db | ||
|
|
d5b5b9afe1 | ||
|
|
0b537ae54c | ||
|
|
b364b3f453 | ||
|
|
ea31281149 | ||
|
|
ce34654924 | ||
|
|
2d34ba13cb | ||
|
|
eee315ea52 | ||
|
|
d2d73cbcbc | ||
|
|
c786f55370 | ||
|
|
8ff4bd5e0f | ||
|
|
169a22d643 | ||
|
|
4a8cda75ad | ||
|
|
1ebaf5be84 | ||
|
|
a05aae7d7a | ||
|
|
f9ea7890a8 | ||
|
|
0488d95f38 | ||
|
|
6d0d19dffb | ||
|
|
e3e0593162 | ||
|
|
7511aee558 | ||
|
|
6a3c35d97c | ||
|
|
032b4d4cc5 | ||
|
|
3fb8b483b4 | ||
|
|
e14788102c | ||
|
|
96d9859f45 | ||
|
|
56e6c038dc | ||
|
|
1f5da3d313 | ||
|
|
fc1aed3e99 | ||
|
|
4b754348f5 | ||
|
|
eee1a87742 | ||
|
|
1b52d228d4 | ||
|
|
3842222956 | ||
|
|
dce00d511b | ||
|
|
0de862191b | ||
|
|
485877a826 | ||
|
|
7478c27ce8 | ||
|
|
a5c216310b | ||
|
|
c55c98c4f2 | ||
|
|
6b3fc59143 | ||
|
|
6779b2bdfe | ||
|
|
7097946eee | ||
|
|
67ec8f3376 | ||
|
|
0723476fa5 | ||
|
|
a50173c259 | ||
|
|
280782b3ae | ||
|
|
5621394dc3 | ||
|
|
fa420c38d4 | ||
|
|
ae33fd278f | ||
|
|
68a779f76b | ||
|
|
f730dde6e7 | ||
|
|
8cf514408b | ||
|
|
72c8446eb7 | ||
|
|
f5d85e854f | ||
|
|
4e845f3972 | ||
|
|
03af112272 | ||
|
|
66c1433be1 | ||
|
|
621e2edd65 | ||
|
|
03045cb2d0 | ||
|
|
a572b803d2 | ||
|
|
7c52c41d7b | ||
|
|
21fea57d99 | ||
|
|
965fa17b7b | ||
|
|
826bb56d59 | ||
|
|
3f12a843a6 | ||
|
|
e895351481 | ||
|
|
1eac1e6cb4 | ||
|
|
0fab94639f | ||
|
|
348f2bb9da | ||
|
|
2f78d2eb94 | ||
|
|
4826d393d1 | ||
|
|
b82aa99bca | ||
|
|
080ebb9ee4 | ||
|
|
4f77af8e76 | ||
|
|
cffcea8d44 | ||
|
|
4622b6b14c | ||
|
|
395b962c3d | ||
|
|
f0f194e373 | ||
|
|
45615c9d81 | ||
|
|
d9e2669063 | ||
|
|
d31b63e48b | ||
|
|
5612636fe4 | ||
|
|
be19241630 | ||
|
|
2f243d2f52 | ||
|
|
8911458961 | ||
|
|
4aeee88ef8 | ||
|
|
936d3b3c30 | ||
|
|
0a89a07e6d | ||
|
|
79654bfde0 | ||
|
|
379ff3d781 | ||
|
|
c797408167 | ||
|
|
20ad945fbf | ||
|
|
702ec961ff | ||
|
|
5e4f8924fd | ||
|
|
f55fd7d9f2 | ||
|
|
ae29091aa6 | ||
|
|
e9f6715c6e | ||
|
|
7730cb3d25 | ||
|
|
c36291be60 | ||
|
|
c1164cda77 | ||
|
|
e770df42d9 | ||
|
|
14c717d698 | ||
|
|
b32bc66901 | ||
|
|
9e09a21eac | ||
|
|
f5c8d7509e | ||
|
|
95164b3556 | ||
|
|
4d06199e0d | ||
|
|
bd292ef782 | ||
|
|
b626d74a5f | ||
|
|
e7d4d2c39b | ||
|
|
48c9157fb1 | ||
|
|
e37fe163b7 | ||
|
|
4ca2d82da8 | ||
|
|
2850c5cfe6 | ||
|
|
75df1c82f1 | ||
|
|
754b995e1c | ||
|
|
bafa43d4c7 | ||
|
|
760f39d129 | ||
|
|
99b622ffd1 | ||
|
|
7b097e51c9 | ||
|
|
16735e1e68 | ||
|
|
18e24a6e4d | ||
|
|
29e15384ed | ||
|
|
229f610825 | ||
|
|
e9def90e9a | ||
|
|
f844179fd3 | ||
|
|
ccf6b0aa33 | ||
|
|
ca3cd7c3fa | ||
|
|
b5587d930e | ||
|
|
4c034da565 | ||
|
|
a1711b97b3 | ||
|
|
e293d56cc1 | ||
|
|
7f84e4242c | ||
|
|
ffb18422c1 | ||
|
|
33e00f2fb5 | ||
|
|
5c3bbb801d | ||
|
|
def3c95b7f | ||
|
|
bd3fa9a3db | ||
|
|
ef053c41fe | ||
|
|
03cda81af6 | ||
|
|
51f99db269 | ||
|
|
94c100c211 | ||
|
|
943cb99a84 | ||
|
|
eb90d1bcbb | ||
|
|
7156ef7daa | ||
|
|
fb89e06e2b | ||
|
|
6b77349b4a | ||
|
|
2c5582b9e9 | ||
|
|
7ce2c7edb5 | ||
|
|
b5bfe4c607 | ||
|
|
b9827f29e7 | ||
|
|
3ec1e8d9df | ||
|
|
55eb8dee4d | ||
|
|
da22677bf9 | ||
|
|
c654d42c14 | ||
|
|
4b945c839f | ||
|
|
e4b8fa3c26 | ||
|
|
6d67614004 | ||
|
|
1052b93c4f | ||
|
|
0692db3481 | ||
|
|
68f4891107 | ||
|
|
37a50f8996 | ||
|
|
352ab1a3f9 | ||
|
|
519de432c7 | ||
|
|
4d380ec645 | ||
|
|
0326d66b24 | ||
|
|
f392b4aeb3 | ||
|
|
a21a79e846 | ||
|
|
ca6d4a53c4 | ||
|
|
66e585eebe | ||
|
|
deebb497d3 | ||
|
|
466e93ea46 | ||
|
|
3008879d23 | ||
|
|
e8166bcb1b | ||
|
|
27908af67d | ||
|
|
c9288c5b92 | ||
|
|
188f17524f | ||
|
|
2b86786419 | ||
|
|
bcc8375958 | ||
|
|
f0bb588e32 | ||
|
|
3cf8fc4b35 | ||
|
|
64f0ba4a9a | ||
|
|
611fa3c7a4 | ||
|
|
69d23bc10f | ||
|
|
a6c3f3c082 | ||
|
|
64b8168916 | ||
|
|
27409530d7 | ||
|
|
b1c0a13386 | ||
|
|
fcb3bedf0d | ||
|
|
e68fdc396e | ||
|
|
a98a19acfa | ||
|
|
1a8ebf89db | ||
|
|
f086145fc4 | ||
|
|
8cbcd9777f | ||
|
|
2e47fe5815 | ||
|
|
61d19e65e2 | ||
|
|
c64fc2ea6d | ||
|
|
5b4c698a9c | ||
|
|
7f3a05ad0f | ||
|
|
a5c7c98af8 | ||
|
|
00148f4d5e | ||
|
|
9459b0f4f5 | ||
|
|
ee96875fcc | ||
|
|
4bc287ff48 | ||
|
|
73a9b6ec7d | ||
|
|
5cf365b4f5 | ||
|
|
5a239ef4b2 | ||
|
|
344c027a91 | ||
|
|
c1409d95a4 | ||
|
|
adb1ee4c0a | ||
|
|
5e389f99d4 | ||
|
|
c054e3d197 | ||
|
|
53fd03e115 | ||
|
|
99155920af | ||
|
|
05acf9da2c | ||
|
|
4ef90d4316 | ||
|
|
4d61d462ef | ||
|
|
d68c7eb5e4 | ||
|
|
3c284f09ab | ||
|
|
f59bd5ea23 | ||
|
|
b6e9c6da66 | ||
|
|
f8296ba0fc | ||
|
|
90e0228601 | ||
|
|
15eb278ab1 | ||
|
|
c1ba1707fc | ||
|
|
4ae6091442 | ||
|
|
5c63d2e031 | ||
|
|
166a67d5ee | ||
|
|
6b5c47882a | ||
|
|
b8b6d0d3ae | ||
|
|
144c156dc6 | ||
|
|
edc1c68efb | ||
|
|
e6446b3931 | ||
|
|
d1ade871f0 | ||
|
|
72ef8fc73a | ||
|
|
abaceb458f | ||
|
|
d22cdf8540 | ||
|
|
7f9d16e971 | ||
|
|
f48895cbf3 | ||
|
|
bd12a98533 | ||
|
|
ab63994c97 | ||
|
|
537a062d25 | ||
|
|
5d7064c01e | ||
|
|
f822898c2f | ||
|
|
b2cf48de33 | ||
|
|
188a0f1b3c | ||
|
|
e4d6c8c382 | ||
|
|
f787e99f16 | ||
|
|
128b345b9a | ||
|
|
4c758a62b1 | ||
|
|
2a672254a2 | ||
|
|
159d89036e | ||
|
|
407846a8c6 | ||
|
|
d8a8877875 | ||
|
|
448a9ec4e3 | ||
|
|
4819d3dc53 | ||
|
|
f8361f6389 | ||
|
|
596926008a | ||
|
|
9fbd71ec6b | ||
|
|
727d34d727 | ||
|
|
6d1f67fc59 | ||
|
|
6910f8e590 | ||
|
|
ca25f1fd20 | ||
|
|
0dc5ed38ed | ||
|
|
3694ffe61d | ||
|
|
2d3b410a3c | ||
|
|
38f2b965db | ||
|
|
cde4b0fb45 | ||
|
|
ed9a29b713 | ||
|
|
06cdd7e951 | ||
|
|
232dafa8d0 | ||
|
|
31ba99a7cd | ||
|
|
885b5c0e10 | ||
|
|
cd54cacb1b | ||
|
|
eb7433f3ac | ||
|
|
86ad2c5b4d | ||
|
|
68bcc8ea40 | ||
|
|
44228d91b0 | ||
|
|
dbf1040245 | ||
|
|
f223b9c292 | ||
|
|
3e18883b4b | ||
|
|
5090ad6111 | ||
|
|
11cbf9571b | ||
|
|
a15f387526 | ||
|
|
d56ba46cb2 | ||
|
|
dd8e15952f | ||
|
|
22a8b3095c | ||
|
|
5d68c800ed | ||
|
|
a5ff3c8287 | ||
|
|
06405073a9 | ||
|
|
0abd0a40db | ||
|
|
7cfc894914 | ||
|
|
230976a139 | ||
|
|
74ad5da477 | ||
|
|
d0d7f948f0 | ||
|
|
da7b5db350 | ||
|
|
bf902e1dd2 | ||
|
|
12cfddc611 | ||
|
|
1d8c0567a8 | ||
|
|
a9f2a8ddfe | ||
|
|
b5a1d3ace2 | ||
|
|
3499ee6ac7 | ||
|
|
eae062f815 | ||
|
|
2ce5e22193 | ||
|
|
c1c20aa427 | ||
|
|
af592f303c | ||
|
|
d0fc8fb005 | ||
|
|
a85b1f539a | ||
|
|
d3130f3648 | ||
|
|
f48825decb | ||
|
|
920266bce4 | ||
|
|
d714819759 | ||
|
|
517cce0371 | ||
|
|
e5bcbdf487 | ||
|
|
62b3a3dca9 | ||
|
|
fafb75b0e5 | ||
|
|
43cd7a4035 | ||
|
|
708ad63474 | ||
|
|
3f545317c9 | ||
|
|
c8d8f86a52 | ||
|
|
f1f2d40469 | ||
|
|
a15b73a6ee | ||
|
|
bcac1a3c53 | ||
|
|
a63c25b020 | ||
|
|
668b17b3fa | ||
|
|
3b9dc73210 | ||
|
|
1bd48be605 | ||
|
|
eca94d9081 | ||
|
|
615687b2ed | ||
|
|
ee95df673b | ||
|
|
e429773794 | ||
|
|
e89ec11427 | ||
|
|
43ab68b95d | ||
|
|
3455e8bd82 | ||
|
|
9899769713 | ||
|
|
8b584f8297 | ||
|
|
e088db72e0 | ||
|
|
8298c0ad16 | ||
|
|
f378fdc495 | ||
|
|
897f969017 | ||
|
|
17907964ac | ||
|
|
9431704230 | ||
|
|
1b5735543f | ||
|
|
886695d914 | ||
|
|
0fd6a6ca90 | ||
|
|
d391fd9a59 | ||
|
|
face536c39 | ||
|
|
3015f5e418 | ||
|
|
fc9f9e570e | ||
|
|
f1e925d416 | ||
|
|
a761f6a75c | ||
|
|
ea600a752b | ||
|
|
fa6d0c33d7 | ||
|
|
b516828b37 | ||
|
|
5a2589bd94 | ||
|
|
4f4bfdbba0 | ||
|
|
a830fcdb58 | ||
|
|
fd8a6a2c5d | ||
|
|
ac244ede10 | ||
|
|
5d4e7a9e31 | ||
|
|
1cce39f113 | ||
|
|
f9768c6352 | ||
|
|
fe134ab3ab | ||
|
|
f97ff51a72 | ||
|
|
b05bf30e57 | ||
|
|
6baef60a48 | ||
|
|
b101bfbf5f | ||
|
|
f3adbe7c8f | ||
|
|
a8c4c71dd7 | ||
|
|
eb5de9c12f | ||
|
|
23218e7ae2 | ||
|
|
8c5e4df741 | ||
|
|
92505f4aea | ||
|
|
d1a723a53a | ||
|
|
de45e4af6d | ||
|
|
ad3487376c | ||
|
|
3b3333a27f | ||
|
|
9c865b976b | ||
|
|
494b28cfb3 | ||
|
|
d4d1f2c697 | ||
|
|
00381ea64b | ||
|
|
9781d35aba | ||
|
|
ead0c96187 | ||
|
|
eed62bd9d4 | ||
|
|
6d5c44ab58 | ||
|
|
5b28bfd6c8 | ||
|
|
bad0cbc72a | ||
|
|
b6fb244f14 | ||
|
|
1c84145810 | ||
|
|
f945077e42 | ||
|
|
e0dd53b4ad | ||
|
|
267a1ff5b7 | ||
|
|
93cefc5280 | ||
|
|
b3e0955d88 | ||
|
|
c7339c33e0 | ||
|
|
1ac4030556 | ||
|
|
478871c06e | ||
|
|
d18d971962 | ||
|
|
b26e1a3c32 | ||
|
|
f12a9bb5ff | ||
|
|
cf850f8993 | ||
|
|
ec062f7241 | ||
|
|
fc798bc4ea | ||
|
|
bd65e0908f | ||
|
|
8a5a9e1ffe | ||
|
|
c6dace2335 | ||
|
|
32e0863675 | ||
|
|
8398d592de | ||
|
|
fa2ff3b9e9 | ||
|
|
3fb93c7cc8 | ||
|
|
8dddd98dfd | ||
|
|
17f16d016c | ||
|
|
21e1a80cec | ||
|
|
d4cc0ed3fb | ||
|
|
a4398c7c32 | ||
|
|
7ebd071273 | ||
|
|
369e8a19c5 | ||
|
|
d73587033f | ||
|
|
70bd1adbab | ||
|
|
f675faade2 | ||
|
|
3c895abefa | ||
|
|
ff1b97bcdb | ||
|
|
19ad74ac52 | ||
|
|
45df585268 | ||
|
|
deb212bbfc | ||
|
|
1fa6b3cd17 | ||
|
|
b0596ab8fb | ||
|
|
1e59bb518a | ||
|
|
efcbb02e83 | ||
|
|
d8a12490f5 | ||
|
|
b5a75c4215 | ||
|
|
5803c3c200 | ||
|
|
c90801e629 | ||
|
|
8073a92614 | ||
|
|
c4c7e45886 | ||
|
|
6a6ae217a3 | ||
|
|
30a0fac952 | ||
|
|
133946af0c | ||
|
|
c6ae4234b8 | ||
|
|
1f55e40a8a |
No files matched your search
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rui314] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
+24
-17
@@ -4,22 +4,28 @@ on:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
MOLD_SUPPRESS_MACHO_WARNING: 1
|
||||
jobs:
|
||||
build-clang:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- test
|
||||
- test-asan
|
||||
- test-tsan
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: rui314/setup-mold@v1
|
||||
with:
|
||||
make-default: false
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: apt-get
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install git build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git bsdmainutils dwarfdump pkg-config
|
||||
run: sudo apt-get update && sudo apt-get install git build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libtbb-dev git bsdmainutils dwarfdump
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=clang CXX=clang++ LDFLAGS=-fuse-ld=mold -j$(nproc)
|
||||
- name: make test
|
||||
run: make -j$(nproc) test
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(nproc) CC=clang CXX=clang++ ${{ matrix.target }}
|
||||
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
@@ -27,7 +33,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
run: |
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang bsdmainutils dwarfdump qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev cmake clang bsdmainutils dwarfdump qemu-user gcc-10-i686-linux-gnu gcc-10-aarch64-linux-gnu gcc-10-riscv64-linux-gnu gcc-10-arm-linux-gnueabihf g++-10-i686-linux-gnu g++-10-aarch64-linux-gnu g++-10-riscv64-linux-gnu g++-10-arm-linux-gnueabihf
|
||||
ln -sf /usr/bin/i686-linux-gnu-gcc-10 /usr/bin/i686-linux-gnu-gcc
|
||||
ln -sf /usr/bin/i686-linux-gnu-g++-10 /usr/bin/i686-linux-gnu-g++
|
||||
ln -sf /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc
|
||||
@@ -38,19 +44,20 @@ jobs:
|
||||
ln -sf /usr/bin/riscv64-linux-gnu-g++-10 /usr/bin/riscv64-linux-gnu-g++
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make CC=gcc CXX=g++ -j$(nproc)
|
||||
- name: make test
|
||||
run: make -j$(nproc) test-all
|
||||
- name: make test-all
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(nproc) CC=gcc CXX=g++ test-all
|
||||
|
||||
build-macos:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- test
|
||||
- test-asan
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: brew
|
||||
run: brew install xxhash
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu)
|
||||
- name: make test
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu) test
|
||||
run: PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" make -j$(sysctl -n hw.logicalcpu) ${{ matrix.target }}
|
||||
@@ -8,6 +8,7 @@ compile_commands.json
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get install -y --no-install-recommends build-essential git \
|
||||
wget pkg-config cmake libstdc++-11-dev zlib1g-dev gpg gpg-agent && \
|
||||
wget cmake libstdc++-11-dev zlib1g-dev gpg gpg-agent && \
|
||||
bash -c "$(wget -O- https://apt.llvm.org/llvm.sh)" && \
|
||||
apt-get install -y --no-install-recommends clang-14 && \
|
||||
apt clean && \
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
# If you want to enable ASAN, run `make` with the following options:
|
||||
#
|
||||
# make CXXFLAGS='-fsanitize=address -g' LDFLAGS=-fsanitize=address USE_MIMALLOC=0
|
||||
|
||||
VERSION = 1.2.0
|
||||
VERSION = 1.3.0
|
||||
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
@@ -22,9 +18,6 @@ ifeq ($(origin CXX), default)
|
||||
CXX = c++
|
||||
endif
|
||||
|
||||
# Allow overriding pkg-config binary
|
||||
PKG_CONFIG = pkg-config
|
||||
|
||||
# If you want to keep symbols in the installed binary, run make with
|
||||
# `STRIP=true` to run /bin/true instead of the strip command.
|
||||
STRIP = strip
|
||||
@@ -51,7 +44,11 @@ MOLD_CXXFLAGS := -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
|
||||
MOLD_LDFLAGS := -pthread -lz -lm -ldl
|
||||
|
||||
GIT_HASH := $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
# Get a hash of the current git head. We don't want to use the git
|
||||
# command because the command prints out a warning if running under
|
||||
# sudo.
|
||||
GIT_HASH := $(shell [ -f .git/HEAD ] && if grep -q '^ref:' .git/HEAD; then cat .git/`sed 's/^ref: //' .git/HEAD`; else cat .git/HEAD; fi)
|
||||
|
||||
ifneq ($(GIT_HASH),)
|
||||
MOLD_CXXFLAGS += -DGIT_HASH=\"$(GIT_HASH)\"
|
||||
endif
|
||||
@@ -97,19 +94,17 @@ ifeq ($(OS), Linux)
|
||||
endif
|
||||
endif
|
||||
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
ifneq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
NEEDS_LIBCRYPTO = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
NEEDS_LIBCRYPTO = 0
|
||||
endif
|
||||
|
||||
ifeq ($(NEEDS_LIBCRYPTO), 1)
|
||||
MOLD_CXXFLAGS += $(shell $(PKG_CONFIG) --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell $(PKG_CONFIG) --libs-only-L openssl) -lcrypto
|
||||
MOLD_LDFLAGS += -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on riscv64 system
|
||||
# RV32 system doesn't tested yet
|
||||
# seems like '-atomic' would be better but not working.
|
||||
# '-latomic' flag is needed building on riscv64 system.
|
||||
# Seems like '-atomic' would be better but not working.
|
||||
ifeq ($(ARCH), riscv64)
|
||||
MOLD_LDFLAGS += -latomic
|
||||
endif
|
||||
@@ -132,7 +127,7 @@ all: mold mold-wrapper.so
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64.mold
|
||||
ln -sf mold ld64
|
||||
|
||||
mold-wrapper.so: elf/mold-wrapper.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
@@ -167,33 +162,22 @@ endif
|
||||
echo 'Passed all tests'; \
|
||||
fi
|
||||
|
||||
test-x86-64: all
|
||||
@echo x86_64
|
||||
CC=x86_64-linux-gnu-gcc CXX=x86_64-linux-gnu-g++ GCC=x86_64-linux-gnu-gcc GXX=x86_64-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=x86_64 QEMU='qemu-x86_64 -L /usr/x86_64-linux-gnu' $(MAKE) test
|
||||
test-arch:
|
||||
TEST_CC=${TRIPLE}-gcc \
|
||||
TEST_CXX=${TRIPLE}-g++ \
|
||||
TEST_GCC=${TRIPLE}-gcc \
|
||||
TEST_GXX=${TRIPLE}-g++ \
|
||||
OBJDUMP=${TRIPLE}-objdump \
|
||||
MACHINE=${MACHINE} \
|
||||
QEMU="qemu-${MACHINE} -L /usr/${TRIPLE}" \
|
||||
$(MAKE) test
|
||||
|
||||
test-i386: all
|
||||
@echo i386
|
||||
CC=i686-linux-gnu-gcc CXX=i686-linux-gnu-g++ GCC=i686-linux-gnu-gcc GXX=i686-linux-gnu-g++ OBJDUMP=x86_64-linux-gnu-objdump MACHINE=i386 QEMU='qemu-i386 -L /usr/i686-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm64: all
|
||||
@echo arm64
|
||||
CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GCC=aarch64-linux-gnu-gcc GXX=aarch64-linux-gnu-g++ OBJDUMP=aarch64-linux-gnu-objdump MACHINE=aarch64 QEMU='qemu-aarch64 -L /usr/aarch64-linux-gnu' $(MAKE) test
|
||||
|
||||
test-arm32: all
|
||||
@echo arm
|
||||
CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ GCC=arm-linux-gnueabihf-gcc GXX=arm-linux-gnueabihf-g++ OBJDUMP=arm-linux-gnueabihf-objdump MACHINE=arm QEMU='qemu-arm -L /usr/arm-linux-gnueabihf' $(MAKE) test
|
||||
|
||||
test-riscv64: all
|
||||
@echo riscv64
|
||||
CC=riscv64-linux-gnu-gcc CXX=riscv64-linux-gnu-g++ GCC=riscv64-linux-gnu-gcc GXX=riscv64-linux-gnu-g++ OBJDUMP=riscv64-linux-gnu-objdump MACHINE=riscv64 QEMU='qemu-riscv64 -L /usr/riscv64-linux-gnu' $(MAKE) test
|
||||
|
||||
|
||||
test-all:
|
||||
$(MAKE) test-x86-64
|
||||
$(MAKE) test-i386
|
||||
$(MAKE) test-arm64
|
||||
$(MAKE) test-arm32
|
||||
$(MAKE) test-riscv64
|
||||
test-all: all
|
||||
$(MAKE) test-arch TRIPLE=x86_64-linux-gnu MACHINE=x86_64
|
||||
$(MAKE) test-arch TRIPLE=i686-linux-gnu MACHINE=i386
|
||||
$(MAKE) test-arch TRIPLE=aarch64-linux-gnu MACHINE=aarch64
|
||||
$(MAKE) test-arch TRIPLE=arm-linux-gnueabihf MACHINE=arm
|
||||
$(MAKE) test-arch TRIPLE=riscv64-linux-gnu MACHINE=riscv64
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
@@ -205,7 +189,12 @@ install: all
|
||||
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
|
||||
|
||||
$(INSTALL) -d $D$(LIBEXECDIR)/mold
|
||||
ln -sf $(BINDIR)/mold $D$(LIBEXECDIR)/mold/ld
|
||||
|
||||
# We want to make a symblink with a relative path, so that users can
|
||||
# move the entire directory to other place without breaking the reference.
|
||||
# GNU ln supports `--relative` to do that, but that's not supported by
|
||||
# non-GNU systems. So we use Python to compute a relative path.
|
||||
ln -sf `python3 -c "import os.path; print(os.path.relpath('$(BINDIR)/mold', '$(LIBEXECDIR)/mold'))"` $D$(LIBEXECDIR)/mold/ld
|
||||
|
||||
$(INSTALL) -d $D$(MANDIR)/man1
|
||||
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
|
||||
@@ -218,7 +207,13 @@ uninstall:
|
||||
rm -f $D$(MANDIR)/man1/mold.1
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold mold-*-linux.tar.gz
|
||||
test-asan test-ubsan:
|
||||
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=address -fsanitize=undefined -O0 -g' LDFLAGS='-fsanitize=address -fsanitize=undefined' test
|
||||
|
||||
.PHONY: all test tests check clean test-x86-64 test-i386 test-arm64 test-arm32 test-riscv64 test-all
|
||||
test-tsan:
|
||||
$(MAKE) USE_MIMALLOC=0 CXXFLAGS='-fsanitize=thread -O0 -g' LDFLAGS=-fsanitize=thread test
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64 mold-*-linux.tar.gz
|
||||
|
||||
.PHONY: all test tests check clean test-arch test-all test-asan test-ubsan test-tsan
|
||||
@@ -27,8 +27,8 @@ mold currently supports x86-64, i386, ARM32, ARM64 and 64-bit RISC-V.
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
If you are using a compiled language such as C, C++ or Rust, a build
|
||||
consists of two phases. In the first phase, a compiler compiles a
|
||||
source file into an object file (`.o` files). In the second phase,
|
||||
consists of two phases. In the first phase, a compiler compiles
|
||||
source files into object files (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
|
||||
@@ -39,7 +39,7 @@ most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
|
||||
## Install
|
||||
|
||||
Binary packages for the following distros are currently available.
|
||||
Binary packages for the following systems are currently available.
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
@@ -47,32 +47,21 @@ Binary packages for the following distros are currently available.
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you need a
|
||||
recent version of a C++ compiler and a C++ standard library. GCC 10.2
|
||||
or Clang 12.0.0 as well as libstdc++ 10 or libc++ 7 are recommended.
|
||||
|
||||
I'm using Ubuntu 20.04 as a development platform. In that environment,
|
||||
you can build mold by the following commands.
|
||||
or Clang 12.0.0 (or later) as well as libstdc++ 10 or libc++ 7 (or
|
||||
later) are recommended.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
#### Ubuntu 20.04 and later / Debian 11 and later
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential git clang cmake libstdc++-10-dev libssl-dev libxxhash-dev zlib1g-dev pkg-config
|
||||
```
|
||||
|
||||
#### Fedora 34 and later
|
||||
|
||||
```shell
|
||||
sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstdc++-devel
|
||||
```
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It recognizes your Linux distribution and tries to install
|
||||
necessary packages. You may want to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
git checkout v1.2.0
|
||||
git checkout v1.3.0
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
```
|
||||
@@ -86,19 +75,10 @@ By default, `mold` is installed to `/usr/local/bin`.
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`make` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of running `make -j$(nproc)`. The shell script creates a
|
||||
Ubuntu 18.04 Docker image, installs necessary tools and libraries to it,
|
||||
builds mold and auxiliary files, and packs them into a single tar file
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use `mold` executable in it.
|
||||
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
```shell
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt update
|
||||
sudo apt-get install bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-10-aarch64-linux-gnu g++-10-aarch64-linux-gnu
|
||||
```
|
||||
directory instead of running `make -j$(nproc)`. The shell script pulls a
|
||||
Docker image, builds mold and auxiliary files inside it, and packs
|
||||
them into a single tar file `mold-$version-$arch-linux.tar.gz`.
|
||||
You can extract the tar file anywhere and use `mold` executable in it.
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -115,7 +95,7 @@ following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 (upcoming version) or later: pass `-fuse-ld=mold`
|
||||
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
@@ -123,7 +103,7 @@ following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your $PREFIX), and `ld` command should be there. The
|
||||
depending on your `$PREFIX`), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
@@ -170,12 +150,14 @@ replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Action</summary>
|
||||
If you want to use mold in your GitHub-hosted CI to speed up continuous
|
||||
build, you can use <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action. GitHub runs a CI on a two-core machine, but mold is
|
||||
still significantly faster than the default GNU linker there
|
||||
especially when a program being linked is large.
|
||||
<details><summary>GitHub Actions</summary>
|
||||
|
||||
You can use our <a href=https://github.com/rui314/setup-mold>setup-mold</a>
|
||||
GitHub Action to speed up GitHub-hosted continuous build. GitHub Actions
|
||||
runs on a two-core machine, but mold is still significantly faster than
|
||||
the default GNU linker there especially when a program being linked is
|
||||
large.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
@@ -184,7 +166,7 @@ mold leaves its identification string in `.comment` section in an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```shell
|
||||
readelf -p .comment <executable-file>
|
||||
$ readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
[ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
|
||||
@@ -214,6 +196,21 @@ artificially capped to 16 so that the bars fit in the GIF.
|
||||
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
# Logo
|
||||
## License
|
||||
|
||||
-
|
||||
mold is available under AGPL. Note that that does not mean that you
|
||||
have to license your program under AGPL if you use mold to link your
|
||||
program. An output of the mold linker is a derived work of the object
|
||||
files and libraries you pass to the linker but not a derived work of
|
||||
the mold linker itself.
|
||||
|
||||
Besides that, you can also buy a commercial, non-AGPL license with
|
||||
technical support from our company, Blue Whale Systems PTE LTD. If you
|
||||
are a big company, please consider obtaining it before making hundreds
|
||||
or thousands of developers of your company to depend on mold. mold is
|
||||
mostly a single-person open-source project, and just like other
|
||||
open-source projects, we are not legally obligated to keep maintaining
|
||||
it. A legally-binding commercial license contract addresses the
|
||||
concern. By purchasing a license, you are guaranteed that mold will be
|
||||
maintained for you. Please [contact us](mailto:[email protected])
|
||||
for a commercial license inquiry.
|
||||
@@ -1,3 +1,28 @@
|
||||
// This file contains functions to read an archive file (.a file).
|
||||
// An archive file is just a bundle of object files. It's similar to
|
||||
// tar or zip, but the contents are not compressed.
|
||||
//
|
||||
// An archive file is either "regular" or "thin". A regular archive
|
||||
// contains object files directly, while a thin archive contains only
|
||||
// pathnames. In the latter case, actual file contents have to be read
|
||||
// from given pathnames. A regular archive is sometimes called "fat"
|
||||
// archive as opposed to "thin".
|
||||
//
|
||||
// If an archive file is given to the linker, the linker pulls out
|
||||
// object files that are needed to resolve undefined symbols. So,
|
||||
// bunding object files as an archive and giving that archive to the
|
||||
// linker has a different meaning than directly giving the same set of
|
||||
// object files to the linker. The former links only needed object
|
||||
// files, while the latter links all the given object files.
|
||||
//
|
||||
// Therefore, if you link libc.a for example, not all the libc
|
||||
// functions are linked to your binary. Instead, only object files
|
||||
// that provides functions and variables used in your program get
|
||||
// linked. To make this efficient, static library functions are
|
||||
// usually separated to each object file in an archive file. You can
|
||||
// see the contents of libc.a by runnning `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
@@ -93,6 +118,7 @@ read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile<C>::must_open(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return __builtin_bswap16(x);
|
||||
else if constexpr (sizeof(T) == 4)
|
||||
return __builtin_bswap32(x);
|
||||
else
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
};
|
||||
|
||||
using ibig16 = BigEndian<int16_t>;
|
||||
using ibig32 = BigEndian<int32_t>;
|
||||
using ibig64 = BigEndian<int64_t>;
|
||||
using ubig16 = BigEndian<uint16_t>;
|
||||
using ubig32 = BigEndian<uint32_t>;
|
||||
using ubig64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -48,6 +48,7 @@ read_response_file(C &ctx, std::string_view path) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename C>
|
||||
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
@@ -60,4 +61,16 @@ std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::string_view string_trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+6
-5
@@ -89,7 +89,8 @@ static std::vector<u8> do_compress(std::string_view input) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(std::string_view input) {
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u64> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
@@ -134,7 +135,7 @@ void ZlibCompressor::write_to(u8 *buf) {
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
*(ubig32 *)(end - 4) = checksum;
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
GzipCompressor::GzipCompressor(std::string_view input) {
|
||||
@@ -183,10 +184,10 @@ void GzipCompressor::write_to(u8 *buf) {
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + size();
|
||||
end[-10] = 0x3; // two-byte zlib stream terminator
|
||||
end[-10] = 3; // two-byte zlib stream terminator
|
||||
end[-9] = 0;
|
||||
*(u32 *)(end - 8) = checksum;
|
||||
*(u32 *)(end - 4) = uncompressed_size;
|
||||
*(ul32 *)(end - 8) = checksum;
|
||||
*(ul32 *)(end - 4) = uncompressed_size;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -9,19 +9,19 @@
|
||||
|
||||
set -e
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
# Unlike Linux, macOS's uname returns arm64 for aarch64.
|
||||
arch=$(uname -m)
|
||||
dest=mold-$version-$arch-linux
|
||||
[ $arch = arm64 ] && arch=aarch64
|
||||
|
||||
if [ $arch = x86_64 ]; then
|
||||
image=rui314/mold-builder:v1-x86_64
|
||||
elif [ $arch = aarch64 -o $arch = arm64 ]; then
|
||||
image=rui314/mold-builder:v1-aarch64
|
||||
else
|
||||
if [ $arch != x86_64 -a $arch != aarch64 ]; then
|
||||
echo "Error: no docker image for $arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$(grep '^VERSION =' $(dirname $0)/Makefile | sed 's/.* = //')
|
||||
dest=mold-$version-$arch-linux
|
||||
image=rui314/mold-builder:v1-$arch
|
||||
|
||||
docker images -q $image 2> /dev/null || docker pull $image
|
||||
|
||||
docker run -it --rm -v "$(pwd):/mold:Z" -u "$(id -u):$(id -g)" $image \
|
||||
@@ -30,6 +30,6 @@ cd /tmp/mold &&
|
||||
make clean &&
|
||||
make -j\$(nproc) CXX=clang++-14 CXXFLAGS='-I/openssl/include -O2 $CXXFLAGS' LDFLAGS='-static-libstdc++ /openssl/libcrypto.a' NEEDS_LIBCRYPTO=0 LTO=${LTO:-0} &&
|
||||
make install PREFIX=/ DESTDIR=$dest &&
|
||||
ln -sfr $dest/bin/mold $dest/libexec/mold/ld &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
cp mold /mold"
|
||||
cp mold /mold &&
|
||||
cp mold-wrapper.so /mold"
|
||||
+83
-7
@@ -473,6 +473,20 @@ Set
|
||||
.Ar dir
|
||||
to root directory.
|
||||
.Pp
|
||||
.It Fl -color-diagnostics Ns = Ns Op Sy auto | always | never
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -no-color-diagnostics
|
||||
.Pp
|
||||
Show diagnostics messages in color using ANSI escape sequences.
|
||||
.Ar auto
|
||||
means that
|
||||
.Nm
|
||||
prints out messages in color only if the standard output is connected to a TTY. \
|
||||
Default is
|
||||
.Ar auto .
|
||||
.Pp
|
||||
.It Fl -defsym Ns = Ns Ar symbol Ns = Ns Ar value
|
||||
.Pp
|
||||
.It Fl -compress-debug-sections Ns = Ns Op Sy none | zlib | zlib-gabi | zlib-gnu
|
||||
Compress DWARF debug info
|
||||
.Pf ( Sy .debug_*
|
||||
@@ -555,6 +569,10 @@ Mark all symbols in the given
|
||||
.Ar libraries
|
||||
hidden.
|
||||
.Pp
|
||||
.It Fl -fatal-warnings
|
||||
.It Fl -no-fatal-warnings
|
||||
Treat warnings as errors.
|
||||
.Pp
|
||||
.It Fl -fini Ns = Ns Ar symbol
|
||||
Call
|
||||
.Ar symbol
|
||||
@@ -574,12 +592,67 @@ hides that latency.
|
||||
.It Fl -no-gc-sections
|
||||
Remove unreferenced sections.
|
||||
.Pp
|
||||
.It Fl -gdb-index
|
||||
Create a
|
||||
.Li .gdb_index
|
||||
section to speed up GNU debugger. To use this, you need to compile source files \
|
||||
with the
|
||||
.Fl -ggnu-pubnames
|
||||
compiler flag.
|
||||
.Pp
|
||||
.It Fl -hash-style Ns = Ns Op Sy sysv | gnu | both
|
||||
Set hash style.
|
||||
.Pp
|
||||
.It Fl -icf Ns = Ns Sy all
|
||||
.It Fl -icf Ns = Ns Op Sy none | safe | all
|
||||
.It Fl -no-icf
|
||||
Fold identical code.
|
||||
It is not uncommon for a program to contain many identical functions that differ \
|
||||
only in name. For example, a C++ template
|
||||
.Sy std::vector
|
||||
is very likely to be instantiated to the identical code for
|
||||
.Sy std::vector<int>
|
||||
and
|
||||
.Sy std::vector<unsigned>
|
||||
because the container cares only about the size of the parameter type. \
|
||||
Identical Code Folding (ICF) is a size optimization to identify and merge \
|
||||
such identical functions.
|
||||
.Pp
|
||||
If
|
||||
.Fl -icf=all
|
||||
is given,
|
||||
.Nm
|
||||
tries to merge all identical functions. This reduces the size of the output \
|
||||
most, but it is not
|
||||
.Dq safe
|
||||
optimization. It is guaranteed in C and C++ that two pointeres pointing two \
|
||||
different functions will never be equal, but
|
||||
.Fl -icf=all
|
||||
breaks that assumption as two functions have the same address after merging. \
|
||||
So a care must be taken when you use that flag that your program does not \
|
||||
depend on the function pointer uniqueness.
|
||||
.Pp
|
||||
.Fl -icf=safe
|
||||
is a flag to merge functions only when it is safe to do so. That is, if a \
|
||||
program does not take an address of a function, it is safe to merge that \
|
||||
function with other function, as you cannot compare a function pointer \
|
||||
with something else without taking an address of a function.
|
||||
.FL -icf=safe
|
||||
needs to be used with a compiler that supports
|
||||
.Sy .llvm_addrsig
|
||||
section which contains the information as to what symbols are address-taken. \
|
||||
LLVM/Clang supports that section by default. Since GCC does not suppor it yet, \
|
||||
you cannot use
|
||||
.Fl -icf=safe
|
||||
with GCC (it doesn't do any harm but can't optimize at all.)
|
||||
.Pp
|
||||
.Fl -icf=none
|
||||
and
|
||||
.Fl -no-icf
|
||||
disables ICF.
|
||||
.Pp
|
||||
.It Fl -ignore-data-address-equality
|
||||
Make ICF to merge not only functions but also data. This option should be \
|
||||
used in combination with
|
||||
.Fl -icf=all .
|
||||
.Pp
|
||||
.It Fl -image-base Ns = Ns Ar addr
|
||||
Set the base address to
|
||||
@@ -620,6 +693,14 @@ to run executables or shared libraries linked with
|
||||
.Fl -pack-dyn-relocs=relr ,
|
||||
and only ChromeOS, Android and Fuchsia support it as of now in 2022.
|
||||
.Pp
|
||||
.It -package-metadata Ns = Ns string
|
||||
Embed
|
||||
.Ar string
|
||||
to a .note.package section. This option in intended to be used by a \
|
||||
package management command such as
|
||||
.Cm rpm
|
||||
to embed metadata regarding a package to each executable file.
|
||||
.Pp
|
||||
.It Fl -perf
|
||||
Print performance statistics.
|
||||
.Pp
|
||||
@@ -1044,20 +1125,15 @@ Mark object to interpose all DSOs but executable.
|
||||
.It Fl EL
|
||||
.It Fl O Ns Ar number
|
||||
.It Fl -allow-shlib-undefined
|
||||
.It Fl -color-diagnostics
|
||||
.It Fl -dc
|
||||
.It Fl -dp
|
||||
.It Fl -end-group
|
||||
.It Fl -fatal-warnings
|
||||
.It Fl -gdb-index
|
||||
.It Fl -no-add-needed
|
||||
.It Fl -no-allow-shlib-undefined
|
||||
.It Fl -no-copy-dt-needed-entries
|
||||
.It Fl -no-fatal-warnings
|
||||
.It Fl -no-undefined-version
|
||||
.It Fl -nostdlib
|
||||
.It Fl -plugin
|
||||
.It Fl -plugin-opt
|
||||
.It Fl -rpath-link Ns = Ns Ar dir
|
||||
.It Fl -sort-common
|
||||
.It Fl -sort-section
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB |
+64
-69
@@ -35,23 +35,10 @@ namespace mold::elf {
|
||||
|
||||
using E = ARM32;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static i32 sign_extend(u32 val, u32 size) {
|
||||
return (i64)(val << (31 - size)) >> (31 - size);
|
||||
};
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
@@ -64,9 +51,9 @@ static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (sign << 10) | imm10;
|
||||
*(u16 *)(loc + 2) =
|
||||
(*(u16 *)(loc + 2) & 0xd000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | (sign << 10) | imm10;
|
||||
*(ul16 *)(loc + 2) =
|
||||
(*(ul16 *)(loc + 2) & 0xd000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
@@ -75,9 +62,9 @@ static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
*(u16 *)loc = (*(u16 *)loc & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
*(u16 *)(loc + 2) =
|
||||
((*(u16 *)(loc + 2)) & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
*(ul16 *)(loc + 2) =
|
||||
((*(ul16 *)(loc + 2)) & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -85,15 +72,18 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0xe52de004, // 1: push {lr}
|
||||
0xe59fe004, // ldr lr, [pc, #4]
|
||||
0xe08fe00e, // add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // .word .got.plt - 1b - 16
|
||||
0xe52de004, // push {lr}
|
||||
0xe59fe004, // ldr lr, 2f
|
||||
0xe08fe00e, // 1: add lr, pc, lr
|
||||
0xe5bef008, // ldr pc, [lr, #8]!
|
||||
0x00000000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320f000, // nop
|
||||
0xe320f000, // nop
|
||||
0xe320f000, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr - 16;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
static const u32 plt[] = {
|
||||
@@ -105,7 +95,7 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *ent = buf + sizeof(plt0) + sym->get_plt_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,20 +113,20 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * sizeof(plt);
|
||||
memcpy(ent, plt, sizeof(plt));
|
||||
*(u32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
*(ul32 *)(ent + 12) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
@@ -167,17 +157,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_ARM_ABS32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_ARM_RELATIVE, 0};
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_ARM_REL32:
|
||||
*(u32 *)loc = S + A - P;
|
||||
*(ul32 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_ARM_THM_CALL:
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
@@ -187,26 +177,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
write_thm_b_imm(loc, 4);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
*(ul16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else if (T) {
|
||||
write_thm_b_imm(loc, S + A - P);
|
||||
*(u16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
*(ul16 *)(loc + 2) |= (1 << 12); // rewrite with BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(S + A - P, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite with BLX
|
||||
*(ul16 *)(loc + 2) &= ~(1 << 12); // rewrite with BLX
|
||||
}
|
||||
continue;
|
||||
case R_ARM_BASE_PREL:
|
||||
*(u32 *)loc = GOT + A - P;
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_PREL:
|
||||
*(u32 *)loc = G + A - P;
|
||||
*(ul32 *)loc = G + A - P;
|
||||
continue;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(u32 *)loc = G + A;
|
||||
*(ul32 *)loc = G + A;
|
||||
continue;
|
||||
case R_ARM_TARGET2:
|
||||
*(u32 *)loc = GOT + G + A - P;
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
continue;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24: {
|
||||
@@ -220,13 +210,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
val = S + A - P;
|
||||
}
|
||||
|
||||
*(u32 *)loc = (*(u32 *)loc & 0xff00'0000) | ((val >> 2) & 0x00ff'ffff);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | ((val >> 2) & 0x00ff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP11: {
|
||||
assert(T);
|
||||
u32 val = (S + A - P) >> 1;
|
||||
*(u16 *)loc = (*(u16 *)loc & 0xf800) | (val & 0x07ff);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | (val & 0x07ff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
@@ -251,7 +241,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
continue;
|
||||
case R_ARM_PREL31: {
|
||||
u32 val = S + A - P;
|
||||
*(u32 *)loc = (*(u32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
continue;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
@@ -270,34 +260,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
continue;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(u32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
continue;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(u32 *)loc = S + A - ctx.tls_begin + 8;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin + 8;
|
||||
continue;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1)
|
||||
*(u32 *)loc = S - ctx.tls_begin + 8;
|
||||
*(ul32 *)loc = S - ctx.tls_begin + 8;
|
||||
else
|
||||
*(u32 *)loc = sym.get_tlsdesc_addr(ctx) + A - P - 6;
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - P - 6;
|
||||
continue;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.get_tlsdesc_idx(ctx) == -1) {
|
||||
// BL -> NOP
|
||||
*(u32 *)loc = 0x8000f3af;
|
||||
*(ul32 *)loc = 0x8000f3af;
|
||||
} else {
|
||||
u64 addr = ctx.tls_trampoline->shdr.sh_addr;
|
||||
write_thm_b_imm(loc, align_to(addr - P - 4, 4));
|
||||
*(u16 *)(loc + 2) &= ~(1 << 12); // rewrite BL with BLX
|
||||
*(ul16 *)(loc + 2) &= ~(1 << 12); // rewrite BL with BLX
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
@@ -315,7 +305,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -326,7 +316,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -339,16 +329,19 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul32 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -366,7 +359,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -377,7 +370,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -493,10 +486,11 @@ void ThumbToArmSection::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
i64 offset = 0;
|
||||
|
||||
static u16 insn[] = {
|
||||
0x4778, // bx pc
|
||||
0x46c0, // nop
|
||||
0, 0, // b <imm24>
|
||||
static u8 insn[] = {
|
||||
0x40, 0xf2, 0x00, 0x0c, // movw ip, $0
|
||||
0xc0, 0xf2, 0x00, 0x0c, // movt ip, $0
|
||||
0xfc, 0x44, // add ip, pc
|
||||
0x60, 0x47, // bx ip
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ENTRY_SIZE);
|
||||
@@ -505,7 +499,8 @@ void ThumbToArmSection::copy_buf(Context<E> &ctx) {
|
||||
memcpy(buf + offset, insn, sizeof(insn));
|
||||
|
||||
u32 val = sym->get_addr(ctx) - this->shdr.sh_addr - offset - 12;
|
||||
*(u32 *)(buf + offset + 4) = 0xea00'0000 | (0x00ff'ffff & (val >> 2));
|
||||
write_thm_mov_imm(buf + offset, val);
|
||||
write_thm_mov_imm(buf + offset + 4, val >> 16);
|
||||
offset += sizeof(insn);
|
||||
}
|
||||
}
|
||||
@@ -556,8 +551,8 @@ void sort_arm_exidx(Context<E> &ctx) {
|
||||
// CANTUNWIND is value 1. The most significant is set in (2) but not
|
||||
// in (3). So they can be distinguished just by looking at a value.
|
||||
struct Entry {
|
||||
u32 addr;
|
||||
u32 val;
|
||||
ul32 addr;
|
||||
ul32 val;
|
||||
};
|
||||
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
|
||||
+112
-218
@@ -7,15 +7,18 @@ namespace mold::elf {
|
||||
|
||||
using E = ARM64;
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = (val & 0x1ffffc) << 3;
|
||||
u32 lo = (val & 3) << 29;
|
||||
*(u32 *)buf = (*(u32 *)buf & 0x9f00001f) | hi | lo;
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 32, 14);
|
||||
u32 lo = bits(val, 13, 12);
|
||||
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
u32 hi = bits(val, 20, 2);
|
||||
u32 lo = bits(val, 1, 0);
|
||||
u32 op = *(ul32 *)buf & 0b1001'1111'0000'0000'0000'0000'0001'1111;
|
||||
*(ul32 *)buf = (lo << 29) | (hi << 5) | op;
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
@@ -39,9 +42,9 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_adr(buf + 4, bits(page(gotplt) - page(plt + 4), 32, 12));
|
||||
*(u32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(buf + 12) |= ((gotplt) & 0xfff) << 10;
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
@@ -58,9 +61,9 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(gotplt) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(u32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
write_adrp(ent, page(gotplt) - page(plt));
|
||||
*(ul32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -89,25 +92,25 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_adr(ent, bits(page(got) - page(plt), 32, 12));
|
||||
*(u32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
write_adrp(ent, page(got) - page(plt));
|
||||
*(ul32 *)(ent + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
*(u64 *)loc = val;
|
||||
*(ul64 *)loc = val;
|
||||
return;
|
||||
case R_AARCH64_PREL32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
@@ -116,8 +119,7 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = get_range_extn();
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
@@ -145,7 +147,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
@@ -153,59 +155,59 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
*(ul64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_AARCH64_ABS64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
*(ul64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_AARCH64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
*(ul64 *)loc = S + A;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
continue;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(u32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(u32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(u32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
continue;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(u32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
continue;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
i64 val = page(S + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
@@ -219,7 +221,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(u32 *)loc |= 1;
|
||||
*(ul32 *)loc |= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -228,83 +230,83 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i64 val = S + A - P;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
RangeExtensionRef ref = range_extn[i];
|
||||
RangeExtensionRef ref = extra.range_extn[i];
|
||||
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(u32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
*(ul32 *)loc |= (val >> 2) & 0x3ffffff;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
|
||||
*(u32 *)loc |= bits(val, 20, 2) << 5;
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(u64 *)loc = S + A - P;
|
||||
*(ul64 *)loc = S + A - P;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(u32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
overflow_check(val, 0, 1 << 15);
|
||||
*(u32 *)loc |= bits(val, 14, 3) << 10;
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tls_begin + 16;
|
||||
overflow_check(val, 0, (i64)1 << 24);
|
||||
*(u32 *)loc |= bits(val, 23, 12) << 10;
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(u32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
continue;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(u32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21: {
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tls_begin + 16);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
*(u32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
*(ul32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
|
||||
} else {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
|
||||
write_adr(loc, bits(val, 32, 12));
|
||||
write_adrp(loc, val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -312,23 +314,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tls_begin + 16) & 0xffff;
|
||||
*(u32 *)loc = 0xf2800000 | (offset_lo << 5);
|
||||
*(ul32 *)loc = 0xf2800000 | (offset_lo << 5);
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
*(ul32 *)loc = 0xd503201f;
|
||||
} else {
|
||||
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
}
|
||||
continue;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (ctx.relax_tlsdesc && !sym.is_imported) {
|
||||
// blr x2 -> nop
|
||||
*(u32 *)loc = 0xd503201f;
|
||||
*(ul32 *)loc = 0xd503201f;
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
@@ -345,7 +347,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -356,7 +358,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -365,17 +367,20 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
continue;
|
||||
case R_AARCH64_ABS32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -393,7 +398,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -404,7 +409,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -498,11 +503,17 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We always create a thunk for an absolute symbol conservatively
|
||||
// because `shrink_sections` may increase a distance between a
|
||||
// branch instruction and an absolute symbol. Branching to an
|
||||
// absolute location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
@@ -520,15 +531,23 @@ static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
return;
|
||||
|
||||
members[0]->offset = 0;
|
||||
|
||||
// Initialize input sections with very large dummy offsets so that
|
||||
// sections that have got real offsets are separated from the ones
|
||||
// without in the virtual address space.
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->offset = 1 << 31;
|
||||
members[i]->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
@@ -572,8 +591,8 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->get_range_extn();
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -583,6 +602,10 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, sym, *isec, rel))
|
||||
continue;
|
||||
@@ -609,7 +632,10 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) { return *a < *b; });
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
@@ -620,10 +646,10 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
std::vector<RangeExtensionRef> &range_extn = isec->extra.range_extn;
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
@@ -641,126 +667,6 @@ static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
static void gc_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec.thunks) {
|
||||
i64 sz = thunk->symbols.size();
|
||||
thunk->symbol_map.resize(sz);
|
||||
thunk->used.reset(new std::atomic_bool[sz]{});
|
||||
}
|
||||
|
||||
// Mark referenced thunk symbols
|
||||
tbb::parallel_for_each(osec.members, [&](InputSection<E> *isec) {
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->get_range_extn();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
RangeExtensionRef &ref = range_extn[i];
|
||||
if (ref.thunk_idx == -1)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
|
||||
if (!is_reachable(ctx, sym, *isec, rels[i]))
|
||||
osec.thunks[ref.thunk_idx]->used[ref.sym_idx] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Remove unreferenced thunk symbols
|
||||
tbb::parallel_for_each(osec.thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
i64 i = 0;
|
||||
for (i64 j = 0; j < thunk->symbols.size(); j++) {
|
||||
if (thunk->used[j]) {
|
||||
thunk->symbol_map[j] = i;
|
||||
thunk->symbols[i] = thunk->symbols[j];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
thunk->symbols.resize(i);
|
||||
});
|
||||
}
|
||||
|
||||
static void shrink_section(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks = osec.thunks;
|
||||
std::span<InputSection<E> *> members = osec.members;
|
||||
|
||||
i64 offset = 0;
|
||||
|
||||
auto add_thunk = [&] {
|
||||
thunks[0]->offset = offset;
|
||||
offset += thunks[0]->size();
|
||||
thunks = thunks.subspan(1);
|
||||
};
|
||||
|
||||
auto add_isec = [&] {
|
||||
offset = align_to(offset, 1 << members[0]->p2align);
|
||||
members[0]->offset = offset;
|
||||
offset += members[0]->sh_size;
|
||||
members = members.subspan(1);
|
||||
};
|
||||
|
||||
while (!thunks.empty() && !members.empty()) {
|
||||
if (thunks[0]->offset < members[0]->offset)
|
||||
add_thunk();
|
||||
else
|
||||
add_isec();
|
||||
}
|
||||
|
||||
while (!thunks.empty())
|
||||
add_thunk();
|
||||
while (!members.empty())
|
||||
add_isec();
|
||||
|
||||
assert(offset <= osec.shdr.sh_size);
|
||||
osec.shdr.sh_size = offset;
|
||||
}
|
||||
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
i64 create_range_extension_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "create_range_extension_thunks");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->range_extn.resize(file->sections.size());
|
||||
|
||||
// First, we create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would need thunks. To do so, we start
|
||||
// with an initial layout in which output sections are separated far
|
||||
// apart.
|
||||
for (i64 i = 0; Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_ALLOC)
|
||||
chunk->shdr.sh_addr = i++ << 31;
|
||||
|
||||
std::vector<OutputSection<E> *> sections;
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (!osec->members.empty() && (osec->shdr.sh_flags & SHF_EXECINSTR))
|
||||
sections.push_back(osec.get());
|
||||
|
||||
for (OutputSection<E> *osec : sections)
|
||||
create_thunks(ctx, *osec);
|
||||
|
||||
// Recompute file layout.
|
||||
set_osec_offsets(ctx);
|
||||
|
||||
// Based on the current file layout, remove thunk symbols that turned
|
||||
// out to be unnecessary.
|
||||
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
|
||||
gc_thunk_symbols(ctx, *osec);
|
||||
});
|
||||
|
||||
// Recompute output section sizes that contain thunks. New section
|
||||
// sizes must be equal to or smaller than previous values, so all
|
||||
// relocations that were previously reachable will still be reachable
|
||||
// after this step.
|
||||
for (OutputSection<E> *osec : sections)
|
||||
shrink_section(ctx, *osec);
|
||||
|
||||
// Compute the final layout.
|
||||
return set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
@@ -778,21 +684,9 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adr(loc, bits(page(S) - page(P), 32, 12));
|
||||
*(u32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
write_adrp(loc, page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
void write_thunks(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_thunks");
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
tbb::parallel_for_each(osec->thunks,
|
||||
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
|
||||
thunk->copy_buf(ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+38
-35
@@ -12,8 +12,8 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 8;
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl GOTPLT+4
|
||||
@@ -21,8 +21,8 @@ static void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
@@ -45,11 +45,11 @@ static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
*(ul32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
*(ul32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -74,7 +74,7 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
@@ -85,13 +85,13 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
*(ul32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
@@ -99,10 +99,10 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
@@ -111,7 +111,7 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
@@ -149,16 +149,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
@@ -232,7 +232,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
*(ul32 *)(loc + 5) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32: {
|
||||
@@ -241,7 +241,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x81, 0xe8, 0, 0, 0, 0, // add $0, %rax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
*(ul32 *)(loc + 6) = ctx.tls_end - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -325,7 +325,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -336,7 +336,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -363,12 +363,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
@@ -384,10 +384,13 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write16(S + A);
|
||||
continue;
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul32 *)loc = *val;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_PC8:
|
||||
write8s(S + A);
|
||||
@@ -396,22 +399,22 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write16s(S + A);
|
||||
continue;
|
||||
case R_386_PC32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
continue;
|
||||
case R_386_GOTPC:
|
||||
*(u32 *)loc = GOT + A;
|
||||
*(ul32 *)loc = GOT + A;
|
||||
continue;
|
||||
case R_386_GOTOFF:
|
||||
*(u32 *)loc = S + A - GOT;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
continue;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(u32 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul32 *)loc = S + A - ctx.tls_begin;
|
||||
continue;
|
||||
case R_386_SIZE32:
|
||||
*(u32 *)loc = sym.esym().st_size + A;
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
continue;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -428,7 +431,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -439,7 +442,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+109
-125
@@ -7,15 +7,6 @@ namespace mold::elf {
|
||||
|
||||
using E = RISCV64;
|
||||
|
||||
static u32 bit(u32 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
}
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u32 bits(u32 val, i64 hi, i64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
static u32 itype(u32 val) {
|
||||
return val << 20;
|
||||
}
|
||||
@@ -56,43 +47,43 @@ static u32 cjtype(u32 val) {
|
||||
bit(val, 1) << 3 | bit(val, 5) << 2;
|
||||
}
|
||||
|
||||
static void write_itype(u32 *loc, u32 val) {
|
||||
static void write_itype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'11111'111'11111'1111111;
|
||||
*loc = (*loc & mask) | itype(val);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & mask) | itype(val);
|
||||
}
|
||||
|
||||
static void write_stype(u32 *loc, u32 val) {
|
||||
static void write_stype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | stype(val);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & mask) | stype(val);
|
||||
}
|
||||
|
||||
static void write_btype(u32 *loc, u32 val) {
|
||||
static void write_btype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b000000'11111'11111'111'00000'1111111;
|
||||
*loc = (*loc & mask) | btype(val);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & mask) | btype(val);
|
||||
}
|
||||
|
||||
static void write_utype(u32 *loc, u32 val) {
|
||||
static void write_utype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | utype(val);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & mask) | utype(val);
|
||||
}
|
||||
|
||||
static void write_jtype(u32 *loc, u32 val) {
|
||||
static void write_jtype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b000000'00000'00000'000'11111'1111111;
|
||||
*loc = (*loc & mask) | jtype(val);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & mask) | jtype(val);
|
||||
}
|
||||
|
||||
static void write_cbtype(u16 *loc, u32 val) {
|
||||
static void write_cbtype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b1110001110000011;
|
||||
*loc = (*loc & mask) | cbtype(val);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & mask) | cbtype(val);
|
||||
}
|
||||
|
||||
static void write_cjtype(u16 *loc, u32 val) {
|
||||
static void write_cjtype(u8 *loc, u32 val) {
|
||||
u32 mask = 0b1110000000000011;
|
||||
*loc = (*loc & mask) | cjtype(val);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & mask) | cjtype(val);
|
||||
}
|
||||
|
||||
static void write_plt_header(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset);
|
||||
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
|
||||
|
||||
static const u32 plt0[] = {
|
||||
0x00000397, // auipc t2, %pcrel_hi(.got.plt)
|
||||
@@ -110,13 +101,13 @@ static void write_plt_header(Context<E> &ctx) {
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
write_utype(buf, gotplt - plt);
|
||||
write_itype(buf + 2, gotplt - plt);
|
||||
write_itype(buf + 4, gotplt - plt);
|
||||
write_itype(buf + 8, gotplt - plt);
|
||||
write_itype(buf + 16, gotplt - plt);
|
||||
}
|
||||
|
||||
static void write_plt_entry(Context<E> &ctx, Symbol<E> &sym) {
|
||||
u32 *ent = (u32 *)(ctx.buf + ctx.plt->shdr.sh_offset + ctx.plt_hdr_size +
|
||||
sym.get_plt_idx(ctx) * ctx.plt_size);
|
||||
u8 *ent = ctx.buf + ctx.plt->shdr.sh_offset + ctx.plt_hdr_size +
|
||||
sym.get_plt_idx(ctx) * ctx.plt_size;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
@@ -130,7 +121,7 @@ static void write_plt_entry(Context<E> &ctx, Symbol<E> &sym) {
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, gotplt - plt);
|
||||
write_itype(ent + 1, gotplt - plt);
|
||||
write_itype(ent + 4, gotplt - plt);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -142,7 +133,7 @@ void PltSection<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x00000e17, // auipc t3, %pcrel_hi([email protected])
|
||||
@@ -152,33 +143,33 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u32 *ent = buf + sym->get_pltgot_idx(ctx) * 4;
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * 16;
|
||||
u64 got = sym->get_got_addr(ctx);
|
||||
u64 plt = sym->get_plt_addr(ctx);
|
||||
|
||||
memcpy(ent, data, sizeof(data));
|
||||
write_utype(ent, got - plt);
|
||||
write_itype(ent + 1, got - plt);
|
||||
write_itype(ent + 4, got - plt);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += val;
|
||||
*(ul32 *)loc += val;
|
||||
return;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= val;
|
||||
*(ul16 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= val;
|
||||
*(ul32 *)loc -= val;
|
||||
return;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
@@ -190,10 +181,10 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
*loc = val;
|
||||
return;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
return;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
@@ -202,8 +193,7 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
i64 frag_idx = 0;
|
||||
|
||||
@@ -218,7 +208,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
i64 r_offset = rel.r_offset + r_deltas[i];
|
||||
i64 r_offset = rel.r_offset + extra.r_deltas[i];
|
||||
u8 *loc = base + r_offset;
|
||||
|
||||
const SectionFragmentRef<E> *frag_ref = nullptr;
|
||||
@@ -226,25 +216,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
frag_ref = &rel_fragments[frag_idx++];
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (sym.is_absolute() || !ctx.arg.pic) {
|
||||
*(u64 *)loc = S + A;
|
||||
*(ul64 *)loc = S + A;
|
||||
} else if (sym.is_imported) {
|
||||
*dynrel++ = {P, R_RISCV_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
*(ul64 *)loc = A;
|
||||
} else {
|
||||
if (!is_relr_reloc(ctx, rel))
|
||||
*dynrel++ = {P, R_RISCV_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
*(ul64 *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_TLS_DTPMOD32:
|
||||
@@ -256,68 +246,68 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
break;
|
||||
case R_RISCV_BRANCH:
|
||||
write_btype((u32 *)loc, S + A - P);
|
||||
write_btype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_JAL:
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
write_jtype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_CALL:
|
||||
case R_RISCV_CALL_PLT: {
|
||||
if (r_deltas[i + 1] - r_deltas[i] != 0) {
|
||||
if (extra.r_deltas[i + 1] - extra.r_deltas[i] != 0) {
|
||||
// auipc + jalr -> jal
|
||||
assert(r_deltas[i + 1] - r_deltas[i] == -4);
|
||||
u32 jalr = *(u32 *)&contents[rels[i].r_offset + 4];
|
||||
*(u32 *)loc = (0b11111'000000 & jalr) | 0b101111;
|
||||
write_jtype((u32 *)loc, S + A - P);
|
||||
assert(extra.r_deltas[i + 1] - extra.r_deltas[i] == -4);
|
||||
u32 jalr = *(ul32 *)&contents[rels[i].r_offset + 4];
|
||||
*(ul32 *)loc = (0b11111'000000 & jalr) | 0b101111;
|
||||
write_jtype(loc, S + A - P);
|
||||
} else {
|
||||
u64 val = sym.esym().is_undef_weak() ? 0 : S + A - P;
|
||||
write_utype((u32 *)loc, val);
|
||||
write_itype((u32 *)(loc + 4), val);
|
||||
write_utype(loc, val);
|
||||
write_itype(loc + 4, val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_RISCV_GOT_HI20:
|
||||
*(u32 *)loc = G + GOT + A - P;
|
||||
*(ul32 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
*(u32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_TLS_GD_HI20:
|
||||
*(u32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_RISCV_PCREL_HI20:
|
||||
if (sym.esym().is_undef_weak()) {
|
||||
// Calling an undefined weak symbol does not make sense.
|
||||
// We make such call into an infinite loop. This should
|
||||
// help debugging of a faulty program.
|
||||
*(u32 *)loc = P;
|
||||
*(ul32 *)loc = P;
|
||||
} else {
|
||||
*(u32 *)loc = S + A - P;
|
||||
*(ul32 *)loc = S + A - P;
|
||||
}
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_I:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_itype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
write_itype(loc, *(ul32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_I:
|
||||
case R_RISCV_TPREL_LO12_I:
|
||||
write_itype((u32 *)loc, S + A);
|
||||
write_itype(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_PCREL_LO12_S:
|
||||
assert(sym.get_input_section() == this);
|
||||
assert(sym.value < r_offset);
|
||||
write_stype((u32 *)loc, *(u32 *)(base + sym.value));
|
||||
write_stype(loc, *(ul32 *)(base + sym.value));
|
||||
break;
|
||||
case R_RISCV_LO12_S:
|
||||
case R_RISCV_TPREL_LO12_S:
|
||||
write_stype((u32 *)loc, S + A);
|
||||
write_stype(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_HI20:
|
||||
write_utype((u32 *)loc, S + A);
|
||||
write_utype(loc, S + A);
|
||||
break;
|
||||
case R_RISCV_TPREL_HI20:
|
||||
write_utype((u32 *)loc, S + A - ctx.tls_begin);
|
||||
write_utype(loc, S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_RISCV_TPREL_ADD:
|
||||
break;
|
||||
@@ -325,31 +315,31 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
*(ul16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
*(ul32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
*(ul64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
*(ul16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
*(ul32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_RVC_BRANCH:
|
||||
write_cbtype((u16 *)loc, S + A - P);
|
||||
write_cbtype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_JUMP:
|
||||
write_cjtype((u16 *)loc, S + A - P);
|
||||
write_cjtype(loc, S + A - P);
|
||||
break;
|
||||
case R_RISCV_RVC_LUI:
|
||||
Error(ctx) << *this << ": unsupported relocation: " << rel;
|
||||
@@ -364,13 +354,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_32_PCREL:
|
||||
*(u32 *)loc = S + A - P;
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -393,9 +383,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_RISCV_PCREL_HI20:
|
||||
case R_RISCV_TLS_GOT_HI20:
|
||||
case R_RISCV_TLS_GD_HI20: {
|
||||
u32 *loc = (u32 *)(base + rels[i].r_offset + r_deltas[i]);
|
||||
u32 val = *loc;
|
||||
*loc = *(u32 *)&contents[rels[i].r_offset];
|
||||
u8 *loc = base + rels[i].r_offset + extra.r_deltas[i];
|
||||
u32 val = *(ul32 *)loc;
|
||||
*(ul32 *)loc = *(ul32 *)&contents[rels[i].r_offset];
|
||||
write_utype(loc, val);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +394,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -415,7 +405,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -424,41 +414,44 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::tie(frag, addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_RISCV_32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD16:
|
||||
*(u16 *)loc += S + A;
|
||||
*(ul16 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD32:
|
||||
*(u32 *)loc += S + A;
|
||||
*(ul32 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_ADD64:
|
||||
*(u64 *)loc += S + A;
|
||||
*(ul64 *)loc += S + A;
|
||||
break;
|
||||
case R_RISCV_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB16:
|
||||
*(u16 *)loc -= S + A;
|
||||
*(ul16 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB32:
|
||||
*(u32 *)loc -= S + A;
|
||||
*(ul32 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB64:
|
||||
*(u64 *)loc -= S + A;
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_RISCV_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - (S + A)) & 0b0011'1111);
|
||||
@@ -470,10 +463,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET16:
|
||||
*(u16 *)loc = S + A;
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_RISCV_SET32:
|
||||
*(u32 *)loc = S + A;
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -490,8 +483,8 @@ template <>
|
||||
void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
// A non-alloc section isn't relaxed, so just copy it as one big chunk.
|
||||
if (!(shdr().sh_flags & SHF_ALLOC)) {
|
||||
if (is_compressed())
|
||||
uncompress(ctx, buf);
|
||||
if (compressed)
|
||||
uncompress_to(ctx, buf);
|
||||
else
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
@@ -499,12 +492,11 @@ void InputSection<E>::copy_contents_riscv(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
// Memory-allocated sections may be relaxed, so copy each segment
|
||||
// individually.
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<i32> r_deltas = get_r_deltas();
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 pos = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
i64 delta = r_deltas[i + 1] - r_deltas[i];
|
||||
i64 delta = extra.r_deltas[i + 1] - extra.r_deltas[i];
|
||||
if (delta == 0)
|
||||
continue;
|
||||
assert(delta < 0);
|
||||
@@ -523,7 +515,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -534,7 +526,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -643,23 +635,18 @@ static bool is_resizable(Context<E> &ctx, InputSection<E> *isec) {
|
||||
return isec && (isec->shdr().sh_flags & SHF_ALLOC);
|
||||
}
|
||||
|
||||
// Initializes r_deltas and sorted_symbols.
|
||||
static void initialize_storage(Context<E> &ctx) {
|
||||
// Initializes sorted_symbols.
|
||||
static void sort_symbols(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->r_deltas.resize(file->sections.size());
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (is_resizable(ctx, isec.get()))
|
||||
isec->get_r_deltas().resize(isec->get_rels(ctx).size() + 1);
|
||||
|
||||
file->sorted_symbols.resize(file->sections.size());
|
||||
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
file->sorted_symbols[isec->shndx].push_back(sym);
|
||||
isec->extra.sorted_symbols.push_back(sym);
|
||||
|
||||
for (std::vector<Symbol<E> *> &vec : file->sorted_symbols)
|
||||
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec)
|
||||
sort(isec->extra.sorted_symbols,
|
||||
[](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -686,17 +673,17 @@ static i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
|
||||
// Relax R_RISCV_CALL and R_RISCV_CALL_PLT relocations.
|
||||
static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<i32> r_deltas = isec.get_r_deltas();
|
||||
std::span<Symbol<E> *> syms = isec.get_sorted_symbols();
|
||||
std::span<Symbol<E> *> syms = isec.extra.sorted_symbols;
|
||||
i64 delta = 0;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
isec.extra.r_deltas.resize(rels.size() + 1);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
i64 delta2 = 0;
|
||||
|
||||
r_deltas[i] += delta;
|
||||
isec.extra.r_deltas[i] += delta;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_RISCV_ALIGN: {
|
||||
@@ -709,10 +696,7 @@ static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
// The total bytes of NOPs is stored to r_addend, so the next
|
||||
// instruction is r_addend away.
|
||||
u64 next_loc = loc + r.r_addend;
|
||||
|
||||
u64 alignment = (std::popcount<u64>(r.r_addend) == 1)
|
||||
? r.r_addend : next_power_of_two(r.r_addend);
|
||||
|
||||
u64 alignment = bit_ceil(r.r_addend);
|
||||
if (next_loc % alignment)
|
||||
delta2 = align_to(loc, alignment) - next_loc;
|
||||
break;
|
||||
@@ -745,7 +729,7 @@ static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
|
||||
for (Symbol<E> *sym : syms)
|
||||
sym->value += delta;
|
||||
r_deltas[rels.size()] += delta;
|
||||
isec.extra.r_deltas[rels.size()] += delta;
|
||||
|
||||
isec.sh_size += delta;
|
||||
}
|
||||
@@ -795,7 +779,7 @@ static void relax_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
// relocations.
|
||||
i64 riscv_resize_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "riscv_resize_sections");
|
||||
initialize_storage(ctx);
|
||||
sort_symbols(ctx);
|
||||
|
||||
// Find R_RISCV_CALL AND R_RISCV_CALL_PLT that can be relaxed.
|
||||
// This step should only shrink sections.
|
||||
|
||||
+45
-42
@@ -18,7 +18,7 @@ static void write_compact_plt(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(ul32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ static void write_ibtplt(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(u32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
*(ul32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 2;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
@@ -65,8 +65,8 @@ static void write_ibtplt(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 6) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
*(ul32 *)(ent + 6) = relplt_idx++;
|
||||
*(ul32 *)(ent + 12) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ static void write_plt(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
|
||||
*(ul32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
@@ -97,9 +97,9 @@ static void write_plt(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : ctx.plt->symbols) {
|
||||
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(u32 *)(ent + 7) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
*(ul32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(ul32 *)(ent + 7) = relplt_idx++;
|
||||
*(ul32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,14 +123,14 @@ void PltGotSection<E>::copy_buf(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * E::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(ul32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
@@ -138,16 +138,16 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)loc = val;
|
||||
*(ul64 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
return;
|
||||
}
|
||||
unreachable();
|
||||
@@ -233,7 +233,7 @@ static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
@@ -271,30 +271,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
overflow_check(val, -(1 << 15), 1 << 15);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write64 = [&](u64 val) {
|
||||
*(u64 *)loc = val;
|
||||
*(ul64 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
|
||||
#define A (frag_ref ? (u64)frag_ref->addend : (u64)rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
|
||||
#define GOT ctx.gotplt->shdr.sh_addr
|
||||
@@ -404,7 +404,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = val;
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
@@ -414,7 +414,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 9) = val;
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -545,7 +545,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -556,7 +556,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -578,21 +578,21 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
overflow_check(val, 0, 1 << 16);
|
||||
*(u16 *)loc = val;
|
||||
*(ul16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
overflow_check(val, 0, (i64)1 << 32);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
auto write32s = [&](u64 val) {
|
||||
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
|
||||
*(u32 *)loc = val;
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? addend : rel.r_addend)
|
||||
#define A (frag ? (u64)addend : (u64)rel.r_addend)
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -608,28 +608,31 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A;
|
||||
if (!frag) {
|
||||
if (std::optional<u64> val = get_tombstone(sym)) {
|
||||
*(ul64 *)loc = *val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u32 *)loc = *val;
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
write32s(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (std::optional<u64> val = get_tombstone(sym))
|
||||
*(u64 *)loc = *val;
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(u64 *)loc = S + A - ctx.tls_begin;
|
||||
*(ul64 *)loc = S + A - ctx.tls_begin;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = sym.esym().st_size + A;
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -652,7 +655,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -664,7 +667,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, file, sym);
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+375
-407
File diff suppressed because it is too large.
Load diff
+510
@@ -0,0 +1,510 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// The hash function for .gdb_index.
|
||||
static u32 gdb_hash(std::string_view name) {
|
||||
u32 h = 0;
|
||||
for (u8 c : name) {
|
||||
if ('A' <= c && c <= 'Z')
|
||||
c = 'a' + c - 'A';
|
||||
h = h * 67 + c - 113;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Split .debug_info into so-called "compilation units". A .debug_info
|
||||
// section usually contains one compunit unless it was created by `ld -r`.
|
||||
// This is for --gdb-index.
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
read_compunits(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
file.debug_info->uncompress(ctx);
|
||||
std::string_view data = file.debug_info->contents;
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
if (data.size() < 4)
|
||||
Fatal(ctx) << *file.debug_info << ": corrupted .debug_info";
|
||||
if (*(ul32 *)data.data() == 0xffffffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(ul32 *)data.data() + 4;
|
||||
vec.push_back(data.substr(0, len));
|
||||
data = data.substr(len);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Parses .debug_gnu_pubnames and .debug_gnu_pubtypes. These sections
|
||||
// start with a 14 bytes header followed by (4-byte offset, 1-byte type,
|
||||
// null-terminated string) tuples.
|
||||
//
|
||||
// The 4-byte offset is an offset into .debug_info that contains details
|
||||
// about the name. The 1-byte type is a type of the corresponding name
|
||||
// (e.g. function, variable or datatype). The string is a name of a
|
||||
// function, a variable or a type.
|
||||
template <typename E>
|
||||
std::vector<GdbIndexName> read_pubnames(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<GdbIndexName> vec;
|
||||
|
||||
auto get_cu_idx = [&](InputSection<E> &isec, i64 offset) {
|
||||
i64 off = 0;
|
||||
for (i64 i = 0; i < file.compunits.size(); i++) {
|
||||
if (offset == off)
|
||||
return file.compunits_idx + i;
|
||||
off += file.compunits[i].size();
|
||||
}
|
||||
Fatal(ctx) << isec << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
auto read = [&](InputSection<E> &isec) {
|
||||
isec.uncompress(ctx);
|
||||
std::string_view contents = isec.contents;
|
||||
|
||||
while (!contents.empty()) {
|
||||
if (contents.size() < 14)
|
||||
Fatal(ctx) << isec << ": corrupted header";
|
||||
|
||||
u32 len = *(ul32 *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(ul32 *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(ul32 *)data.data();
|
||||
data = data.substr(4);
|
||||
if (offset == 0)
|
||||
break;
|
||||
|
||||
u8 type = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string_view name = data.data();
|
||||
data = data.substr(name.size() + 1);
|
||||
|
||||
vec.push_back({name, gdb_hash(name), (type << 24) | cu_idx});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (file.debug_pubnames)
|
||||
read(*file.debug_pubnames);
|
||||
if (file.debug_pubtypes)
|
||||
read(*file.debug_pubtypes);
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
auto less = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} <
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
auto equal = [](const GdbIndexName &a, const GdbIndexName &b) {
|
||||
return std::tuple{a.hash, a.attr, a.name} ==
|
||||
std::tuple{b.hash, b.attr, b.name};
|
||||
};
|
||||
|
||||
std::sort(vec.begin(), vec.end(), less);
|
||||
vec.erase(std::unique(vec.begin(), vec.end(), equal), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u8 *get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (u8 *buf = chunk->get_uncompressed_data())
|
||||
return buf;
|
||||
return ctx.buf + chunk->shdr.sh_offset;
|
||||
}
|
||||
|
||||
// Try to find a compilation unit from .debug_info and its
|
||||
// corresponding record from .debug_abbrev and returns them.
|
||||
template <typename E>
|
||||
static std::tuple<u8 *, u8 *, u32>
|
||||
find_compunit(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *cu = get_buffer(ctx, ctx.debug_info) + offset;
|
||||
u32 dwarf_version = *(ul16 *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(ul32 *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != E::word_size)
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(ul32 *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != E::word_size)
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
switch (u32 unit_type = cu[6]; unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
cu += 12;
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
cu += 20;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DW_UT_* value: 0x"
|
||||
<< std::hex << unit_type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unknown DWARF version: "
|
||||
<< dwarf_version;
|
||||
}
|
||||
|
||||
u32 abbrev_code = read_uleb(cu);
|
||||
|
||||
// Find a .debug_abbrev record corresponding to the .debug_info record.
|
||||
// We assume the .debug_info record at a given offset is of
|
||||
// DW_TAG_compile_unit which describes a compunit.
|
||||
u8 *abbrev = get_buffer(ctx, ctx.debug_abbrev) + abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << file << ": --gdb-index: .debug_abbrev does not contain"
|
||||
<< " a record for the first .debug_info record";
|
||||
|
||||
if (code == abbrev_code) {
|
||||
// Found a record
|
||||
u64 abbrev_tag = read_uleb(abbrev);
|
||||
if (abbrev_tag != DW_TAG_compile_unit && abbrev_tag != DW_TAG_skeleton_unit)
|
||||
Fatal(ctx) << file << ": --gdb-index: the first entry's tag is not"
|
||||
<< " DW_TAG_compile_unit/DW_TAG_skeleton_unit but 0x"
|
||||
<< std::hex << abbrev_tag;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip an uninteresting record
|
||||
read_uleb(abbrev); // tag
|
||||
abbrev++; // has_children byte
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
if (form == DW_FORM_implicit_const)
|
||||
read_uleb(abbrev);
|
||||
}
|
||||
}
|
||||
|
||||
abbrev++; // skip has_children byte
|
||||
return {cu, abbrev, dwarf_version};
|
||||
}
|
||||
|
||||
// Estimate the number of address ranges contained in a given file.
|
||||
// It may over-estimate but never under-estimate.
|
||||
template <typename E>
|
||||
i64 estimate_address_areas(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
// Each CU contains zero or one address area.
|
||||
i64 ret = file.compunits.size();
|
||||
|
||||
// In DWARF 4, a CU can refer address ranges in .debug_ranges.
|
||||
// .debug_ranges contains a vector of [begin, end) address pairs.
|
||||
// The last entry must be a null terminator, so we do -1.
|
||||
if (file.debug_ranges)
|
||||
ret += file.debug_ranges->sh_size / E::word_size / 2 - 1;
|
||||
|
||||
// In DWARF 5, a CU can refer address ranges in .debug_rnglists, which
|
||||
// contains variable-length entries. The smallest possible range entry
|
||||
// is one byte for the code and two ULEB128 values (each can be as
|
||||
// small as one byte), so 3 bytes.
|
||||
if (file.debug_rnglists)
|
||||
ret += file.debug_rnglists->sh_size / 3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields. This class reads them.
|
||||
template <typename E>
|
||||
class DebugInfoReader {
|
||||
public:
|
||||
DebugInfoReader(Context<E> &ctx, ObjectFile<E> &file, u8 *cu)
|
||||
: ctx(ctx), file(file), cu(cu) {}
|
||||
|
||||
u64 read(u64 form);
|
||||
|
||||
Context<E> &ctx;
|
||||
ObjectFile<E> &file;
|
||||
u8 *cu;
|
||||
};
|
||||
|
||||
// Read value of the given DW_FORM_* form. If a value is not scalar,
|
||||
// returns a dummy value 0.
|
||||
template <typename E>
|
||||
inline u64 DebugInfoReader<E>::read(u64 form) {
|
||||
switch (form) {
|
||||
case DW_FORM_flag_present:
|
||||
return 0;
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_flag:
|
||||
case DW_FORM_strx1:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_ref1:
|
||||
return *cu++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(ul16 *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(ul24 *)cu;
|
||||
cu += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(ul32 *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(ul64 *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(typename E::WordTy *)cu;
|
||||
cu += E::word_size;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx:
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_ref_udata:
|
||||
case DW_FORM_loclistx:
|
||||
case DW_FORM_rnglistx:
|
||||
return read_uleb(cu);
|
||||
case DW_FORM_string:
|
||||
cu += strlen((char *)cu) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset
|
||||
// (until an end of list entry).
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, typename E::WordTy *range) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0) {
|
||||
// base address selection entry
|
||||
base = range[i + 1];
|
||||
} else {
|
||||
vec.push_back(range[i] + base);
|
||||
vec.push_back(range[i + 1] + base);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset
|
||||
// (until an end of list entry).
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
typename E::WordTy *addrx) {
|
||||
std::vector<u64> vec;
|
||||
u64 base = 0;
|
||||
|
||||
for (;;) {
|
||||
switch (*rnglist++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return vec;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(rnglist)];
|
||||
break;
|
||||
case DW_RLE_startx_endx:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
break;
|
||||
case DW_RLE_startx_length:
|
||||
vec.push_back(addrx[read_uleb(rnglist)]);
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
vec.push_back(base + read_uleb(rnglist));
|
||||
break;
|
||||
case DW_RLE_base_address:
|
||||
base = *(typename E::WordTy *)rnglist;
|
||||
rnglist += E::word_size;
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(typename E::WordTy *)rnglist);
|
||||
rnglist += E::word_size;
|
||||
vec.push_back(*(typename E::WordTy *)rnglist);
|
||||
rnglist += E::word_size;
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(typename E::WordTy *)rnglist);
|
||||
rnglist += E::word_size;
|
||||
vec.push_back(vec.back() + read_uleb(rnglist));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a list of address ranges explained by a compunit at the
|
||||
// `offset` in an output .debug_info section.
|
||||
//
|
||||
// .debug_info contains DWARF debug info records, so this function
|
||||
// parses DWARF. If a designated compunit contains multiple ranges, the
|
||||
// ranges are read from .debug_ranges (or .debug_rnglists for DWARF5).
|
||||
// Otherwise, a range is read directly from .debug_info (or possibly
|
||||
// from .debug_addr for DWARF5).
|
||||
template <typename E>
|
||||
std::vector<u64>
|
||||
read_address_areas(Context<E> &ctx, ObjectFile<E> &file, i64 offset) {
|
||||
u8 *cu;
|
||||
u8 *abbrev;
|
||||
u32 dwarf_version;
|
||||
std::tie(cu, abbrev, dwarf_version) = find_compunit(ctx, file, offset);
|
||||
|
||||
DebugInfoReader<E> reader{ctx, file, cu};
|
||||
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
std::optional<u64> rnglists_base;
|
||||
typename E::WordTy *addrx = nullptr;
|
||||
|
||||
// Read all interesting debug records.
|
||||
for (;;) {
|
||||
u64 name = read_uleb(abbrev);
|
||||
u64 form = read_uleb(abbrev);
|
||||
if (name == 0 && form == 0)
|
||||
break;
|
||||
|
||||
u64 val = reader.read(form);
|
||||
|
||||
switch (name) {
|
||||
case DW_AT_low_pc:
|
||||
low_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_high_pc:
|
||||
high_pc = {form, val};
|
||||
break;
|
||||
case DW_AT_rnglists_base:
|
||||
rnglists_base = val;
|
||||
break;
|
||||
case DW_AT_addr_base:
|
||||
addrx = (typename E::WordTy *)(get_buffer(ctx, ctx.debug_addr) + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (dwarf_version <= 4) {
|
||||
typename E::WordTy *range_begin =
|
||||
(typename E::WordTy *)(get_buffer(ctx, ctx.debug_ranges) + ranges.value);
|
||||
return read_debug_range(ctx, file, range_begin);
|
||||
}
|
||||
|
||||
assert(dwarf_version == 5);
|
||||
|
||||
u8 *buf = get_buffer(ctx, ctx.debug_rnglists);
|
||||
if (ranges.form == DW_FORM_sec_offset)
|
||||
return read_rnglist_range(ctx, file, buf + ranges.value, addrx);
|
||||
|
||||
if (!rnglists_base)
|
||||
Fatal(ctx) << file << ": --gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + *rnglists_base;
|
||||
return read_rnglist_range(ctx, file, base + *(ul32 *)base, addrx);
|
||||
}
|
||||
|
||||
// Handle a contiguous address range.
|
||||
if (low_pc.form && high_pc.form) {
|
||||
u64 lo;
|
||||
|
||||
switch (low_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
lo = low_pc.value;
|
||||
break;
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
lo = addrx[low_pc.value];
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_low_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
|
||||
switch (high_pc.form) {
|
||||
case DW_FORM_addr:
|
||||
return {lo, high_pc.value};
|
||||
case DW_FORM_addrx:
|
||||
case DW_FORM_addrx1:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_addrx4:
|
||||
return {lo, addrx[high_pc.value]};
|
||||
case DW_FORM_udata:
|
||||
case DW_FORM_data1:
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_data8:
|
||||
return {lo, lo + high_pc.value};
|
||||
default:
|
||||
Fatal(ctx) << file << ": --gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<std::string_view> \
|
||||
read_compunits(Context<E> &, ObjectFile<E> &); \
|
||||
template std::vector<GdbIndexName> \
|
||||
read_pubnames(Context<E> &, ObjectFile<E> &); \
|
||||
template i64 \
|
||||
estimate_address_areas(Context<E> &, ObjectFile<E> &); \
|
||||
template std::vector<u64> \
|
||||
read_address_areas(Context<E> &, ObjectFile<E> &, i64)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../inttypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
@@ -62,6 +64,7 @@ static constexpr u32 SHT_PREINIT_ARRAY = 16;
|
||||
static constexpr u32 SHT_GROUP = 17;
|
||||
static constexpr u32 SHT_SYMTAB_SHNDX = 18;
|
||||
static constexpr u32 SHT_RELR = 19;
|
||||
static constexpr u32 SHT_LLVM_ADDRSIG = 0x6fff4c03;
|
||||
static constexpr u32 SHT_GNU_HASH = 0x6ffffff6;
|
||||
static constexpr u32 SHT_GNU_VERDEF = 0x6ffffffd;
|
||||
static constexpr u32 SHT_GNU_VERNEED = 0x6ffffffe;
|
||||
@@ -80,6 +83,7 @@ static constexpr u32 SHF_LINK_ORDER = 0x80;
|
||||
static constexpr u32 SHF_GROUP = 0x200;
|
||||
static constexpr u32 SHF_TLS = 0x400;
|
||||
static constexpr u32 SHF_COMPRESSED = 0x800;
|
||||
static constexpr u32 SHF_GNU_RETAIN = 0x200000;
|
||||
static constexpr u32 SHF_EXCLUDE = 0x80000000;
|
||||
|
||||
static constexpr u32 GRP_COMDAT = 1;
|
||||
@@ -225,6 +229,7 @@ static constexpr u32 NT_GNU_HWCAP = 2;
|
||||
static constexpr u32 NT_GNU_BUILD_ID = 3;
|
||||
static constexpr u32 NT_GNU_GOLD_VERSION = 4;
|
||||
static constexpr u32 NT_GNU_PROPERTY_TYPE_0 = 5;
|
||||
static constexpr u32 NT_FDO_PACKAGING_METADATA = 0xcafe1a7e;
|
||||
|
||||
static constexpr u32 GNU_PROPERTY_AARCH64_FEATURE_1_AND = 0xc0000000;
|
||||
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002;
|
||||
@@ -1054,8 +1059,16 @@ static constexpr u32 DW_AT_low_pc = 0x11;
|
||||
static constexpr u32 DW_AT_high_pc = 0x12;
|
||||
static constexpr u32 DW_AT_producer = 0x25;
|
||||
static constexpr u32 DW_AT_ranges = 0x55;
|
||||
static constexpr u32 DW_AT_addr_base = 0x73;
|
||||
static constexpr u32 DW_AT_rnglists_base = 0x74;
|
||||
|
||||
static constexpr u32 DW_TAG_compile_unit = 0x11;
|
||||
static constexpr u32 DW_TAG_skeleton_unit = 0x4a;
|
||||
|
||||
static constexpr u32 DW_UT_compile = 0x01;
|
||||
static constexpr u32 DW_UT_partial = 0x03;
|
||||
static constexpr u32 DW_UT_skeleton = 0x04;
|
||||
static constexpr u32 DW_UT_split_compile = 0x05;
|
||||
|
||||
static constexpr u32 DW_FORM_addr = 0x01;
|
||||
static constexpr u32 DW_FORM_block2 = 0x03;
|
||||
@@ -1081,6 +1094,34 @@ static constexpr u32 DW_FORM_indirect = 0x16;
|
||||
static constexpr u32 DW_FORM_sec_offset = 0x17;
|
||||
static constexpr u32 DW_FORM_exprloc = 0x18;
|
||||
static constexpr u32 DW_FORM_flag_present = 0x19;
|
||||
static constexpr u32 DW_FORM_strx = 0x1a;
|
||||
static constexpr u32 DW_FORM_addrx = 0x1b;
|
||||
static constexpr u32 DW_FORM_ref_sup4 = 0x1c;
|
||||
static constexpr u32 DW_FORM_strp_sup = 0x1d;
|
||||
static constexpr u32 DW_FORM_data16 = 0x1e;
|
||||
static constexpr u32 DW_FORM_line_strp = 0x1f;
|
||||
static constexpr u32 DW_FORM_ref_sig8 = 0x20;
|
||||
static constexpr u32 DW_FORM_implicit_const = 0x21;
|
||||
static constexpr u32 DW_FORM_loclistx = 0x22;
|
||||
static constexpr u32 DW_FORM_rnglistx = 0x23;
|
||||
static constexpr u32 DW_FORM_ref_sup8 = 0x24;
|
||||
static constexpr u32 DW_FORM_strx1 = 0x25;
|
||||
static constexpr u32 DW_FORM_strx2 = 0x26;
|
||||
static constexpr u32 DW_FORM_strx3 = 0x27;
|
||||
static constexpr u32 DW_FORM_strx4 = 0x28;
|
||||
static constexpr u32 DW_FORM_addrx1 = 0x29;
|
||||
static constexpr u32 DW_FORM_addrx2 = 0x2a;
|
||||
static constexpr u32 DW_FORM_addrx3 = 0x2b;
|
||||
static constexpr u32 DW_FORM_addrx4 = 0x2c;
|
||||
|
||||
static constexpr u32 DW_RLE_end_of_list = 0x00;
|
||||
static constexpr u32 DW_RLE_base_addressx = 0x01;
|
||||
static constexpr u32 DW_RLE_startx_endx = 0x02;
|
||||
static constexpr u32 DW_RLE_startx_length = 0x03;
|
||||
static constexpr u32 DW_RLE_offset_pair = 0x04;
|
||||
static constexpr u32 DW_RLE_base_address = 0x05;
|
||||
static constexpr u32 DW_RLE_start_end = 0x06;
|
||||
static constexpr u32 DW_RLE_start_length = 0x07;
|
||||
|
||||
struct Elf64Sym {
|
||||
bool is_defined() const { return !is_undef(); }
|
||||
@@ -1097,13 +1138,13 @@ struct Elf64Sym {
|
||||
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
|
||||
}
|
||||
|
||||
u32 st_name;
|
||||
ul32 st_name;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
u64 st_value;
|
||||
u64 st_size;
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
struct Elf32Sym {
|
||||
@@ -1121,185 +1162,185 @@ struct Elf32Sym {
|
||||
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
|
||||
}
|
||||
|
||||
u32 st_name;
|
||||
u32 st_value;
|
||||
u32 st_size;
|
||||
ul32 st_name;
|
||||
ul32 st_value;
|
||||
ul32 st_size;
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u16 st_shndx;
|
||||
ul16 st_shndx;
|
||||
};
|
||||
|
||||
struct Elf64Shdr {
|
||||
u32 sh_name;
|
||||
u32 sh_type;
|
||||
u64 sh_flags;
|
||||
u64 sh_addr;
|
||||
u64 sh_offset;
|
||||
u64 sh_size;
|
||||
u32 sh_link;
|
||||
u32 sh_info;
|
||||
u64 sh_addralign;
|
||||
u64 sh_entsize;
|
||||
ul32 sh_name;
|
||||
ul32 sh_type;
|
||||
ul64 sh_flags;
|
||||
ul64 sh_addr;
|
||||
ul64 sh_offset;
|
||||
ul64 sh_size;
|
||||
ul32 sh_link;
|
||||
ul32 sh_info;
|
||||
ul64 sh_addralign;
|
||||
ul64 sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf32Shdr {
|
||||
u32 sh_name;
|
||||
u32 sh_type;
|
||||
u32 sh_flags;
|
||||
u32 sh_addr;
|
||||
u32 sh_offset;
|
||||
u32 sh_size;
|
||||
u32 sh_link;
|
||||
u32 sh_info;
|
||||
u32 sh_addralign;
|
||||
u32 sh_entsize;
|
||||
ul32 sh_name;
|
||||
ul32 sh_type;
|
||||
ul32 sh_flags;
|
||||
ul32 sh_addr;
|
||||
ul32 sh_offset;
|
||||
ul32 sh_size;
|
||||
ul32 sh_link;
|
||||
ul32 sh_info;
|
||||
ul32 sh_addralign;
|
||||
ul32 sh_entsize;
|
||||
};
|
||||
|
||||
struct Elf64Ehdr {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry;
|
||||
u64 e_phoff;
|
||||
u64 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
ul16 e_type;
|
||||
ul16 e_machine;
|
||||
ul32 e_version;
|
||||
ul64 e_entry;
|
||||
ul64 e_phoff;
|
||||
ul64 e_shoff;
|
||||
ul32 e_flags;
|
||||
ul16 e_ehsize;
|
||||
ul16 e_phentsize;
|
||||
ul16 e_phnum;
|
||||
ul16 e_shentsize;
|
||||
ul16 e_shnum;
|
||||
ul16 e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf32Ehdr {
|
||||
u8 e_ident[16];
|
||||
u16 e_type;
|
||||
u16 e_machine;
|
||||
u32 e_version;
|
||||
u32 e_entry;
|
||||
u32 e_phoff;
|
||||
u32 e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize;
|
||||
u16 e_phentsize;
|
||||
u16 e_phnum;
|
||||
u16 e_shentsize;
|
||||
u16 e_shnum;
|
||||
u16 e_shstrndx;
|
||||
ul16 e_type;
|
||||
ul16 e_machine;
|
||||
ul32 e_version;
|
||||
ul32 e_entry;
|
||||
ul32 e_phoff;
|
||||
ul32 e_shoff;
|
||||
ul32 e_flags;
|
||||
ul16 e_ehsize;
|
||||
ul16 e_phentsize;
|
||||
ul16 e_phnum;
|
||||
ul16 e_shentsize;
|
||||
ul16 e_shnum;
|
||||
ul16 e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64Phdr {
|
||||
u32 p_type;
|
||||
u32 p_flags;
|
||||
u64 p_offset;
|
||||
u64 p_vaddr;
|
||||
u64 p_paddr;
|
||||
u64 p_filesz;
|
||||
u64 p_memsz;
|
||||
u64 p_align;
|
||||
ul32 p_type;
|
||||
ul32 p_flags;
|
||||
ul64 p_offset;
|
||||
ul64 p_vaddr;
|
||||
ul64 p_paddr;
|
||||
ul64 p_filesz;
|
||||
ul64 p_memsz;
|
||||
ul64 p_align;
|
||||
};
|
||||
|
||||
struct Elf32Phdr {
|
||||
u32 p_type;
|
||||
u32 p_offset;
|
||||
u32 p_vaddr;
|
||||
u32 p_paddr;
|
||||
u32 p_filesz;
|
||||
u32 p_memsz;
|
||||
u32 p_flags;
|
||||
u32 p_align;
|
||||
ul32 p_type;
|
||||
ul32 p_offset;
|
||||
ul32 p_vaddr;
|
||||
ul32 p_paddr;
|
||||
ul32 p_filesz;
|
||||
ul32 p_memsz;
|
||||
ul32 p_flags;
|
||||
ul32 p_align;
|
||||
};
|
||||
|
||||
struct Elf64Rel {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
ul64 r_offset;
|
||||
ul32 r_type;
|
||||
ul32 r_sym;
|
||||
};
|
||||
|
||||
struct Elf32Rel {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
ul32 r_offset;
|
||||
u8 r_type;
|
||||
ul24 r_sym;
|
||||
};
|
||||
|
||||
struct Elf64Rela {
|
||||
u64 r_offset;
|
||||
u32 r_type;
|
||||
u32 r_sym;
|
||||
i64 r_addend;
|
||||
ul64 r_offset;
|
||||
ul32 r_type;
|
||||
ul32 r_sym;
|
||||
il64 r_addend;
|
||||
};
|
||||
|
||||
struct Elf32Rela {
|
||||
u32 r_offset;
|
||||
u32 r_type : 8;
|
||||
u32 r_sym : 24;
|
||||
i32 r_addend;
|
||||
ul32 r_offset;
|
||||
u8 r_type;
|
||||
ul24 r_sym;
|
||||
il32 r_addend;
|
||||
};
|
||||
|
||||
struct Elf64Dyn {
|
||||
u64 d_tag;
|
||||
u64 d_val;
|
||||
ul64 d_tag;
|
||||
ul64 d_val;
|
||||
};
|
||||
|
||||
struct Elf32Dyn {
|
||||
u32 d_tag;
|
||||
u32 d_val;
|
||||
ul32 d_tag;
|
||||
ul32 d_val;
|
||||
};
|
||||
|
||||
struct ElfVerneed {
|
||||
u16 vn_version;
|
||||
u16 vn_cnt;
|
||||
u32 vn_file;
|
||||
u32 vn_aux;
|
||||
u32 vn_next;
|
||||
ul16 vn_version;
|
||||
ul16 vn_cnt;
|
||||
ul32 vn_file;
|
||||
ul32 vn_aux;
|
||||
ul32 vn_next;
|
||||
};
|
||||
|
||||
struct ElfVernaux {
|
||||
u32 vna_hash;
|
||||
u16 vna_flags;
|
||||
u16 vna_other;
|
||||
u32 vna_name;
|
||||
u32 vna_next;
|
||||
ul32 vna_hash;
|
||||
ul16 vna_flags;
|
||||
ul16 vna_other;
|
||||
ul32 vna_name;
|
||||
ul32 vna_next;
|
||||
};
|
||||
|
||||
struct ElfVerdef {
|
||||
u16 vd_version;
|
||||
u16 vd_flags;
|
||||
u16 vd_ndx;
|
||||
u16 vd_cnt;
|
||||
u32 vd_hash;
|
||||
u32 vd_aux;
|
||||
u32 vd_next;
|
||||
ul16 vd_version;
|
||||
ul16 vd_flags;
|
||||
ul16 vd_ndx;
|
||||
ul16 vd_cnt;
|
||||
ul32 vd_hash;
|
||||
ul32 vd_aux;
|
||||
ul32 vd_next;
|
||||
};
|
||||
|
||||
struct ElfVerdaux {
|
||||
u32 vda_name;
|
||||
u32 vda_next;
|
||||
ul32 vda_name;
|
||||
ul32 vda_next;
|
||||
};
|
||||
|
||||
struct Elf64Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_reserved;
|
||||
u64 ch_size;
|
||||
u64 ch_addralign;
|
||||
ul32 ch_type;
|
||||
ul32 ch_reserved;
|
||||
ul64 ch_size;
|
||||
ul64 ch_addralign;
|
||||
};
|
||||
|
||||
struct Elf32Chdr {
|
||||
u32 ch_type;
|
||||
u32 ch_size;
|
||||
u32 ch_addralign;
|
||||
ul32 ch_type;
|
||||
ul32 ch_size;
|
||||
ul32 ch_addralign;
|
||||
};
|
||||
|
||||
struct ElfNhdr {
|
||||
u32 n_namesz;
|
||||
u32 n_descsz;
|
||||
u32 n_type;
|
||||
ul32 n_namesz;
|
||||
ul32 n_descsz;
|
||||
ul32 n_type;
|
||||
};
|
||||
|
||||
struct X86_64 {
|
||||
using WordTy = u64;
|
||||
using WordTy = ul64;
|
||||
|
||||
static constexpr u32 R_NONE = R_X86_64_NONE;
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
@@ -1318,7 +1359,6 @@ struct X86_64 {
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
@@ -1331,7 +1371,7 @@ template <> struct ElfDyn<X86_64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<X86_64> : public Elf64Chdr {};
|
||||
|
||||
struct I386 {
|
||||
using WordTy = u32;
|
||||
using WordTy = ul32;
|
||||
|
||||
static constexpr u32 R_NONE = R_386_NONE;
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
@@ -1350,7 +1390,6 @@ struct I386 {
|
||||
static constexpr u32 e_machine = EM_386;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
@@ -1363,7 +1402,7 @@ template <> struct ElfDyn<I386> : public Elf32Dyn {};
|
||||
template <> struct ElfChdr<I386> : public Elf32Chdr {};
|
||||
|
||||
struct ARM64 {
|
||||
using WordTy = u64;
|
||||
using WordTy = ul64;
|
||||
|
||||
static constexpr u32 R_NONE = R_AARCH64_NONE;
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
@@ -1382,7 +1421,6 @@ struct ARM64 {
|
||||
static constexpr u32 e_machine = EM_AARCH64;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
@@ -1395,7 +1433,7 @@ template <> struct ElfDyn<ARM64> : public Elf64Dyn {};
|
||||
template <> struct ElfChdr<ARM64> : public Elf64Chdr {};
|
||||
|
||||
struct ARM32 {
|
||||
using WordTy = u32;
|
||||
using WordTy = ul32;
|
||||
|
||||
static constexpr u32 R_NONE = R_ARM_NONE;
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
@@ -1414,7 +1452,6 @@ struct ARM32 {
|
||||
static constexpr u32 e_machine = EM_ARM;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = true;
|
||||
};
|
||||
|
||||
@@ -1427,7 +1464,7 @@ template <> struct ElfDyn<ARM32> : public Elf32Dyn {};
|
||||
template <> struct ElfChdr<ARM32> : public Elf32Chdr {};
|
||||
|
||||
struct RISCV64 {
|
||||
using WordTy = u64;
|
||||
using WordTy = ul64;
|
||||
|
||||
static constexpr u32 R_NONE = R_RISCV_NONE;
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
@@ -1445,7 +1482,6 @@ struct RISCV64 {
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr bool is_rel = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool supports_tlsdesc = false;
|
||||
};
|
||||
|
||||
|
||||
+11
-17
@@ -26,13 +26,13 @@ static bool is_init_fini(const InputSection<E> &isec) {
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && !isec->extra().is_visited.exchange(true);
|
||||
return isec && isec->is_alive && !isec->is_visited.exchange(true);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->extra().is_visited);
|
||||
assert(isec->is_visited);
|
||||
|
||||
// A relocation can refer either a section fragment (i.e. a piece of
|
||||
// string in a mergeable string section) or a symbol. Mark all
|
||||
@@ -45,12 +45,12 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
@@ -102,11 +102,12 @@ collect_root_set(Context<E> &ctx) {
|
||||
// reduce the amount of non-memory-mapped segments, you should
|
||||
// use `strip` command, compile without debug info or use
|
||||
// -strip-all linker option.
|
||||
if (!(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
isec->extra().is_visited = true;
|
||||
u32 flags = isec->shdr().sh_flags;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
|
||||
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
|
||||
isec->shdr().sh_type == SHT_NOTE)
|
||||
(flags & SHF_GNU_RETAIN) || isec->shdr().sh_type == SHT_NOTE)
|
||||
enqueue_section(isec.get());
|
||||
}
|
||||
});
|
||||
@@ -132,7 +133,7 @@ collect_root_set(Context<E> &ctx) {
|
||||
// We just keep all CIEs and everything that are referenced by them.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (CieRecord<E> &cie : file->cies)
|
||||
for (ElfRel<E> &rel : cie.get_rels())
|
||||
for (const ElfRel<E> &rel : cie.get_rels())
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
|
||||
@@ -159,7 +160,7 @@ static void sweep(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->extra().is_visited) {
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
isec->kill();
|
||||
@@ -188,9 +189,6 @@ template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->extras.resize(file->sections.size());
|
||||
|
||||
mark_nonalloc_fragments(ctx);
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
@@ -201,10 +199,6 @@ void gc_sections(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+89
-46
@@ -40,22 +40,35 @@
|
||||
// regular trees are equal. Given this formulation, we want to find as
|
||||
// many identical vertices as possible.
|
||||
//
|
||||
// Solving such problem is computationally intensive, but mold is quite fast.
|
||||
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads.
|
||||
// This is contrary to lld and gold, which take about 5 and 50 seconds to
|
||||
// run ICF under the same condition, respectively.
|
||||
// Just like a lot of problems with graph, this problem doesn't have a
|
||||
// straightforward "optimal" solution, and we need to resort to heuristics.
|
||||
//
|
||||
// mold's ICF is faster because we are using a better algorithm.
|
||||
// It's actually me who developed and implemented the lld's ICF algorithm,
|
||||
// and I can say that mold's algorithm is better than that in all aspects.
|
||||
// It scales better for number of available cores, require less overall
|
||||
// computation, and has a smaller working set. So, it's better with a single
|
||||
// thread and even better with multiple threads.
|
||||
// mold approaches this problem by hashing program trees with increasing depth
|
||||
// on each iteration.
|
||||
// For example, when we start, we only hash individual functions with
|
||||
// their call into other functions omitted. From the second iteration, we
|
||||
// put the function they call into the hash by appending the hash of those
|
||||
// functions from the previous iteration. This means that the nth iteration
|
||||
// hashes call chain up to (n-1) levels deep.
|
||||
// We use a cryptographic hash function, so the unique number of hashes will
|
||||
// only monotonically increase as we take into account of deeper trees with
|
||||
// iterations (otherwise, that means we have found a hash collision). We stop
|
||||
// when the unique number of hashes stop increasing; this is based on the fact
|
||||
// that once we observe an iteration with the same amount of unique hashes as
|
||||
// the previous iteration, it will remain unchanged for further iterations.
|
||||
// This is provable, but here we omit the proof for brevity.
|
||||
//
|
||||
// When compared to other approaches, mold's approach has a relatively cheaper
|
||||
// cost per iteration, and as a bonus, is highly parallelizable.
|
||||
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads,
|
||||
// whereas lld takes 5 seconds and gold takes 50 seconds under the same
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
@@ -73,6 +86,13 @@ template<> struct hash<Digest> {
|
||||
return *(int64_t *)&k[0];
|
||||
}
|
||||
};
|
||||
|
||||
std::string to_string(Digest digest) {
|
||||
char buf[HASH_SIZE * 2];
|
||||
for (int i = 0; i < HASH_SIZE; i++)
|
||||
sprintf(buf + i * 2, "%02x", digest[i]);
|
||||
return {buf, sizeof(buf)};
|
||||
}
|
||||
}
|
||||
|
||||
namespace mold::elf {
|
||||
@@ -98,12 +118,13 @@ static void uniquify_cies(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_eligible(InputSection<E> &isec) {
|
||||
static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
const ElfShdr<E> &shdr = isec.shdr();
|
||||
std::string_view name = isec.name();
|
||||
|
||||
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
|
||||
bool is_executable = (shdr.sh_flags & SHF_EXECINSTR);
|
||||
bool is_exec = (shdr.sh_flags & SHF_EXECINSTR) ||
|
||||
ctx.arg.ignore_data_address_equality;
|
||||
bool is_relro = (name == ".data.rel.ro" ||
|
||||
name.starts_with(".data.rel.ro."));
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
|
||||
@@ -112,9 +133,10 @@ static bool is_eligible(InputSection<E> &isec) {
|
||||
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
|
||||
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
|
||||
bool is_enumerable = is_c_identifier(name);
|
||||
bool is_addr_taken = !ctx.arg.icf_all && isec.address_significant;
|
||||
|
||||
return is_alloc && is_executable && is_readonly && !is_bss &&
|
||||
!is_empty && !is_init && !is_fini && !is_enumerable;
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
}
|
||||
|
||||
static Digest digest_final(SHA256_CTX &sha) {
|
||||
@@ -175,6 +197,8 @@ struct LeafEq {
|
||||
}
|
||||
};
|
||||
|
||||
// Early merge of leaf nodes, which can be processed without constructing the
|
||||
// entire graph. This reduces the vertex count and improves memory efficiency.
|
||||
template <typename E>
|
||||
static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
Timer t(ctx, "merge_leaf_nodes");
|
||||
@@ -191,30 +215,30 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
if (!is_eligible(*isec)) {
|
||||
if (!is_eligible(ctx, *isec)) {
|
||||
non_eligible++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_leaf(ctx, *isec)) {
|
||||
leaf++;
|
||||
isec->extra().icf_leaf = true;
|
||||
isec->icf_leaf = true;
|
||||
auto [it, inserted] = map.insert({isec.get(), isec.get()});
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec.get();
|
||||
} else {
|
||||
eligible++;
|
||||
isec->extra().icf_eligible = true;
|
||||
isec->icf_eligible = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
|
||||
if (isec && isec->is_alive && isec->extra().icf_leaf) {
|
||||
if (isec && isec->is_alive && isec->icf_leaf) {
|
||||
auto it = map.find(isec.get());
|
||||
assert(it != map.end());
|
||||
isec->extra().leader = it->second;
|
||||
isec->leader = it->second;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -245,10 +269,10 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash((u64)frag);
|
||||
} else if (!isec) {
|
||||
hash('3');
|
||||
} else if (isec->extra().leader) {
|
||||
} else if (isec->leader) {
|
||||
hash('4');
|
||||
hash((u64)isec->extra().leader);
|
||||
} else if (isec->extra().icf_eligible) {
|
||||
hash((u64)isec->leader);
|
||||
} else if (isec->icf_eligible) {
|
||||
hash('5');
|
||||
} else {
|
||||
hash('6');
|
||||
@@ -271,7 +295,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
|
||||
hash(fde.get_rels(isec.file).size());
|
||||
|
||||
for (ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
hash(rel.r_type);
|
||||
hash(rel.r_offset - fde.input_offset);
|
||||
@@ -282,7 +306,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < isec.get_rels(ctx).size(); i++) {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
|
||||
hash(rel.r_offset);
|
||||
hash(rel.r_type);
|
||||
hash(isec.get_addend(rel));
|
||||
@@ -309,7 +333,7 @@ static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
if (isec && isec->is_alive && isec->icf_eligible)
|
||||
num_sections[i]++;
|
||||
});
|
||||
|
||||
@@ -324,12 +348,12 @@ static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
i64 idx = section_indices[i];
|
||||
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
|
||||
if (isec && isec->is_alive && isec->extra().icf_eligible)
|
||||
if (isec && isec->is_alive && isec->icf_eligible)
|
||||
sections[idx++] = isec.get();
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
sections[i]->extra().icf_idx = i;
|
||||
sections[i]->icf_idx = i;
|
||||
});
|
||||
|
||||
return sections;
|
||||
@@ -347,6 +371,9 @@ compute_digests(Context<E> &ctx, std::span<InputSection<E> *> sections) {
|
||||
return digests;
|
||||
}
|
||||
|
||||
// Build a graph, treating every function as a vertex and every function call
|
||||
// as an edge. See the description at the top for a more detailed formulation.
|
||||
// We use u32 indices here to improve cache locality.
|
||||
template <typename E>
|
||||
static void gather_edges(Context<E> &ctx,
|
||||
std::span<InputSection<E> *> sections,
|
||||
@@ -362,18 +389,18 @@ static void gather_edges(Context<E> &ctx,
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> &isec = *sections[i];
|
||||
assert(isec.extra().icf_eligible);
|
||||
assert(isec.icf_eligible);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
} else {
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
if (isec->icf_eligible)
|
||||
num_edges[i]++;
|
||||
}
|
||||
}
|
||||
@@ -392,12 +419,13 @@ static void gather_edges(Context<E> &ctx,
|
||||
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
|
||||
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
|
||||
frag_idx++;
|
||||
ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
} else {
|
||||
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.get_frag())
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
if (isec->extra().icf_eligible)
|
||||
edges[idx++] = isec->extra().icf_idx;
|
||||
if (isec->icf_eligible)
|
||||
edges[idx++] = isec->icf_idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -458,11 +486,11 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->extra().leader) {
|
||||
if (isec.get() == isec->extra().leader)
|
||||
if (isec && isec->is_alive && isec->leader) {
|
||||
if (isec.get() == isec->leader)
|
||||
leaders.push_back(isec.get());
|
||||
else
|
||||
map.insert({isec->extra().leader, isec.get()});
|
||||
map.insert({isec->leader, isec.get()});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -495,9 +523,8 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "icf");
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->extras.resize(file->sections.size());
|
||||
if (ctx.objs.empty())
|
||||
return;
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
@@ -505,6 +532,11 @@ void icf_sections(Context<E> &ctx) {
|
||||
// Prepare for the propagation rounds.
|
||||
std::vector<InputSection<E> *> sections = gather_sections(ctx);
|
||||
|
||||
// We allocate 3 arrays to store hashes for each vertex.
|
||||
// Index 0 and 1 are used for tree hashes from the previous iteration and the current iteration.
|
||||
// They switch roles every iteration --- see `slot` below.
|
||||
// Index 2 stores the initial, single-vertex hash --- this is combined with hashes from the connected vertices to
|
||||
// form the tree hash described above.
|
||||
std::vector<std::vector<Digest>> digests(3);
|
||||
digests[0] = compute_digests<E>(ctx, sections);
|
||||
digests[1].resize(digests[0].size());
|
||||
@@ -521,6 +553,17 @@ void icf_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "propagate");
|
||||
tbb::affinity_partitioner ap;
|
||||
|
||||
// A cheap test that the graph hasn't converged yet.
|
||||
// The loop after this one uses a strict condition, but it's expensive
|
||||
// as it requires sorting the entire hash collection.
|
||||
//
|
||||
// For nodes that have a cycle in downstream (i.e. recursive
|
||||
// functions and functions that calls recursive functions) will always
|
||||
// change with the iterations. Nodes that doesn't (i.e. non-recursive
|
||||
// functions) will stop changing as soon as the propagation depth reaches
|
||||
// the call tree depth.
|
||||
// Here, we test whether we have reached sufficient depth for the latter,
|
||||
// which is a necessary (but not sufficient) condition for convergence.
|
||||
i64 num_changed = -1;
|
||||
for (;;) {
|
||||
i64 n = propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
@@ -529,8 +572,12 @@ void icf_sections(Context<E> &ctx) {
|
||||
num_changed = n;
|
||||
}
|
||||
|
||||
// Run the pass until the unique number of hashes stop increasing, at which
|
||||
// point we have achieved convergence (proof omitted for brevity).
|
||||
i64 num_classes = -1;
|
||||
for (;;) {
|
||||
// count_num_classes requires sorting which is O(n log n), so do a little
|
||||
// more work beforehand to amortize that log factor.
|
||||
for (i64 i = 0; i < 10; i++)
|
||||
propagate<E>(digests, edges, edge_indices, slot, ap);
|
||||
|
||||
@@ -558,7 +605,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
auto it = map->find(digest[i]);
|
||||
assert(it != map->end());
|
||||
sections[i]->extra().leader = it->second;
|
||||
sections[i]->leader = it->second;
|
||||
});
|
||||
|
||||
// Since free'ing the map is slow, postpone it.
|
||||
@@ -575,7 +622,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file == file) {
|
||||
InputSection<E> *isec = sym->get_input_section();
|
||||
if (isec && isec->extra().leader && isec->extra().leader != isec) {
|
||||
if (isec && isec->leader && isec->leader != isec) {
|
||||
isec->kill();
|
||||
isec->killed_by_icf = true;
|
||||
}
|
||||
@@ -588,10 +635,6 @@ void icf_sections(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+140
-67
@@ -63,6 +63,15 @@ void InputFile<E>::clear_symbols() {
|
||||
}
|
||||
}
|
||||
|
||||
// Find the source filename. It should be listed in symtab as STT_FILE.
|
||||
template <typename E>
|
||||
std::string_view InputFile<E>::get_source_name() const {
|
||||
for (i64 i = 0; i < first_global; i++)
|
||||
if (Symbol<E> *sym = symbols[i]; sym->get_type() == STT_FILE)
|
||||
return sym->name();
|
||||
return "";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E>::ObjectFile(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name, bool is_in_lib)
|
||||
@@ -105,11 +114,11 @@ u32 ObjectFile<E>::read_note_gnu_property(Context<E> &ctx,
|
||||
continue;
|
||||
|
||||
while (!desc.empty()) {
|
||||
u32 type = *(u32 *)desc.data();
|
||||
u32 size = *(u32 *)(desc.data() + 4);
|
||||
u32 type = *(ul32 *)desc.data();
|
||||
u32 size = *(ul32 *)(desc.data() + 4);
|
||||
desc = desc.substr(8);
|
||||
if (type == GNU_PROPERTY_X86_FEATURE_1_AND)
|
||||
ret |= *(u32 *)desc.data();
|
||||
ret |= *(ul32 *)desc.data();
|
||||
desc = desc.substr(align_to(size, E::word_size));
|
||||
}
|
||||
}
|
||||
@@ -122,7 +131,8 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->elf_sections.size(); i++) {
|
||||
const ElfShdr<E> &shdr = this->elf_sections[i];
|
||||
|
||||
if ((shdr.sh_flags & SHF_EXCLUDE) && !(shdr.sh_flags & SHF_ALLOC))
|
||||
if ((shdr.sh_flags & SHF_EXCLUDE) && !(shdr.sh_flags & SHF_ALLOC) &&
|
||||
shdr.sh_type != SHT_LLVM_ADDRSIG)
|
||||
continue;
|
||||
|
||||
switch (shdr.sh_type) {
|
||||
@@ -131,10 +141,15 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (shdr.sh_info >= this->elf_syms.size())
|
||||
Fatal(ctx) << *this << ": invalid symbol index";
|
||||
const ElfSym<E> &sym = this->elf_syms[shdr.sh_info];
|
||||
std::string_view signature = symbol_strtab.data() + sym.st_name;
|
||||
std::string_view signature = this->symbol_strtab.data() + sym.st_name;
|
||||
|
||||
// Ignore a broken comdat group GCC emits for .debug_macros.
|
||||
// https://github.com/rui314/mold/issues/438
|
||||
if (signature.starts_with("wm4."))
|
||||
continue;
|
||||
|
||||
// Get comdat group members.
|
||||
std::span<u32> entries = this->template get_data<u32>(ctx, shdr);
|
||||
std::span<ul32> entries = this->template get_data<ul32>(ctx, shdr);
|
||||
|
||||
if (entries.empty())
|
||||
Fatal(ctx) << *this << ": empty SHT_GROUP";
|
||||
@@ -167,11 +182,14 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// making the stack area executable is too dangerous. Tell our
|
||||
// users about the difference if that matters.
|
||||
if (name == ".note.GNU-stack") {
|
||||
if (shdr.sh_flags & SHF_EXECINSTR)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
" https://github.com/rui314/mold/tree/main/docs/execstack.md"
|
||||
" for more info.";
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
if (!ctx.arg.z_execstack && !ctx.arg.z_execstack_if_needed)
|
||||
Warn(ctx) << *this << ": this file may cause a segmentation"
|
||||
" fault because it requires an executable stack. See"
|
||||
" https://github.com/rui314/mold/tree/main/docs/execstack.md"
|
||||
" for more info.";
|
||||
needs_executable_stack = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -199,16 +217,22 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG) {
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
llvm_addrsig = this->sections[i].get();
|
||||
}
|
||||
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
InputSection<E> *isec = this->sections[i].get();
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = isec;
|
||||
if (name == ".debug_abbrev")
|
||||
debug_abbrev = isec;
|
||||
if (name == ".debug_ranges")
|
||||
debug_ranges = isec;
|
||||
if (name == ".debug_rnglists")
|
||||
debug_rnglists = isec;
|
||||
|
||||
// If --gdb-index is given, contents of .debug_gnu_pubnames and
|
||||
// .debug_gnu_pubtypes are copied to .gdb_index, so keeping them
|
||||
@@ -222,6 +246,16 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
debug_pubtypes = isec;
|
||||
isec->is_alive = false;
|
||||
}
|
||||
|
||||
// .debug_types is similar to .debug_info but contains type info
|
||||
// only. It exists only in DWARF 4, has been removed in DWARF 5 and
|
||||
// neither GCC nor Clang generate it by default
|
||||
// (-fdebug-types-section is needed). As such there is probably
|
||||
// little need to support it.
|
||||
if (name == ".debug_types")
|
||||
Fatal(ctx) << *this << ": mold's --gdb-index is not compatible"
|
||||
" with .debug_types; to fix this error, remove"
|
||||
" -fdebug-types-section and recompile";
|
||||
}
|
||||
|
||||
static Counter counter("regular_sections");
|
||||
@@ -290,7 +324,7 @@ void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
|
||||
// This function parses an input .eh_frame section.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
|
||||
@@ -299,13 +333,13 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
i64 rel_idx = 0;
|
||||
|
||||
for (std::string_view data = contents; !data.empty();) {
|
||||
i64 size = *(u32 *)data.data();
|
||||
i64 size = *(ul32 *)data.data();
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
i64 begin_offset = data.data() - contents.data();
|
||||
i64 end_offset = begin_offset + size + 4;
|
||||
i64 id = *(u32 *)(data.data() + 4);
|
||||
i64 id = *(ul32 *)(data.data() + 4);
|
||||
data = data.substr(size + 4);
|
||||
|
||||
i64 rel_begin = rel_idx;
|
||||
@@ -341,7 +375,7 @@ void ObjectFile<E>::read_ehframe(Context<E> &ctx, InputSection<E> &isec) {
|
||||
};
|
||||
|
||||
for (i64 i = fdes_begin; i < fdes.size(); i++) {
|
||||
i64 cie_offset = *(i32 *)(contents.data() + fdes[i].input_offset + 4);
|
||||
i64 cie_offset = *(il32 *)(contents.data() + fdes[i].input_offset + 4);
|
||||
fdes[i].cie_idx = find_cie(fdes[i].input_offset + 4 - cie_offset);
|
||||
}
|
||||
|
||||
@@ -408,7 +442,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
if (esym.is_common())
|
||||
Fatal(ctx) << *this << ": common local symbol?";
|
||||
|
||||
std::string_view name = symbol_strtab.data() + esym.st_name;
|
||||
std::string_view name = this->symbol_strtab.data() + esym.st_name;
|
||||
if (name.empty() && esym.st_type == STT_SECTION)
|
||||
if (InputSection<E> *sec = get_section(esym))
|
||||
name = sec->name();
|
||||
@@ -437,7 +471,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
|
||||
// Get a symbol name
|
||||
std::string_view key = symbol_strtab.data() + esym.st_name;
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
std::string_view name = key;
|
||||
|
||||
// Parse symbol version after atsign
|
||||
@@ -464,27 +498,24 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
// We expect them to be sorted, so sort them if necessary.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
|
||||
if (!std::is_same_v<E, RISCV64>)
|
||||
return;
|
||||
if constexpr (std::is_same_v<E, RISCV64>) {
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_type != E::R_NONE && b.r_type != E::R_NONE &&
|
||||
a.r_offset < b.r_offset;
|
||||
};
|
||||
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_type != E::R_NONE && b.r_type != E::R_NONE &&
|
||||
a.r_offset < b.r_offset;
|
||||
};
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
sorted_rels.resize(sections.size());
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
if (std::is_sorted(rels.begin(), rels.end(), less))
|
||||
continue;
|
||||
|
||||
for (i64 i = 1; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];;
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
if (std::is_sorted(rels.begin(), rels.end(), less))
|
||||
continue;
|
||||
|
||||
sorted_rels[isec->shndx] = {rels.begin(), rels.end()};
|
||||
sort(sorted_rels[isec->shndx], less);
|
||||
isec->extra.sorted_rels = {rels.begin(), rels.end()};
|
||||
sort(isec->extra.sorted_rels, less);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,16 +555,10 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
sec.shdr().sh_flags);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
|
||||
// If thes section contents are compressed, uncompress them.
|
||||
if (sec.is_compressed()) {
|
||||
u8 *buf = new u8[sec.sh_size];
|
||||
sec.uncompress(ctx, buf);
|
||||
data = {(char *)buf, sec.sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
sec.uncompress(ctx);
|
||||
|
||||
std::string_view data = sec.contents;
|
||||
const char *begin = data.data();
|
||||
u64 entsize = sec.shdr().sh_entsize;
|
||||
HyperLogLog estimator;
|
||||
@@ -656,7 +681,7 @@ void ObjectFile<E>::register_section_pieces(Context<E> &ctx) {
|
||||
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
if (rels.empty())
|
||||
continue;
|
||||
|
||||
@@ -728,15 +753,46 @@ void ObjectFile<E>::register_section_pieces(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::fill_addrsig(Context<E> &ctx) {
|
||||
if (llvm_addrsig) {
|
||||
const u8 *start = reinterpret_cast<const u8 *>(llvm_addrsig->contents.data());
|
||||
const u8 *end = start + llvm_addrsig->contents.size();
|
||||
const u8 *cur = start;
|
||||
while (cur != end) {
|
||||
u64 symIndex = read_uleb(cur);
|
||||
if (this->symbols[symIndex]->file != this) continue;
|
||||
InputSection<E> *section = this->symbols[symIndex]->get_input_section();
|
||||
if (section)
|
||||
section->address_significant = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (Symbol<E> *sym : this->symbols) {
|
||||
if (sym->file != this) continue;
|
||||
InputSection<E> *section = sym->get_input_section();
|
||||
if (
|
||||
section &&
|
||||
(!llvm_addrsig // We don't have address significance information and needs to be safe
|
||||
|| (sym->is_imported || sym->is_exported)) // The symbol might be referenced from the
|
||||
// outside in an address-significant manner
|
||||
) {
|
||||
section->address_significant = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
sections.resize(this->elf_sections.size());
|
||||
symtab_sec = this->find_section(SHT_SYMTAB);
|
||||
|
||||
if (symtab_sec) {
|
||||
// In ELF, all local symbols precede global symbols in the symbol table.
|
||||
// sh_info has an index of the first global symbol.
|
||||
this->first_global = symtab_sec->sh_info;
|
||||
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
|
||||
symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
}
|
||||
|
||||
initialize_sections(ctx);
|
||||
@@ -925,7 +981,7 @@ void ObjectFile<E>::eliminate_duplicate_comdat_groups() {
|
||||
if (group->owner == this->priority)
|
||||
continue;
|
||||
|
||||
std::span<u32> entries = pair.second;
|
||||
std::span<ul32> entries = pair.second;
|
||||
for (u32 i : entries)
|
||||
if (sections[i])
|
||||
sections[i]->kill();
|
||||
@@ -937,6 +993,18 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
if (!this->is_alive)
|
||||
return;
|
||||
|
||||
auto report_undef = [&](Symbol<E> &sym) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = this->get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
};
|
||||
|
||||
for (i64 i = this->first_global; i < this->symbols.size(); i++) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
@@ -949,7 +1017,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
// imported symbol, it's handled as if no symbols were found.
|
||||
if (sym.file && sym.file->is_dso &&
|
||||
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
|
||||
report_undef(ctx, *this, sym);
|
||||
report_undef(sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -959,7 +1027,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
|
||||
// If a symbol name is in the form of "foo@version", search for
|
||||
// symbol "foo" and check if the symbol has version "version".
|
||||
std::string_view key = symbol_strtab.data() + esym.st_name;
|
||||
std::string_view key = this->symbol_strtab.data() + esym.st_name;
|
||||
if (i64 pos = key.find('@'); pos != key.npos) {
|
||||
Symbol<E> *sym2 = get_symbol(ctx, key.substr(0, pos));
|
||||
if (sym2->file && sym2->file->is_dso &&
|
||||
@@ -979,7 +1047,7 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
report_undef(ctx, *this, sym);
|
||||
report_undef(sym);
|
||||
|
||||
// Convert remaining undefined symbols to dynamic symbols.
|
||||
if (ctx.arg.shared) {
|
||||
@@ -1026,7 +1094,7 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
// Scan relocations against exception frames
|
||||
for (CieRecord<E> &cie : cies) {
|
||||
for (ElfRel<E> &rel : cie.get_rels()) {
|
||||
for (const ElfRel<E> &rel : cie.get_rels()) {
|
||||
Symbol<E> &sym = *this->symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_imported) {
|
||||
@@ -1063,10 +1131,14 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
if (!has_common_symbol)
|
||||
return;
|
||||
|
||||
OutputSection<E> *osec =
|
||||
OutputSection<E> *common =
|
||||
OutputSection<E>::get_instance(ctx, ".common", SHT_NOBITS,
|
||||
SHF_WRITE | SHF_ALLOC);
|
||||
|
||||
OutputSection<E> *tls_common =
|
||||
OutputSection<E>::get_instance(ctx, ".tls_common", SHT_NOBITS,
|
||||
SHF_WRITE | SHF_ALLOC | SHF_TLS);
|
||||
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
if (!this->elf_syms[i].is_common())
|
||||
continue;
|
||||
@@ -1082,17 +1154,20 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
|
||||
elf_sections2.push_back({});
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
shdr.sh_flags = SHF_ALLOC;
|
||||
|
||||
bool is_tls = (sym.get_type() == STT_TLS);
|
||||
shdr.sh_flags = is_tls ? (SHF_ALLOC | SHF_TLS) : SHF_ALLOC;
|
||||
shdr.sh_type = SHT_NOBITS;
|
||||
shdr.sh_size = this->elf_syms[i].st_size;
|
||||
shdr.sh_addralign = this->elf_syms[i].st_value;
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, ".common", idx);
|
||||
isec->output_section = osec;
|
||||
std::make_unique<InputSection<E>>(ctx, *this,
|
||||
is_tls ? ".tls_common" : ".common",
|
||||
idx);
|
||||
isec->output_section = is_tls ? tls_common : common;
|
||||
|
||||
sym.file = this;
|
||||
sym.shndx = idx;
|
||||
@@ -1187,9 +1262,9 @@ void ObjectFile<E>::write_symtab(Context<E> &ctx) {
|
||||
esym.st_name = strtab_off;
|
||||
|
||||
if (sym.get_type() == STT_TLS)
|
||||
esym.st_value = sym.get_addr(ctx) - ctx.tls_begin;
|
||||
esym.st_value = sym.get_addr(ctx, false) - ctx.tls_begin;
|
||||
else
|
||||
esym.st_value = sym.get_addr(ctx);
|
||||
esym.st_value = sym.get_addr(ctx, false);
|
||||
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
esym.st_shndx = isec->output_section->shndx;
|
||||
@@ -1251,6 +1326,7 @@ SharedFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
template <typename E>
|
||||
SharedFile<E>::SharedFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
: InputFile<E>(ctx, mf) {
|
||||
this->is_needed = ctx.as_needed;
|
||||
this->is_alive = !ctx.as_needed;
|
||||
}
|
||||
|
||||
@@ -1259,7 +1335,7 @@ std::string SharedFile<E>::get_soname(Context<E> &ctx) {
|
||||
if (ElfShdr<E> *sec = this->find_section(SHT_DYNAMIC))
|
||||
for (ElfDyn<E> &dyn : this->template get_data<ElfDyn<E>>(ctx, *sec))
|
||||
if (dyn.d_tag == DT_SONAME)
|
||||
return symbol_strtab.data() + dyn.d_val;
|
||||
return this->symbol_strtab.data() + dyn.d_val;
|
||||
|
||||
if (this->mf->given_fullpath)
|
||||
return this->filename;
|
||||
@@ -1273,7 +1349,7 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (!symtab_sec)
|
||||
return;
|
||||
|
||||
symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
soname = get_soname(ctx);
|
||||
version_strings = read_verdef(ctx);
|
||||
|
||||
@@ -1294,7 +1370,7 @@ void SharedFile<E>::parse(Context<E> &ctx) {
|
||||
if (ver == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
std::string_view name = symbol_strtab.data() + esyms[i].st_name;
|
||||
std::string_view name = this->symbol_strtab.data() + esyms[i].st_name;
|
||||
bool is_hidden = (!vers.empty() && (vers[i] & VERSYM_HIDDEN));
|
||||
|
||||
this->elf_syms2.push_back(esyms[i]);
|
||||
@@ -1456,14 +1532,11 @@ void SharedFile<E>::write_symtab(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputFile<E>; \
|
||||
template class ObjectFile<E>; \
|
||||
template class SharedFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+94
-41
@@ -10,8 +10,8 @@ bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<ElfRel<E>> x = get_rels();
|
||||
std::span<ElfRel<E>> y = other.get_rels();
|
||||
std::span<const ElfRel<E>> x = get_rels();
|
||||
std::span<const ElfRel<E>> y = other.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
@@ -36,12 +36,10 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, shdr().sh_size};
|
||||
|
||||
bool compressed;
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
|
||||
if (name.starts_with(".zdebug")) {
|
||||
sh_size = *(ubig64 *)&contents[4];
|
||||
sh_size = *(ub64 *)&contents[4];
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
compressed = true;
|
||||
} else if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
@@ -55,28 +53,37 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
compressed = false;
|
||||
}
|
||||
|
||||
// Uncompress early if the relocation is REL-type so that we can read
|
||||
// addends from section contents. If RELA-type, we don't need to do this
|
||||
// because addends are in relocations.
|
||||
if (compressed && E::is_rel) {
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
}
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
// directly into the mmap'ed output file, but we want to uncompress
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if (E::is_rel)
|
||||
uncompress(ctx);
|
||||
|
||||
output_section =
|
||||
OutputSection<E>::get_instance(ctx, name, shdr().sh_type, shdr().sh_flags);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool InputSection<E>::is_compressed() {
|
||||
return !E::is_rel &&
|
||||
(name().starts_with(".zdebug") || (shdr().sh_flags & SHF_COMPRESSED));
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!compressed || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
contents = {(char *)buf, sh_size};
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
if (!compressed || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
auto do_uncompress = [&](std::string_view data) {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
@@ -100,7 +107,8 @@ void InputSection<E>::uncompress(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
if (hdr.ch_type != ELFCOMPRESS_ZLIB)
|
||||
Fatal(ctx) << *this << ": unsupported compression type";
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
do_uncompress(contents.substr(sizeof(ElfChdr<E>)));
|
||||
}
|
||||
|
||||
@@ -213,8 +221,8 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
// Copy data
|
||||
if constexpr (std::is_same_v<E, RISCV64>) {
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else if (is_compressed()) {
|
||||
uncompress(ctx, buf);
|
||||
} else if (compressed) {
|
||||
uncompress_to(ctx, buf);
|
||||
} else {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
}
|
||||
@@ -226,33 +234,78 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
void report_undef(Context<E> &ctx, InputFile<E> &file, Symbol<E> &sym) {
|
||||
if (ctx.arg.warn_once && !ctx.warned.insert({(void *)&sym, 1}))
|
||||
return;
|
||||
|
||||
switch (ctx.arg.unresolved_symbols) {
|
||||
case UNRESOLVED_ERROR:
|
||||
Error(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_WARN:
|
||||
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
|
||||
break;
|
||||
case UNRESOLVED_IGNORE:
|
||||
break;
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
for (const ElfSym<E> &esym : file.elf_syms) {
|
||||
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
|
||||
std::string_view name = file.symbol_strtab.data() + esym.st_name;
|
||||
if (ctx.arg.demangle)
|
||||
return demangle(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Record an undefined symbol error which will be displayed all at
|
||||
// once by report_undef_errors().
|
||||
template <typename E>
|
||||
void InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
std::stringstream ss;
|
||||
if (std::string_view source = file.get_source_name(); !source.empty())
|
||||
ss << ">>> referenced by " << source << "\n";
|
||||
else
|
||||
ss << ">>> referenced by " << *this << "\n";
|
||||
|
||||
ss << ">>> " << file;
|
||||
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
|
||||
ss << ":(" << func << ")";
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
}
|
||||
|
||||
// Report all undefined symbols, grouped by symbol.
|
||||
template <typename E>
|
||||
void report_undef_errors(Context<E> &ctx) {
|
||||
constexpr i64 max_errors = 3;
|
||||
|
||||
for (auto &pair : ctx.undef_errors) {
|
||||
std::string_view sym_name = pair.first;
|
||||
std::span<std::string> errors = pair.second;
|
||||
|
||||
if (ctx.arg.demangle)
|
||||
sym_name = demangle(sym_name);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "undefined symbol: " << sym_name << "\n";
|
||||
|
||||
for (i64 i = 0; i < errors.size() && i < max_errors; i++)
|
||||
ss << errors[i];
|
||||
|
||||
if (errors.size() > max_errors)
|
||||
ss << ">>> referenced " << (errors.size() - max_errors) << " more times\n";
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
Error(ctx) << ss.str();
|
||||
else if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
Warn(ctx) << ss.str();
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template struct CieRecord<E>; \
|
||||
template class InputSection<E>; \
|
||||
template void report_undef(Context<E> &, InputFile<E> &, Symbol<E> &)
|
||||
template void report_undef_errors(Context<E> &)
|
||||
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -405,10 +405,6 @@ void parse_dynamic_list(Context<E> &ctx, std::string path) {
|
||||
template \
|
||||
void parse_dynamic_list(Context<E> &ctx, std::string path)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+46
-49
@@ -79,13 +79,14 @@
|
||||
// the compiler backend.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../lto.h"
|
||||
#include "lto.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if 0
|
||||
@@ -304,6 +305,44 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
// This function restarts mold itself with `--:lto-pass2` and
|
||||
// `--:ignore-ir-file` flags. We do this as a workaround for the old
|
||||
// linker plugins that do not support the get_symbols_v3 API.
|
||||
//
|
||||
// get_symbols_v1 and get_symbols_v2 don't provide a way to ignore an
|
||||
// object file we previously passed to the linker plugin. So we can't
|
||||
// "unload" object files in archives that we ended up not choosing to
|
||||
// include into the final output.
|
||||
//
|
||||
// As a workaround, we restart the linker with a list of object files
|
||||
// the linker has to ignore, so that it won't read the object files
|
||||
// from archives next time.
|
||||
//
|
||||
// This is an ugly hack and should be removed once GCC adopts the v3 API.
|
||||
template <typename E>
|
||||
static void restart_process(Context<E> &ctx) {
|
||||
std::vector<const char *> args;
|
||||
|
||||
for (std::string_view arg : ctx.cmdline_args)
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
args.push_back("--:lto-pass2");
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
execv(self.c_str(), (char * const *)args.data());
|
||||
std::cerr << "execv failed: " << errno_string() << "\n";
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static PluginStatus
|
||||
get_symbols_v2(const void *handle, int nsyms, PluginSymbol *psyms) {
|
||||
@@ -564,44 +603,6 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// This function restarts mold itself with `--:lto-pass2` and
|
||||
// `--:ignore-ir-file` flags. We do this as a workaround for the old
|
||||
// linker plugins that do not support the get_symbols_v3 API.
|
||||
//
|
||||
// get_symbols_v1 and get_symbols_v2 don't provide a way to ignore an
|
||||
// object file we previously passed to the linker plugin. So we can't
|
||||
// "unload" object files in archives that we ended up not choosing to
|
||||
// include into the final output.
|
||||
//
|
||||
// As a workaround, we restart the linker with a list of object files
|
||||
// the linker has to ignore, so that it won't read the object files
|
||||
// from archives next time.
|
||||
//
|
||||
// This is an ugly hack and should be removed once GCC adopts the v3 API.
|
||||
template <typename E>
|
||||
static void restart_process(Context<E> &ctx) {
|
||||
std::vector<const char *> args;
|
||||
|
||||
for (std::string_view arg : ctx.cmdline_args)
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
args.push_back("--:lto-pass2");
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
execv(self.c_str(), (char * const *)args.data());
|
||||
std::cerr << "execv failed: " << errno_string() << "\n";
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
@@ -614,21 +615,21 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->is_lto_obj)
|
||||
continue;
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
if (esym.is_undef() && sym.file && sym.file != file &&
|
||||
!sym.file->is_dso && ((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
if (sym.file && !sym.file->is_dso &&
|
||||
((ObjectFile<E> *)sym.file)->is_lto_obj) {
|
||||
std::scoped_lock lock(sym.mu);
|
||||
sym.referenced_by_regular_obj = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
@@ -652,10 +653,6 @@ void lto_cleanup(Context<E> &ctx) {
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &); \
|
||||
template void lto_cleanup(Context<E> &)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+6
-6
@@ -72,15 +72,15 @@ enum PluginOutputFileType {
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
int fd;
|
||||
off_t offset;
|
||||
off_t filesize;
|
||||
int32_t fd;
|
||||
uint64_t offset;
|
||||
uint64_t filesize;
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct PluginSection {
|
||||
const void *handle;
|
||||
int shndx;
|
||||
uint32_t shndx;
|
||||
};
|
||||
|
||||
struct PluginSymbol {
|
||||
@@ -89,10 +89,10 @@ struct PluginSymbol {
|
||||
char def;
|
||||
char symbol_type;
|
||||
char section_kind;
|
||||
int visibility;
|
||||
int32_t visibility;
|
||||
uint64_t size;
|
||||
char *comdat_key;
|
||||
int resolution;
|
||||
int32_t resolution;
|
||||
};
|
||||
|
||||
enum PluginSymbolKind {
|
||||
+88
-104
@@ -1,6 +1,7 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
@@ -41,10 +42,11 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
count++;
|
||||
|
||||
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
|
||||
return new ObjectFile<E>;
|
||||
return nullptr;
|
||||
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
@@ -90,7 +92,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, child, mf->name));
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -103,7 +106,8 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(new_lto_obj(ctx, mf, ""));
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
|
||||
ctx.objs.push_back(file);
|
||||
return;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type: " << type;
|
||||
@@ -138,7 +142,7 @@ static i64 get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
template <typename E>
|
||||
static i64
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
@@ -184,51 +188,51 @@ MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool, bool>> state;
|
||||
ctx.is_static = ctx.arg.is_static;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
const std::string &arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (read_flag(args, "as-needed")) {
|
||||
if (arg == "--as-needed") {
|
||||
ctx.as_needed = true;
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
} else if (arg == "--no-as-needed") {
|
||||
ctx.as_needed = false;
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
} else if (arg == "--whole-archive") {
|
||||
ctx.whole_archive = true;
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
} else if (arg == "--no-whole-archive") {
|
||||
ctx.whole_archive = false;
|
||||
} else if (read_flag(args, "Bstatic")) {
|
||||
} else if (arg == "--Bstatic") {
|
||||
ctx.is_static = true;
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
} else if (arg == "--Bdynamic") {
|
||||
ctx.is_static = false;
|
||||
} else if (read_flag(args, "start-lib")) {
|
||||
} else if (arg == "--start-lib") {
|
||||
ctx.in_lib = true;
|
||||
} else if (read_flag(args, "end-lib")) {
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
parse_version_script(ctx, std::string(arg));
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
parse_dynamic_list(ctx, std::string(arg));
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
} else if (arg.starts_with("--version-script=")) {
|
||||
parse_version_script(ctx, arg.substr(strlen("--version-script=")));
|
||||
} else if (arg.starts_with("--dynamic-list=")) {
|
||||
parse_dynamic_list(ctx, arg.substr(strlen("--dynamic-list=")));
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
} else if (arg == "--pop-state") {
|
||||
if (state.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
|
||||
} else if (arg.starts_with("-l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, arg.substr(2));
|
||||
mf->given_fullpath = false;
|
||||
read_file(ctx, mf);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(args[0])));
|
||||
args = args.subspan(1);
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,13 +359,6 @@ static void show_stats(Context<E> &ctx) {
|
||||
sec->print_stats(ctx);
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
@@ -373,8 +370,7 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
std::vector<std::string_view> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation == -1)
|
||||
@@ -382,6 +378,7 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if (ctx.arg.emulation != E::e_machine) {
|
||||
#if !MOLD_DEBUG_X86_64_ONLY && !MOLD_DEBUG_ARM64_ONLY
|
||||
switch (ctx.arg.emulation) {
|
||||
case EM_386:
|
||||
return elf_main<I386>(argc, argv);
|
||||
@@ -392,25 +389,12 @@ static int elf_main(int argc, char **argv) {
|
||||
case EM_RISCV:
|
||||
return elf_main<RISCV64>(argc, argv);
|
||||
}
|
||||
unreachable();
|
||||
#endif
|
||||
Fatal(ctx) << "unknown emulation: " << ctx.arg.emulation;
|
||||
}
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx.arg.preload)
|
||||
try_resume_daemon(ctx);
|
||||
|
||||
i64 thread_count = ctx.arg.thread_count;
|
||||
if (thread_count == 0)
|
||||
thread_count = get_default_thread_count();
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
thread_count);
|
||||
|
||||
install_signal_handler();
|
||||
|
||||
if (!ctx.arg.directory.empty())
|
||||
@@ -418,6 +402,19 @@ static int elf_main(int argc, char **argv) {
|
||||
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
|
||||
<< ": " << errno_string();
|
||||
|
||||
if (ctx.arg.relocatable) {
|
||||
combine_objects(ctx, file_args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fork a subprocess unless --no-fork is given.
|
||||
std::function<void()> on_complete;
|
||||
if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
get_symbol(ctx, name)->wrap = true;
|
||||
@@ -427,31 +424,12 @@ static int elf_main(int argc, char **argv) {
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
get_symbol(ctx, name)->write_to_symtab = true;
|
||||
|
||||
// Preload input files
|
||||
std::function<void()> on_complete;
|
||||
std::function<void()> wait_for_client;
|
||||
|
||||
if (ctx.arg.preload)
|
||||
daemonize(ctx, &wait_for_client, &on_complete);
|
||||
else if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->traced = true;
|
||||
|
||||
// Parse input files
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
if (ctx.arg.preload) {
|
||||
wait_for_client();
|
||||
if (!reload_input_files(ctx)) {
|
||||
std::vector<char *> args(argv, argv + argc);
|
||||
args.push_back((char *)"--no-preload");
|
||||
args.push_back(nullptr);
|
||||
return elf_main<E>(argc + 1, args.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Uniquify shared object files by soname
|
||||
{
|
||||
std::unordered_set<std::string_view> seen;
|
||||
@@ -488,6 +466,10 @@ static int elf_main(int argc, char **argv) {
|
||||
// Set is_import and is_export bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
@@ -521,6 +503,12 @@ static int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
|
||||
check_cet_errors(ctx);
|
||||
|
||||
// Handle `-z execstack-if-needed`.
|
||||
if (ctx.arg.z_execstack_if_needed)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->needs_executable_stack)
|
||||
ctx.arg.z_execstack = true;
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
// were imported symbols.
|
||||
@@ -532,6 +520,12 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
// Beyond this point, no new symbols will be added to the result.
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
@@ -557,16 +551,6 @@ static int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
@@ -583,6 +567,16 @@ static int elf_main(int argc, char **argv) {
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_rels(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
|
||||
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
|
||||
});
|
||||
|
||||
// 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.
|
||||
@@ -594,6 +588,10 @@ static int elf_main(int argc, char **argv) {
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
|
||||
// Print reports about undefined symbols, if needed.
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
report_undef_errors(ctx);
|
||||
|
||||
// Fill .gnu.version_d section contents.
|
||||
if (ctx.verdef)
|
||||
ctx.verdef->construct(ctx);
|
||||
@@ -624,12 +622,12 @@ static int elf_main(int argc, char **argv) {
|
||||
chunk->update_shdr(ctx);
|
||||
|
||||
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
|
||||
return !chunk->is_output_section() && chunk->shdr.sh_size == 0;
|
||||
return chunk->kind() != OUTPUT_SECTION && chunk->shdr.sh_size == 0;
|
||||
});
|
||||
|
||||
// Set section indices.
|
||||
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
|
||||
if (!ctx.chunks[i]->is_header())
|
||||
if (ctx.chunks[i]->kind() != HEADER)
|
||||
ctx.chunks[i]->shndx = shndx++;
|
||||
|
||||
// Some types of section header refer other section by index.
|
||||
@@ -640,12 +638,6 @@ static int elf_main(int argc, char **argv) {
|
||||
// Assign offsets to output sections
|
||||
i64 filesize = set_osec_offsets(ctx);
|
||||
|
||||
// On ARM64, we may need to create so-called "range extension thunks"
|
||||
// to extend branch instructions reach, as they can jump only to
|
||||
// ±128 MiB.
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
filesize = create_range_extension_thunks(ctx);
|
||||
|
||||
// On RISC-V, branches are encode using multiple instructions so
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
@@ -669,7 +661,8 @@ static int elf_main(int argc, char **argv) {
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
@@ -685,12 +678,9 @@ static int elf_main(int argc, char **argv) {
|
||||
chunk->copy_buf(ctx);
|
||||
});
|
||||
|
||||
ctx.checkpoint();
|
||||
report_undef_errors(ctx);
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
write_thunks(ctx);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>)
|
||||
sort_arm_exidx(ctx);
|
||||
|
||||
@@ -707,12 +697,11 @@ static int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
if (ctx.buildid) {
|
||||
Timer t(ctx, "build_id");
|
||||
if (ctx.buildid)
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
}
|
||||
|
||||
t_copy.stop();
|
||||
ctx.checkpoint();
|
||||
|
||||
// Close the output file. This is the end of the linker's main job.
|
||||
ctx.output_file->close(ctx);
|
||||
@@ -721,12 +710,6 @@ static int elf_main(int argc, char **argv) {
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
// Handle --print-dependencies
|
||||
if (ctx.arg.print_dependencies == 1)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
@@ -753,20 +736,21 @@ static int elf_main(int argc, char **argv) {
|
||||
|
||||
for (std::function<void()> &fn : ctx.on_exit)
|
||||
fn();
|
||||
ctx.checkpoint();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#if MOLD_DEBUG_ARM64_ONLY
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
#else
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+5
-6
@@ -40,6 +40,9 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
}
|
||||
});
|
||||
|
||||
if (map.size() <= 1)
|
||||
return map;
|
||||
|
||||
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
|
||||
for (auto it = range.begin(); it != range.end(); it++) {
|
||||
std::vector<Symbol<E> *> &vec = it->second;
|
||||
@@ -72,7 +75,7 @@ void print_map(Context<E> &ctx) {
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
|
||||
if (!osec->is_output_section())
|
||||
if (osec->kind() != OUTPUT_SECTION)
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
@@ -109,10 +112,6 @@ void print_map(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+29
-26
@@ -1,5 +1,6 @@
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#include <alloca.h>
|
||||
#include <dlfcn.h>
|
||||
#include <spawn.h>
|
||||
#include <stdarg.h>
|
||||
@@ -31,13 +32,25 @@ static void debug_print(char *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void get_args(va_list *ap, int argc, char **argv) {
|
||||
for (int i = 1; i < argc - 1; i++) {
|
||||
char *arg = va_arg(*ap, char *);
|
||||
if (!arg)
|
||||
break;
|
||||
argv[i] = arg;
|
||||
}
|
||||
static int count_args(va_list *ap) {
|
||||
va_list aq;
|
||||
va_copy(aq, *ap);
|
||||
|
||||
int i = 0;
|
||||
while (va_arg(aq, char *))
|
||||
i++;
|
||||
va_end(aq);
|
||||
return i;
|
||||
}
|
||||
|
||||
static void copy_args(char **argv, const char *arg0, va_list *ap) {
|
||||
int i = 1;
|
||||
char *arg;
|
||||
while ((arg = va_arg(*ap, char *)))
|
||||
argv[i++] = arg;
|
||||
|
||||
((const char **)argv)[0] = arg0;
|
||||
((const char **)argv)[i] = NULL;
|
||||
}
|
||||
|
||||
static bool is_ld(const char *path) {
|
||||
@@ -52,10 +65,8 @@ static bool is_ld(const char *path) {
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
debug_print("execvpe %s\n", file);
|
||||
|
||||
if (!strcmp(file, "ld") || is_ld(file)) {
|
||||
if (!strcmp(file, "ld") || is_ld(file))
|
||||
file = get_mold_path();
|
||||
((const char **)argv)[0] = file;
|
||||
}
|
||||
|
||||
for (int i = 0; envp[i]; i++)
|
||||
putenv(envp[i]);
|
||||
@@ -66,12 +77,8 @@ int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
|
||||
int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
debug_print("execve %s\n", path);
|
||||
|
||||
if (is_ld(path)) {
|
||||
if (is_ld(path))
|
||||
path = get_mold_path();
|
||||
((const char **)argv)[0] = path;
|
||||
}
|
||||
|
||||
typeof(execve) *real = dlsym(RTLD_NEXT, "execve");
|
||||
return real(path, argv, envp);
|
||||
}
|
||||
@@ -79,8 +86,8 @@ int execve(const char *path, char *const *argv, char *const *envp) {
|
||||
int execl(const char *path, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(&ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
va_end(ap);
|
||||
return execve(path, argv, environ);
|
||||
}
|
||||
@@ -88,8 +95,8 @@ int execl(const char *path, const char *arg0, ...) {
|
||||
int execlp(const char *file, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(&ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
va_end(ap);
|
||||
return execvpe(file, argv, environ);
|
||||
}
|
||||
@@ -97,8 +104,8 @@ int execlp(const char *file, const char *arg0, ...) {
|
||||
int execle(const char *path, const char *arg0, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
char *argv[4096] = {(char *)arg0};
|
||||
get_args(&ap, 4096, argv);
|
||||
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
|
||||
copy_args(argv, arg0, &ap);
|
||||
char **env = va_arg(ap, char **);
|
||||
va_end(ap);
|
||||
return execve(path, argv, env);
|
||||
@@ -117,12 +124,8 @@ int posix_spawn(pid_t *pid, const char *path,
|
||||
const posix_spawnattr_t *attrp,
|
||||
char *const *argv, char *const *envp) {
|
||||
debug_print("posix_spawn %s\n", path);
|
||||
|
||||
if (is_ld(path)) {
|
||||
if (is_ld(path))
|
||||
path = get_mold_path();
|
||||
((const char **)argv)[0] = path;
|
||||
}
|
||||
|
||||
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
|
||||
return real(pid, path, file_actions, attrp, argv, envp);
|
||||
}
|
||||
+203
-296
File diff suppressed because it is too large.
Load diff
+274
-472
File diff suppressed because it is too large.
Load diff
+54
-16
@@ -82,6 +82,7 @@ void create_synthetic_sections(Context<E> &ctx) {
|
||||
|
||||
ctx.versym = push(new VersymSection<E>);
|
||||
ctx.verneed = push(new VerneedSection<E>);
|
||||
ctx.note_package = push(new NotePackageSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (std::is_same_v<E, ARM32>) {
|
||||
@@ -128,8 +129,6 @@ static void mark_live_objects(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void do_resolve_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_resolve_symbols");
|
||||
|
||||
auto for_each_file = [&](std::function<void(InputFile<E> *)> fn) {
|
||||
tbb::parallel_for_each(ctx.objs, fn);
|
||||
tbb::parallel_for_each(ctx.dsos, fn);
|
||||
@@ -163,7 +162,7 @@ void do_resolve_symbols(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void resolve_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_resolve_symbols");
|
||||
Timer t(ctx, "resolve_symbols");
|
||||
|
||||
std::vector<ObjectFile<E> *> objs = ctx.objs;
|
||||
std::vector<SharedFile<E> *> dsos = ctx.dsos;
|
||||
@@ -207,7 +206,7 @@ void resolve_symbols(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = true;
|
||||
file->is_alive = !file->is_needed;
|
||||
});
|
||||
|
||||
do_resolve_symbols(ctx);
|
||||
@@ -365,7 +364,8 @@ ObjectFile<E> *create_internal_file(Context<E> &ctx) {
|
||||
obj->priority = 1;
|
||||
|
||||
auto add = [&](std::string_view name, u8 st_type = STT_NOTYPE) {
|
||||
ElfSym<E> esym = {};
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = st_type;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
@@ -431,7 +431,8 @@ ObjectFile<E> *create_internal_file(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < ctx.arg.defsyms.size(); i++) {
|
||||
Symbol<E> *sym = ctx.arg.defsyms[i].first;
|
||||
ElfSym<E> esym = {};
|
||||
ElfSym<E> esym;
|
||||
memset(&esym, 0, sizeof(esym));
|
||||
esym.st_type = STT_NOTYPE;
|
||||
esym.st_shndx = SHN_ABS;
|
||||
esym.st_bind = STB_GLOBAL;
|
||||
@@ -550,7 +551,7 @@ R"(# This is an output of the mold linker's --print-dependencies=full option.
|
||||
|
||||
std::unordered_set<void *> visited;
|
||||
|
||||
for (ElfRel<E> &r : isec->get_rels(ctx)) {
|
||||
for (const ElfRel<E> &r : isec->get_rels(ctx)) {
|
||||
if (r.r_type == E::R_NONE)
|
||||
continue;
|
||||
|
||||
@@ -753,7 +754,7 @@ void shuffle_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.shuffle_sections_seed)
|
||||
seed = *ctx.arg.shuffle_sections_seed;
|
||||
else
|
||||
seed = std::random_device()();
|
||||
seed = ((u64)std::random_device()() << 32) | std::random_device()();
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
@@ -806,6 +807,11 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.output_sections,
|
||||
[&](std::unique_ptr<OutputSection<E>> &osec) {
|
||||
// This pattern will be processed in the next loop.
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
return;
|
||||
|
||||
// Since one output section may contain millions of input sections,
|
||||
// we first split input sections into groups and assign offsets to
|
||||
// groups.
|
||||
@@ -845,6 +851,16 @@ void compute_section_sizes(Context<E> &ctx) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// On ARM64, we may need to create so-called "range extension thunks" to
|
||||
// extend branch instructions reach, as they can jump only to ±128 MiB.
|
||||
// In this case, we compute the sizes of sections while inserting thunks.
|
||||
// This pass cannot be parallelized (`create_range_extension_thunks` is
|
||||
// parallelized internally, but the function itself is not thread-safe.
|
||||
if constexpr (std::is_same_v<E, ARM64>)
|
||||
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
|
||||
if (osec->shdr.sh_flags & SHF_EXECINSTR)
|
||||
create_range_extension_thunks(ctx, *osec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -1048,8 +1064,8 @@ void apply_version_script(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// Otherwise, use glob pattern matchers.
|
||||
VersionMatcher matcher;
|
||||
VersionMatcher cpp_matcher;
|
||||
MultiGlob matcher;
|
||||
MultiGlob cpp_matcher;
|
||||
|
||||
for (VersionPattern &v : ctx.version_patterns) {
|
||||
if (v.is_cpp) {
|
||||
@@ -1061,6 +1077,9 @@ void apply_version_script(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
matcher.compile();
|
||||
cpp_matcher.compile();
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file != file)
|
||||
@@ -1093,6 +1112,8 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->symbols.size() - file->first_global; i++) {
|
||||
// Match VERSION part of symbol foo@VERSION with version definitions.
|
||||
// The symbols' VERSION parts are in file->symvers.
|
||||
if (!file->symvers[i])
|
||||
continue;
|
||||
|
||||
@@ -1118,6 +1139,16 @@ void parse_symbol_version(Context<E> &ctx) {
|
||||
sym->ver_idx = it->second;
|
||||
if (!is_default)
|
||||
sym->ver_idx |= VERSYM_HIDDEN;
|
||||
|
||||
// If both symbol `foo` and `foo@VERSION` are defined, `foo@VERSION`
|
||||
// hides `foo` so that all references to `foo` are resolved to a
|
||||
// versioned symbol. Likewise, if `foo@VERSION` and `foo@@VERSION` are
|
||||
// defined, the default one takes precedence.
|
||||
Symbol<E> *sym2 = get_symbol(ctx, sym->name());
|
||||
if (sym2->file == file && !file->symvers[sym2->sym_idx - file->first_global])
|
||||
if (sym2->ver_idx == ctx.default_version ||
|
||||
(sym2->ver_idx & ~VERSYM_HIDDEN) == (sym->ver_idx & ~VERSYM_HIDDEN))
|
||||
sym2->ver_idx = VER_NDX_LOCAL;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1144,12 +1175,14 @@ void compute_import_export(Context<E> &ctx) {
|
||||
sym->ver_idx == VER_NDX_LOCAL)
|
||||
continue;
|
||||
|
||||
// If we are using a symbol in a DSO, we need to import it at runtime.
|
||||
if (sym->file != file && sym->file->is_dso) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_imported = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are creating a DSO, all global symbols are exported by default.
|
||||
if (sym->file == file) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
sym->is_exported = true;
|
||||
@@ -1163,6 +1196,14 @@ void compute_import_export(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void mark_addrsig(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_addrsig");
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->fill_addrsig(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void clear_padding(Context<E> &ctx) {
|
||||
Timer t(ctx, "clear_padding");
|
||||
@@ -1463,7 +1504,7 @@ void fix_synthetic_symbols(Context<E> &ctx) {
|
||||
|
||||
// _end, _etext, _edata and the like
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk->is_header())
|
||||
if (chunk->kind() == HEADER)
|
||||
continue;
|
||||
|
||||
if (chunk->shdr.sh_flags & SHF_ALLOC) {
|
||||
@@ -1665,6 +1706,7 @@ void write_dependency_file(Context<E> &ctx) {
|
||||
template void apply_version_script(Context<E> &); \
|
||||
template void parse_symbol_version(Context<E> &); \
|
||||
template void compute_import_export(Context<E> &); \
|
||||
template void mark_addrsig(Context<E> &); \
|
||||
template void clear_padding(Context<E> &); \
|
||||
template i64 get_section_rank(Context<E> &, Chunk<E> *); \
|
||||
template i64 set_osec_offsets(Context<E> &); \
|
||||
@@ -1672,10 +1714,6 @@ void write_dependency_file(Context<E> &ctx) {
|
||||
template i64 compress_debug_sections(Context<E> &); \
|
||||
template void write_dependency_file(Context<E> &);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+18
-21
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../output-file.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -298,7 +299,7 @@ void ROutputEhdr<E>::write_to(Context<E> &ctx) {
|
||||
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
|
||||
memcpy(&hdr.e_ident, "\177ELF", 4);
|
||||
hdr.e_ident[EI_CLASS] = (E::word_size == 8) ? ELFCLASS64 : ELFCLASS32;
|
||||
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
|
||||
hdr.e_ident[EI_DATA] = ELFDATA2LSB;
|
||||
hdr.e_ident[EI_VERSION] = EV_CURRENT;
|
||||
hdr.e_type = ET_REL;
|
||||
hdr.e_machine = E::e_machine;
|
||||
@@ -413,36 +414,36 @@ std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::unique_ptr<RObjectFile<E>>>
|
||||
open_files(Context<E> &ctx, std::span<std::string_view> args) {
|
||||
open_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files;
|
||||
bool whole_archive = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
if (read_flag(args, "whole-archive")) {
|
||||
const std::string &arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--whole-archive") {
|
||||
whole_archive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_flag(args, "no-whole-archive")) {
|
||||
if (arg == "--no-whole-archive") {
|
||||
whole_archive = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string_view arg;
|
||||
if (read_arg(ctx, args, arg, "version-script") ||
|
||||
read_arg(ctx, args, arg, "dynamic-list"))
|
||||
if (arg.starts_with("--version-script=") ||
|
||||
arg.starts_with("--dynamic-list="))
|
||||
continue;
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
|
||||
if (read_arg(ctx, args, arg, "l")) {
|
||||
mf = find_library(ctx, std::string(arg));
|
||||
if (arg == "-l") {
|
||||
mf = find_library(ctx, arg.substr(2));
|
||||
} else {
|
||||
if (arg.starts_with('-'))
|
||||
continue;
|
||||
arg = args[0];
|
||||
args = args.subspan(1);
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, std::string(arg));
|
||||
mf = MappedFile<Context<E>>::must_open(ctx, arg);
|
||||
}
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
@@ -482,7 +483,7 @@ static bool contains(std::unordered_set<std::string_view> &a,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
void combine_objects(Context<E> &ctx, std::span<std::string> file_args) {
|
||||
// Read object files
|
||||
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
|
||||
|
||||
@@ -574,8 +575,8 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
|
||||
// Open an output file
|
||||
i64 filesize = assign_offsets(ctx);
|
||||
std::unique_ptr<OutputFile<E>> out =
|
||||
OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
std::unique_ptr<OutputFile<Context<E>>> out =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
memset(out->buf, 0, filesize);
|
||||
ctx.buf = out->buf;
|
||||
|
||||
@@ -586,12 +587,8 @@ void combine_objects(Context<E> &ctx, std::span<std::string_view> file_args) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void combine_objects(Context<E> &, std::span<std::string_view>);
|
||||
template void combine_objects(Context<E> &, std::span<std::string>);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+8
-205
@@ -3,17 +3,12 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define DAEMON_TIMEOUT 30
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
@@ -60,198 +55,8 @@ std::function<void()> fork_child() {
|
||||
};
|
||||
}
|
||||
|
||||
static std::string base64(u8 *data, u64 size) {
|
||||
static const char chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_";
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
auto encode = [&](u32 x) {
|
||||
out << chars[x & 0b111111]
|
||||
<< chars[(x >> 6) & 0b111111]
|
||||
<< chars[(x >> 12) & 0b111111]
|
||||
<< chars[(x >> 18) & 0b111111];
|
||||
};
|
||||
|
||||
i64 i = 0;
|
||||
for (; i < size - 3; i += 3)
|
||||
encode((data[i + 2] << 16) | (data[i + 1] << 8) | data[i]);
|
||||
|
||||
if (i == size - 1)
|
||||
encode(data[i]);
|
||||
else if (i == size - 2)
|
||||
encode((data[i + 1] << 8) | data[i]);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::string compute_sha256(std::span<std::string_view> argv) {
|
||||
SHA256_CTX sha;
|
||||
SHA256_Init(&sha);
|
||||
|
||||
for (std::string_view arg : argv) {
|
||||
if (arg != "-preload" && arg != "--preload") {
|
||||
SHA256_Update(&sha, arg.data(), arg.size());
|
||||
char buf[] = {0};
|
||||
SHA256_Update(&sha, buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
u8 digest[SHA256_SIZE];
|
||||
SHA256_Final(digest, &sha);
|
||||
return base64(digest, SHA256_SIZE);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void send_fd(Context<E> &ctx, i64 conn, i64 fd) {
|
||||
struct iovec iov;
|
||||
char dummy = '1';
|
||||
iov.iov_base = &dummy;
|
||||
iov.iov_len = 1;
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char buf[CMSG_SPACE(sizeof(int))];
|
||||
msg.msg_control = buf;
|
||||
msg.msg_controllen = CMSG_LEN(sizeof(int));
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||
*(int *)CMSG_DATA(cmsg) = fd;
|
||||
|
||||
if (sendmsg(conn, &msg, 0) == -1)
|
||||
Fatal(ctx) << "sendmsg failed: " << errno_string();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 recv_fd(Context<E> &ctx, i64 conn) {
|
||||
struct iovec iov;
|
||||
char buf[1];
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = sizeof(buf);
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
msg.msg_control = (caddr_t)cmsgbuf;
|
||||
msg.msg_controllen = sizeof(cmsgbuf);
|
||||
|
||||
i64 len = recvmsg(conn, &msg, 0);
|
||||
if (len <= 0)
|
||||
Fatal(ctx) << "recvmsg failed: " << errno_string();
|
||||
|
||||
struct cmsghdr *cmsg;
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
return *(int *)CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void try_resume_daemon(Context<E> &ctx) {
|
||||
i64 conn = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
std::string path = "/tmp/mold-" + compute_sha256(ctx.cmdline_args);
|
||||
|
||||
struct sockaddr_un name = {};
|
||||
name.sun_family = AF_UNIX;
|
||||
memcpy(name.sun_path, path.data(), path.size());
|
||||
|
||||
if (connect(conn, (struct sockaddr *)&name, sizeof(name)) != 0) {
|
||||
close(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
send_fd(ctx, conn, STDOUT_FILENO);
|
||||
send_fd(ctx, conn, STDERR_FILENO);
|
||||
|
||||
char buf[1];
|
||||
i64 r = read(conn, buf, 1);
|
||||
close(conn);
|
||||
if (r == 1)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void daemonize(Context<E> &ctx, std::function<void()> *wait_for_client,
|
||||
std::function<void()> *on_complete) {
|
||||
if (daemon(1, 0) == -1)
|
||||
Fatal(ctx) << "daemon failed: " << errno_string();
|
||||
|
||||
i64 sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1)
|
||||
Fatal(ctx) << "socket failed: " << errno_string();
|
||||
|
||||
socket_tmpfile =
|
||||
strdup(("/tmp/mold-" + compute_sha256(ctx.cmdline_args)).c_str());
|
||||
|
||||
struct sockaddr_un name = {};
|
||||
name.sun_family = AF_UNIX;
|
||||
strcpy(name.sun_path, socket_tmpfile);
|
||||
|
||||
u32 orig_mask = umask(0177);
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1) {
|
||||
if (errno != EADDRINUSE)
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
|
||||
unlink(socket_tmpfile);
|
||||
if (bind(sock, (struct sockaddr *)&name, sizeof(name)) == -1)
|
||||
Fatal(ctx) << "bind failed: " << errno_string();
|
||||
}
|
||||
|
||||
umask(orig_mask);
|
||||
|
||||
if (listen(sock, 0) == -1)
|
||||
Fatal(ctx) << "listen failed: " << errno_string();
|
||||
|
||||
static i64 conn = -1;
|
||||
|
||||
*wait_for_client = [=, &ctx] {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(sock, &rfds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = DAEMON_TIMEOUT;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
i64 res = select(sock + 1, &rfds, NULL, NULL, &tv);
|
||||
if (res == -1)
|
||||
Fatal(ctx) << "select failed: " << errno_string();
|
||||
|
||||
if (res == 0) {
|
||||
std::cout << "timeout\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
conn = accept(sock, NULL, NULL);
|
||||
if (conn == -1)
|
||||
Fatal(ctx) << "accept failed: " << errno_string();
|
||||
unlink(socket_tmpfile);
|
||||
|
||||
dup2(recv_fd(ctx, conn), STDOUT_FILENO);
|
||||
dup2(recv_fd(ctx, conn), STDERR_FILENO);
|
||||
};
|
||||
|
||||
*on_complete = [=] {
|
||||
char buf[] = {1};
|
||||
int n = write(conn, buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
}
|
||||
|
||||
static std::string get_self_path() {
|
||||
return std::filesystem::read_symlink("/proc/self/exe");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
// Look for mold-wrapper.so from the same directory as the executable is.
|
||||
std::filesystem::path path = self.parent_path() / "mold-wrapper.so";
|
||||
std::error_code ec;
|
||||
@@ -277,13 +82,18 @@ std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
#ifdef __APPLE__
|
||||
// LD_PRELOAD is not supported on macOS
|
||||
Fatal(ctx) << "mold -run is not supported on macOS";
|
||||
#endif
|
||||
|
||||
std::string_view arg1 = argv[1];
|
||||
assert(arg1 == "-run" || arg1 == "--run");
|
||||
if (!argv[2])
|
||||
Fatal(ctx) << "-run: argument missing";
|
||||
|
||||
// Get the mold-wrapper.so path
|
||||
std::string self = get_self_path();
|
||||
std::string self = std::filesystem::read_symlink("/proc/self/exe");
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
@@ -307,15 +117,8 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void try_resume_daemon(Context<E> &); \
|
||||
template void daemonize(Context<E> &, std::function<void()> *, \
|
||||
std::function<void()> *); \
|
||||
template void process_run_subcommand(Context<E> &, int, char **)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::elf
|
||||
+3
-3
@@ -41,7 +41,7 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf) {
|
||||
continue;
|
||||
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
sec.sh_size / sizeof(ElfSym<E>)};
|
||||
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
auto skip = [](u8 type) {
|
||||
return type == STT_NOTYPE || type == STT_FILE || type == STT_SECTION;
|
||||
@@ -73,7 +73,7 @@ FileType get_file_type(MappedFile<C> *mf) {
|
||||
return FileType::EMPTY;
|
||||
|
||||
if (data.starts_with("\177ELF")) {
|
||||
switch (*(u16 *)(data.data() + 16)) {
|
||||
switch (*(ul16 *)(data.data() + 16)) {
|
||||
case 1: {
|
||||
// ET_REL
|
||||
elf::Elf32Ehdr &ehdr = *(elf::Elf32Ehdr *)data.data();
|
||||
@@ -95,7 +95,7 @@ FileType get_file_type(MappedFile<C> *mf) {
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(u32 *)(data.data() + 12)) {
|
||||
switch (*(ul32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 6: // MH_DYLIB
|
||||
|
||||
+11
-13
@@ -2,9 +2,9 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
|
||||
while (!pat.empty()) {
|
||||
@@ -20,8 +20,7 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.push_back({BRACKET});
|
||||
std::vector<bool> &bitmap = vec.back().bitmap;
|
||||
bitmap.resize(256);
|
||||
std::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
bool negate = false;
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
@@ -60,9 +59,9 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitmap[i] = true;
|
||||
bitset[i] = true;
|
||||
} else {
|
||||
bitmap[(u8)pat[0]] = true;
|
||||
bitset[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
@@ -71,8 +70,7 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
for (i64 i = 0; i < 256; i++)
|
||||
bitmap[i] = !bitmap[i];
|
||||
bitset.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
@@ -89,14 +87,14 @@ std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
|
||||
}
|
||||
}
|
||||
|
||||
return {GlobPattern{std::move(vec)}};
|
||||
return {Glob{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool GlobPattern::match(std::string_view str) {
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
@@ -138,7 +136,7 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
str = str.substr(1);
|
||||
break;
|
||||
case BRACKET:
|
||||
if (str.empty() || !e.bitmap[str[0]])
|
||||
if (str.empty() || !e.bitset[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
@@ -148,4 +146,4 @@ bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash -x
|
||||
set -e
|
||||
source /etc/os-release
|
||||
|
||||
# The first line for each distro installs a build dependency.
|
||||
# The second line installs extra packages for `make test`.
|
||||
|
||||
case "$ID$VERSION_ID" in
|
||||
ubuntu20.04)
|
||||
apt install -y git cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y file bsdmainutils
|
||||
;;
|
||||
ubuntu22.04 | debian11)
|
||||
apt install -y git cmake libssl-dev zlib1g-dev gcc g++
|
||||
apt-get install -y file bsdmainutils
|
||||
;;
|
||||
fedora*)
|
||||
dnf install -y git gcc-g++ cmake openssl-devel zlib-devel
|
||||
dnf install -y glibc-static file libstdc++-static diffutils
|
||||
;;
|
||||
opensuse-leap*)
|
||||
zypper install -y git make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed*)
|
||||
zypper install -y git make cmake zlib-devel libopenssl-devel gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo*)
|
||||
emerge dev-vcs/git dev-util/cmake sys-libs/zlib
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID $VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// This file defines integral types for file input/output. You should
|
||||
// use these types instead of the plain integers (such as u32 or i32)
|
||||
// when reading from/writing to an mmap'ed file area.
|
||||
//
|
||||
// Here is why you need these types. In C/C++, all data accesses must
|
||||
// be aligned. That is, if you read an N byte value from memory, the
|
||||
// address of that value must be a multiple of N. For example, reading
|
||||
// an u32 value from address 16 is legal, while reading from 18 is not
|
||||
// (since 18 is not a multiple of 4.) An unaligned access yields an
|
||||
// undefined behavior.
|
||||
//
|
||||
// All data members of the ELF data structures are naturally aligned,
|
||||
// so it may look like we don't need the integral types defined in
|
||||
// this file to access them. But there's a catch; if an object file is
|
||||
// in an archive file (a .a file), the beginning of the file is
|
||||
// guaranteed to be aligned only to a 2 bytes boundary, because ar
|
||||
// aligns each member only to a 2 byte boundary. Therefore, any data
|
||||
// members larger than 2 bytes may be unaligned in an mmap'ed memory,
|
||||
// and thus you always need to use the integral types in this file to
|
||||
// access it.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
#error "mold does not support big-endian hosts"
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, size_t SIZE = sizeof(T)>
|
||||
class LittleEndian {
|
||||
public:
|
||||
LittleEndian() { *this = 0; }
|
||||
LittleEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
T x = 0;
|
||||
memcpy(&x, val, SIZE);
|
||||
return x;
|
||||
}
|
||||
|
||||
LittleEndian &operator=(T x) {
|
||||
memcpy(&val, &x, SIZE);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LittleEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
LittleEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
LittleEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
LittleEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
LittleEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
LittleEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[SIZE];
|
||||
};
|
||||
|
||||
using il16 = LittleEndian<int16_t>;
|
||||
using il32 = LittleEndian<int32_t>;
|
||||
using il64 = LittleEndian<int64_t>;
|
||||
using ul16 = LittleEndian<uint16_t>;
|
||||
using ul24 = LittleEndian<uint32_t, 3>;
|
||||
using ul32 = LittleEndian<uint32_t>;
|
||||
using ul64 = LittleEndian<uint64_t>;
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = delete;
|
||||
|
||||
operator T() const {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
return bswap(x);
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
x = bswap(x);
|
||||
memcpy(&val, &x, sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t val[sizeof(T)];
|
||||
|
||||
static T bswap(T x) {
|
||||
if constexpr (sizeof(T) == 2)
|
||||
return __builtin_bswap16(x);
|
||||
else if constexpr (sizeof(T) == 4)
|
||||
return __builtin_bswap32(x);
|
||||
else
|
||||
return __builtin_bswap64(x);
|
||||
}
|
||||
};
|
||||
|
||||
using ib16 = BigEndian<int16_t>;
|
||||
using ib32 = BigEndian<int32_t>;
|
||||
using ib64 = BigEndian<int64_t>;
|
||||
using ub16 = BigEndian<uint16_t>;
|
||||
using ub32 = BigEndian<uint32_t>;
|
||||
using ub64 = BigEndian<uint64_t>;
|
||||
|
||||
} // namespace mold
|
||||
+279
-71
@@ -1,11 +1,11 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
static u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
using E = ARM64;
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
@@ -16,8 +16,8 @@ static u64 encode_page(u64 val) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubsSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
u32 *buf = (u32 *)(ctx.buf + this->hdr.offset);
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *buf = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
static const u32 insn[] = {
|
||||
@@ -26,10 +26,10 @@ void StubsSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_size);
|
||||
static_assert(sizeof(insn) == E::stub_size);
|
||||
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + ARM64::word_size * i;
|
||||
u64 this_addr = this->hdr.addr + ARM64::stub_size * i;
|
||||
u64 la_addr = ctx.lazy_symbol_ptr.hdr.addr + E::word_size * i;
|
||||
u64 this_addr = this->hdr.addr + E::stub_size * i;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[0] |= encode_page(page(la_addr) - page(this_addr));
|
||||
@@ -39,9 +39,9 @@ void StubsSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
u32 *start = (u32 *)(ctx.buf + this->hdr.offset);
|
||||
u32 *buf = start;
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
ul32 *start = (ul32 *)(ctx.buf + this->hdr.offset);
|
||||
ul32 *buf = start;
|
||||
|
||||
static const u32 insn0[] = {
|
||||
0x90000011, // adrp x17, $__dyld_private@PAGE
|
||||
@@ -52,14 +52,14 @@ void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == ARM64::stub_helper_hdr_size);
|
||||
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
|
||||
u64 dyld_private = get_symbol(ctx, "__dyld_private")->get_addr(ctx);
|
||||
buf[0] |= encode_page(page(dyld_private) - page(this->hdr.addr));
|
||||
buf[1] |= bits(dyld_private, 11, 0) << 10;
|
||||
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_addr(ctx);
|
||||
u64 stub_binder = get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx);
|
||||
buf[3] |= encode_page(page(stub_binder) - page(this->hdr.addr - 12));
|
||||
buf[4] |= bits(stub_binder, 11, 0) << 10;
|
||||
|
||||
@@ -72,7 +72,7 @@ void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
0x00000000, // addr: .long <idx>
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == ARM64::stub_helper_size);
|
||||
static_assert(sizeof(insn) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
buf[1] |= bits((start - buf - 1) * 4, 27, 2);
|
||||
@@ -81,31 +81,32 @@ void StubHelperSection<ARM64>::copy_buf(Context<ARM64> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<ARM64>
|
||||
read_reloc(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
static Relocation<E>
|
||||
read_reloc(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr, MachRel *rels, i64 &idx) {
|
||||
i64 addend = 0;
|
||||
|
||||
switch (rels[idx].type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
addend = read_addend((u8 *)file.mf->data + hdr.offset, rels[idx]);
|
||||
switch (MachRel &r = rels[idx]; r.p2size) {
|
||||
case 2:
|
||||
addend = *(il32 *)((u8 *)file.mf->data + hdr.offset + r.offset);
|
||||
break;
|
||||
case 3:
|
||||
addend = *(il64 *)((u8 *)file.mf->data + hdr.offset + r.offset);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_ADDEND:
|
||||
addend = rels[idx++].offset;
|
||||
addend = rels[idx++].idx;
|
||||
break;
|
||||
}
|
||||
|
||||
MachRel &r = rels[idx];
|
||||
Relocation<ARM64> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
Relocation<E> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
|
||||
if (r.is_extern) {
|
||||
rel.sym = file.syms[r.idx];
|
||||
@@ -113,13 +114,8 @@ read_reloc(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
return rel;
|
||||
}
|
||||
|
||||
u32 addr;
|
||||
if (r.is_pcrel)
|
||||
addr = hdr.addr + r.offset + addend;
|
||||
else
|
||||
addr = addend;
|
||||
|
||||
Subsection<ARM64> *target = file.find_subsection(ctx, addr);
|
||||
u64 addr = r.is_pcrel ? (hdr.addr + r.offset + addend) : addend;
|
||||
Subsection<E> *target = file.find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
@@ -129,10 +125,11 @@ read_reloc(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<ARM64>>
|
||||
read_relocations(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<ARM64>> vec;
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++)
|
||||
@@ -141,13 +138,26 @@ read_relocations(Context<ARM64> &ctx, ObjectFile<ARM64> &file,
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
for (Relocation<ARM64> &r : get_rels()) {
|
||||
Symbol<ARM64> *sym = r.sym;
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
@@ -159,94 +169,108 @@ void Subsection<ARM64>::scan_relocations(Context<ARM64> &ctx) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<ARM64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<ARM64>::apply_reloc(Context<ARM64> &ctx, u8 *buf) {
|
||||
std::span<Relocation<ARM64>> rels = get_rels();
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
std::span<Relocation<E>> rels = get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<ARM64> &r = rels[i];
|
||||
Relocation<E> &r = rels[i];
|
||||
u8 *loc = buf + r.offset;
|
||||
i64 val = r.addend;
|
||||
u64 pc = get_addr(ctx) + r.offset;
|
||||
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
|
||||
// Compute a relocated value.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_SUBTRACTOR: {
|
||||
Relocation<ARM64> s = rels[++i];
|
||||
Relocation<E> s = rels[++i];
|
||||
assert(s.type == ARM64_RELOC_UNSIGNED);
|
||||
u64 val1 = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
u64 val2 = s.sym ? s.sym->get_addr(ctx) : s.subsec->get_addr(ctx);
|
||||
val = val2 - val1;
|
||||
val += val2 - val1;
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES)
|
||||
val -= ctx.tls_begin;
|
||||
|
||||
// Write a computed value to the output buffer.
|
||||
switch (r.type) {
|
||||
case ARM64_RELOC_UNSIGNED:
|
||||
case ARM64_RELOC_SUBTRACTOR:
|
||||
case ARM64_RELOC_POINTER_TO_GOT:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
val -= pc;
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(i32 *)(buf + r.offset) = val;
|
||||
*(ul32 *)loc = val;
|
||||
else if (r.p2size == 3)
|
||||
*(i64 *)(buf + r.offset) = val;
|
||||
*(ul64 *)loc = val;
|
||||
else
|
||||
unreachable();
|
||||
break;
|
||||
case ARM64_RELOC_BRANCH26:
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 27, 2);
|
||||
case ARM64_RELOC_BRANCH26: {
|
||||
assert(r.is_pcrel);
|
||||
val -= pc;
|
||||
|
||||
i64 lo = -(1 << 27);
|
||||
i64 hi = 1 << 27;
|
||||
|
||||
if (val < lo || hi <= val) {
|
||||
val = isec.osec.thunks[r.thunk_idx]->get_addr(r.thunk_sym_idx) - pc;
|
||||
assert(lo <= val && val < hi);
|
||||
}
|
||||
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case ARM64_RELOC_PAGE21:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21:
|
||||
assert(r.is_pcrel);
|
||||
*(u32 *)(buf + r.offset) |=
|
||||
encode_page(page(val) - page(get_addr(ctx) + r.offset));
|
||||
*(ul32 *)loc |= encode_page(page(val) - page(pc));
|
||||
break;
|
||||
case ARM64_RELOC_PAGEOFF12:
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: {
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset;
|
||||
|
||||
u32 insn = *(u32 *)(buf + r.offset);
|
||||
assert(!r.is_pcrel);
|
||||
u32 insn = *(ul32 *)loc;
|
||||
i64 scale = 0;
|
||||
if ((insn & 0x3b000000) == 0x39000000)
|
||||
scale = insn >> 30;
|
||||
*(u32 *)(buf + r.offset) |= bits(val, 11, scale) << 10;
|
||||
if ((insn & 0x3b000000) == 0x39000000) {
|
||||
scale = bits(insn, 31, 30);
|
||||
if (scale == 0 && (insn & 0x04800000) == 0x04800000)
|
||||
scale = 4;
|
||||
}
|
||||
*(ul32 *)loc |= bits(val, 11, scale) << 10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -255,4 +279,188 @@ void Subsection<ARM64>::apply_reloc(Context<ARM64> &ctx, u8 *buf) {
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = -1;
|
||||
sym->thunk_sym_idx = -1;
|
||||
sym->flags &= (u8)~NEEDS_RANGE_EXTN_THUNK;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
|
||||
Subsection<E> &subsec, Relocation<E> &rel) {
|
||||
// We pessimistically assume that PLT entries are unreacahble.
|
||||
if (sym.stub_idx != -1)
|
||||
return false;
|
||||
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
if (!sym.subsec || &sym.subsec->isec.osec != &subsec.isec.osec)
|
||||
return false;
|
||||
|
||||
if (sym.subsec->output_offset == -1)
|
||||
return false;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.addend;
|
||||
i64 P = subsec.get_addr(ctx) + rel.offset;
|
||||
i64 val = S + A - P;
|
||||
return -(1 << 27) <= val && val < (1 << 27);
|
||||
}
|
||||
|
||||
// We create a thunk no further than 100 MiB from any section.
|
||||
static constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
|
||||
|
||||
// We create a thunk for each 10 MiB input sections.
|
||||
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
// ARM64's call/jump instructions take 27 bits displacement, so they
|
||||
// can refer only up to ±128 MiB. If a branch target is further than
|
||||
// that, we need to let it branch to a linker-synthesized code
|
||||
// sequence that construct a full 32 bit address in a register and
|
||||
// jump there. That linker-synthesized code is called "thunk".
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<Subsection<E> *> members = osec.members;
|
||||
if (members.empty())
|
||||
return;
|
||||
|
||||
members[0]->output_offset = 0;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
|
||||
members[i]->output_offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < members.size()) {
|
||||
// Move D foward as far as we can jump from B to D.
|
||||
while (d < members.size() &&
|
||||
offset - members[b]->output_offset < MAX_DISTANCE) {
|
||||
offset = align_to(offset, 1 << members[d]->p2align);
|
||||
members[d]->output_offset = offset;
|
||||
offset += members[d]->input_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by GROUP_SIZE.
|
||||
while (c < members.size() &&
|
||||
members[c]->output_offset - members[b]->output_offset < GROUP_SIZE)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
if (c > 0) {
|
||||
i64 c_end = members[c - 1]->output_offset + members[c - 1]->input_size;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
}
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
thunk.offset = offset;
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
std::span<Relocation<E>> rels = subsec->get_rels();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
Relocation<E> &r = rels[i];
|
||||
if (!r.sym->file || r.type != ARM64_RELOC_BRANCH26)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, *r.sym, *subsec, r))
|
||||
continue;
|
||||
|
||||
// If the symbol is already in another thunk, reuse it.
|
||||
if (r.sym->thunk_idx != -1) {
|
||||
r.thunk_idx = r.sym->thunk_idx;
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to this thunk if it's not added already.
|
||||
r.thunk_idx = thunk.thunk_idx;
|
||||
r.thunk_sym_idx = -1;
|
||||
|
||||
if (!(r.sym->flags.fetch_or(NEEDS_RANGE_EXTN_THUNK) &
|
||||
NEEDS_RANGE_EXTN_THUNK)) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(r.sym);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
offset += thunk.size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->value} <
|
||||
std::tuple{b->file->priority, b->value};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->thunk_idx = thunk.thunk_idx;
|
||||
sym->thunk_sym_idx = i++;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(members.begin() + b, members.begin() + c,
|
||||
[&](Subsection<E> *subsec) {
|
||||
for (Relocation<E> &r : subsec->get_rels())
|
||||
if (r.thunk_idx == thunk.thunk_idx)
|
||||
r.thunk_sym_idx = r.sym->thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next group.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
|
||||
osec.hdr.size = offset;
|
||||
}
|
||||
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.hdr.offset + offset;
|
||||
|
||||
static const u32 data[] = {
|
||||
0x90000010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x91000210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f0200, // br x16
|
||||
};
|
||||
|
||||
static_assert(ENTRY_SIZE == sizeof(data));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.hdr.addr + offset + i * ENTRY_SIZE;
|
||||
|
||||
u8 *loc = buf + i * ENTRY_SIZE;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
*(ul32 *)loc |= encode_page(page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
+53
-59
@@ -2,25 +2,27 @@
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
template <>
|
||||
void StubsSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
void StubsSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + this->hdr.offset;
|
||||
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
// `ff 25 xx xx xx xx` is a RIP-relative indirect jump instruction,
|
||||
// i.e., `jmp *IMM(%rip)`. It loads an address from la_symbol_ptr
|
||||
// and jump there.
|
||||
static_assert(X86_64::stub_size == 6);
|
||||
static_assert(E::stub_size == 6);
|
||||
buf[i * 6] = 0xff;
|
||||
buf[i * 6 + 1] = 0x25;
|
||||
*(u32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * X86_64::word_size -
|
||||
*(ul32 *)(buf + i * 6 + 2) =
|
||||
ctx.lazy_symbol_ptr.hdr.addr + i * E::word_size -
|
||||
(this->hdr.addr + i * 6 + 6);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
void StubHelperSection<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *start = ctx.buf + this->hdr.offset;
|
||||
u8 *buf = start;
|
||||
|
||||
@@ -31,12 +33,12 @@ void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
0x90, // nop
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn0) == X86_64::stub_helper_hdr_size);
|
||||
static_assert(sizeof(insn0) == E::stub_helper_hdr_size);
|
||||
|
||||
memcpy(buf, insn0, sizeof(insn0));
|
||||
*(u32 *)(buf + 3) =
|
||||
*(ul32 *)(buf + 3) =
|
||||
get_symbol(ctx, "__dyld_private")->get_addr(ctx) - this->hdr.addr - 7;
|
||||
*(u32 *)(buf + 11) =
|
||||
*(ul32 *)(buf + 11) =
|
||||
get_symbol(ctx, "dyld_stub_binder")->get_got_addr(ctx) - this->hdr.addr - 15;
|
||||
|
||||
buf += 16;
|
||||
@@ -47,11 +49,11 @@ void StubHelperSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
0xe9, 0, 0, 0, 0, // jmp $__stub_helper
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == X86_64::stub_helper_size);
|
||||
static_assert(sizeof(insn) == E::stub_helper_size);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(u32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(u32 *)(buf + 6) = start - buf - 10;
|
||||
*(ul32 *)(buf + 1) = ctx.stubs.bind_offsets[i];
|
||||
*(ul32 *)(buf + 6) = start - buf - 10;
|
||||
buf += 10;
|
||||
}
|
||||
}
|
||||
@@ -71,14 +73,14 @@ static i64 get_reloc_addend(u32 type) {
|
||||
|
||||
static i64 read_addend(u8 *buf, const MachRel &r) {
|
||||
if (r.p2size == 2)
|
||||
return *(i32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
return *(il32 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
if (r.p2size == 3)
|
||||
return *(i64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
return *(il64 *)(buf + r.offset) + get_reloc_addend(r.type);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
static Relocation<X86_64>
|
||||
read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
static Relocation<E>
|
||||
read_reloc(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr, MachRel &r) {
|
||||
if (r.p2size != 2 && r.p2size != 3)
|
||||
Fatal(ctx) << file << ": invalid r.p2size: " << (u32)r.p2size;
|
||||
@@ -92,7 +94,7 @@ read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
}
|
||||
|
||||
u8 *buf = (u8 *)file.mf->data + hdr.offset;
|
||||
Relocation<X86_64> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
Relocation<E> rel{r.offset, (u8)r.type, (u8)r.p2size, (bool)r.is_pcrel};
|
||||
i64 addend = read_addend(buf, r);
|
||||
|
||||
if (r.is_extern) {
|
||||
@@ -107,7 +109,7 @@ read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
else
|
||||
addr = addend;
|
||||
|
||||
Subsection<X86_64> *target = file.find_subsection(ctx, addr);
|
||||
Subsection<E> *target = file.find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
Fatal(ctx) << file << ": bad relocation: " << r.offset;
|
||||
|
||||
@@ -117,10 +119,10 @@ read_reloc(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
}
|
||||
|
||||
template <>
|
||||
std::vector<Relocation<X86_64>>
|
||||
read_relocations(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
std::vector<Relocation<E>>
|
||||
read_relocations(Context<E> &ctx, ObjectFile<E> &file,
|
||||
const MachSection &hdr) {
|
||||
std::vector<Relocation<X86_64>> vec;
|
||||
std::vector<Relocation<E>> vec;
|
||||
vec.reserve(hdr.nreloc);
|
||||
|
||||
MachRel *rels = (MachRel *)(file.mf->data + hdr.reloff);
|
||||
@@ -130,42 +132,49 @@ read_relocations(Context<X86_64> &ctx, ObjectFile<X86_64> &file,
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
for (Relocation<X86_64> &r : get_rels()) {
|
||||
Symbol<X86_64> *sym = r.sym;
|
||||
void Subsection<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (Relocation<E> &r : get_rels()) {
|
||||
Symbol<E> *sym = r.sym;
|
||||
if (!sym)
|
||||
continue;
|
||||
|
||||
if (sym->is_imported && sym->file->is_dylib)
|
||||
((DylibFile<E> *)sym->file)->is_alive = true;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_GOT;
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
if (sym->is_imported) {
|
||||
if (r.p2size != 3) {
|
||||
Error(ctx) << this->isec << ": " << r << " relocation at offset 0x"
|
||||
<< std::hex << r.offset << " against symbol `"
|
||||
<< *sym << "' can not be used";
|
||||
}
|
||||
r.needs_dynrel = true;
|
||||
}
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
sym->flags |= NEEDS_GOT;
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (sym->file && sym->file->is_dylib())
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
sym->flags |= NEEDS_THREAD_PTR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sym->file && sym->file->is_dylib()) {
|
||||
if (sym->is_imported)
|
||||
sym->flags |= NEEDS_STUB;
|
||||
((DylibFile<X86_64> *)sym->file)->is_needed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Subsection<X86_64>::apply_reloc(Context<X86_64> &ctx, u8 *buf) {
|
||||
for (const Relocation<X86_64> &r : get_rels()) {
|
||||
void Subsection<E>::apply_reloc(Context<E> &ctx, u8 *buf) {
|
||||
for (const Relocation<E> &r : get_rels()) {
|
||||
if (r.sym && !r.sym->file) {
|
||||
Error(ctx) << "undefined symbol: " << isec.file << ": " << *r.sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 val = 0;
|
||||
u64 val = r.addend;
|
||||
|
||||
switch (r.type) {
|
||||
case X86_64_RELOC_UNSIGNED:
|
||||
@@ -174,46 +183,31 @@ void Subsection<X86_64>::apply_reloc(Context<X86_64> &ctx, u8 *buf) {
|
||||
case X86_64_RELOC_SIGNED_1:
|
||||
case X86_64_RELOC_SIGNED_2:
|
||||
case X86_64_RELOC_SIGNED_4:
|
||||
val = r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
if (r.sym->got_idx != -1) {
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid GOT_LOAD relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
val += r.sym ? r.sym->get_addr(ctx) : r.subsec->get_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_GOT:
|
||||
val = r.sym->get_got_addr(ctx);
|
||||
case X86_64_RELOC_GOT_LOAD:
|
||||
val += r.sym->get_got_addr(ctx);
|
||||
break;
|
||||
case X86_64_RELOC_TLV:
|
||||
if (r.sym->tlv_idx != -1) {
|
||||
val = r.sym->get_tlv_addr(ctx);
|
||||
} else {
|
||||
// Relax MOVQ into LEAQ
|
||||
if (buf[r.offset - 2] != 0x8b)
|
||||
Error(ctx) << isec << ": invalid TLV relocation";
|
||||
buf[r.offset - 2] = 0x8d;
|
||||
val = r.sym->get_addr(ctx);
|
||||
}
|
||||
val += r.sym->get_tlv_addr(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << isec << ": unknown reloc: " << (int)r.type;
|
||||
}
|
||||
|
||||
val += r.addend;
|
||||
// An address of a thread-local variable is computed as an offset
|
||||
// to the beginning of the first thread-local section.
|
||||
if (isec.hdr.type == S_THREAD_LOCAL_VARIABLES)
|
||||
val -= ctx.tls_begin;
|
||||
|
||||
if (r.is_pcrel)
|
||||
val -= get_addr(ctx) + r.offset + 4 + get_reloc_addend(r.type);
|
||||
|
||||
if (r.p2size == 2)
|
||||
*(u32 *)(buf + r.offset) = val;
|
||||
*(ul32 *)(buf + r.offset) = val;
|
||||
else if (r.p2size == 3)
|
||||
*(u64 *)(buf + r.offset) = val;
|
||||
*(ul64 *)(buf + r.offset) = val;
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
|
||||
+192
-23
@@ -15,43 +15,79 @@ static const char helpmsg[] = R"(
|
||||
Options:
|
||||
-F<PATH> Add DIR to framework search path
|
||||
-L<PATH> Add DIR to library search path
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-ObjC Load all static archive members that implement
|
||||
an Objective-C class or category
|
||||
-U <SYMBOL> Allow a symbol to be undefined
|
||||
-Z Do not search the standard directories when
|
||||
searching for libraries and frameworks
|
||||
-adhoc_codesign Add ad-hoc code signature to the output file
|
||||
-no_adhoc_codesign
|
||||
-all_load Include all objects from static archives
|
||||
-noall_load
|
||||
-arch <ARCH_NAME> Specify target architecture
|
||||
-bundle Produce a mach-o bundle
|
||||
-compatibility_version <VERSION>
|
||||
Specifies the compatibility version number of the library
|
||||
-current_version <VERSION> Specifies the current version number of the library.
|
||||
-dead_strip Remove unreachable functions and data
|
||||
-dead_strip_dylibs Remove unreachable dylibs from dependencies
|
||||
-demangle Demangle C++ symbols in log messages (default)
|
||||
-dylib Produce a dynamic library
|
||||
-dylib_compatibility_version <VERSION>
|
||||
Alias for -compatibility_version
|
||||
-dylib_current_version <VERSION>
|
||||
Alias for -current_version
|
||||
-dynamic Link against dylibs (default)
|
||||
-e <SYMBOL> Specify the entry point of a main executable
|
||||
-execute Produce an executable (default)
|
||||
-export_dynamic Preserves all global symbols in main executables during LTO
|
||||
-exported_symbol <SYMBOL> Export a given symbol
|
||||
-exported_symbols_list <FILE>
|
||||
Read a list of exported symbols from a given file
|
||||
-filelist <FILE>[,<DIR>] Specify the list of input file names
|
||||
-final_output <NAME>
|
||||
-force_load <FILE> Include all objects from a given static archive
|
||||
-framework <NAME>,[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-headerpad <SIZE> Allocate the size of padding after load commands
|
||||
-headerpad_max_install_names
|
||||
Allocate MAXPATHLEN byte padding after load commands
|
||||
-help Report usage information
|
||||
-hidden-l<LIB>
|
||||
-install_name <NAME>
|
||||
-l<LIB> Search for a given library
|
||||
-lto_library <FILE> Ignored
|
||||
-macos_version_min <VERSION>
|
||||
-map <FILE> Write map file to a given file
|
||||
-needed-l<LIB> Search for a given library
|
||||
-needed-framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-no_deduplicate Ignored
|
||||
-no_uuid Do not generate an LC_UUID load command
|
||||
-o <FILE> Set output filename
|
||||
-object_path_lto <FILE> Write a LTO temporary file to a given path
|
||||
-order_file <FILE> Ignored
|
||||
-pagezero_size <SIZE> Specify the size of the __PAGEZERO segment
|
||||
-platform_version <PLATFORM> <MIN_VERSION> <SDK_VERSION>
|
||||
Set platform, platform version and SDK version
|
||||
-random_uuid Generate a random LC_UUID load command
|
||||
-rpath <PATH> Add PATH to the runpath search path list
|
||||
-search_dylibs_first
|
||||
-search_paths_first
|
||||
-sectcreate <SEGNAME> <SECTNAME> <FILE>
|
||||
-stack_size <SIZE>
|
||||
-stats Show statistics info
|
||||
-syslibroot <DIR> Prepend DIR to library search paths
|
||||
-t Print out each file the linker loads
|
||||
-v Report version information)";
|
||||
-thread_count <NUMBER> Use given number of threads
|
||||
-u <SYMBOL> Force load a given symbol from archive if necessary
|
||||
-uexported_symbol <SYMBOL> Export all but a given symbol
|
||||
-unexported_symbols_list <FILE>
|
||||
Read a list of unexported symbols from a given file
|
||||
-v Report version information
|
||||
-weak_framework <NAME>[,<SUFFIX>]
|
||||
Search for a given framework
|
||||
-weak-l<LIB> Search for a given library)";
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_platform(Context<E> &ctx, std::string_view arg) {
|
||||
@@ -102,9 +138,37 @@ static bool is_directory(std::filesystem::path path) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string> &remaining) {
|
||||
static std::vector<std::string>
|
||||
read_lines(Context<E> &ctx, std::string_view path) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
std::vector<std::string> vec;
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t pos = data.find('\n');
|
||||
std::string_view line;
|
||||
|
||||
if (pos == data.npos) {
|
||||
line = data;
|
||||
data = "";
|
||||
} else {
|
||||
line = data.substr(0, pos);
|
||||
data = data.substr(pos + 1);
|
||||
}
|
||||
|
||||
line = string_trim(line);
|
||||
if (!line.empty() && !line.starts_with('#'))
|
||||
vec.push_back(std::string(line));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::vector<std::string_view> &args = ctx.cmdline_args;
|
||||
std::vector<std::string> remaining;
|
||||
i64 i = 1;
|
||||
|
||||
std::vector<std::string> framework_paths;
|
||||
@@ -116,6 +180,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::string_view arg;
|
||||
std::string_view arg2;
|
||||
std::string_view arg3;
|
||||
u64 hex_arg;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
if (args[i] == name) {
|
||||
@@ -160,6 +225,17 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
return false;
|
||||
};
|
||||
|
||||
auto read_hex = [&](std::string name) {
|
||||
if (!read_arg(name))
|
||||
return false;
|
||||
|
||||
size_t pos;
|
||||
hex_arg = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed " << name << ": " << arg;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (args[i].starts_with('@')) {
|
||||
std::vector<std::string_view> vec =
|
||||
read_response_file(ctx, args[i].substr(1));
|
||||
@@ -169,7 +245,7 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
|
||||
if (read_flag("-help") || read_flag("--help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[i]
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
@@ -182,10 +258,16 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
nostdlib = true;
|
||||
} else if (read_flag("-ObjC")) {
|
||||
ctx.arg.ObjC = true;
|
||||
} else if (read_arg("-U")) {
|
||||
ctx.arg.U.push_back(std::string(arg));
|
||||
} else if (read_flag("-adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = true;
|
||||
} else if (read_flag("-no_adhoc_codesign")) {
|
||||
ctx.arg.adhoc_codesign = false;
|
||||
} else if (read_flag("-all_load")) {
|
||||
remaining.push_back("-all_load");
|
||||
} else if (read_flag("-noall_load")) {
|
||||
remaining.push_back("-noall_load");
|
||||
} else if (read_arg("-arch")) {
|
||||
if (arg == "x86_64")
|
||||
ctx.arg.arch = CPU_TYPE_X86_64;
|
||||
@@ -195,8 +277,15 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
Fatal(ctx) << "unknown -arch: " << arg;
|
||||
} else if (read_flag("-bundle")) {
|
||||
ctx.output_type = MH_BUNDLE;
|
||||
} else if (read_arg("-compatibility_version") ||
|
||||
read_arg("-dylib_compatibility_version")) {
|
||||
ctx.arg.compatibility_version = parse_version(ctx, arg);
|
||||
} else if (read_arg("-current_version") ||
|
||||
read_arg("-dylib_current_version")) {
|
||||
ctx.arg.current_version = parse_version(ctx, arg);
|
||||
} else if (read_flag("-color-diagnostics") ||
|
||||
read_flag("--color-diagnostics")) {
|
||||
ctx.arg.color_diagnostics = true;
|
||||
} else if (read_flag("-dead_strip")) {
|
||||
ctx.arg.dead_strip = true;
|
||||
} else if (read_flag("-dead_strip_dylibs")) {
|
||||
@@ -205,32 +294,55 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag("-dylib")) {
|
||||
ctx.output_type = MH_DYLIB;
|
||||
} else if (read_arg("-headerpad")) {
|
||||
size_t pos;
|
||||
ctx.arg.headerpad = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -headerpad: " << arg;
|
||||
} else if (read_hex("-headerpad")) {
|
||||
ctx.arg.headerpad = hex_arg;
|
||||
} else if (read_flag("-headerpad_max_install_names")) {
|
||||
ctx.arg.headerpad = 1024;
|
||||
} else if (read_flag("-dynamic")) {
|
||||
ctx.arg.dynamic = true;
|
||||
} else if (read_arg("-e")) {
|
||||
ctx.arg.entry = arg;
|
||||
ctx.arg.entry = get_symbol(ctx, arg);
|
||||
} else if (read_flag("-execute")) {
|
||||
ctx.output_type = MH_EXECUTE;
|
||||
} else if (read_flag("-export_dynamic")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_arg("-exported_symbol")) {
|
||||
if (!ctx.arg.exported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-exported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-exported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.exported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-exported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_arg("-fatal_warnings")) {
|
||||
} else if (read_arg("-filelist")) {
|
||||
remaining.push_back("-filelist");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-final_output")) {
|
||||
ctx.arg.final_output = arg;
|
||||
} else if (read_arg("-force_load")) {
|
||||
remaining.push_back("-force_load");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-framework")) {
|
||||
remaining.push_back("-framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-lto_library")) {
|
||||
ctx.arg.lto_library = arg;
|
||||
} else if (read_arg("-macos_version_min")) {
|
||||
ctx.arg.platform = PLATFORM_MACOS;
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg);
|
||||
} else if (read_joined("-hidden-l")) {
|
||||
remaining.push_back("-hidden-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-install_name")) {
|
||||
ctx.arg.install_name = arg;
|
||||
} else if (read_joined("-l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_arg("-map")) {
|
||||
ctx.arg.map = arg;
|
||||
} else if (read_arg("-mllvm")) {
|
||||
ctx.arg.mllvm.push_back(std::string(arg));
|
||||
} else if (read_joined("-needed-l")) {
|
||||
remaining.push_back("-needed-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
@@ -238,28 +350,65 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
remaining.push_back("-needed_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_flag("-no_deduplicate")) {
|
||||
} else if (read_flag("-no_uuid")) {
|
||||
ctx.arg.uuid = UUID_NONE;
|
||||
} else if (read_arg("-o")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg("-pagezero_size")) {
|
||||
size_t pos;
|
||||
pagezero_size = std::stol(std::string(arg), &pos, 16);
|
||||
if (pos != arg.size())
|
||||
Fatal(ctx) << "malformed -pagezero_size: " << arg;
|
||||
} else if (read_arg("-object_path_lto")) {
|
||||
ctx.arg.object_path_lto = arg;
|
||||
} else if (read_arg("-order_file")) {
|
||||
ctx.arg.order_file = read_lines(ctx, arg);
|
||||
} else if (read_hex("-pagezero_size")) {
|
||||
pagezero_size = hex_arg;
|
||||
} else if (read_flag("-perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_arg3("-platform_version")) {
|
||||
ctx.arg.platform = parse_platform(ctx, arg);
|
||||
ctx.arg.platform_min_version = parse_version(ctx, arg2);
|
||||
ctx.arg.platform_sdk_version = parse_version(ctx, arg3);
|
||||
} else if (read_flag("-quick_exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag("-no_quick_exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag("-random_uuid")) {
|
||||
ctx.arg.uuid = UUID_RANDOM;
|
||||
} else if (read_arg("-rpath")) {
|
||||
ctx.arg.rpath.push_back(std::string(arg));
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
Fatal(ctx) << "-search_dylibs_first is not supported";
|
||||
} else if (read_flag("-search_paths_first")) {
|
||||
ctx.arg.search_paths_first = true;
|
||||
} else if (read_flag("-search_dylibs_first")) {
|
||||
ctx.arg.search_paths_first = false;
|
||||
} else if (read_arg3("-sectcreate")) {
|
||||
ctx.arg.sectcreate.push_back({arg, arg2, arg3});
|
||||
} else if (read_hex("-stack_size")) {
|
||||
ctx.arg.stack_size = hex_arg;
|
||||
} else if (read_flag("-stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
} else if (read_arg("-syslibroot")) {
|
||||
ctx.arg.syslibroot.push_back(std::string(arg));
|
||||
} else if (read_flag("-t")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_arg("-thread_count")) {
|
||||
ctx.arg.thread_count = std::stoi(std::string(arg));
|
||||
} else if (read_arg("-u")) {
|
||||
ctx.arg.u.push_back(std::string(arg));
|
||||
} else if (read_arg("-unexported_symbol")) {
|
||||
if (!ctx.arg.unexported_symbols_list.add(arg, 1))
|
||||
Fatal(ctx) << "-unexported_symbol: invalid glob pattern: " << arg;
|
||||
} else if (read_arg("-unexported_symbols_list")) {
|
||||
for (std::string_view pat : read_lines(ctx, arg))
|
||||
if (!ctx.arg.unexported_symbols_list.add(pat, 1))
|
||||
Fatal(ctx) << "-unexported_symbols_list: " << arg
|
||||
<< ": invalid glob pattern: " << pat;
|
||||
} else if (read_flag("-v")) {
|
||||
SyncOut(ctx) << mold_version;
|
||||
} else if (read_arg("-weak_framework")) {
|
||||
remaining.push_back("-weak_framework");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else if (read_joined("-weak-l")) {
|
||||
remaining.push_back("-weak-l");
|
||||
remaining.push_back(std::string(arg));
|
||||
} else {
|
||||
if (args[i][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[i];
|
||||
@@ -268,6 +417,12 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.arg.entry)
|
||||
ctx.arg.entry = get_symbol(ctx, "_main");
|
||||
|
||||
if (ctx.arg.thread_count == 0)
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
|
||||
auto add_search_path = [&](std::vector<std::string> &vec, std::string path) {
|
||||
if (!path.starts_with('/') || ctx.arg.syslibroot.empty()) {
|
||||
if (is_directory(path))
|
||||
@@ -305,18 +460,32 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
|
||||
if (pagezero_size.has_value()) {
|
||||
if (ctx.output_type != MH_EXECUTE)
|
||||
Error(ctx) << "-pagezero_size option can only be used when"
|
||||
Fatal(ctx) << "-pagezero_size option can only be used when"
|
||||
<< " linking a main executable";
|
||||
ctx.arg.pagezero_size = *pagezero_size;
|
||||
} else {
|
||||
ctx.arg.pagezero_size = (ctx.output_type == MH_EXECUTE) ? 0x100000000 : 0;
|
||||
}
|
||||
|
||||
if (ctx.arg.final_output == "") {
|
||||
if (ctx.arg.install_name != "")
|
||||
ctx.arg.final_output = ctx.arg.install_name;
|
||||
else
|
||||
ctx.arg.final_output = ctx.arg.output;
|
||||
}
|
||||
|
||||
if (ctx.arg.uuid == UUID_RANDOM)
|
||||
memcpy(ctx.uuid, get_uuid_v4().data(), 16);
|
||||
|
||||
ctx.arg.exported_symbols_list.compile();
|
||||
ctx.arg.unexported_symbols_list.compile();
|
||||
|
||||
return remaining;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void parse_nonpositional_args(Context<E> &, std::vector<std::string> &)
|
||||
template std::vector<std::string> parse_nonpositional_args(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+16
-15
@@ -11,12 +11,12 @@ static std::vector<Subsection<E> *> collect_root_set(Context<E> &ctx) {
|
||||
rootset.push_back(sym->subsec);
|
||||
};
|
||||
|
||||
mark(get_symbol(ctx, ctx.arg.entry));
|
||||
mark(ctx.arg.entry);
|
||||
|
||||
if (ctx.output_type == MH_DYLIB || ctx.output_type == MH_BUNDLE)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->is_extern)
|
||||
if (sym->file == file && sym->scope == SCOPE_EXTERN)
|
||||
mark(sym);
|
||||
|
||||
return rootset;
|
||||
@@ -37,12 +37,11 @@ static void visit(Context<E> &ctx, Subsection<E> &subsec) {
|
||||
}
|
||||
|
||||
for (UnwindRecord<E> &rec : subsec.get_unwind_records()) {
|
||||
rec.is_alive = true;
|
||||
visit(ctx, *rec.subsec);
|
||||
if (rec.lsda)
|
||||
visit(ctx, *rec.lsda);
|
||||
if (Symbol<E> *sym = rec.personality; sym && sym->subsec)
|
||||
visit(ctx, *sym->subsec);
|
||||
if (rec.personality && rec.personality->subsec)
|
||||
visit(ctx, *rec.personality->subsec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +61,10 @@ static bool refers_live_subsection(Subsection<E> &subsec) {
|
||||
|
||||
template <typename E>
|
||||
static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
subsec->is_alive = false;
|
||||
|
||||
for (Subsection<E> *subsec : rootset)
|
||||
visit(ctx, *subsec);
|
||||
|
||||
@@ -69,7 +72,7 @@ static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
do {
|
||||
repeat = false;
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections) {
|
||||
for (Subsection<E> *subsec : file->subsections) {
|
||||
if ((subsec->isec.hdr.attr & S_ATTR_LIVE_SUPPORT) &&
|
||||
!subsec->is_alive &&
|
||||
refers_live_subsection(*subsec)) {
|
||||
@@ -83,17 +86,16 @@ static void mark(Context<E> &ctx, const std::vector<Subsection<E> *> &rootset) {
|
||||
|
||||
template <typename E>
|
||||
static void sweep(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
std::erase_if(file->subsections,
|
||||
[](const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Symbol<E> *&sym : file->syms)
|
||||
if (sym->file == file && sym->subsec && !sym->subsec->is_alive)
|
||||
sym = nullptr;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
std::erase_if(file->subsections, [](const Subsection<E> *subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -106,7 +108,6 @@ void dead_strip(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void dead_strip(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
-511
@@ -1,511 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static u8 *open_file(std::string path) {
|
||||
i64 fd = ::open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1)
|
||||
return nullptr;
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1) {
|
||||
std::cerr << path << ": fstat failed: " << errno_string();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void *ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (ptr == MAP_FAILED) {
|
||||
std::cerr << path << ": mmap failed: " << errno_string();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return (u8 *)ptr;
|
||||
}
|
||||
|
||||
static void print_bytes(u8 *buf, i64 size) {
|
||||
if (size == 0) {
|
||||
std::cout << "[]\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[" << std::setw(2) << std::setfill('0') << (u32)buf[0];
|
||||
for (i64 i = 1; i < size; i++)
|
||||
std::cout << " " << std::setw(2) << std::setfill('0') << (u32)buf[i];
|
||||
std::cout << "]\n";
|
||||
}
|
||||
|
||||
struct ExportEntry {
|
||||
std::string name;
|
||||
u32 flags;
|
||||
u64 addr;
|
||||
};
|
||||
|
||||
static void read_trie(std::vector<ExportEntry> &vec, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "") {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (read_uleb(buf)) {
|
||||
ExportEntry ent;
|
||||
ent.name = prefix;
|
||||
ent.flags = read_uleb(buf);
|
||||
ent.addr = read_uleb(buf);
|
||||
vec.push_back(std::move(ent));
|
||||
}
|
||||
|
||||
for (i64 i = 0, n = read_uleb(buf); i < n; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(vec, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_unwind_info(u8 *buf, MachSection &sec) {
|
||||
UnwindSectionHeader &hdr = *(UnwindSectionHeader *)(buf + sec.offset);
|
||||
|
||||
std::cout << std::hex << " Unwind info:"
|
||||
<< "\n version: 0x" << hdr.version
|
||||
<< "\n encoding_offset: 0x" << hdr.encoding_offset
|
||||
<< "\n encoding_count: 0x" << hdr.encoding_count
|
||||
<< "\n personality_offset: 0x" << hdr.personality_offset
|
||||
<< "\n personality_count: 0x" << hdr.personality_count
|
||||
<< "\n page_offset: 0x" << hdr.page_offset
|
||||
<< "\n page_count: 0x" << hdr.page_count;
|
||||
|
||||
std::cout << "\n encoding:";
|
||||
u32 *enc = (u32 *)(buf + sec.offset + hdr.encoding_offset);
|
||||
for (i64 i = 0; i < hdr.encoding_count; i++)
|
||||
std::cout << std::hex << "\n 0x" << enc[i];
|
||||
|
||||
std::cout << "\n personality:";
|
||||
u32 *pers = (u32 *)(buf + sec.offset + hdr.personality_offset);
|
||||
for (i64 i = 0; i < hdr.personality_count; i++)
|
||||
std::cout << std::hex << "\n 0x" << pers[i];
|
||||
|
||||
UnwindFirstLevelPage *ent =
|
||||
(UnwindFirstLevelPage *)(buf + sec.offset + hdr.page_offset);
|
||||
|
||||
for (i64 i = 0; i < hdr.page_count; i++) {
|
||||
std::cout << std::hex << "\n function:"
|
||||
<< "\n func_addr: 0x" << ent[i].func_addr
|
||||
<< "\n page_offset: 0x" << ent[i].page_offset
|
||||
<< "\n lsda_offset: 0x" << ent[i].lsda_offset;
|
||||
|
||||
if (i != hdr.page_count - 1) {
|
||||
UnwindLsdaEntry *lsda =
|
||||
(UnwindLsdaEntry *)(buf + sec.offset + ent[i].lsda_offset);
|
||||
i64 lsda_size = ent[i + 1].lsda_offset - ent[i].lsda_offset;
|
||||
for (i64 j = 0; j < lsda_size / sizeof(UnwindLsdaEntry); j++)
|
||||
std::cout << std::hex
|
||||
<< "\n lsda:"
|
||||
<< "\n func_addr: 0x" << lsda[j].func_addr
|
||||
<< "\n lsda_addr: 0x" << lsda[j].lsda_addr;
|
||||
}
|
||||
|
||||
if (ent[i].page_offset == 0)
|
||||
break;
|
||||
|
||||
u8 *addr = buf + sec.offset + ent[i].page_offset;
|
||||
|
||||
switch (u32 kind = *addr; kind) {
|
||||
case UNWIND_SECOND_LEVEL_REGULAR: {
|
||||
std::cout << "\n UNWIND_SECOND_LEVEL_REGULAR:" ;
|
||||
break;
|
||||
}
|
||||
case UNWIND_SECOND_LEVEL_COMPRESSED: {
|
||||
std::cout << "\n UNWIND_SECOND_LEVEL_COMPRESSED" ;
|
||||
UnwindSecondLevelPage &hdr2 = *(UnwindSecondLevelPage *)addr;
|
||||
std::cout << std::hex
|
||||
<< "\n page_offset: 0x" << hdr2.page_offset
|
||||
<< "\n page_count: 0x" << hdr2.page_count
|
||||
<< "\n encoding_offset: 0x" << hdr2.encoding_offset
|
||||
<< "\n encoding_count: 0x" << hdr2.encoding_count;
|
||||
|
||||
UnwindPageEntry *ent2 = (UnwindPageEntry *)(addr + hdr2.page_offset);
|
||||
for (i64 j = 0; j < hdr2.page_count; j++)
|
||||
std::cout << std::hex << "\n ent 0x"
|
||||
<< (ent[i].func_addr + ent2[j].func_addr)
|
||||
<< " 0x" << ent2[j].encoding;
|
||||
|
||||
u32 *enc = (u32 *)(addr + hdr2.encoding_offset);
|
||||
for (i64 j = 0; j < hdr2.encoding_count; j++)
|
||||
std::cout << std::hex << "\n 0x" << enc[j];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "\n bad 2nd-level unwind info header: " << kind;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void dump_compact_unwind(u8 *buf, MachSection &sec) {
|
||||
CompactUnwindEntry *ent = (CompactUnwindEntry *)(buf + sec.offset);
|
||||
i64 nentry = sec.size / sizeof(CompactUnwindEntry);
|
||||
|
||||
std::cout << " Compact unwind:"
|
||||
<< "\n num_entry: " << nentry;
|
||||
|
||||
for (i64 i = 0; i < nentry; i++) {
|
||||
std::cout << std::hex
|
||||
<< "\n entry: 0x" << (i * sizeof(CompactUnwindEntry))
|
||||
<< "\n code_start: 0x" << ent[i].code_start
|
||||
<< "\n code_len: 0x" << ent[i].code_len
|
||||
<< "\n encoding: 0x" << ent[i].encoding
|
||||
<< "\n personality: 0x" << ent[i].personality
|
||||
<< "\n lsda: 0x" << ent[i].lsda;
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void dump_file(std::string path) {
|
||||
u8 *buf = open_file(path);
|
||||
if (!buf) {
|
||||
std::cerr << "cannot open " << path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
MachHeader &hdr = *(MachHeader *)buf;
|
||||
std::cout << "magic: 0x" << std::hex << hdr.magic
|
||||
<< "\ncputype: 0x" << hdr.cputype
|
||||
<< "\ncpusubtype: 0x" << hdr.cpusubtype
|
||||
<< "\nfiletype: 0x" << hdr.filetype
|
||||
<< "\nncmds: 0x" << hdr.ncmds
|
||||
<< "\nsizeofcmds: 0x" << hdr.sizeofcmds
|
||||
<< "\nflags: 0x" << hdr.flags
|
||||
<< "\n\n";
|
||||
|
||||
u8 *p = buf + sizeof(MachHeader);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
std::cout << "fileoff: 0x" << std::hex << (p - buf) << "\n";
|
||||
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
|
||||
auto print_dylib = [&] {
|
||||
DylibCommand &cmd = *(DylibCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
<< "\n timestamp: 0x" << cmd.timestamp
|
||||
<< "\n current_version: 0x" << cmd.current_version
|
||||
<< "\n compatibility_version: 0x" << cmd.compatibility_version
|
||||
<< "\n data: " << (char *)((char *)&cmd + cmd.nameoff)
|
||||
<< "\n";
|
||||
};
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_SYMTAB: {
|
||||
std::cout << "LC_SYMTAB\n";
|
||||
SymtabCommand &cmd = *(SymtabCommand *)&lc;
|
||||
std::cout << " cmdsize: " << cmd.cmdsize
|
||||
<< "\n symoff: 0x" << std::hex << cmd.symoff
|
||||
<< "\n nsyms: " << std::dec << cmd.nsyms
|
||||
<< "\n stroff: 0x" << std::hex << cmd.stroff
|
||||
<< "\n strsize: 0x" << cmd.strsize
|
||||
<< "\n symdata: ";
|
||||
print_bytes(buf + cmd.symoff, cmd.nsyms * sizeof(MachSym));
|
||||
|
||||
std::cout << " strdata: ";
|
||||
print_bytes(buf + cmd.stroff, cmd.strsize);
|
||||
|
||||
MachSym *syms = (MachSym *)(buf + cmd.symoff);
|
||||
for (i64 j = 0; j < cmd.nsyms; j++) {
|
||||
std::cout << " symbol:"
|
||||
<< "\n name: " << (char *)(buf + cmd.stroff + syms[j].stroff)
|
||||
<< "\n stub: " << (u32)syms[j].stub
|
||||
<< "\n pext: " << (u32)syms[j].pext
|
||||
<< "\n type: " << (u32)syms[j].type
|
||||
<< "\n ext: " << (u32)syms[j].ext
|
||||
<< "\n sect: 0x" << (u32)syms[j].sect
|
||||
<< "\n desc: 0x" << (u32)syms[j].desc
|
||||
<< "\n value: 0x" << syms[j].value
|
||||
<< "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_DYSYMTAB: {
|
||||
std::cout << "LC_DYSYMTAB\n";
|
||||
DysymtabCommand &cmd = *(DysymtabCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n ilocalsym: 0x" << cmd.ilocalsym
|
||||
<< "\n nlocalsym: 0x" << cmd.nlocalsym
|
||||
<< "\n iextdefsym: 0x" << cmd.iextdefsym
|
||||
<< "\n nextdefsym: 0x" << cmd.nextdefsym
|
||||
<< "\n iundefsym: 0x" << cmd.iundefsym
|
||||
<< "\n nundefsym: 0x" << cmd.nundefsym
|
||||
<< "\n tocoff: 0x" << cmd.tocoff
|
||||
<< "\n ntoc: 0x" << cmd.ntoc
|
||||
<< "\n modtaboff: 0x" << cmd.modtaboff
|
||||
<< "\n nmodtab: 0x" << cmd.nmodtab
|
||||
<< "\n extrefsymoff: 0x" << cmd.extrefsymoff
|
||||
<< "\n nextrefsyms: 0x" << cmd.nextrefsyms
|
||||
<< "\n indirectsymoff: 0x" << cmd.indirectsymoff
|
||||
<< "\n nindirectsyms: 0x" << cmd.nindirectsyms
|
||||
<< "\n extreloff: 0x" << cmd.extreloff
|
||||
<< "\n nextrel: 0x" << cmd.nextrel
|
||||
<< "\n locreloff: 0x" << cmd.locreloff
|
||||
<< "\n nlocrel: 0x" << cmd.nlocrel
|
||||
<< "\n";
|
||||
|
||||
if (cmd.indirectsymoff) {
|
||||
std::cout << " indirectsymdata: ";
|
||||
print_bytes(buf + cmd.indirectsymoff, 4 * cmd.nindirectsyms);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_LOAD_DYLIB:
|
||||
std::cout << "LC_LOAD_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_LOAD_WEAK_DYLIB:
|
||||
std::cout << "LC_LOAD_WEAK_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_ID_DYLIB:
|
||||
std::cout << "LC_ID_DYLIB\n";
|
||||
print_dylib();
|
||||
break;
|
||||
case LC_LOAD_DYLINKER: {
|
||||
std::cout << "LC_LOAD_DYLINKER\n";
|
||||
DylinkerCommand &cmd = *(DylinkerCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n nameoff: 0x" << cmd.nameoff
|
||||
<< "\n data: " << (char *)((char *)&cmd + cmd.nameoff)
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_SEGMENT_64: {
|
||||
std::cout << "LC_SEGMENT_64\n";
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
std::cout << " cmdsize: " << cmd.cmdsize
|
||||
<< "\n segname: " << cmd.get_segname()
|
||||
<< "\n vmaddr: 0x" << std::hex << cmd.vmaddr
|
||||
<< "\n vmsize: 0x" << cmd.vmsize
|
||||
<< "\n fileoff: 0x" << cmd.fileoff
|
||||
<< "\n filesize: 0x" << cmd.filesize
|
||||
<< "\n maxprot: " << std::dec << cmd.maxprot
|
||||
<< "\n initprot: " << cmd.initprot
|
||||
<< "\n nsects: " << cmd.nsects
|
||||
<< "\n flags: 0x" << std::hex << cmd.flags
|
||||
<< "\n";
|
||||
|
||||
MachSection *sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
for (i64 j = 0; j < cmd.nsects; j++) {
|
||||
std::cout << " section:\n sectname: " << sec[j].get_sectname()
|
||||
<< "\n segname: " << sec[j].get_segname()
|
||||
<< "\n addr: 0x" << std::hex << sec[j].addr
|
||||
<< "\n size: 0x" << sec[j].size
|
||||
<< "\n offset: 0x" << sec[j].offset
|
||||
<< "\n p2align: " << std::dec << sec[j].p2align
|
||||
<< "\n reloff: " << std::hex << sec[j].reloff
|
||||
<< "\n nreloc: " << std::dec << sec[j].nreloc
|
||||
<< "\n type: 0x" << std::hex << sec[j].type
|
||||
<< "\n attr: 0x" << std::hex << sec[j].attr
|
||||
<< "\n";
|
||||
|
||||
if (sec[j].type != S_ZEROFILL) {
|
||||
std::cout << " contents: ";
|
||||
print_bytes(buf + sec[j].offset, sec[j].size);
|
||||
}
|
||||
|
||||
if (sec[j].reloff) {
|
||||
MachRel *rel = (MachRel *)(buf + sec[j].reloff);
|
||||
for (i64 k = 0; k < sec[j].nreloc; k++) {
|
||||
std::cout << " reloc: "
|
||||
<< "\n offset: 0x" << rel[k].offset
|
||||
<< "\n idx: 0x" << rel[k].idx
|
||||
<< "\n is_pcrel: " << rel[k].is_pcrel
|
||||
<< "\n p2size: 0x" << rel[k].p2size
|
||||
<< "\n is_extern: " << rel[k].is_extern
|
||||
<< "\n type: " << rel[k].type
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (sec[j].match("__TEXT", "__unwind_info"))
|
||||
dump_unwind_info(buf, sec[j]);
|
||||
|
||||
if (sec[j].match("__LD", "__compact_unwind"))
|
||||
dump_compact_unwind(buf, sec[j]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_UUID: {
|
||||
std::cout << "LC_UUID\n";
|
||||
UUIDCommand &cmd = *(UUIDCommand *)&lc;
|
||||
std::cout << " data: ";
|
||||
print_bytes(cmd.uuid, sizeof(cmd.uuid));
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
std::cout << "LC_DYLD_INFO_ONLY\n";
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)&lc;
|
||||
|
||||
if (cmd.rebase_off) {
|
||||
std::cout << " rebase: ";
|
||||
print_bytes(buf + cmd.rebase_off, cmd.rebase_size);
|
||||
std::cout << " rebase_off: 0x" << std::hex << cmd.rebase_off
|
||||
<< "\n rebase_size: 0x" << cmd.rebase_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.bind_off) {
|
||||
std::cout << " bind: ";
|
||||
print_bytes(buf + cmd.bind_off, cmd.bind_size);
|
||||
std::cout << " bind_off: 0x" << std::hex << cmd.bind_off
|
||||
<< "\n bind_size: 0x" << cmd.bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.weak_bind_off) {
|
||||
std::cout << " weak_bind: ";
|
||||
print_bytes(buf + cmd.weak_bind_off, cmd.weak_bind_size);
|
||||
std::cout << " weak_bind_off: 0x" << std::hex << cmd.weak_bind_off
|
||||
<< "\n weak_bind_size: 0x" << cmd.weak_bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.lazy_bind_off) {
|
||||
std::cout << " lazy_bind: ";
|
||||
print_bytes(buf + cmd.lazy_bind_off, cmd.lazy_bind_size);
|
||||
std::cout << " lazy_bind_off: 0x" << std::hex << cmd.lazy_bind_off
|
||||
<< "\n lazy_bind_size: 0x" << cmd.lazy_bind_size
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (cmd.export_off) {
|
||||
std::cout << " export: ";
|
||||
print_bytes(buf + cmd.export_off, cmd.export_size);
|
||||
std::cout << " export_off: 0x" << std::hex << cmd.export_off
|
||||
<< "\n export_size: 0x" << cmd.export_size
|
||||
<< "\n";
|
||||
|
||||
std::vector<ExportEntry> vec;
|
||||
read_trie(vec, buf + cmd.export_off);
|
||||
for (ExportEntry &ent : vec)
|
||||
std::cout << " export_sym: " << ent.name << " 0x" << ent.addr << "\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LC_FUNCTION_STARTS: {
|
||||
std::cout << "LC_FUNCTION_STARTS\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
std::cout << " dataoff: 0x" << cmd.dataoff
|
||||
<< "\n datasize: 0x" << cmd.datasize
|
||||
<< "\n data:";
|
||||
|
||||
u8 *p = buf + cmd.dataoff;
|
||||
u64 addr = 0;
|
||||
for (;;) {
|
||||
u64 delta = read_uleb(p);
|
||||
if (!delta)
|
||||
break;
|
||||
addr += delta;
|
||||
std::cout << std::hex << " 0x" << addr;
|
||||
}
|
||||
std::cout << "\n";
|
||||
break;
|
||||
}
|
||||
case LC_MAIN: {
|
||||
std::cout << "LC_MAIN\n";
|
||||
EntryPointCommand &cmd = *(EntryPointCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n entryoff: 0x" << cmd.entryoff
|
||||
<< "\n stacksize: 0x" << cmd.stacksize
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_DATA_IN_CODE: {
|
||||
std::cout << "LC_DATA_IN_CODE\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
std::cout << " dataoff: 0x" << cmd.dataoff
|
||||
<< "\n datasize: 0x" << cmd.datasize
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_SOURCE_VERSION: {
|
||||
std::cout << "LC_SOURCE_VERSION\n";
|
||||
SourceVersionCommand &cmd = *(SourceVersionCommand *)&lc;
|
||||
std::cout << " version: 0x" << cmd.version
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LC_BUILD_VERSION: {
|
||||
std::cout << "LC_BUILD_VERSION\n";
|
||||
BuildVersionCommand &cmd = *(BuildVersionCommand *)&lc;
|
||||
std::cout << " cmdsize: 0x" << cmd.cmdsize
|
||||
<< "\n platform: 0x" << cmd.platform
|
||||
<< "\n minos: 0x" << cmd.minos
|
||||
<< "\n sdk: 0x" << cmd.sdk
|
||||
<< "\n ntools: 0x" << cmd.ntools
|
||||
<< "\n";
|
||||
|
||||
BuildToolVersion *tools =
|
||||
(BuildToolVersion *)((u8 *)&lc + sizeof(BuildVersionCommand));
|
||||
for (i64 i = 0; i < cmd.ntools; i++)
|
||||
std::cout << " tool: 0x" << tools[i].tool
|
||||
<< "\n version: 0x" << tools[i].version
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_VERSION_MIN_MACOSX: {
|
||||
std::cout << "LC_VERSION_MIN_MACOSX\n";
|
||||
VersionMinCommand &cmd = *(VersionMinCommand *)&lc;
|
||||
std::cout << " version: " << (int)cmd.version
|
||||
<< "\n sdk: " << (int)cmd.sdk
|
||||
<< "\n";
|
||||
break;
|
||||
}
|
||||
case LC_CODE_SIGNATURE: {
|
||||
std::cout << "LC_CODE_SIGNATURE\n";
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)&lc;
|
||||
|
||||
CodeSignatureHeader &sig = *(CodeSignatureHeader *)(buf + cmd.dataoff);
|
||||
std::cout << " magic: " << sig.magic
|
||||
<< "\n length: " << sig.length
|
||||
<< "\n count: " << sig.count
|
||||
<< "\n";
|
||||
|
||||
for (i64 i = 0; i < sig.count; i++) {
|
||||
CodeSignatureBlobIndex &idx = ((CodeSignatureBlobIndex *)(&sig + 1))[i];
|
||||
std::cout << " idx type: " << idx.type
|
||||
<< "\n idx offset: " << idx.offset
|
||||
<< "\n";
|
||||
|
||||
CodeSignatureDirectory &dir =
|
||||
*(CodeSignatureDirectory *)((u8 *)&sig + idx.offset);
|
||||
std::cout << std::hex
|
||||
<< " magic: 0x" << dir.magic
|
||||
<< "\n version: 0x" << dir.version
|
||||
<< "\n flags: 0x" << dir.flags
|
||||
<< "\n hash_offset: 0x" << dir.hash_offset
|
||||
<< "\n n_code_slots: 0x" << dir.n_code_slots
|
||||
<< "\n hash_size: 0x" << (u32)dir.hash_size
|
||||
<< "\n hash_type: 0x" << (u32)dir.hash_type
|
||||
<< "\n page_size: 0x" << (1 << dir.page_size)
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cout << "UNKNOWN (0x" << std::hex << lc.cmd << ")\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -0,0 +1,851 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.filename);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.filename) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file == this) {
|
||||
sym->file = nullptr;
|
||||
sym->scope = SCOPE_LOCAL;
|
||||
sym->is_imported = false;
|
||||
sym->is_weak_def = false;
|
||||
sym->subsec = nullptr;
|
||||
sym->value = 0;
|
||||
sym->is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>(mf);
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty() || ctx.all_load;
|
||||
obj->is_hidden = ctx.hidden_l;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE) {
|
||||
// Open an compiler IR file
|
||||
load_lto_plugin(ctx);
|
||||
this->lto_module =
|
||||
ctx.lto.module_create_from_memory(this->mf->data, this->mf->size);
|
||||
if (!this->lto_module)
|
||||
Fatal(ctx) << *this << ": lto_module_create_from_memory failed";
|
||||
|
||||
// Read a symbol table
|
||||
parse_lto_symbols(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
parse_sections(ctx);
|
||||
parse_symbols(ctx);
|
||||
|
||||
MachHeader &mach_hdr = *(MachHeader *)this->mf->data;
|
||||
if (mach_hdr.flags & MH_SUBSECTIONS_VIA_SYMBOLS)
|
||||
split_subsections_via_symbols(ctx);
|
||||
else
|
||||
init_subsections(ctx);
|
||||
|
||||
sort(subsections, [](Subsection<E> *a, Subsection<E> *b) {
|
||||
return a->input_addr < b->input_addr;
|
||||
});
|
||||
|
||||
fix_subsec_members(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
// Read all but a symtab section
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
if (lc.cmd != LC_SEGMENT_64)
|
||||
continue;
|
||||
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
|
||||
for (MachSection &msec : std::span(mach_sec, mach_sec + cmd.nsects)) {
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symbols(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.is_extern)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.is_extern) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
Symbol<E> &sym = local_syms.back();
|
||||
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.scope = SCOPE_LOCAL;
|
||||
sym.is_common = false;
|
||||
sym.is_weak_def = false;
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
Fatal(ctx) << sym << ": local undef symbol?";
|
||||
case N_ABS:
|
||||
sym.value = msym.value;
|
||||
break;
|
||||
case N_SECT:
|
||||
// `value` and `subsec` will be filled by split_subsections
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown symbol type for "
|
||||
<< sym << ": " << (u64)msym.type;
|
||||
}
|
||||
|
||||
this->syms.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>>
|
||||
split_regular_sections(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec(file.sections.size());
|
||||
|
||||
for (i64 i = 0; i < file.sections.size(); i++)
|
||||
if (InputSection<E> *isec = file.sections[i].get())
|
||||
if (!isec->hdr.match("__TEXT", "__cstring"))
|
||||
vec[i].isec = isec;
|
||||
|
||||
// Find all symbols whose type is N_SECT.
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (msym.type == N_SECT && vec[msym.sect - 1].isec) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - vec[msym.sect - 1].isec->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
// Fix regions so that they cover the entire section without overlapping.
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
sort(r, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections_via_symbols(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
auto add = [&](InputSection<E> &isec, u32 offset, u32 size, u8 p2align) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = offset,
|
||||
.input_size = size,
|
||||
.input_addr = (u32)(isec.hdr.addr + offset),
|
||||
.p2align = p2align,
|
||||
};
|
||||
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections.push_back(subsec);
|
||||
};
|
||||
|
||||
// Split regular sections into subsections.
|
||||
for (SplitInfo<E> &info : split_regular_sections(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry)
|
||||
add(isec, r.offset, r.size, isec.hdr.p2align);
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.back();
|
||||
}
|
||||
}
|
||||
|
||||
// Split __cstring section.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections) {
|
||||
if (isec && isec->hdr.match("__TEXT", "__cstring")) {
|
||||
std::string_view str = isec->contents;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < str.size()) {
|
||||
size_t end = str.find('\0', pos);
|
||||
if (end == str.npos)
|
||||
Fatal(ctx) << *this << " corruupted __TEXT,__cstring";
|
||||
|
||||
end = str.find_first_not_of('\0', end);
|
||||
if (end == str.npos)
|
||||
end = str.size();
|
||||
|
||||
// A constant string in __cstring has no alignment info, so we
|
||||
// need to infer it.
|
||||
u8 p2align = std::min<u8>(isec->hdr.p2align, std::countr_zero(pos));
|
||||
add(*isec, pos, end - pos, p2align);
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::init_subsections(Context<E> &ctx) {
|
||||
subsections.resize(sections.size());
|
||||
|
||||
for (i64 i = 0; i < sections.size(); i++) {
|
||||
if (InputSection<E> *isec = sections[i].get()) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_offset = 0,
|
||||
.input_size = (u32)isec->hdr.size,
|
||||
.input_addr = (u32)isec->hdr.addr,
|
||||
.p2align = (u8)isec->hdr.p2align,
|
||||
};
|
||||
subsec_pool.emplace_back(subsec);
|
||||
subsections[i] = subsec;
|
||||
}
|
||||
}
|
||||
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (msym.type == N_SECT)
|
||||
sym_to_subsec[i] = subsections[msym.sect - 1];
|
||||
}
|
||||
|
||||
std::erase(subsections, nullptr);
|
||||
}
|
||||
|
||||
// Fix local symbols `subsec` members.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::fix_subsec_members(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
|
||||
if (!msym.is_extern && msym.type == N_SECT) {
|
||||
Subsection<E> *subsec = sym_to_subsec[i];
|
||||
if (!subsec)
|
||||
subsec = find_subsection(ctx, msym.value);
|
||||
|
||||
if (subsec) {
|
||||
sym.subsec = subsec;
|
||||
sym.value = msym.value - subsec->input_addr;
|
||||
} else {
|
||||
// Subsec is null if a symbol is in a __compact_unwind.
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE)) {
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string> ObjectFile<E>::get_linker_options(Context<E> &ctx) {
|
||||
if (get_file_type(this->mf) == FileType::LLVM_BITCODE)
|
||||
return {};
|
||||
|
||||
auto *cmd = (LinkerOptionCommand *)find_load_command(ctx, LC_LINKER_OPTION);
|
||||
if (!cmd)
|
||||
return {};
|
||||
|
||||
char *buf = (char *)cmd + sizeof(*cmd);
|
||||
std::vector<std::string> vec;
|
||||
for (i64 i = 0; i < cmd->count; i++) {
|
||||
vec.push_back(buf);
|
||||
buf += vec.back().size() + 1;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *ObjectFile<E>::find_subsection(Context<E> &ctx, u32 addr) {
|
||||
auto it = std::upper_bound(subsections.begin(), subsections.end(), addr,
|
||||
[&](u32 addr, Subsection<E> *subsec) {
|
||||
return addr < subsec->input_addr;
|
||||
});
|
||||
|
||||
if (it == subsections.begin())
|
||||
return nullptr;
|
||||
return *(it - 1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Symbol<E> *ObjectFile<E>::find_symbol(Context<E> &ctx, u32 addr) {
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (MachSym &msym = mach_syms[i]; msym.is_extern && msym.value == addr)
|
||||
return this->syms[i];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
};
|
||||
|
||||
if (r.is_pcrel || r.p2size != 3 || r.type)
|
||||
error();
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
if (r.is_extern)
|
||||
error();
|
||||
|
||||
Subsection<E> *target = find_subsection(ctx, src[idx].code_start);
|
||||
if (!target)
|
||||
error();
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_extern) {
|
||||
dst.personality = this->syms[r.idx];
|
||||
} else {
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
dst.personality = find_symbol(ctx, addr);
|
||||
if (!dst.personality)
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported local "
|
||||
<< "personality reference: " << i;
|
||||
}
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
if (r.is_extern)
|
||||
error();
|
||||
|
||||
u32 addr = *(ul32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
Subsection<E> *target = find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
error();
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
error();
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << "__compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO/archive
|
||||
// 4. Weak Defined symbol in a DSO/archive
|
||||
// 5. Common symbol
|
||||
// 6. Common symbol in an archive
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, bool is_common, bool is_weak) {
|
||||
if (is_common) {
|
||||
assert(!file->is_dylib);
|
||||
if (!file->is_alive)
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (file->is_dylib || !file->is_alive) {
|
||||
if (is_weak)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
|
||||
if (is_weak)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
if (!sym.file)
|
||||
return 7 << 24;
|
||||
return get_rank(sym.file, sym.is_common, sym.is_weak_def);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
auto is_private_extern = [&](MachSym &msym) {
|
||||
return this->is_hidden || msym.is_private_extern ||
|
||||
((msym.desc & N_WEAK_REF) && (msym.desc & N_WEAK_DEF));
|
||||
};
|
||||
|
||||
auto merge_scope = [&](Symbol<E> &sym, MachSym &msym) {
|
||||
// If at least one symbol defines it as an EXTERN symbol,
|
||||
// the result is an EXTERN symbol instead of PRIVATE_EXTERN,
|
||||
// so that the symbol is exported.
|
||||
if (sym.scope == SCOPE_EXTERN)
|
||||
return SCOPE_EXTERN;
|
||||
return is_private_extern(msym) ? SCOPE_PRIVATE_EXTERN : SCOPE_EXTERN;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
bool is_weak_def = (msym.desc & N_WEAK_DEF);
|
||||
|
||||
sym.scope = merge_scope(sym, msym);
|
||||
|
||||
if (get_rank(this, msym.is_common(), is_weak_def) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.is_imported = false;
|
||||
sym.is_weak_def = is_weak_def;
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = sym_to_subsec[i];
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
isec->hdr.match("__TEXT", "__swift"))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].is_extern &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void
|
||||
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(ObjectFile<E> *)> feeder) {
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.is_extern)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
bool keep = msym.is_undef() || (msym.is_common() && !sym.is_common);
|
||||
if (keep && !sym.file->is_alive.exchange(true))
|
||||
if (!sym.file->is_dylib)
|
||||
feeder((ObjectFile<E> *)sym.file);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.is_imported = false;
|
||||
sym.is_weak_def = false;
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->mach_syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && sym->file && sym->file != this && !msym.is_undef() &&
|
||||
!msym.is_common() && !(msym.desc & N_WEAK_DEF))
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr);
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_lto_symbols(Context<E> &ctx) {
|
||||
i64 nsyms = ctx.lto.module_get_num_symbols(this->lto_module);
|
||||
this->syms.reserve(nsyms);
|
||||
this->mach_syms2.reserve(nsyms);
|
||||
|
||||
for (i64 i = 0; i < nsyms; i++) {
|
||||
std::string_view name = ctx.lto.module_get_symbol_name(this->lto_module, i);
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
|
||||
u32 attr = ctx.lto.module_get_symbol_attribute(this->lto_module, i);
|
||||
|
||||
MachSym msym = {};
|
||||
msym.p2align = (attr & LTO_SYMBOL_ALIGNMENT_MASK);
|
||||
|
||||
switch (attr & LTO_SYMBOL_DEFINITION_MASK) {
|
||||
case LTO_SYMBOL_DEFINITION_REGULAR:
|
||||
case LTO_SYMBOL_DEFINITION_TENTATIVE:
|
||||
case LTO_SYMBOL_DEFINITION_WEAK:
|
||||
msym.type = N_ABS;
|
||||
break;
|
||||
case LTO_SYMBOL_DEFINITION_UNDEFINED:
|
||||
case LTO_SYMBOL_DEFINITION_WEAKUNDEF:
|
||||
msym.type = N_UNDF;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
switch (attr & LTO_SYMBOL_SCOPE_MASK) {
|
||||
case 0:
|
||||
case LTO_SYMBOL_SCOPE_INTERNAL:
|
||||
case LTO_SYMBOL_SCOPE_HIDDEN:
|
||||
break;
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT:
|
||||
case LTO_SYMBOL_SCOPE_PROTECTED:
|
||||
case LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN:
|
||||
msym.is_extern = true;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
mach_syms2.push_back(msym);
|
||||
}
|
||||
|
||||
mach_syms = mach_syms2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *dylib = new DylibFile<E>(mf);
|
||||
dylib->is_alive = (ctx.needed_l || !ctx.arg.dead_strip_dylibs);
|
||||
dylib->is_weak = ctx.weak_l;
|
||||
ctx.dylib_pool.emplace_back(dylib);
|
||||
|
||||
switch (get_file_type(mf)) {
|
||||
case FileType::TAPI:
|
||||
dylib->parse_tapi(ctx);
|
||||
break;
|
||||
case FileType::MACH_DYLIB:
|
||||
dylib->parse_dylib(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": is not a dylib";
|
||||
}
|
||||
|
||||
return dylib;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
|
||||
const std::string &prefix) {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (*buf) {
|
||||
read_uleb(buf); // size
|
||||
read_uleb(buf); // flags
|
||||
read_uleb(buf); // addr
|
||||
this->syms.push_back(get_symbol(ctx, save_string(ctx, prefix)));
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_tapi(Context<E> &ctx) {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
|
||||
for (std::string_view s : tbd.exports)
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
|
||||
for (std::string_view s : tbd.weak_exports)
|
||||
this->syms.push_back(get_symbol(ctx, s));
|
||||
|
||||
install_name = tbd.install_name;
|
||||
reexported_libs = tbd.reexported_libs;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_ID_DYLIB: {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
install_name = (char *)p + cmd.nameoff;
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
|
||||
if (cmd.export_off)
|
||||
read_trie(ctx, this->mf->data + cmd.export_off);
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_EXPORTS_TRIE: {
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
|
||||
read_trie(ctx, this->mf->data + cmd.dataoff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : this->syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
|
||||
if (get_rank(this, false, false) < get_rank(*sym)) {
|
||||
sym->file = this;
|
||||
sym->scope = SCOPE_LOCAL;
|
||||
sym->is_imported = true;
|
||||
sym->is_weak_def = this->is_weak;
|
||||
sym->subsec = nullptr;
|
||||
sym->value = 0;
|
||||
sym->is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputFile<E>; \
|
||||
template class ObjectFile<E>; \
|
||||
template class DylibFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+8
-14
@@ -14,8 +14,6 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
rels.reserve(hdr.nreloc);
|
||||
|
||||
// Parse mach-o relocations to fill `rels` vector
|
||||
rels = read_relocations(ctx, file, hdr);
|
||||
|
||||
@@ -24,18 +22,15 @@ void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
// Find subsections for this section
|
||||
auto begin = std::lower_bound(
|
||||
file.subsections.begin(), file.subsections.end(), hdr.addr,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
// Find subsections this relocation section refers to
|
||||
auto cmp = [](Subsection<E> *subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
};
|
||||
|
||||
auto end = std::lower_bound(
|
||||
begin, file.subsections.end(), hdr.addr + hdr.size,
|
||||
[](std::unique_ptr<Subsection<E>> &subsec, u32 addr) {
|
||||
return subsec->input_addr < addr;
|
||||
});
|
||||
auto begin = std::lower_bound(file.subsections.begin(),
|
||||
file.subsections.end(), hdr.addr, cmp);
|
||||
auto end = std::lower_bound(begin, file.subsections.end(),
|
||||
hdr.addr + hdr.size, cmp);
|
||||
|
||||
// Assign each subsection a group of relocations
|
||||
i64 i = 0;
|
||||
@@ -54,7 +49,6 @@ void InputSection<E>::parse_relocations(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template class InputSection<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
#include "lto.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
LTOPlugin::~LTOPlugin() {
|
||||
if (dlopen_handle)
|
||||
dlclose(dlopen_handle);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void do_load_plugin(Context<E> &ctx) {
|
||||
void *handle = dlopen(ctx.arg.lto_library.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
ctx.lto.dlopen_handle = handle;
|
||||
|
||||
// I don't want to use a macro, but I couldn't find an easy way to
|
||||
// write the same thing without a macro.
|
||||
#define DLSYM(x) ctx.lto.x = (decltype(ctx.lto.x))dlsym(handle, "lto_" #x)
|
||||
DLSYM(get_version);
|
||||
DLSYM(get_error_message);
|
||||
DLSYM(module_is_object_file);
|
||||
DLSYM(module_is_object_file_for_target);
|
||||
DLSYM(module_has_objc_category);
|
||||
DLSYM(module_is_object_file_in_memory);
|
||||
DLSYM(module_is_object_file_in_memory_for_target);
|
||||
DLSYM(module_create);
|
||||
DLSYM(module_create_from_memory);
|
||||
DLSYM(module_create_from_memory_with_path);
|
||||
DLSYM(module_create_in_local_context);
|
||||
DLSYM(module_create_in_codegen_context);
|
||||
DLSYM(module_create_from_fd);
|
||||
DLSYM(module_create_from_fd_at_offset);
|
||||
DLSYM(module_dispose);
|
||||
DLSYM(module_get_target_triple);
|
||||
DLSYM(module_set_target_triple);
|
||||
DLSYM(module_get_num_symbols);
|
||||
DLSYM(module_get_symbol_name);
|
||||
DLSYM(module_get_symbol_attribute);
|
||||
DLSYM(module_get_linkeropts);
|
||||
DLSYM(module_get_macho_cputype);
|
||||
DLSYM(module_has_ctor_dtor);
|
||||
DLSYM(codegen_set_diagnostic_handler);
|
||||
DLSYM(codegen_create);
|
||||
DLSYM(codegen_create_in_local_context);
|
||||
DLSYM(codegen_dispose);
|
||||
DLSYM(codegen_add_module);
|
||||
DLSYM(codegen_set_module);
|
||||
DLSYM(codegen_set_debug_model);
|
||||
DLSYM(codegen_set_pic_model);
|
||||
DLSYM(codegen_set_cpu);
|
||||
DLSYM(codegen_set_assembler_path);
|
||||
DLSYM(codegen_set_assembler_args);
|
||||
DLSYM(codegen_add_must_preserve_symbol);
|
||||
DLSYM(codegen_write_merged_modules);
|
||||
DLSYM(codegen_compile);
|
||||
DLSYM(codegen_compile_to_file);
|
||||
DLSYM(codegen_optimize);
|
||||
DLSYM(codegen_compile_optimized);
|
||||
DLSYM(api_version);
|
||||
DLSYM(set_debug_options);
|
||||
DLSYM(codegen_debug_options);
|
||||
DLSYM(codegen_debug_options_array);
|
||||
DLSYM(initialize_disassembler);
|
||||
DLSYM(codegen_set_should_internalize);
|
||||
DLSYM(codegen_set_should_embed_uselists);
|
||||
#undef DLSYM
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx) {
|
||||
std:call_once(ctx.lto_plugin_loaded, [&]() { do_load_plugin(ctx); });
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx) {
|
||||
LTOCodeGen *cg = ctx.lto.codegen_create();
|
||||
|
||||
// Add bitcode files to CodeGen.
|
||||
for (const std::string &opt : ctx.arg.mllvm)
|
||||
ctx.lto.codegen_debug_options(cg, opt.c_str());
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
ctx.lto.codegen_add_module(cg, file->lto_module);
|
||||
|
||||
// Mark symbols that have to be preserved. All symbols that are not
|
||||
// marked here may be internalized and deleted as an extenrally-
|
||||
// visible symbol.
|
||||
if (ctx.arg.dylib || ctx.arg.export_dynamic) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (!file->lto_module) {
|
||||
for (i64 i = 0; i < file->mach_syms.size(); i++) {
|
||||
MachSym &msym = file->mach_syms[i];
|
||||
Symbol<E> &sym = *file->syms[i];
|
||||
if (msym.is_undef() && !sym.file->is_dylib &&
|
||||
((ObjectFile<E> *)sym.file)->lto_module)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym.name.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->file == file && sym->scope != SCOPE_LOCAL)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, sym->name.data());
|
||||
}
|
||||
|
||||
if (ctx.arg.entry->file)
|
||||
ctx.lto.codegen_add_must_preserve_symbol(cg, ctx.arg.entry->name.data());
|
||||
|
||||
// Run the compiler backend to do LTO.
|
||||
size_t size;
|
||||
u8 *data = (u8 *)ctx.lto.codegen_compile(cg, &size);
|
||||
if (!data)
|
||||
Fatal(ctx) << "lto_codegen_compile failed: " << ctx.lto.get_error_message();
|
||||
|
||||
if (!ctx.arg.object_path_lto.empty()) {
|
||||
FILE *out = fopen(ctx.arg.object_path_lto.c_str(), "w");
|
||||
if (!out)
|
||||
Fatal(ctx) << "-object_path_lto: cannot open " << ctx.arg.object_path_lto
|
||||
<< ": " << errno_string();
|
||||
fwrite(data, size, 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// Remove bitcode object files from ctx.objs.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->lto_module) {
|
||||
file->clear_symbols();
|
||||
file->is_alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_alive; });
|
||||
|
||||
// Add a result of LTO as a new object file.
|
||||
MappedFile<Context<E>> *mf = new MappedFile<Context<E>>;
|
||||
mf->name = "<LTO>";
|
||||
mf->data = data;
|
||||
mf->size = size;
|
||||
ctx.mf_pool.emplace_back(mf);
|
||||
|
||||
ObjectFile<E> *obj = ObjectFile<E>::create(ctx, mf, "");
|
||||
obj->parse(ctx);
|
||||
obj->is_alive = true;
|
||||
obj->priority = 100; // regular file starts with priority 10,000
|
||||
obj->resolve_symbols(ctx);
|
||||
ctx.objs.push_back(obj);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template void load_lto_plugin(Context<E> &); \
|
||||
template void do_lto(Context<E> &);
|
||||
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
enum LTOSymbolAttributes {
|
||||
LTO_SYMBOL_ALIGNMENT_MASK = 0x0000001F,
|
||||
LTO_SYMBOL_PERMISSIONS_MASK = 0x000000E0,
|
||||
LTO_SYMBOL_PERMISSIONS_CODE = 0x000000A0,
|
||||
LTO_SYMBOL_PERMISSIONS_DATA = 0x000000C0,
|
||||
LTO_SYMBOL_PERMISSIONS_RODATA = 0x00000080,
|
||||
LTO_SYMBOL_DEFINITION_MASK = 0x00000700,
|
||||
LTO_SYMBOL_DEFINITION_REGULAR = 0x00000100,
|
||||
LTO_SYMBOL_DEFINITION_TENTATIVE = 0x00000200,
|
||||
LTO_SYMBOL_DEFINITION_WEAK = 0x00000300,
|
||||
LTO_SYMBOL_DEFINITION_UNDEFINED = 0x00000400,
|
||||
LTO_SYMBOL_DEFINITION_WEAKUNDEF = 0x00000500,
|
||||
LTO_SYMBOL_SCOPE_MASK = 0x00003800,
|
||||
LTO_SYMBOL_SCOPE_INTERNAL = 0x00000800,
|
||||
LTO_SYMBOL_SCOPE_HIDDEN = 0x00001000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT = 0x00001800,
|
||||
LTO_SYMBOL_SCOPE_PROTECTED = 0x00002000,
|
||||
LTO_SYMBOL_SCOPE_DEFAULT_CAN_BE_HIDDEN = 0x00002800,
|
||||
LTO_SYMBOL_COMDAT = 0x00004000,
|
||||
LTO_SYMBOL_ALIAS = 0x00008000,
|
||||
};
|
||||
|
||||
enum LTODebugModel {
|
||||
LTO_DEBUG_MODEL_NONE = 0,
|
||||
LTO_DEBUG_MODEL_DWARF = 1,
|
||||
};
|
||||
|
||||
enum LTOCodegenModel {
|
||||
LTO_CODEGEN_PIC_MODEL_STATIC = 0,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC = 1,
|
||||
LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC = 2,
|
||||
LTO_CODEGEN_PIC_MODEL_DEFAULT = 3,
|
||||
};
|
||||
|
||||
enum LTOCodegenDiagnosticSeverity {
|
||||
LTO_DS_ERROR = 0,
|
||||
LTO_DS_WARNING = 1,
|
||||
LTO_DS_REMARK = 3,
|
||||
LTO_DS_NOTE = 2,
|
||||
};
|
||||
|
||||
struct LTOObjectBuffer {
|
||||
const char *buffer;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
using LTOModule = void;
|
||||
using LTOCodeGen = void;
|
||||
|
||||
typedef void LTODiagnosticHandler(LTOCodegenDiagnosticSeverity severity,
|
||||
const char *diag, void *ctxt);
|
||||
|
||||
struct LTOPlugin {
|
||||
void *dlopen_handle = nullptr;
|
||||
~LTOPlugin();
|
||||
|
||||
const char (*get_version)();
|
||||
|
||||
const char (*get_error_message)();
|
||||
|
||||
bool (*module_is_object_file)(const char *path);
|
||||
|
||||
bool (*module_is_object_file_for_target)(const char *path,
|
||||
const char *target_triple_prefix);
|
||||
|
||||
bool (*module_has_objc_category)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory)(const void *mem, size_t length);
|
||||
|
||||
bool (*module_is_object_file_in_memory_for_target)(
|
||||
const void *mem, size_t length, const char *target_triple_prefix);
|
||||
|
||||
LTOModule *(*module_create)(const char *path);
|
||||
|
||||
LTOModule *(*module_create_from_memory)(const void *mem, size_t length);
|
||||
|
||||
LTOModule *(*module_create_from_memory_with_path)(const void *mem,
|
||||
size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_local_context)(const void *mem, size_t length,
|
||||
const char *path);
|
||||
|
||||
LTOModule *(*module_create_in_codegen_context)(const void *mem, size_t length,
|
||||
const char *path,
|
||||
LTOCodeGen *cg);
|
||||
|
||||
LTOModule *(*module_create_from_fd)(int fd, const char *path,
|
||||
size_t file_size);
|
||||
|
||||
LTOModule *(*module_create_from_fd_at_offset)(int fd, const char *path,
|
||||
size_t file_size,
|
||||
size_t map_size, off_t offset);
|
||||
|
||||
void (*module_dispose)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_target_triple)(LTOModule *mod);
|
||||
|
||||
void (*module_set_target_triple)(LTOModule *mod, const char *triple);
|
||||
|
||||
unsigned (*module_get_num_symbols)(LTOModule *mod);
|
||||
|
||||
const char *(*module_get_symbol_name)(LTOModule *mod, unsigned index);
|
||||
|
||||
uint32_t (*module_get_symbol_attribute)(LTOModule *mod, unsigned index);
|
||||
|
||||
const char *(*module_get_linkeropts)(LTOModule *mod);
|
||||
|
||||
bool (*module_get_macho_cputype)(LTOModule *mod, unsigned *out_cputype,
|
||||
unsigned *out_cpusubtype);
|
||||
|
||||
bool (*module_has_ctor_dtor)(LTOModule *mod);
|
||||
|
||||
void (*codegen_set_diagnostic_handler)(LTOCodeGen *cg, LTODiagnosticHandler,
|
||||
void *);
|
||||
|
||||
LTOCodeGen *(*codegen_create)();
|
||||
|
||||
LTOCodeGen *(*codegen_create_in_local_context)();
|
||||
|
||||
void (*codegen_dispose)(LTOCodeGen *cg);
|
||||
|
||||
bool (*codegen_add_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
void (*codegen_set_module)(LTOCodeGen *cg, LTOModule *mod);
|
||||
|
||||
bool (*codegen_set_debug_model)(LTOCodeGen *cg, LTODebugModel);
|
||||
|
||||
bool (*codegen_set_pic_model)(LTOCodeGen *cg, LTOCodegenModel);
|
||||
|
||||
void (*codegen_set_cpu)(LTOCodeGen *cg, const char *cpu);
|
||||
|
||||
void (*codegen_set_assembler_path)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
void (*codegen_set_assembler_args)(LTOCodeGen *cg, const char **args,
|
||||
int nargs);
|
||||
|
||||
void (*codegen_add_must_preserve_symbol)(LTOCodeGen *cg, const char *symbol);
|
||||
|
||||
bool (*codegen_write_merged_modules)(LTOCodeGen *cg, const char *path);
|
||||
|
||||
const void *(*codegen_compile)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
bool (*codegen_compile_to_file)(LTOCodeGen *cg, const char **name);
|
||||
|
||||
bool (*codegen_optimize)(LTOCodeGen *cg);
|
||||
|
||||
const void *(*codegen_compile_optimized)(LTOCodeGen *cg, size_t *length);
|
||||
|
||||
unsigned (*api_version)();
|
||||
|
||||
void (*set_debug_options)(const char *const *options, int number);
|
||||
|
||||
void (*codegen_debug_options)(LTOCodeGen *cg, const char *);
|
||||
|
||||
void (*codegen_debug_options_array)(LTOCodeGen *cg, const char *const *,
|
||||
int number);
|
||||
|
||||
void (*initialize_disassembler)();
|
||||
|
||||
void (*codegen_set_should_internalize)(LTOCodeGen *cg,
|
||||
bool should_internalize);
|
||||
|
||||
void (*codegen_set_should_embed_uselists)(LTOCodeGen *cg,
|
||||
bool should_embed_use_lists);
|
||||
};
|
||||
|
||||
} // namespace mold::macho
|
||||
+231
-179
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../big-endian.h"
|
||||
#include "../inttypes.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace mold::macho {
|
||||
@@ -19,6 +20,12 @@ typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
class ARM64;
|
||||
class X86_64;
|
||||
|
||||
template <typename E>
|
||||
std::string rel_to_string(u8 r_type);
|
||||
|
||||
static constexpr u32 FAT_MAGIC = 0xcafebabe;
|
||||
|
||||
static constexpr u32 MH_OBJECT = 0x1;
|
||||
@@ -282,6 +289,11 @@ static constexpr u32 REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5;
|
||||
|
||||
static constexpr u32 REFERENCED_DYNAMICALLY = 0x0010;
|
||||
|
||||
static constexpr u32 SELF_LIBRARY_ORDINAL = 0x0;
|
||||
static constexpr u32 MAX_LIBRARY_ORDINAL = 0xfd;
|
||||
static constexpr u32 DYNAMIC_LOOKUP_ORDINAL = 0xfe;
|
||||
static constexpr u32 EXECUTABLE_ORDINAL = 0xff;
|
||||
|
||||
static constexpr u32 N_NO_DEAD_STRIP = 0x0020;
|
||||
static constexpr u32 N_DESC_DISCARDED = 0x0020;
|
||||
static constexpr u32 N_WEAK_REF = 0x0040;
|
||||
@@ -318,6 +330,24 @@ static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGE21 = 8;
|
||||
static constexpr u32 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 = 9;
|
||||
static constexpr u32 ARM64_RELOC_ADDEND = 10;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<ARM64>(u8 type) {
|
||||
switch (type) {
|
||||
case ARM64_RELOC_UNSIGNED: return "ARM64_RELOC_UNSIGNED";
|
||||
case ARM64_RELOC_SUBTRACTOR: return "ARM64_RELOC_SUBTRACTOR";
|
||||
case ARM64_RELOC_BRANCH26: return "ARM64_RELOC_BRANCH26";
|
||||
case ARM64_RELOC_PAGE21: return "ARM64_RELOC_PAGE21";
|
||||
case ARM64_RELOC_PAGEOFF12: return "ARM64_RELOC_PAGEOFF12";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGE21: return "ARM64_RELOC_GOT_LOAD_PAGE21";
|
||||
case ARM64_RELOC_GOT_LOAD_PAGEOFF12: return "ARM64_RELOC_GOT_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_POINTER_TO_GOT: return "ARM64_RELOC_POINTER_TO_GOT";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGE21: return "ARM64_RELOC_TLVP_LOAD_PAGE21";
|
||||
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: return "ARM64_RELOC_TLVP_LOAD_PAGEOFF12";
|
||||
case ARM64_RELOC_ADDEND: return "ARM64_RELOC_ADDEND";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
static constexpr u32 X86_64_RELOC_UNSIGNED = 0;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED = 1;
|
||||
static constexpr u32 X86_64_RELOC_BRANCH = 2;
|
||||
@@ -329,33 +359,50 @@ static constexpr u32 X86_64_RELOC_SIGNED_2 = 7;
|
||||
static constexpr u32 X86_64_RELOC_SIGNED_4 = 8;
|
||||
static constexpr u32 X86_64_RELOC_TLV = 9;
|
||||
|
||||
template <>
|
||||
inline std::string rel_to_string<X86_64>(u8 type) {
|
||||
switch (type) {
|
||||
case X86_64_RELOC_UNSIGNED: return "X86_64_RELOC_UNSIGNED";
|
||||
case X86_64_RELOC_SIGNED: return "X86_64_RELOC_SIGNED";
|
||||
case X86_64_RELOC_BRANCH: return "X86_64_RELOC_BRANCH";
|
||||
case X86_64_RELOC_GOT_LOAD: return "X86_64_RELOC_GOT_LOAD";
|
||||
case X86_64_RELOC_GOT: return "X86_64_RELOC_GOT";
|
||||
case X86_64_RELOC_SUBTRACTOR: return "X86_64_RELOC_SUBTRACTOR";
|
||||
case X86_64_RELOC_SIGNED_1: return "X86_64_RELOC_SIGNED_1";
|
||||
case X86_64_RELOC_SIGNED_2: return "X86_64_RELOC_SIGNED_2";
|
||||
case X86_64_RELOC_SIGNED_4: return "X86_64_RELOC_SIGNED_4";
|
||||
case X86_64_RELOC_TLV: return "X86_64_RELOC_TLV";
|
||||
}
|
||||
return "unknown (" + std::to_string(type) + ")";
|
||||
}
|
||||
|
||||
struct FatHeader {
|
||||
ubig32 magic;
|
||||
ubig32 nfat_arch;
|
||||
ub32 magic;
|
||||
ub32 nfat_arch;
|
||||
};
|
||||
|
||||
struct FatArch {
|
||||
ubig32 cputype;
|
||||
ubig32 cpusubtype;
|
||||
ubig32 offset;
|
||||
ubig32 size;
|
||||
ubig32 align;
|
||||
ub32 cputype;
|
||||
ub32 cpusubtype;
|
||||
ub32 offset;
|
||||
ub32 size;
|
||||
ub32 align;
|
||||
};
|
||||
|
||||
struct MachHeader {
|
||||
u32 magic;
|
||||
u32 cputype;
|
||||
u32 cpusubtype;
|
||||
u32 filetype;
|
||||
u32 ncmds;
|
||||
u32 sizeofcmds;
|
||||
u32 flags;
|
||||
u32 reserved;
|
||||
ul32 magic;
|
||||
ul32 cputype;
|
||||
ul32 cpusubtype;
|
||||
ul32 filetype;
|
||||
ul32 ncmds;
|
||||
ul32 sizeofcmds;
|
||||
ul32 flags;
|
||||
ul32 reserved;
|
||||
};
|
||||
|
||||
struct LoadCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
};
|
||||
|
||||
struct SegmentCommand {
|
||||
@@ -363,17 +410,17 @@ struct SegmentCommand {
|
||||
return {segname, strnlen(segname, sizeof(segname))};
|
||||
}
|
||||
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
char segname[16];
|
||||
u64 vmaddr;
|
||||
u64 vmsize;
|
||||
u64 fileoff;
|
||||
u64 filesize;
|
||||
u32 maxprot;
|
||||
u32 initprot;
|
||||
u32 nsects;
|
||||
u32 flags;
|
||||
ul64 vmaddr;
|
||||
ul64 vmsize;
|
||||
ul64 fileoff;
|
||||
ul64 filesize;
|
||||
ul32 maxprot;
|
||||
ul32 initprot;
|
||||
ul32 nsects;
|
||||
ul32 flags;
|
||||
};
|
||||
|
||||
struct MachSection {
|
||||
@@ -401,138 +448,144 @@ struct MachSection {
|
||||
|
||||
char sectname[16];
|
||||
char segname[16];
|
||||
u64 addr;
|
||||
u64 size;
|
||||
u32 offset;
|
||||
u32 p2align;
|
||||
u32 reloff;
|
||||
u32 nreloc;
|
||||
u32 type : 8;
|
||||
u32 attr : 24;
|
||||
u32 reserved1;
|
||||
u32 reserved2;
|
||||
u32 reserved3;
|
||||
ul64 addr;
|
||||
ul64 size;
|
||||
ul32 offset;
|
||||
ul32 p2align;
|
||||
ul32 reloff;
|
||||
ul32 nreloc;
|
||||
u8 type;
|
||||
ul24 attr;
|
||||
ul32 reserved1;
|
||||
ul32 reserved2;
|
||||
ul32 reserved3;
|
||||
};
|
||||
|
||||
struct DylibCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
u32 timestamp;
|
||||
u32 current_version;
|
||||
u32 compatibility_version;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
ul32 timestamp;
|
||||
ul32 current_version;
|
||||
ul32 compatibility_version;
|
||||
};
|
||||
|
||||
struct DylinkerCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 nameoff;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 nameoff;
|
||||
};
|
||||
|
||||
struct SymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 symoff;
|
||||
u32 nsyms;
|
||||
u32 stroff;
|
||||
u32 strsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 symoff;
|
||||
ul32 nsyms;
|
||||
ul32 stroff;
|
||||
ul32 strsize;
|
||||
};
|
||||
|
||||
struct DysymtabCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 ilocalsym;
|
||||
u32 nlocalsym;
|
||||
u32 iextdefsym;
|
||||
u32 nextdefsym;
|
||||
u32 iundefsym;
|
||||
u32 nundefsym;
|
||||
u32 tocoff;
|
||||
u32 ntoc;
|
||||
u32 modtaboff;
|
||||
u32 nmodtab;
|
||||
u32 extrefsymoff;
|
||||
u32 nextrefsyms;
|
||||
u32 indirectsymoff;
|
||||
u32 nindirectsyms;
|
||||
u32 extreloff;
|
||||
u32 nextrel;
|
||||
u32 locreloff;
|
||||
u32 nlocrel;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 ilocalsym;
|
||||
ul32 nlocalsym;
|
||||
ul32 iextdefsym;
|
||||
ul32 nextdefsym;
|
||||
ul32 iundefsym;
|
||||
ul32 nundefsym;
|
||||
ul32 tocoff;
|
||||
ul32 ntoc;
|
||||
ul32 modtaboff;
|
||||
ul32 nmodtab;
|
||||
ul32 extrefsymoff;
|
||||
ul32 nextrefsyms;
|
||||
ul32 indirectsymoff;
|
||||
ul32 nindirectsyms;
|
||||
ul32 extreloff;
|
||||
ul32 nextrel;
|
||||
ul32 locreloff;
|
||||
ul32 nlocrel;
|
||||
};
|
||||
|
||||
struct VersionMinCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 version;
|
||||
u32 sdk;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 version;
|
||||
ul32 sdk;
|
||||
};
|
||||
|
||||
struct DyldInfoCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 rebase_off;
|
||||
u32 rebase_size;
|
||||
u32 bind_off;
|
||||
u32 bind_size;
|
||||
u32 weak_bind_off;
|
||||
u32 weak_bind_size;
|
||||
u32 lazy_bind_off;
|
||||
u32 lazy_bind_size;
|
||||
u32 export_off;
|
||||
u32 export_size;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 rebase_off;
|
||||
ul32 rebase_size;
|
||||
ul32 bind_off;
|
||||
ul32 bind_size;
|
||||
ul32 weak_bind_off;
|
||||
ul32 weak_bind_size;
|
||||
ul32 lazy_bind_off;
|
||||
ul32 lazy_bind_size;
|
||||
ul32 export_off;
|
||||
ul32 export_size;
|
||||
};
|
||||
|
||||
struct UUIDCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
u8 uuid[16];
|
||||
};
|
||||
|
||||
struct RpathCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 path_off;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 path_off;
|
||||
};
|
||||
|
||||
struct LinkEditDataCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 dataoff;
|
||||
u32 datasize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 dataoff;
|
||||
ul32 datasize;
|
||||
};
|
||||
|
||||
struct BuildToolVersion {
|
||||
u32 tool;
|
||||
u32 version;
|
||||
ul32 tool;
|
||||
ul32 version;
|
||||
};
|
||||
|
||||
struct BuildVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u32 platform;
|
||||
u32 minos;
|
||||
u32 sdk;
|
||||
u32 ntools;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 platform;
|
||||
ul32 minos;
|
||||
ul32 sdk;
|
||||
ul32 ntools;
|
||||
};
|
||||
|
||||
struct EntryPointCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 entryoff;
|
||||
u64 stacksize;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 entryoff;
|
||||
ul64 stacksize;
|
||||
};
|
||||
|
||||
struct SourceVersionCommand {
|
||||
u32 cmd;
|
||||
u32 cmdsize;
|
||||
u64 version;
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul64 version;
|
||||
};
|
||||
|
||||
struct DataInCodeEntry {
|
||||
u32 offset;
|
||||
u16 length;
|
||||
u16 kind;
|
||||
ul32 offset;
|
||||
ul16 length;
|
||||
ul16 kind;
|
||||
};
|
||||
|
||||
struct LinkerOptionCommand {
|
||||
ul32 cmd;
|
||||
ul32 cmdsize;
|
||||
ul32 count;
|
||||
};
|
||||
|
||||
// This struct is named `n_list` on BSD and macOS.
|
||||
@@ -542,34 +595,33 @@ struct MachSym {
|
||||
}
|
||||
|
||||
bool is_common() const {
|
||||
return type == N_UNDF && ext && value;
|
||||
return type == N_UNDF && is_extern && value;
|
||||
}
|
||||
|
||||
u32 stroff;
|
||||
u8 ext : 1;
|
||||
ul32 stroff;
|
||||
u8 is_extern : 1;
|
||||
u8 type : 3;
|
||||
u8 pext : 1;
|
||||
u8 is_private_extern : 1;
|
||||
u8 stub : 3;
|
||||
u8 sect;
|
||||
union {
|
||||
u16 desc;
|
||||
ul16 desc;
|
||||
struct {
|
||||
u16 : 8;
|
||||
u16 p2align : 4;
|
||||
u16 : 4;
|
||||
u8 padding;
|
||||
u8 p2align : 4;
|
||||
};
|
||||
};
|
||||
u64 value;
|
||||
ul64 value;
|
||||
};
|
||||
|
||||
// This struct is named `relocation_info` on BSD and macOS.
|
||||
struct MachRel {
|
||||
u32 offset;
|
||||
u32 idx : 24;
|
||||
u32 is_pcrel : 1;
|
||||
u32 p2size : 2;
|
||||
u32 is_extern : 1;
|
||||
u32 type : 4;
|
||||
ul32 offset;
|
||||
ul24 idx;
|
||||
u8 is_pcrel : 1;
|
||||
u8 p2size : 2;
|
||||
u8 is_extern : 1;
|
||||
u8 type : 4;
|
||||
};
|
||||
|
||||
// __TEXT,__unwind_info section contents
|
||||
@@ -580,47 +632,47 @@ static constexpr u32 UNWIND_SECOND_LEVEL_COMPRESSED = 3;
|
||||
static constexpr u32 UNWIND_PERSONALITY_MASK = 0x30000000;
|
||||
|
||||
struct UnwindSectionHeader {
|
||||
u32 version;
|
||||
u32 encoding_offset;
|
||||
u32 encoding_count;
|
||||
u32 personality_offset;
|
||||
u32 personality_count;
|
||||
u32 page_offset;
|
||||
u32 page_count;
|
||||
ul32 version;
|
||||
ul32 encoding_offset;
|
||||
ul32 encoding_count;
|
||||
ul32 personality_offset;
|
||||
ul32 personality_count;
|
||||
ul32 page_offset;
|
||||
ul32 page_count;
|
||||
};
|
||||
|
||||
struct UnwindFirstLevelPage {
|
||||
u32 func_addr;
|
||||
u32 page_offset;
|
||||
u32 lsda_offset;
|
||||
ul32 func_addr;
|
||||
ul32 page_offset;
|
||||
ul32 lsda_offset;
|
||||
};
|
||||
|
||||
struct UnwindSecondLevelPage {
|
||||
u32 kind;
|
||||
u16 page_offset;
|
||||
u16 page_count;
|
||||
u16 encoding_offset;
|
||||
u16 encoding_count;
|
||||
ul32 kind;
|
||||
ul16 page_offset;
|
||||
ul16 page_count;
|
||||
ul16 encoding_offset;
|
||||
ul16 encoding_count;
|
||||
};
|
||||
|
||||
struct UnwindLsdaEntry {
|
||||
u32 func_addr;
|
||||
u32 lsda_addr;
|
||||
ul32 func_addr;
|
||||
ul32 lsda_addr;
|
||||
};
|
||||
|
||||
struct UnwindPageEntry {
|
||||
u32 func_addr : 24;
|
||||
u32 encoding : 8;
|
||||
ul24 func_addr;
|
||||
u8 encoding;
|
||||
};
|
||||
|
||||
// __LD,__compact_unwind section contents
|
||||
|
||||
struct CompactUnwindEntry {
|
||||
u64 code_start;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
u64 personality;
|
||||
u64 lsda;
|
||||
ul64 code_start;
|
||||
ul32 code_len;
|
||||
ul32 encoding;
|
||||
ul64 personality;
|
||||
ul64 lsda;
|
||||
};
|
||||
|
||||
// __LINKEDIT,__code_signature
|
||||
@@ -635,39 +687,39 @@ static constexpr u32 CS_EXECSEG_MAIN_BINARY = 1;
|
||||
static constexpr u32 CS_HASHTYPE_SHA256 = 2;
|
||||
|
||||
struct CodeSignatureHeader {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 count;
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 count;
|
||||
};
|
||||
|
||||
struct CodeSignatureBlobIndex {
|
||||
ubig32 type;
|
||||
ubig32 offset;
|
||||
u32 padding;
|
||||
ub32 type;
|
||||
ub32 offset;
|
||||
ub32 padding;
|
||||
};
|
||||
|
||||
struct CodeSignatureDirectory {
|
||||
ubig32 magic;
|
||||
ubig32 length;
|
||||
ubig32 version;
|
||||
ubig32 flags;
|
||||
ubig32 hash_offset;
|
||||
ubig32 ident_offset;
|
||||
ubig32 n_special_slots;
|
||||
ubig32 n_code_slots;
|
||||
ubig32 code_limit;
|
||||
ub32 magic;
|
||||
ub32 length;
|
||||
ub32 version;
|
||||
ub32 flags;
|
||||
ub32 hash_offset;
|
||||
ub32 ident_offset;
|
||||
ub32 n_special_slots;
|
||||
ub32 n_code_slots;
|
||||
ub32 code_limit;
|
||||
u8 hash_size;
|
||||
u8 hash_type;
|
||||
u8 platform;
|
||||
u8 page_size;
|
||||
ubig32 spare2;
|
||||
ubig32 scatter_offset;
|
||||
ubig32 team_offset;
|
||||
ubig32 spare3;
|
||||
ubig64 code_limit64;
|
||||
ubig64 exec_seg_base;
|
||||
ubig64 exec_seg_limit;
|
||||
ubig64 exec_seg_flags;
|
||||
ub32 spare2;
|
||||
ub32 scatter_offset;
|
||||
ub32 team_offset;
|
||||
ub32 spare3;
|
||||
ub64 code_limit64;
|
||||
ub64 exec_seg_base;
|
||||
ub64 exec_seg_limit;
|
||||
ub64 exec_seg_flags;
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
|
||||
+559
-112
@@ -1,6 +1,8 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
#include "../sha.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
@@ -9,6 +11,9 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
@@ -20,9 +25,93 @@ split_string(std::string_view str, char sep) {
|
||||
return {str.substr(0, pos), str.substr(pos + 1)};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool has_lto_obj(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->lto_module)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void resolve_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "resolve_symbols");
|
||||
|
||||
auto for_each_file = [&](std::function<void(InputFile<E> *)> fn) {
|
||||
tbb::parallel_for_each(ctx.objs, fn);
|
||||
tbb::parallel_for_each(ctx.dylibs, fn);
|
||||
};
|
||||
|
||||
for_each_file([&](InputFile<E> *file) { file->resolve_symbols(ctx); });
|
||||
|
||||
for (std::string_view name : ctx.arg.u)
|
||||
if (InputFile<E> *file = get_symbol(ctx, name)->file)
|
||||
file->is_alive = true;
|
||||
|
||||
if (InputFile<E> *file = ctx.arg.entry->file)
|
||||
file->is_alive = true;
|
||||
|
||||
std::vector<ObjectFile<E> *> live_objs;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->is_alive)
|
||||
live_objs.push_back(file);
|
||||
|
||||
for (i64 i = 0; i < live_objs.size(); i++) {
|
||||
live_objs[i]->mark_live_objects(ctx, [&](ObjectFile<E> *file) {
|
||||
live_objs.push_back(file);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove symbols of eliminated files.
|
||||
for_each_file([&](InputFile<E> *file) {
|
||||
if (!file->is_alive)
|
||||
file->clear_symbols();
|
||||
});
|
||||
|
||||
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_alive; });
|
||||
std::erase_if(ctx.dylibs, [](InputFile<E> *file) { return !file->is_alive; });
|
||||
|
||||
for_each_file([&](InputFile<E> *file) { file->resolve_symbols(ctx); });
|
||||
|
||||
if (has_lto_obj(ctx))
|
||||
do_lto(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void handle_exported_symbols_list(Context<E> &ctx) {
|
||||
Timer t(ctx, "handle_exported_symbols_list");
|
||||
if (ctx.arg.exported_symbols_list.empty())
|
||||
return;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym && sym->file == file)
|
||||
if (sym->scope == SCOPE_EXTERN || sym->scope == SCOPE_PRIVATE_EXTERN)
|
||||
sym->scope = ctx.arg.exported_symbols_list.find(sym->name)
|
||||
? SCOPE_EXTERN : SCOPE_PRIVATE_EXTERN;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void handle_unexported_symbols_list(Context<E> &ctx) {
|
||||
Timer t(ctx, "handle_unexported_symbols_list");
|
||||
if (ctx.arg.unexported_symbols_list.empty())
|
||||
return;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym && sym->file == file)
|
||||
if (sym->scope == SCOPE_EXTERN &&
|
||||
ctx.arg.unexported_symbols_list.find(sym->name))
|
||||
sym->scope = SCOPE_PRIVATE_EXTERN;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void create_internal_file(Context<E> &ctx) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
obj->is_alive = true;
|
||||
obj->mach_syms = obj->mach_syms2;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
ctx.objs.push_back(obj);
|
||||
|
||||
@@ -38,7 +127,7 @@ static void create_internal_file(Context<E> &ctx) {
|
||||
switch (ctx.output_type) {
|
||||
case MH_EXECUTE: {
|
||||
Symbol<E> *sym = add("__mh_execute_header");
|
||||
sym->is_extern = true;
|
||||
sym->scope = SCOPE_EXTERN;
|
||||
sym->referenced_dynamically = true;
|
||||
sym->value = ctx.arg.pagezero_size;
|
||||
break;
|
||||
@@ -52,6 +141,30 @@ static void create_internal_file(Context<E> &ctx) {
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
add("___dso_handle");
|
||||
}
|
||||
|
||||
// Remove unreferenced subsections to eliminate code and data
|
||||
// referenced by duplicated weakdef symbols.
|
||||
template <typename E>
|
||||
static void remove_unreferenced_subsections(Context<E> &ctx) {
|
||||
Timer t(ctx, "remove_unreferenced_subsections");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (i64 i = 0; i < file->mach_syms.size(); i++) {
|
||||
MachSym &msym = file->mach_syms[i];
|
||||
Symbol<E> &sym = *file->syms[i];
|
||||
if (sym.file != file && (msym.type == N_SECT) && (msym.desc & N_WEAK_DEF))
|
||||
file->sym_to_subsec[i]->is_alive = false;
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::erase_if(file->subsections, [](Subsection<E> *subsec) {
|
||||
return !subsec->is_alive;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -66,32 +179,32 @@ static bool compare_segments(const std::unique_ptr<OutputSegment<E>> &a,
|
||||
return 1;
|
||||
if (name == "__DATA")
|
||||
return 2;
|
||||
return INT_MAX;
|
||||
if (name == "__LINKEDIT")
|
||||
return 4;
|
||||
return 3;
|
||||
};
|
||||
|
||||
std::string_view na = a->cmd.get_segname();
|
||||
std::string_view nb = b->cmd.get_segname();
|
||||
i64 ra = get_rank(na);
|
||||
i64 rb = get_rank(nb);
|
||||
if (ra != INT_MAX || rb != INT_MAX)
|
||||
return ra < rb;
|
||||
|
||||
if (na == "__LINKEDIT" || nb == "__LINKEDIT")
|
||||
return na != "__LINKEDIT";
|
||||
return na < nb;
|
||||
std::string_view x = a->cmd.get_segname();
|
||||
std::string_view y = b->cmd.get_segname();
|
||||
return std::tuple{get_rank(x), x} < std::tuple{get_rank(y), y};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool compare_chunks(const Chunk<E> *a, const Chunk<E> *b) {
|
||||
assert(a->hdr.get_segname() == b->hdr.get_segname());
|
||||
|
||||
if ((a->hdr.type == S_ZEROFILL) != (b->hdr.type == S_ZEROFILL))
|
||||
return a->hdr.type != S_ZEROFILL;
|
||||
auto is_bss = [](const Chunk<E> *x) {
|
||||
return x->hdr.type == S_ZEROFILL || x->hdr.type == S_THREAD_LOCAL_ZEROFILL;
|
||||
};
|
||||
|
||||
if (is_bss(a) != is_bss(b))
|
||||
return !is_bss(a);
|
||||
|
||||
static const std::string_view rank[] = {
|
||||
// __TEXT
|
||||
"__mach_header",
|
||||
"__text",
|
||||
"__StaticInit",
|
||||
"__stubs",
|
||||
"__stub_helper",
|
||||
"__gcc_except_tab",
|
||||
@@ -99,8 +212,11 @@ static bool compare_chunks(const Chunk<E> *a, const Chunk<E> *b) {
|
||||
"__unwind_info",
|
||||
// __DATA_CONST
|
||||
"__got",
|
||||
"__const",
|
||||
// __DATA
|
||||
"__mod_init_func",
|
||||
"__la_symbol_ptr",
|
||||
"__thread_ptrs",
|
||||
"__data",
|
||||
"__thread_ptr",
|
||||
"__thread_data",
|
||||
@@ -122,30 +238,173 @@ static bool compare_chunks(const Chunk<E> *a, const Chunk<E> *b) {
|
||||
"__code_signature",
|
||||
};
|
||||
|
||||
auto get_rank = [](const Chunk<E> *chunk) -> i64 {
|
||||
std::string_view name = chunk->hdr.get_sectname();
|
||||
auto get_rank = [](std::string_view name) {
|
||||
i64 i = 0;
|
||||
for (; i < sizeof(rank) / sizeof(rank[0]); i++)
|
||||
if (name == rank[i])
|
||||
return i;
|
||||
return INT_MAX;
|
||||
return i;
|
||||
};
|
||||
|
||||
i64 ra = get_rank(a);
|
||||
i64 rb = get_rank(b);
|
||||
if (ra == INT_MAX && rb == INT_MAX)
|
||||
return a->hdr.get_sectname() < b->hdr.get_sectname();
|
||||
return ra < rb;
|
||||
std::string_view x = a->hdr.get_sectname();
|
||||
std::string_view y = b->hdr.get_sectname();
|
||||
return std::tuple{get_rank(x), x} < std::tuple{get_rank(y), y};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void claim_unresolved_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "claim_unresolved_symbols");
|
||||
|
||||
for (std::string_view name : ctx.arg.U)
|
||||
if (Symbol<E> *sym = get_symbol(ctx, name); !sym->file)
|
||||
sym->is_imported = true;
|
||||
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (i64 i = 0; i < file->mach_syms.size(); i++) {
|
||||
MachSym &msym = file->mach_syms[i];
|
||||
if (!msym.is_extern || !msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (sym.is_imported) {
|
||||
if (!sym.file ||
|
||||
(!sym.file->is_dylib && file->priority < sym.file->priority)) {
|
||||
sym.file = file;
|
||||
sym.scope = SCOPE_PRIVATE_EXTERN;
|
||||
sym.is_imported = true;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void merge_cstring_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "merge_cstring_sections");
|
||||
|
||||
struct Entry {
|
||||
Entry(Subsection<E> *subsec) : owner(subsec) {}
|
||||
|
||||
Entry(const Entry &other) :
|
||||
owner(other.owner.load()), p2align(other.p2align.load()) {}
|
||||
|
||||
std::atomic<Subsection<E> *> owner = nullptr;
|
||||
std::atomic_uint8_t p2align = 0;
|
||||
};
|
||||
|
||||
struct SubsecRef {
|
||||
Subsection<E> &subsec;
|
||||
u64 hash = 0;
|
||||
Entry *ent = nullptr;
|
||||
};
|
||||
|
||||
std::vector<std::vector<SubsecRef>> vec(ctx.objs.size());
|
||||
|
||||
// Estimate the number of unique strings.
|
||||
HyperLogLog estimator;
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
ObjectFile<E> *file = ctx.objs[i];
|
||||
HyperLogLog e;
|
||||
|
||||
for (Subsection<E> *subsec : file->subsections) {
|
||||
if (&subsec->isec.osec == ctx.cstring) {
|
||||
std::string_view str = subsec->get_contents();
|
||||
u64 h = hash_string(str);
|
||||
vec[i].push_back({*subsec, h, nullptr});
|
||||
estimator.insert(h);
|
||||
}
|
||||
}
|
||||
estimator.merge(e);
|
||||
});
|
||||
|
||||
// Create a hash map large enough to hold all strings.
|
||||
ConcurrentMap<Entry> map(estimator.get_cardinality() * 3 / 2);
|
||||
|
||||
// Insert all strings into the hash table.
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
ObjectFile<E> *file = ctx.objs[i];
|
||||
|
||||
for (i64 j = 0; j < vec[i].size(); j++) {
|
||||
SubsecRef &ref = vec[i][j];
|
||||
std::string_view s = ref.subsec.get_contents();
|
||||
ref.ent = map.insert(s, ref.hash, {&ref.subsec}).first;
|
||||
|
||||
Subsection<E> *existing = ref.ent->owner;
|
||||
while (existing->isec.file.priority < file->priority &&
|
||||
!ref.ent->owner.compare_exchange_weak(existing, &ref.subsec));
|
||||
|
||||
update_maximum(ref.ent->p2align, ref.subsec.p2align.load());
|
||||
}
|
||||
});
|
||||
|
||||
// Decide who will become the owner for each subsection.
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
ObjectFile<E> *file = ctx.objs[i];
|
||||
|
||||
for (i64 j = 0; j < vec[i].size(); j++) {
|
||||
SubsecRef &ref = vec[i][j];
|
||||
if (ref.ent->owner != &ref.subsec) {
|
||||
ref.subsec.is_coalesced = true;
|
||||
ref.subsec.replacer = ref.ent->owner;
|
||||
|
||||
static Counter counter("num_merged_strings");
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Merge strings
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec)
|
||||
for (Relocation<E> &r : isec->rels)
|
||||
if (r.subsec && r.subsec->is_coalesced)
|
||||
r.subsec = r.subsec->replacer;
|
||||
});
|
||||
|
||||
auto replace = [&](InputFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->syms)
|
||||
if (sym->subsec && sym->subsec->is_coalesced)
|
||||
sym->subsec = sym->subsec->replacer;
|
||||
};
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, replace);
|
||||
tbb::parallel_for_each(ctx.dylibs, replace);
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
std::erase_if(file->subsections, [](Subsection<E> *subsec) {
|
||||
return subsec->is_coalesced;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void create_synthetic_chunks(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<Subsection<E>> &subsec : file->subsections)
|
||||
subsec->isec.osec.add_subsec(subsec.get());
|
||||
Timer t(ctx, "create_synthetic_chunks");
|
||||
|
||||
// First, we add subsections specified by -order_file to output sections.
|
||||
for (std::string_view name : ctx.arg.order_file)
|
||||
if (Symbol<E> *sym = get_symbol(ctx, name); sym->file)
|
||||
if (Subsection<E> *subsec = sym->subsec)
|
||||
if (!subsec->added_to_osec)
|
||||
subsec->isec.osec.add_subsec(subsec);
|
||||
|
||||
// Add remaining subsections to output sections.
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
if (!subsec->added_to_osec)
|
||||
subsec->isec.osec.add_subsec(subsec);
|
||||
|
||||
// Add output sections to segments.
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
if (chunk != ctx.data && chunk->is_regular &&
|
||||
if (chunk != ctx.data && chunk->is_output_section &&
|
||||
((OutputSection<E> *)chunk)->members.empty())
|
||||
continue;
|
||||
|
||||
@@ -154,6 +413,7 @@ static void create_synthetic_chunks(Context<E> &ctx) {
|
||||
seg->chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
// Sort segments and output sections.
|
||||
sort(ctx.segments, compare_segments<E>);
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
@@ -162,15 +422,18 @@ static void create_synthetic_chunks(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static void scan_unwind_info(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (UnwindRecord<E> &rec : file->unwind_records)
|
||||
if (!ctx.arg.dead_strip || rec.is_alive)
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
for (UnwindRecord<E> &rec : subsec->get_unwind_records())
|
||||
if (rec.personality)
|
||||
rec.personality->flags |= NEEDS_GOT;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void export_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "export_symbols");
|
||||
|
||||
ctx.got.add(ctx, get_symbol(ctx, "dyld_stub_binder"));
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
@@ -200,6 +463,8 @@ static void export_symbols(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static i64 assign_offsets(Context<E> &ctx) {
|
||||
Timer t(ctx, "assign_offsets");
|
||||
|
||||
i64 sect_idx = 1;
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
for (Chunk<E> *chunk : seg->chunks)
|
||||
@@ -217,11 +482,56 @@ static i64 assign_offsets(Context<E> &ctx) {
|
||||
return fileoff;
|
||||
}
|
||||
|
||||
// An address of a symbol of type S_THREAD_LOCAL_VARIABLES is computed
|
||||
// as a relative address to the beginning of the first thread-local
|
||||
// section. This function finds the beginnning address.
|
||||
template <typename E>
|
||||
static u64 get_tls_begin(Context<E> &ctx) {
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
for (Chunk<E> *chunk : seg->chunks)
|
||||
if (chunk->hdr.type == S_THREAD_LOCAL_REGULAR ||
|
||||
chunk->hdr.type == S_THREAD_LOCAL_ZEROFILL)
|
||||
return chunk->hdr.addr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void fix_synthetic_symbol_values(Context<E> &ctx) {
|
||||
get_symbol(ctx, "__dyld_private")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "__mh_dylib_header")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "__mh_bundle_header")->value = ctx.data->hdr.addr;
|
||||
get_symbol(ctx, "___dso_handle")->value = ctx.data->hdr.addr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void copy_sections_to_output_file(Context<E> &ctx) {
|
||||
Timer t(ctx, "copy_sections_to_output_file");
|
||||
|
||||
tbb::parallel_for_each(ctx.segments,
|
||||
[&](std::unique_ptr<OutputSegment<E>> &seg) {
|
||||
Timer t2(ctx, std::string(seg->cmd.get_segname()), &t);
|
||||
|
||||
// Fill text segment paddings with NOPs
|
||||
if (seg->cmd.get_segname() == "__TEXT")
|
||||
memset(ctx.buf + seg->cmd.fileoff, 0x90, seg->cmd.filesize);
|
||||
|
||||
tbb::parallel_for_each(seg->chunks, [&](Chunk<E> *sec) {
|
||||
if (sec->hdr.type != S_ZEROFILL) {
|
||||
Timer t3(ctx, std::string(sec->hdr.get_sectname()), &t2);
|
||||
sec->copy_buf(ctx);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void compute_uuid(Context<E> &ctx) {
|
||||
Timer t(ctx, "copy_sections_to_output_file");
|
||||
|
||||
u8 buf[SHA256_SIZE];
|
||||
SHA256(ctx.buf, ctx.output_file->filesize, buf);
|
||||
memcpy(ctx.uuid, buf, 16);
|
||||
ctx.mach_hdr.copy_buf(ctx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -247,14 +557,25 @@ MappedFile<Context<E>> *find_framework(Context<E> &ctx, std::string name) {
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
for (std::string dir : ctx.arg.library_paths) {
|
||||
for (std::string ext : {".tbd", ".dylib", ".a"}) {
|
||||
std::string path = dir + "/lib" + name + ext;
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path))
|
||||
return mf;
|
||||
auto search = [&](std::vector<std::string> extn) -> MappedFile<Context<E>> * {
|
||||
for (std::string dir : ctx.arg.library_paths) {
|
||||
for (std::string e : extn) {
|
||||
std::string path = dir + "/lib" + name + e;
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
// -search_paths_first
|
||||
if (ctx.arg.search_paths_first)
|
||||
return search({".tbd", ".dylib", ".a"});
|
||||
|
||||
// -search_dylibs_first
|
||||
if (MappedFile<Context<E>> *mf = search({".tbd", ".dylib"}))
|
||||
return mf;
|
||||
return search({".a"});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -271,8 +592,7 @@ strip_universal_header(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
bool is_needed = false) {
|
||||
static void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (get_file_type(mf) == FileType::MACH_UNIVERSAL)
|
||||
mf = strip_universal_header(ctx, mf);
|
||||
|
||||
@@ -280,20 +600,25 @@ static void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
case FileType::TAPI:
|
||||
case FileType::MACH_DYLIB:
|
||||
ctx.dylibs.push_back(DylibFile<E>::create(ctx, mf));
|
||||
if (is_needed || !ctx.arg.dead_strip_dylibs)
|
||||
ctx.dylibs.back()->is_needed = true;
|
||||
break;
|
||||
case FileType::MACH_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
ctx.objs.push_back(ObjectFile<E>::create(ctx, mf, ""));
|
||||
break;
|
||||
case FileType::AR:
|
||||
if (!ctx.all_load && !ctx.loaded_archives.insert(mf->name).second) {
|
||||
// If the same .a file is specified more than once, ignore all
|
||||
// but the first one because they would be ignored anyway.
|
||||
break;
|
||||
}
|
||||
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
|
||||
if (get_file_type(child) == FileType::MACH_OBJ)
|
||||
ctx.objs.push_back(ObjectFile<E>::create(ctx, child, mf->name));
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << mf->name << ": unknown file type";
|
||||
break;
|
||||
// Fatal(ctx) << mf->name << ": unknown file type";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,107 +648,204 @@ read_filelist(Context<E> &ctx, std::string arg) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool has_dylib(Context<E> &ctx, std::string_view path) {
|
||||
for (DylibFile<E> *file : ctx.dylibs)
|
||||
if (file->install_name == path)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
auto must_find_library = [&](std::string arg) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, arg);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "library not found: -l" << arg;
|
||||
return mf;
|
||||
};
|
||||
|
||||
while (!args.empty()) {
|
||||
if (args[0].starts_with("-filelist")) {
|
||||
for (std::string &path : read_filelist(ctx, args[1])) {
|
||||
const std::string &opt = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (!opt.starts_with('-')) {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, opt));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (opt == "-all_load") {
|
||||
ctx.all_load = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (opt == "-noall_load") {
|
||||
ctx.all_load = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args.empty())
|
||||
Fatal(ctx) << opt << ": missing argument";
|
||||
|
||||
const std::string &arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (opt == "-filelist") {
|
||||
for (std::string &path : read_filelist(ctx, arg)) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "-filepath " << args[1] << ": cannot open file: " << path;
|
||||
Fatal(ctx) << "-filepath " << arg << ": cannot open file: " << path;
|
||||
read_file(ctx, mf);
|
||||
}
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-framework" || args[0] == "-needed_framework") {
|
||||
bool needed = (args[0] == "-needed_framework");
|
||||
read_file(ctx, find_framework(ctx, args[1]), needed);
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-l" || args[0] == "-needed-l") {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, args[1]);
|
||||
if (!mf)
|
||||
Fatal(ctx) << "library not found: -l" << args[1];
|
||||
bool needed = (args[0] == "-needed-l");
|
||||
read_file(ctx, mf, needed);
|
||||
args = args.subspan(2);
|
||||
} else if (opt == "-force_load") {
|
||||
bool orig = ctx.all_load;
|
||||
ctx.all_load = true;
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, arg));
|
||||
ctx.all_load = orig;
|
||||
} else if (opt == "-framework") {
|
||||
read_file(ctx, find_framework(ctx, arg));
|
||||
} else if (opt == "-needed_framework") {
|
||||
ctx.needed_l = true;
|
||||
read_file(ctx, find_framework(ctx, arg));
|
||||
} else if (opt == "-weak_framework") {
|
||||
ctx.weak_l = true;
|
||||
read_file(ctx, find_framework(ctx, arg));
|
||||
} else if (opt == "-l") {
|
||||
read_file(ctx, must_find_library(arg));
|
||||
} else if (opt == "-needed-l") {
|
||||
ctx.needed_l = true;
|
||||
read_file(ctx, must_find_library(arg));
|
||||
} else if (opt == "-hidden-l") {
|
||||
ctx.hidden_l = true;
|
||||
read_file(ctx, must_find_library(arg));
|
||||
} else if (opt == "-weak-l") {
|
||||
ctx.weak_l = true;
|
||||
read_file(ctx, must_find_library(arg));
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, args[0]));
|
||||
args = args.subspan(1);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
ctx.needed_l = false;
|
||||
ctx.hidden_l = false;
|
||||
ctx.weak_l = false;
|
||||
}
|
||||
|
||||
// An object file can contain linker directives to load other object
|
||||
// files or libraries, so process them if any.
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
std::vector<std::string> opts = file->get_linker_options(ctx);
|
||||
|
||||
for (i64 j = 0; j < opts.size();) {
|
||||
if (opts[j] == "-framework") {
|
||||
read_file(ctx, find_framework(ctx, opts[j + 1]));
|
||||
j += 2;
|
||||
} else if (opts[j].starts_with("-l")) {
|
||||
read_file(ctx, must_find_library(opts[j].substr(2)));
|
||||
j++;
|
||||
} else {
|
||||
Fatal(ctx) << *file << ": unknown LC_LINKER_OPTION command: " << opts[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->priority = ctx.file_priority++;
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->priority = ctx.file_priority++;
|
||||
|
||||
for (i64 i = 0; i < ctx.dylibs.size(); i++)
|
||||
ctx.dylibs[i]->dylib_idx = i + 1;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void parse_object_files(Context<E> &ctx) {
|
||||
Timer t(ctx, "parse_object_files");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
file->parse(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void print_stats(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
static Counter subsections("num_subsections");
|
||||
subsections += file->subsections.size();
|
||||
|
||||
static Counter syms("num_syms");
|
||||
syms += file->syms.size();
|
||||
|
||||
static Counter rels("num_rels");
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec)
|
||||
rels += isec->rels.size();
|
||||
}
|
||||
|
||||
Counter::print();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int do_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
if (argc > 1 && std::string_view(argv[1]) == "-dump") {
|
||||
if (argc != 3)
|
||||
Fatal(ctx) << "usage: ld64.mold -dump <executable-name>\n";
|
||||
dump_file(argv[2]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < argc; i++)
|
||||
ctx.cmdline_args.push_back(argv[i]);
|
||||
|
||||
std::vector<std::string> file_args;
|
||||
parse_nonpositional_args(ctx, file_args);
|
||||
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
|
||||
|
||||
if (ctx.arg.arch != E::cputype) {
|
||||
if (ctx.arg.arch == CPU_TYPE_X86_64)
|
||||
#if !defined(MOLD_DEBUG_X86_64_ONLY) && !defined(MOLD_DEBUG_ARM64_ONLY)
|
||||
switch (ctx.arg.arch) {
|
||||
case CPU_TYPE_X86_64:
|
||||
return do_main<X86_64>(argc, argv);
|
||||
case CPU_TYPE_ARM64:
|
||||
return do_main<X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
Fatal(ctx) << "unknown cputype: " << ctx.arg.arch;
|
||||
}
|
||||
|
||||
Timer t(ctx, "all");
|
||||
|
||||
// Handle -sectcreate
|
||||
for (SectCreateOption arg : ctx.arg.sectcreate) {
|
||||
MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::must_open(ctx, std::string(arg.filename));
|
||||
SectCreateSection<E> *sec =
|
||||
new SectCreateSection<E>(ctx, arg.segname, arg.sectname, mf->get_contents());
|
||||
ctx.chunk_pool.emplace_back(sec);
|
||||
}
|
||||
|
||||
if (ctx.arg.adhoc_codesign)
|
||||
ctx.code_sig.reset(new CodeSignatureSection<E>(ctx));
|
||||
|
||||
read_input_files(ctx, file_args);
|
||||
|
||||
i64 priority = 1;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->priority = priority++;
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->priority = priority++;
|
||||
|
||||
for (i64 i = 0; i < ctx.dylibs.size(); i++)
|
||||
ctx.dylibs[i]->dylib_idx = i + 1;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->parse(ctx);
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->parse(ctx);
|
||||
parse_object_files(ctx);
|
||||
|
||||
if (ctx.arg.ObjC)
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (!file->archive_name.empty() && file->is_objc_object(ctx))
|
||||
file->is_alive = true;
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_alive)
|
||||
file->resolve_regular_symbols(ctx);
|
||||
else
|
||||
file->resolve_lazy_symbols(ctx);
|
||||
}
|
||||
resolve_symbols(ctx);
|
||||
remove_unreferenced_subsections(ctx);
|
||||
|
||||
std::vector<ObjectFile<E> *> live_objs;
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->is_alive)
|
||||
live_objs.push_back(file);
|
||||
if (ctx.output_type == MH_EXECUTE && !ctx.arg.entry->file)
|
||||
Error(ctx) << "undefined entry point symbol: " << *ctx.arg.entry;
|
||||
|
||||
for (i64 i = 0; i < live_objs.size(); i++)
|
||||
append(live_objs, live_objs[i]->mark_live_objects(ctx));
|
||||
// Handle -exported_symbol and -exported_symbols_list
|
||||
handle_exported_symbols_list(ctx);
|
||||
|
||||
for (DylibFile<E> *dylib : ctx.dylibs)
|
||||
dylib->resolve_symbols(ctx);
|
||||
|
||||
if (ctx.output_type == MH_EXECUTE && !get_symbol(ctx, ctx.arg.entry)->file)
|
||||
Error(ctx) << "undefined entry point symbol: " << ctx.arg.entry;
|
||||
// Handle -unexported_symbol and -unexported_symbols_list
|
||||
handle_unexported_symbols_list(ctx);
|
||||
|
||||
create_internal_file(ctx);
|
||||
|
||||
std::erase_if(ctx.objs, [](ObjectFile<E> *file) { return !file->is_alive; });
|
||||
std::erase_if(ctx.dylibs, [](DylibFile<E> *file) { return !file->is_alive; });
|
||||
|
||||
if (ctx.arg.trace) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
SyncOut(ctx) << *file;
|
||||
@@ -434,6 +856,10 @@ static int do_main(int argc, char **argv) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->convert_common_symbols(ctx);
|
||||
|
||||
claim_unresolved_symbols(ctx);
|
||||
|
||||
merge_cstring_sections(ctx);
|
||||
|
||||
if (ctx.arg.dead_strip)
|
||||
dead_strip(ctx);
|
||||
|
||||
@@ -442,34 +868,51 @@ static int do_main(int argc, char **argv) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
file->check_duplicate_symbols(ctx);
|
||||
|
||||
bool has_pagezero_seg = ctx.arg.pagezero_size;
|
||||
for (i64 i = 0; i < ctx.segments.size(); i++)
|
||||
ctx.segments[i]->seg_idx = i + 1;
|
||||
ctx.segments[i]->seg_idx = (has_pagezero_seg ? i + 1 : i);
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
for (std::unique_ptr<Subsection<E> > &subsec : file->subsections)
|
||||
for (Subsection<E> *subsec : file->subsections)
|
||||
subsec->scan_relocations(ctx);
|
||||
|
||||
scan_unwind_info(ctx);
|
||||
|
||||
if (ctx.arg.dead_strip_dylibs)
|
||||
std::erase_if(ctx.dylibs, [](DylibFile<E> *file) { return !file->is_needed; });
|
||||
|
||||
export_symbols(ctx);
|
||||
|
||||
i64 output_size = assign_offsets(ctx);
|
||||
ctx.tls_begin = get_tls_begin(ctx);
|
||||
fix_synthetic_symbol_values(ctx);
|
||||
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, output_size, 0777);
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, output_size, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
for (std::unique_ptr<OutputSegment<E>> &seg : ctx.segments)
|
||||
seg->copy_buf(ctx);
|
||||
ctx.code_sig.write_signature(ctx);
|
||||
copy_sections_to_output_file(ctx);
|
||||
|
||||
if (ctx.code_sig)
|
||||
ctx.code_sig->write_signature(ctx);
|
||||
else if (ctx.arg.uuid == UUID_HASH)
|
||||
compute_uuid(ctx);
|
||||
|
||||
ctx.output_file->close(ctx);
|
||||
ctx.checkpoint();
|
||||
t.stop();
|
||||
|
||||
if (ctx.arg.perf)
|
||||
print_timer_records(ctx.timer_records);
|
||||
|
||||
if (ctx.arg.stats)
|
||||
print_stats(ctx);
|
||||
|
||||
if (!ctx.arg.map.empty())
|
||||
print_map(ctx);
|
||||
|
||||
if (ctx.arg.quick_exit) {
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -483,7 +926,11 @@ Do not report bugs because it's too early to manage missing features as bugs.
|
||||
)";
|
||||
}
|
||||
|
||||
#ifdef MOLD_DEBUG_X86_64_ONLY
|
||||
return do_main<X86_64>(argc, argv);
|
||||
#else
|
||||
return do_main<ARM64>(argc, argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -30,8 +30,9 @@ void print_map(Context<E> &ctx) {
|
||||
if (file.is_alive) {
|
||||
out << "[" << std::setw(3) << i << "] " << file << "\n";
|
||||
for (Symbol<E> *sym : file.syms)
|
||||
if (sym->file == &file)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
if (sym && sym->file == &file)
|
||||
if (!sym->subsec || sym->subsec->is_alive)
|
||||
syms.push_back({sym->get_addr(ctx), 0, (u32)i, sym->name});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +75,6 @@ void print_map(Context<E> &ctx) {
|
||||
#define INSTANTIATE(E) \
|
||||
template void print_map(Context<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE_ALL;
|
||||
|
||||
} // namespace mold::macho
|
||||
+269
-183
@@ -1,16 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "macho.h"
|
||||
#include "lto.h"
|
||||
#include "../mold.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/task_group.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
#if MOLD_DEBUG_X86_64_ONLY
|
||||
# define INSTANTIATE_ALL INSTANTIATE(X86_64)
|
||||
#elif MOLD_DEBUG_ARM64_ONLY
|
||||
# define INSTANTIATE_ALL INSTANTIATE(ARM64)
|
||||
#else
|
||||
# define INSTANTIATE_ALL \
|
||||
INSTANTIATE(X86_64); \
|
||||
INSTANTIATE(ARM64);
|
||||
#endif
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
static constexpr i64 COMMON_PAGE_SIZE = 0x4000;
|
||||
@@ -24,7 +38,7 @@ template <typename E> struct Context;
|
||||
template <typename E> struct Symbol;
|
||||
|
||||
//
|
||||
// object-file.cc
|
||||
// input-files.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
@@ -32,108 +46,133 @@ struct Relocation {
|
||||
u32 offset = 0;
|
||||
u8 type = -1;
|
||||
u8 p2size = 0;
|
||||
bool is_pcrel = false;
|
||||
bool is_pcrel : 1 = false;
|
||||
bool needs_dynrel : 1 = false;
|
||||
i64 addend = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
Subsection<E> *subsec = nullptr;
|
||||
|
||||
// For range extension thunks
|
||||
i32 thunk_idx = -1;
|
||||
i32 thunk_sym_idx = -1;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Relocation<E> &rel) {
|
||||
out << rel_to_string<E>(rel.type);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct UnwindRecord {
|
||||
UnwindRecord(u32 len, u32 enc) : code_len(len), encoding(enc) {}
|
||||
|
||||
inline u64 get_func_raddr(Context<E> &ctx) const;
|
||||
u64 get_func_addr(Context<E> &ctx) const {
|
||||
return subsec->get_addr(ctx) + offset;
|
||||
}
|
||||
|
||||
Subsection<E> *subsec = nullptr;
|
||||
u32 offset = 0;
|
||||
u32 code_len;
|
||||
u32 encoding;
|
||||
Symbol<E> *personality = nullptr;
|
||||
Subsection<E> *lsda = nullptr;
|
||||
u32 offset = 0;
|
||||
u32 code_len = 0;
|
||||
u32 encoding = 0;
|
||||
u32 lsda_offset = 0;
|
||||
bool is_alive = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class InputFile {
|
||||
public:
|
||||
virtual ~InputFile() = default;
|
||||
virtual void resolve_symbols(Context<E> &ctx) = 0;
|
||||
|
||||
void clear_symbols();
|
||||
|
||||
MappedFile<Context<E>> *mf = nullptr;
|
||||
std::string_view filename;
|
||||
std::vector<Symbol<E> *> syms;
|
||||
i64 priority = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
bool is_dylib = false;
|
||||
bool is_hidden = false;
|
||||
bool is_weak = false;
|
||||
std::string archive_name;
|
||||
|
||||
virtual bool is_dylib() const = 0;
|
||||
|
||||
protected:
|
||||
InputFile() = default;
|
||||
InputFile(MappedFile<Context<E>> *mf) : mf(mf), filename(mf->name) {}
|
||||
InputFile() : filename("<internal>") {}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class ObjectFile : public InputFile<E> {
|
||||
public:
|
||||
ObjectFile() = default;
|
||||
ObjectFile(MappedFile<Context<E>> *mf) : InputFile<E>(mf) {}
|
||||
|
||||
static ObjectFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name);
|
||||
bool is_dylib() const override { return false; }
|
||||
void parse(Context<E> &ctx);
|
||||
Subsection<E> *find_subsection(Context<E> &ctx, u32 addr);
|
||||
Symbol<E> *find_symbol(Context<E> &ctx, u32 addr);
|
||||
std::vector<std::string> get_linker_options(Context<E> &ctx);
|
||||
void parse_compact_unwind(Context<E> &ctx, MachSection &hdr);
|
||||
void resolve_regular_symbols(Context<E> &ctx);
|
||||
void resolve_lazy_symbols(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx) override;
|
||||
bool is_objc_object(Context<E> &ctx);
|
||||
std::vector<ObjectFile *> mark_live_objects(Context<E> &ctx);
|
||||
void mark_live_objects(Context<E> &ctx,
|
||||
std::function<void(ObjectFile<E> *)> feeder);
|
||||
void convert_common_symbols(Context<E> &ctx);
|
||||
void check_duplicate_symbols(Context<E> &ctx);
|
||||
|
||||
Relocation<E> read_reloc(Context<E> &ctx, const MachSection &hdr, MachRel r);
|
||||
|
||||
std::vector<std::unique_ptr<InputSection<E>>> sections;
|
||||
std::vector<std::unique_ptr<Subsection<E>>> subsections;
|
||||
std::vector<u32> sym_to_subsec;
|
||||
std::vector<Subsection<E> *> subsections;
|
||||
std::vector<Subsection<E> *> sym_to_subsec;
|
||||
std::span<MachSym> mach_syms;
|
||||
std::vector<Symbol<E>> local_syms;
|
||||
std::vector<UnwindRecord<E>> unwind_records;
|
||||
std::span<DataInCodeEntry> data_in_code_entries;
|
||||
LTOModule *lto_module = nullptr;
|
||||
|
||||
// For the internal file and LTO object files
|
||||
std::vector<MachSym> mach_syms2;
|
||||
|
||||
private:
|
||||
void parse_sections(Context<E> &ctx);
|
||||
void parse_symtab(Context<E> &ctx);
|
||||
void split_subsections(Context<E> &ctx);
|
||||
void parse_symbols(Context<E> &ctx);
|
||||
void split_subsections_via_symbols(Context<E> &ctx);
|
||||
void init_subsections(Context<E> &ctx);
|
||||
void fix_subsec_members(Context<E> &ctx);
|
||||
void parse_data_in_code(Context<E> &ctx);
|
||||
LoadCommand *find_load_command(Context<E> &ctx, u32 type);
|
||||
i64 find_subsection_idx(Context<E> &ctx, u32 addr);
|
||||
void override_symbol(Context<E> &ctx, i64 symidx);
|
||||
InputSection<E> *get_common_sec(Context<E> &ctx);
|
||||
void parse_lto_symbols(Context<E> &ctx);
|
||||
|
||||
MachSection *unwind_sec = nullptr;
|
||||
std::unique_ptr<MachSection> common_hdr;
|
||||
InputSection<E> *common_sec = nullptr;
|
||||
std::vector<Subsection<E> *> subsec_pool;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class DylibFile : public InputFile<E> {
|
||||
public:
|
||||
static DylibFile *create(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
void parse(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx);
|
||||
|
||||
void parse_tapi(Context<E> &ctx);
|
||||
void parse_dylib(Context<E> &ctx);
|
||||
void resolve_symbols(Context<E> &ctx) override;
|
||||
|
||||
std::string_view install_name;
|
||||
i64 dylib_idx = 0;
|
||||
std::atomic_bool is_needed = false;
|
||||
|
||||
bool is_dylib() const override { return true; }
|
||||
std::vector<std::string_view> reexported_libs;
|
||||
|
||||
private:
|
||||
void parse_dylib(Context<E> &ctx);
|
||||
void read_trie(Context<E> &ctx, u8 *start, i64 offset = 0,
|
||||
const std::string &prefix = "");
|
||||
|
||||
DylibFile() {
|
||||
this->is_alive = true;
|
||||
DylibFile(MappedFile<Context<E>> *mf) : InputFile<E>(mf) {
|
||||
this->is_dylib = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -187,13 +226,17 @@ public:
|
||||
u32 input_offset = 0;
|
||||
u32 input_size = 0;
|
||||
u32 input_addr = 0;
|
||||
u32 output_offset = -1;
|
||||
u32 rel_offset = 0;
|
||||
u32 nrels = 0;
|
||||
u32 unwind_offset = 0;
|
||||
u32 nunwind = 0;
|
||||
u32 raddr = -1;
|
||||
u16 p2align = 0;
|
||||
std::atomic_bool is_alive = false;
|
||||
Subsection<E> *replacer; // Used if is_coalesced is true
|
||||
|
||||
std::atomic_uint8_t p2align = 0;
|
||||
std::atomic_bool is_alive = true;
|
||||
bool is_coalesced : 1 = false;
|
||||
bool added_to_osec : 1 = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -205,9 +248,16 @@ read_relocations(Context<E> &ctx, ObjectFile<E> &file, const MachSection &hdr);
|
||||
//
|
||||
|
||||
enum {
|
||||
NEEDS_GOT = 1 << 0,
|
||||
NEEDS_STUB = 1 << 1,
|
||||
NEEDS_THREAD_PTR = 1 << 2,
|
||||
NEEDS_GOT = 1 << 0,
|
||||
NEEDS_STUB = 1 << 1,
|
||||
NEEDS_THREAD_PTR = 1 << 2,
|
||||
NEEDS_RANGE_EXTN_THUNK = 1 << 3,
|
||||
};
|
||||
|
||||
enum {
|
||||
SCOPE_LOCAL, // input file scope
|
||||
SCOPE_PRIVATE_EXTERN, // output file scope (non-exported symbol)
|
||||
SCOPE_EXTERN, // global scope (exported symbol)
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -229,10 +279,15 @@ struct Symbol {
|
||||
|
||||
std::atomic_uint8_t flags = 0;
|
||||
|
||||
u8 is_extern : 1 = false;
|
||||
u8 is_lazy : 1 = false;
|
||||
u8 is_common : 1 = false;
|
||||
u8 referenced_dynamically : 1 = false;
|
||||
u8 scope : 2 = SCOPE_LOCAL;
|
||||
bool is_common : 1 = false;
|
||||
bool is_weak_def : 1 = false;
|
||||
bool is_imported : 1 = false;
|
||||
bool referenced_dynamically : 1 = false;
|
||||
|
||||
// For range extension thunks
|
||||
i32 thunk_idx = -1;
|
||||
i32 thunk_sym_idx = -1;
|
||||
|
||||
inline u64 get_addr(Context<E> &ctx) const;
|
||||
inline u64 get_got_addr(Context<E> &ctx) const;
|
||||
@@ -242,6 +297,14 @@ struct Symbol {
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym);
|
||||
|
||||
// This operator defines a total order over symbols. This is used to
|
||||
// make the output deterministic.
|
||||
template <typename E>
|
||||
inline bool operator<(const Symbol<E> &a, const Symbol<E> &b) {
|
||||
return std::tuple{a.file->priority, a.value} <
|
||||
std::tuple{b.file->priority, b.value};
|
||||
}
|
||||
|
||||
//
|
||||
// output-chunks.cc
|
||||
//
|
||||
@@ -253,14 +316,15 @@ public:
|
||||
get_instance(Context<E> &ctx, std::string_view name);
|
||||
|
||||
void set_offset(Context<E> &ctx, i64 fileoff, u64 vmaddr);
|
||||
void copy_buf(Context<E> &ctx);
|
||||
|
||||
std::string_view name;
|
||||
SegmentCommand cmd = {};
|
||||
i32 seg_idx = -1;
|
||||
std::vector<Chunk<E> *> chunks;
|
||||
|
||||
private:
|
||||
void set_offset_regular(Context<E> &ctx, i64 fileoff, u64 vmaddr);
|
||||
void set_offset_linkedit(Context<E> &ctx, i64 fileoff, u64 vmaddr);
|
||||
|
||||
OutputSegment(std::string_view name);
|
||||
};
|
||||
|
||||
@@ -277,7 +341,7 @@ public:
|
||||
MachSection hdr = {};
|
||||
u32 sect_idx = 0;
|
||||
bool is_hidden = false;
|
||||
bool is_regular = false;
|
||||
bool is_output_section = false;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -295,6 +359,28 @@ public:
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class RangeExtensionThunk {};
|
||||
|
||||
template <>
|
||||
class RangeExtensionThunk<ARM64> {
|
||||
public:
|
||||
RangeExtensionThunk(OutputSection<ARM64> &osec)
|
||||
: output_section(osec) {}
|
||||
|
||||
i64 size() const { return symbols.size() * ENTRY_SIZE; }
|
||||
u64 get_addr(i64 idx) const;
|
||||
void copy_buf(Context<ARM64> &ctx);
|
||||
|
||||
static constexpr i64 ENTRY_SIZE = 12;
|
||||
|
||||
OutputSection<ARM64> &output_section;
|
||||
i32 thunk_idx = -1;
|
||||
i64 offset = -1;
|
||||
std::mutex mu;
|
||||
std::vector<Symbol<ARM64> *> symbols;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSection : public Chunk<E> {
|
||||
public:
|
||||
@@ -305,7 +391,7 @@ public:
|
||||
OutputSection(Context<E> &ctx, std::string_view segname,
|
||||
std::string_view sectname)
|
||||
: Chunk<E>(ctx, segname, sectname) {
|
||||
this->is_regular = true;
|
||||
this->is_output_section = true;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
@@ -316,9 +402,13 @@ public:
|
||||
this->hdr.p2align = std::max<u32>(this->hdr.p2align, subsec->p2align);
|
||||
this->hdr.attr |= subsec->isec.hdr.attr;
|
||||
this->hdr.type = subsec->isec.hdr.type;
|
||||
|
||||
assert(!subsec->added_to_osec);
|
||||
subsec->added_to_osec = true;
|
||||
}
|
||||
|
||||
std::vector<Subsection<E> *> members;
|
||||
std::vector<std::unique_ptr<RangeExtensionThunk<E>>> thunks;
|
||||
};
|
||||
|
||||
class RebaseEncoder {
|
||||
@@ -337,9 +427,9 @@ private:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputRebaseSection : public Chunk<E> {
|
||||
class RebaseSection : public Chunk<E> {
|
||||
public:
|
||||
OutputRebaseSection(Context<E> &ctx)
|
||||
RebaseSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__rebase") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
@@ -353,14 +443,14 @@ public:
|
||||
class BindEncoder {
|
||||
public:
|
||||
BindEncoder();
|
||||
void add(i64 dylib_idx, std::string_view sym, i64 flags, i64 seg_idx,
|
||||
i64 offset);
|
||||
|
||||
template <typename E> void add(Symbol<E> &sym, i64 seg_idx, i64 offset);
|
||||
void finish();
|
||||
|
||||
std::vector<u8> buf;
|
||||
|
||||
private:
|
||||
std::string_view last_sym;
|
||||
std::string_view last_name;
|
||||
i64 last_flags = -1;
|
||||
i64 last_dylib = -1;
|
||||
i64 last_seg = -1;
|
||||
@@ -368,9 +458,9 @@ private:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputBindSection : public Chunk<E> {
|
||||
class BindSection : public Chunk<E> {
|
||||
public:
|
||||
OutputBindSection(Context<E> &ctx)
|
||||
BindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__binding") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
@@ -382,15 +472,15 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputLazyBindSection : public Chunk<E> {
|
||||
class LazyBindSection : public Chunk<E> {
|
||||
public:
|
||||
OutputLazyBindSection(Context<E> &ctx)
|
||||
LazyBindSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__lazy_binding") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.p2align = 3;
|
||||
}
|
||||
|
||||
void add(Context<E> &ctx, Symbol<E> &sym, i64 flags);
|
||||
void add(Context<E> &ctx, Symbol<E> &sym);
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
@@ -400,14 +490,8 @@ public:
|
||||
|
||||
class ExportEncoder {
|
||||
public:
|
||||
void add(std::string_view name, u32 flags, u64 addr);
|
||||
i64 finish();
|
||||
|
||||
void write_trie(u8 *buf) {
|
||||
write_trie(buf, root);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::string_view name;
|
||||
u32 flags;
|
||||
@@ -416,15 +500,16 @@ private:
|
||||
|
||||
struct TrieNode {
|
||||
std::string_view prefix;
|
||||
bool is_leaf = false;
|
||||
u32 flags = 0;
|
||||
std::vector<std::unique_ptr<TrieNode>> children;
|
||||
u64 addr = 0;
|
||||
u32 flags = 0;
|
||||
u32 offset = -1;
|
||||
std::vector<TrieNode> children;
|
||||
bool is_leaf = false;
|
||||
};
|
||||
|
||||
static i64 common_prefix_len(std::span<Entry> entries, i64 len);
|
||||
static TrieNode construct_trie(std::span<Entry> entries, i64 len);
|
||||
void construct_trie(TrieNode &node, std::span<Entry> entries, i64 len,
|
||||
tbb::task_group *tg, i64 grain_size, bool divide);
|
||||
|
||||
static i64 set_offset(TrieNode &node, i64 offset);
|
||||
void write_trie(u8 *buf, TrieNode &node);
|
||||
|
||||
@@ -433,9 +518,9 @@ private:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputExportSection : public Chunk<E> {
|
||||
class ExportSection : public Chunk<E> {
|
||||
public:
|
||||
OutputExportSection(Context<E> &ctx)
|
||||
ExportSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__export") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
@@ -448,9 +533,9 @@ private:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputFunctionStartsSection : public Chunk<E> {
|
||||
class FunctionStartsSection : public Chunk<E> {
|
||||
public:
|
||||
OutputFunctionStartsSection(Context<E> &ctx)
|
||||
FunctionStartsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__func_starts") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
@@ -462,63 +547,34 @@ public:
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputSymtabSection : public Chunk<E> {
|
||||
class SymtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputSymtabSection(Context<E> &ctx)
|
||||
SymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__symbol_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.p2align = 3;
|
||||
}
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
Symbol<E> *sym;
|
||||
i64 stroff;
|
||||
};
|
||||
std::vector<i64> symtab_offsets;
|
||||
std::vector<i64> strtab_offsets;
|
||||
|
||||
std::vector<Entry> locals;
|
||||
std::vector<Entry> globals;
|
||||
std::vector<Entry> undefs;
|
||||
i64 num_locals = 0;
|
||||
i64 num_globals = 0;
|
||||
i64 num_undefs =0;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputStrtabSection : public Chunk<E> {
|
||||
class StrtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputStrtabSection(Context<E> &ctx)
|
||||
StrtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__string_table") {
|
||||
this->is_hidden = true;
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.p2align = 3;
|
||||
this->hdr.size = 1;
|
||||
}
|
||||
|
||||
i64 add_string(std::string_view str);
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::string contents{1, '\0'};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class OutputIndirectSymtabSection : public Chunk<E> {
|
||||
public:
|
||||
OutputIndirectSymtabSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__LINKEDIT", "__ind_sym_tab") {
|
||||
this->is_hidden = true;
|
||||
}
|
||||
|
||||
static constexpr i64 ENTRY_SIZE = 4;
|
||||
|
||||
void compute_size(Context<E> &ctx) override;
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
struct Entry {
|
||||
Symbol<E> *sym;
|
||||
i64 symtab_idx;
|
||||
};
|
||||
|
||||
std::vector<Entry> stubs;
|
||||
std::vector<Entry> gots;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -558,6 +614,7 @@ public:
|
||||
this->hdr.p2align = std::countr_zero(2U);
|
||||
this->hdr.type = S_SYMBOL_STUBS;
|
||||
this->hdr.attr = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
this->hdr.reserved1 = 0;
|
||||
this->hdr.reserved2 = E::stub_size;
|
||||
}
|
||||
|
||||
@@ -580,20 +637,6 @@ public:
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class UnwindEncoder {
|
||||
public:
|
||||
std::vector<u8> encode(Context<E> &ctx, std::span<UnwindRecord<E>> records);
|
||||
|
||||
private:
|
||||
u32 encode_personality(Context<E> &ctx, Symbol<E> *sym);
|
||||
|
||||
std::vector<std::span<UnwindRecord<E>>>
|
||||
split_records(Context<E> &ctx, std::span<UnwindRecord<E>>);
|
||||
|
||||
std::vector<Symbol<E> *> personalities;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class UnwindInfoSection : public Chunk<E> {
|
||||
public:
|
||||
@@ -612,7 +655,7 @@ template <typename E>
|
||||
class GotSection : public Chunk<E> {
|
||||
public:
|
||||
GotSection(Context<E> &ctx) : Chunk<E>(ctx, "__DATA_CONST", "__got") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.p2align = 3;
|
||||
this->hdr.type = S_NON_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
@@ -620,6 +663,7 @@ public:
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::vector<Symbol<E> *> syms;
|
||||
std::vector<Subsection<E> *> subsections;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -627,7 +671,7 @@ class LazySymbolPtrSection : public Chunk<E> {
|
||||
public:
|
||||
LazySymbolPtrSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__la_symbol_ptr") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.p2align = 3;
|
||||
this->hdr.type = S_LAZY_SYMBOL_POINTERS;
|
||||
}
|
||||
|
||||
@@ -639,7 +683,7 @@ class ThreadPtrsSection : public Chunk<E> {
|
||||
public:
|
||||
ThreadPtrsSection(Context<E> &ctx)
|
||||
: Chunk<E>(ctx, "__DATA", "__thread_ptrs") {
|
||||
this->hdr.p2align = std::countr_zero(8U);
|
||||
this->hdr.p2align = 3;
|
||||
this->hdr.type = S_THREAD_LOCAL_VARIABLE_POINTERS;
|
||||
}
|
||||
|
||||
@@ -649,6 +693,20 @@ public:
|
||||
std::vector<Symbol<E> *> syms;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class SectCreateSection : public Chunk<E> {
|
||||
public:
|
||||
SectCreateSection(Context<E> &ctx, std::string_view seg, std::string_view sect,
|
||||
std::string_view contents)
|
||||
: Chunk<E>(ctx, seg, sect), contents(contents) {
|
||||
this->hdr.size = contents.size();
|
||||
}
|
||||
|
||||
void copy_buf(Context<E> &ctx) override;
|
||||
|
||||
std::string_view contents;
|
||||
};
|
||||
|
||||
//
|
||||
// mapfile.cc
|
||||
//
|
||||
@@ -656,36 +714,6 @@ public:
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// dumper.cc
|
||||
//
|
||||
|
||||
void dump_file(std::string path);
|
||||
|
||||
//
|
||||
// output-file.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile>
|
||||
open(Context<E> &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(Context<E> &ctx) = 0;
|
||||
virtual ~OutputFile() {}
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
OutputFile(std::string path, i64 filesize, bool is_mmapped)
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
//
|
||||
// yaml.cc
|
||||
//
|
||||
@@ -709,12 +737,10 @@ parse_yaml(std::string_view str);
|
||||
//
|
||||
|
||||
struct TextDylib {
|
||||
std::string_view uuid;
|
||||
std::string_view install_name;
|
||||
std::string_view current_version = "1.0";
|
||||
std::string_view parent_umbrella;
|
||||
std::vector<std::string_view> reexported_libs;
|
||||
std::vector<std::string_view> exports;
|
||||
std::vector<std::string_view> weak_exports;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -725,8 +751,7 @@ TextDylib parse_tbd(Context<E> &ctx, MappedFile<Context<E>> *mf);
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string> &remaining);
|
||||
std::vector<std::string> parse_nonpositional_args(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// dead-strip.cc
|
||||
@@ -735,10 +760,34 @@ void parse_nonpositional_args(Context<E> &ctx,
|
||||
template <typename E>
|
||||
void dead_strip(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// lto.cc
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
void load_lto_plugin(Context<E> &ctx);
|
||||
|
||||
template <typename E>
|
||||
void do_lto(Context<E> &ctx);
|
||||
|
||||
//
|
||||
// arch-arm64.cc
|
||||
//
|
||||
|
||||
void create_range_extension_thunks(Context<ARM64> &ctx, OutputSection<ARM64> &osec);
|
||||
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
|
||||
enum UuidKind { UUID_NONE, UUID_HASH, UUID_RANDOM };
|
||||
|
||||
struct SectCreateOption {
|
||||
std::string_view segname;
|
||||
std::string_view sectname;
|
||||
std::string_view filename;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct Context {
|
||||
Context() {
|
||||
@@ -769,49 +818,83 @@ struct Context {
|
||||
|
||||
// Command-line arguments
|
||||
struct {
|
||||
MultiGlob exported_symbols_list;
|
||||
MultiGlob unexported_symbols_list;
|
||||
Symbol<E> *entry = nullptr;
|
||||
UuidKind uuid = UUID_HASH;
|
||||
bool ObjC = false;
|
||||
bool adhoc_codesign = true;
|
||||
bool adhoc_codesign = std::is_same_v<E, ARM64>;
|
||||
bool color_diagnostics = false;
|
||||
bool dead_strip = true;
|
||||
bool dead_strip = false;
|
||||
bool dead_strip_dylibs = false;
|
||||
bool deduplicate = true;
|
||||
bool demangle = false;
|
||||
bool demangle = true;
|
||||
bool dylib = false;
|
||||
bool dynamic = true;
|
||||
bool export_dynamic = false;
|
||||
bool fatal_warnings = false;
|
||||
bool noinhibit_exec = false;
|
||||
bool perf = false;
|
||||
bool quick_exit = true;
|
||||
bool search_paths_first = true;
|
||||
bool stats = false;
|
||||
bool trace = false;
|
||||
i64 arch = CPU_TYPE_ARM64;
|
||||
i64 compatibility_version = 0;
|
||||
i64 current_version = 0;
|
||||
i64 filler = 0;
|
||||
i64 headerpad = 256;
|
||||
i64 pagezero_size = 0;
|
||||
i64 platform = PLATFORM_MACOS;
|
||||
i64 platform_min_version = 0;
|
||||
i64 platform_sdk_version = 0;
|
||||
i64 stack_size = 0;
|
||||
i64 thread_count = 0;
|
||||
std::string chroot;
|
||||
std::string entry = "_main";
|
||||
std::string final_output;
|
||||
std::string install_name;
|
||||
std::string lto_library;
|
||||
std::string map;
|
||||
std::string object_path_lto;
|
||||
std::string output = "a.out";
|
||||
std::vector<SectCreateOption> sectcreate;
|
||||
std::vector<std::string> U;
|
||||
std::vector<std::string> framework_paths;
|
||||
std::vector<std::string> library_paths;
|
||||
std::vector<std::string> mllvm;
|
||||
std::vector<std::string> order_file;
|
||||
std::vector<std::string> rpath;
|
||||
std::vector<std::string> syslibroot;
|
||||
std::vector<std::string> u;
|
||||
} arg;
|
||||
|
||||
std::vector<std::string_view> cmdline_args;
|
||||
u32 output_type = MH_EXECUTE;
|
||||
i64 file_priority = 10000;
|
||||
bool all_load = false;
|
||||
bool needed_l = false;
|
||||
bool hidden_l = false;
|
||||
bool weak_l = false;
|
||||
std::unordered_set<std::string_view> loaded_archives;
|
||||
|
||||
u8 uuid[16] = {};
|
||||
bool has_error = false;
|
||||
u64 tls_begin = 0;
|
||||
|
||||
tbb::concurrent_hash_map<std::string_view, Symbol<E>> symbol_map;
|
||||
LTOPlugin lto = {};
|
||||
std::once_flag lto_plugin_loaded;
|
||||
|
||||
std::unique_ptr<OutputFile<E>> output_file;
|
||||
tbb::concurrent_hash_map<std::string_view, Symbol<E>, HashCmp> symbol_map;
|
||||
|
||||
std::unique_ptr<OutputFile<Context<E>>> output_file;
|
||||
u8 *buf;
|
||||
bool overwrite_output_file = false;
|
||||
|
||||
tbb::concurrent_vector<std::unique_ptr<ObjectFile<E>>> obj_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<DylibFile<E>>> dylib_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<u8[]>> string_pool;
|
||||
tbb::concurrent_vector<std::unique_ptr<MappedFile<Context<E>>>> mf_pool;
|
||||
std::vector<std::unique_ptr<OutputSection<E>>> osec_pool;
|
||||
std::vector<std::unique_ptr<Chunk<E>>> chunk_pool;
|
||||
|
||||
tbb::concurrent_vector<std::unique_ptr<TimerRecord>> timer_records;
|
||||
|
||||
@@ -832,18 +915,17 @@ struct Context {
|
||||
UnwindInfoSection<E> unwind_info{*this};
|
||||
GotSection<E> got{*this};
|
||||
LazySymbolPtrSection<E> lazy_symbol_ptr{*this};
|
||||
CodeSignatureSection<E> code_sig{*this};
|
||||
DataInCodeSection<E> data_in_code{*this};
|
||||
ThreadPtrsSection<E> thread_ptrs{*this};
|
||||
RebaseSection<E> rebase{*this};
|
||||
BindSection<E> bind{*this};
|
||||
LazyBindSection<E> lazy_bind{*this};
|
||||
ExportSection<E> export_{*this};
|
||||
FunctionStartsSection<E> function_starts{*this};
|
||||
SymtabSection<E> symtab{*this};
|
||||
StrtabSection<E> strtab{*this};
|
||||
|
||||
OutputRebaseSection<E> rebase{*this};
|
||||
OutputBindSection<E> bind{*this};
|
||||
OutputLazyBindSection<E> lazy_bind{*this};
|
||||
OutputExportSection<E> export_{*this};
|
||||
OutputFunctionStartsSection<E> function_starts{*this};
|
||||
OutputSymtabSection<E> symtab{*this};
|
||||
OutputIndirectSymtabSection<E> indir_symtab{*this};
|
||||
OutputStrtabSection<E> strtab{*this};
|
||||
std::unique_ptr<CodeSignatureSection<E>> code_sig;
|
||||
|
||||
OutputSection<E> *text = nullptr;
|
||||
OutputSection<E> *data = nullptr;
|
||||
@@ -867,13 +949,15 @@ std::ostream &operator<<(std::ostream &out, const InputSection<E> &sec) {
|
||||
|
||||
template <typename E>
|
||||
u64 Subsection<E>::get_addr(Context<E> &ctx) const {
|
||||
return ctx.arg.pagezero_size + raddr;
|
||||
return isec.osec.hdr.addr + output_offset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 Symbol<E>::get_addr(Context<E> &ctx) const {
|
||||
if (subsec)
|
||||
if (subsec) {
|
||||
assert(subsec->is_alive);
|
||||
return subsec->get_addr(ctx) + value;
|
||||
}
|
||||
if (stub_idx != -1)
|
||||
return ctx.stubs.hdr.addr + stub_idx * E::stub_size;
|
||||
return value;
|
||||
@@ -900,7 +984,10 @@ inline Symbol<E> *get_symbol(Context<E> &ctx, std::string_view name) {
|
||||
|
||||
template <typename E>
|
||||
inline std::ostream &operator<<(std::ostream &out, const Symbol<E> &sym) {
|
||||
out << sym.name;
|
||||
if (opt_demangle && sym.name.starts_with("__Z"))
|
||||
out << demangle(sym.name.substr(1));
|
||||
else
|
||||
out << sym.name;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -912,9 +999,8 @@ Chunk<E>::Chunk(Context<E> &ctx, std::string_view segname,
|
||||
hdr.set_sectname(sectname);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 UnwindRecord<E>::get_func_raddr(Context<E> &ctx) const {
|
||||
return subsec->raddr + offset;
|
||||
inline u64 RangeExtensionThunk<ARM64>::get_addr(i64 idx) const {
|
||||
return output_section.hdr.addr + offset + idx * ENTRY_SIZE;
|
||||
}
|
||||
|
||||
} // namespace mold::macho
|
||||
@@ -1,622 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include "../archive-file.h"
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
std::ostream &operator<<(std::ostream &out, const InputFile<E> &file) {
|
||||
if (file.archive_name.empty())
|
||||
out << path_clean(file.mf->name);
|
||||
else
|
||||
out << path_clean(file.archive_name) << "(" << path_clean(file.mf->name) + ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *
|
||||
ObjectFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
obj->mf = mf;
|
||||
obj->archive_name = archive_name;
|
||||
obj->is_alive = archive_name.empty();
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
parse_sections(ctx);
|
||||
parse_symtab(ctx);
|
||||
split_subsections(ctx);
|
||||
parse_data_in_code(ctx);
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec)
|
||||
isec->parse_relocations(ctx);
|
||||
|
||||
if (unwind_sec)
|
||||
parse_compact_unwind(ctx, *unwind_sec);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_sections(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
// Read all but a symtab section
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
p += lc.cmdsize;
|
||||
if (lc.cmd != LC_SEGMENT_64)
|
||||
continue;
|
||||
|
||||
SegmentCommand &cmd = *(SegmentCommand *)&lc;
|
||||
MachSection *mach_sec = (MachSection *)((u8 *)&lc + sizeof(cmd));
|
||||
|
||||
for (MachSection &msec : std::span(mach_sec, mach_sec + cmd.nsects)) {
|
||||
sections.push_back(nullptr);
|
||||
|
||||
if (msec.match("__LD", "__compact_unwind")) {
|
||||
unwind_sec = &msec;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msec.attr & S_ATTR_DEBUG)
|
||||
continue;
|
||||
|
||||
sections.back().reset(new InputSection<E>(ctx, *this, msec));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_symtab(Context<E> &ctx) {
|
||||
SymtabCommand *cmd = (SymtabCommand *)find_load_command(ctx, LC_SYMTAB);
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
mach_syms = {(MachSym *)(this->mf->data + cmd->symoff), cmd->nsyms};
|
||||
this->syms.reserve(mach_syms.size());
|
||||
|
||||
i64 nlocal = 0;
|
||||
for (MachSym &msym : mach_syms)
|
||||
if (!msym.ext)
|
||||
nlocal++;
|
||||
local_syms.reserve(nlocal);
|
||||
|
||||
for (MachSym &msym : mach_syms) {
|
||||
std::string_view name = (char *)(this->mf->data + cmd->stroff + msym.stroff);
|
||||
|
||||
if (msym.ext) {
|
||||
this->syms.push_back(get_symbol(ctx, name));
|
||||
} else {
|
||||
local_syms.emplace_back(name);
|
||||
this->syms.push_back(&local_syms.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u32 symidx;
|
||||
bool is_alt_entry;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct SplitInfo {
|
||||
SplitInfo(InputSection<E> *isec) : isec(isec) {}
|
||||
InputSection<E> *isec;
|
||||
std::vector<SplitRegion> regions;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<SplitInfo<E>> split(Context<E> &ctx, ObjectFile<E> &file) {
|
||||
std::vector<SplitInfo<E>> vec;
|
||||
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
|
||||
vec.push_back({isec.get()});
|
||||
|
||||
for (i64 i = 0; i < file.mach_syms.size(); i++) {
|
||||
MachSym &msym = file.mach_syms[i];
|
||||
if (msym.type == N_SECT && file.sections[msym.sect - 1]) {
|
||||
SplitRegion r;
|
||||
r.offset = msym.value - file.sections[msym.sect - 1]->hdr.addr;
|
||||
r.symidx = i;
|
||||
r.is_alt_entry = (msym.desc & N_ALT_ENTRY);
|
||||
vec[msym.sect - 1].regions.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
std::erase_if(vec, [](const SplitInfo<E> &info) { return !info.isec; });
|
||||
|
||||
sort(vec, [](const SplitInfo<E> &a, const SplitInfo<E> &b) {
|
||||
return a.isec->hdr.addr < b.isec->hdr.addr;
|
||||
});
|
||||
|
||||
for (SplitInfo<E> &info : vec) {
|
||||
std::vector<SplitRegion> &r = info.regions;
|
||||
|
||||
if (r.empty()) {
|
||||
r.push_back({0, (u32)info.isec->hdr.size, (u32)-1, false});
|
||||
continue;
|
||||
}
|
||||
|
||||
sort(r, [](const SplitRegion &a, const SplitRegion &b) {
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
|
||||
if (r[0].offset > 0)
|
||||
r.insert(r.begin(), {0, r[0].offset, (u32)-1, false});
|
||||
|
||||
for (i64 i = 1; i < r.size(); i++)
|
||||
if (r[i - 1].offset == r[i].offset)
|
||||
r[i++].is_alt_entry = true;
|
||||
|
||||
i64 last = -1;
|
||||
for (i64 i = 0; i < r.size(); i++) {
|
||||
if (!r[i].is_alt_entry) {
|
||||
if (last != -1)
|
||||
r[last].size = r[i].offset - r[last].offset;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != -1)
|
||||
r[last].size = info.isec->hdr.size - r[last].offset;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::split_subsections(Context<E> &ctx) {
|
||||
sym_to_subsec.resize(mach_syms.size());
|
||||
|
||||
for (SplitInfo<E> &info : split(ctx, *this)) {
|
||||
InputSection<E> &isec = *info.isec;
|
||||
|
||||
for (SplitRegion &r : info.regions) {
|
||||
if (!r.is_alt_entry) {
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = isec,
|
||||
.input_offset = r.offset,
|
||||
.input_size = r.size,
|
||||
.input_addr = (u32)(isec.hdr.addr + r.offset),
|
||||
.p2align = (u8)isec.hdr.p2align,
|
||||
};
|
||||
subsections.emplace_back(subsec);
|
||||
}
|
||||
|
||||
if (r.symidx != -1)
|
||||
sym_to_subsec[r.symidx] = subsections.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < mach_syms.size(); i++)
|
||||
if (!mach_syms[i].ext)
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_data_in_code(Context<E> &ctx) {
|
||||
if (auto *cmd = (LinkEditDataCommand *)find_load_command(ctx, LC_DATA_IN_CODE))
|
||||
data_in_code_entries = {
|
||||
(DataInCodeEntry *)(this->mf->data + cmd->dataoff),
|
||||
cmd->datasize / sizeof(DataInCodeEntry)
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LoadCommand *ObjectFile<E>::find_load_command(Context<E> &ctx, u32 type) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
if (lc.cmd == type)
|
||||
return &lc;
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
i64 ObjectFile<E>::find_subsection_idx(Context<E> &ctx, u32 addr) {
|
||||
auto it = std::upper_bound(
|
||||
subsections.begin(), subsections.end(), addr,
|
||||
[&](u32 addr, const std::unique_ptr<Subsection<E>> &subsec) {
|
||||
return addr < subsec->input_addr;
|
||||
});
|
||||
|
||||
if (it == subsections.begin())
|
||||
return -1;
|
||||
return it - subsections.begin() - 1;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Subsection<E> *ObjectFile<E>::find_subsection(Context<E> &ctx, u32 addr) {
|
||||
i64 i = find_subsection_idx(ctx, addr);
|
||||
return (i == -1) ? nullptr : subsections[i].get();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse_compact_unwind(Context<E> &ctx, MachSection &hdr) {
|
||||
if (hdr.size % sizeof(CompactUnwindEntry))
|
||||
Fatal(ctx) << *this << ": invalid __compact_unwind section size";
|
||||
|
||||
i64 num_entries = hdr.size / sizeof(CompactUnwindEntry);
|
||||
unwind_records.reserve(num_entries);
|
||||
|
||||
CompactUnwindEntry *src = (CompactUnwindEntry *)(this->mf->data + hdr.offset);
|
||||
|
||||
// Read compact unwind entries
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
unwind_records.emplace_back(src[i].code_len, src[i].encoding);
|
||||
|
||||
// Read relocations
|
||||
MachRel *mach_rels = (MachRel *)(this->mf->data + hdr.reloff);
|
||||
for (i64 i = 0; i < hdr.nreloc; i++) {
|
||||
MachRel &r = mach_rels[i];
|
||||
if (r.offset >= hdr.size)
|
||||
Fatal(ctx) << *this << ": relocation offset too large: " << i;
|
||||
|
||||
i64 idx = r.offset / sizeof(CompactUnwindEntry);
|
||||
UnwindRecord<E> &dst = unwind_records[idx];
|
||||
|
||||
auto error = [&] {
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
};
|
||||
|
||||
switch (r.offset % sizeof(CompactUnwindEntry)) {
|
||||
case offsetof(CompactUnwindEntry, code_start): {
|
||||
if (r.is_pcrel || r.p2size != 3 || r.is_extern || r.type)
|
||||
error();
|
||||
|
||||
Subsection<E> *target = find_subsection(ctx, src[idx].code_start);
|
||||
if (!target)
|
||||
error();
|
||||
dst.subsec = target;
|
||||
dst.offset = src[idx].code_start - target->input_addr;
|
||||
break;
|
||||
}
|
||||
case offsetof(CompactUnwindEntry, personality):
|
||||
if (r.is_pcrel || r.p2size != 3 || !r.is_extern || r.type)
|
||||
error();
|
||||
dst.personality = this->syms[r.idx];
|
||||
break;
|
||||
case offsetof(CompactUnwindEntry, lsda): {
|
||||
if (r.is_pcrel || r.p2size != 3 || r.is_extern || r.type)
|
||||
error();
|
||||
|
||||
i32 addr = *(i32 *)((u8 *)this->mf->data + hdr.offset + r.offset);
|
||||
Subsection<E> *target = find_subsection(ctx, addr);
|
||||
if (!target)
|
||||
error();
|
||||
dst.lsda = target;
|
||||
dst.lsda_offset = addr - target->input_addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": __compact_unwind: unsupported relocation: " << i;
|
||||
}
|
||||
}
|
||||
|
||||
for (i64 i = 0; i < num_entries; i++)
|
||||
if (!unwind_records[i].subsec)
|
||||
Fatal(ctx) << ": __compact_unwind: missing relocation at " << i;
|
||||
|
||||
// Sort unwind entries by offset
|
||||
sort(unwind_records, [](const UnwindRecord<E> &a, const UnwindRecord<E> &b) {
|
||||
return std::tuple(a.subsec->input_addr, a.offset) <
|
||||
std::tuple(b.subsec->input_addr, b.offset);
|
||||
});
|
||||
|
||||
// Associate unwind entries to subsections
|
||||
for (i64 i = 0; i < num_entries;) {
|
||||
Subsection<E> &subsec = *unwind_records[i].subsec;
|
||||
subsec.unwind_offset = i;
|
||||
|
||||
i64 j = i + 1;
|
||||
while (j < num_entries && unwind_records[j].subsec == &subsec)
|
||||
j++;
|
||||
subsec.nunwind = j - i;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
// Here is the list of priorities, from the highest to the lowest.
|
||||
//
|
||||
// 1. Strong defined symbol
|
||||
// 2. Weak defined symbol
|
||||
// 3. Strong defined symbol in a DSO
|
||||
// 4. Weak defined symbol in a DSO
|
||||
// 5. Strong or weak defined symbol in an archive
|
||||
// 6. Common symbol
|
||||
// 7. Unclaimed (nonexistent) symbol
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, MachSym &msym, bool is_lazy) {
|
||||
if (msym.is_common())
|
||||
return (6 << 24) + file->priority;
|
||||
if (is_lazy)
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static u64 get_rank(Symbol<E> &sym) {
|
||||
InputFile<E> *file = sym.file;
|
||||
if (!file)
|
||||
return 7 << 24;
|
||||
if (sym.is_common)
|
||||
return (6 << 24) + file->priority;
|
||||
if (!file->archive_name.empty())
|
||||
return (5 << 24) + file->priority;
|
||||
if (file->is_dylib())
|
||||
return (3 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::override_symbol(Context<E> &ctx, i64 idx) {
|
||||
Symbol<E> &sym = *this->syms[idx];
|
||||
MachSym &msym = mach_syms[idx];
|
||||
|
||||
sym.file = this;
|
||||
sym.is_extern = msym.ext;
|
||||
sym.is_lazy = false;
|
||||
|
||||
switch (msym.type) {
|
||||
case N_UNDF:
|
||||
assert(msym.is_common());
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = true;
|
||||
break;
|
||||
case N_ABS:
|
||||
sym.subsec = nullptr;
|
||||
sym.value = msym.value;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
case N_SECT:
|
||||
sym.subsec = subsections[sym_to_subsec[idx]].get();
|
||||
sym.value = msym.value - sym.subsec->input_addr;
|
||||
sym.is_common = false;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << sym << ": unknown symbol type: " << (u64)msym.type;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_regular_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext || msym.is_undef())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
if (get_rank(this, msym, false) < get_rank(sym))
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_lazy_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext || msym.is_undef() || msym.is_common())
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (get_rank(this, msym, true) < get_rank(sym)) {
|
||||
sym.file = this;
|
||||
sym.subsec = nullptr;
|
||||
sym.value = 0;
|
||||
sym.is_extern = false;
|
||||
sym.is_lazy = true;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool ObjectFile<E>::is_objc_object(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : sections)
|
||||
if (isec->hdr.match("__DATA", "__objc_catlist") ||
|
||||
isec->hdr.match("__TEXT", "__swift"))
|
||||
return true;
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++)
|
||||
if (!mach_syms[i].is_undef() && mach_syms[i].ext &&
|
||||
this->syms[i]->name.starts_with("_OBJC_CLASS_$_"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> ObjectFile<E>::mark_live_objects(Context<E> &ctx) {
|
||||
std::vector<ObjectFile<E> *> vec;
|
||||
assert(this->is_alive);
|
||||
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (!msym.ext)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
if (msym.is_undef()) {
|
||||
if (sym.file && !sym.file->is_alive.exchange(true))
|
||||
vec.push_back((ObjectFile<E> *)sym.file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_rank(this, msym, false) < get_rank(sym))
|
||||
override_symbol(ctx, i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
|
||||
if (sym.file == this && sym.is_common) {
|
||||
InputSection<E> *isec = get_common_sec(ctx);
|
||||
Subsection<E> *subsec = new Subsection<E>{
|
||||
.isec = *isec,
|
||||
.input_size = (u32)msym.value,
|
||||
.p2align = (u8)msym.p2align,
|
||||
};
|
||||
|
||||
subsections.emplace_back(subsec);
|
||||
|
||||
sym.subsec = subsec;
|
||||
sym.value = 0;
|
||||
sym.is_common = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::check_duplicate_symbols(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < this->syms.size(); i++) {
|
||||
Symbol<E> *sym = this->syms[i];
|
||||
MachSym &msym = mach_syms[i];
|
||||
if (sym && !msym.is_undef() && !msym.is_common() && sym->file != this)
|
||||
Error(ctx) << "duplicate symbol: " << *this << ": " << *sym->file
|
||||
<< ": " << *sym;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E> *ObjectFile<E>::get_common_sec(Context<E> &ctx) {
|
||||
if (!common_sec) {
|
||||
MachSection *hdr = new MachSection;
|
||||
common_hdr.reset(hdr);
|
||||
|
||||
memset(hdr, 0, sizeof(*hdr));
|
||||
hdr->set_segname("__DATA");
|
||||
hdr->set_sectname("__common");
|
||||
hdr->type = S_ZEROFILL;
|
||||
|
||||
common_sec = new InputSection<E>(ctx, *this, *hdr);
|
||||
sections.emplace_back(common_sec);
|
||||
}
|
||||
return common_sec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
DylibFile<E> *DylibFile<E>::create(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
DylibFile<E> *dylib = new DylibFile<E>;
|
||||
dylib->mf = mf;
|
||||
ctx.dylib_pool.emplace_back(dylib);
|
||||
return dylib;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::read_trie(Context<E> &ctx, u8 *start, i64 offset,
|
||||
const std::string &prefix) {
|
||||
u8 *buf = start + offset;
|
||||
|
||||
if (*buf) {
|
||||
read_uleb(buf); // size
|
||||
read_uleb(buf); // flags
|
||||
read_uleb(buf); // addr
|
||||
this->syms.push_back(get_symbol(ctx, prefix));
|
||||
} else {
|
||||
buf++;
|
||||
}
|
||||
|
||||
i64 nchild = *buf++;
|
||||
|
||||
for (i64 i = 0; i < nchild; i++) {
|
||||
std::string suffix((char *)buf);
|
||||
buf += suffix.size() + 1;
|
||||
i64 off = read_uleb(buf);
|
||||
read_trie(ctx, start, off, prefix + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse_dylib(Context<E> &ctx) {
|
||||
MachHeader &hdr = *(MachHeader *)this->mf->data;
|
||||
u8 *p = this->mf->data + sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < hdr.ncmds; i++) {
|
||||
LoadCommand &lc = *(LoadCommand *)p;
|
||||
|
||||
switch (lc.cmd) {
|
||||
case LC_ID_DYLIB: {
|
||||
DylibCommand &cmd = *(DylibCommand *)p;
|
||||
install_name = (char *)p + cmd.nameoff;
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_INFO_ONLY: {
|
||||
DyldInfoCommand &cmd = *(DyldInfoCommand *)p;
|
||||
if (cmd.export_off)
|
||||
read_trie(ctx, this->mf->data + cmd.export_off);
|
||||
break;
|
||||
}
|
||||
case LC_DYLD_EXPORTS_TRIE: {
|
||||
LinkEditDataCommand &cmd = *(LinkEditDataCommand *)p;
|
||||
read_trie(ctx, this->mf->data + cmd.dataoff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p += lc.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::parse(Context<E> &ctx) {
|
||||
switch (get_file_type(this->mf)) {
|
||||
case FileType::TAPI: {
|
||||
TextDylib tbd = parse_tbd(ctx, this->mf);
|
||||
for (std::string_view sym : tbd.exports)
|
||||
this->syms.push_back(get_symbol(ctx, sym));
|
||||
install_name = tbd.install_name;
|
||||
break;
|
||||
}
|
||||
case FileType::MACH_DYLIB:
|
||||
parse_dylib(ctx);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << this->mf->name << ": is not a dylib";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void DylibFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
for (Symbol<E> *sym : this->syms) {
|
||||
std::scoped_lock lock(sym->mu);
|
||||
if (sym->file && sym->file->priority < this->priority)
|
||||
continue;
|
||||
sym->file = this;
|
||||
sym->is_extern = true;
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class ObjectFile<E>; \
|
||||
template class DylibFile<E>; \
|
||||
template std::ostream &operator<<(std::ostream &, const InputFile<E> &)
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
+482
-291
File diff suppressed because it is too large.
Load diff
@@ -1,120 +0,0 @@
|
||||
// This file implements the same functionality as elf/output-file.cc.
|
||||
// We have two different implemetntations for ELF and Mach-O because
|
||||
// we implemented a special optimization for ELF or Linux.
|
||||
//
|
||||
// On Linux, it is faster to write to an existing file than creating a
|
||||
// new file and writing to it. So, we overwrite an existing executable
|
||||
// if exists.
|
||||
//
|
||||
// On macOS, it looks like the system does not assume that an executable
|
||||
// is mutated once it is created. Due to some code signing mechanism or
|
||||
// something, we always have to create a fresh file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
std::string tmp_path = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
output_tmpfile = (char *)save_string(ctx, tmp_path).data();
|
||||
|
||||
i64 fd = mkstemp(output_tmpfile);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << output_tmpfile << ": " << errno_string();
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
if (fchmod(fd, perm) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
munmap(this->buf, this->filesize);
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class MallocOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
fwrite(this->buf, this->filesize, 1, stdout);
|
||||
fclose(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputFile> file;
|
||||
if (is_special)
|
||||
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
|
||||
else
|
||||
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
|
||||
return file;
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>
|
||||
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(X86_64);
|
||||
|
||||
} // namespace mold::macho
|
||||
+227
-38
@@ -1,6 +1,19 @@
|
||||
// On macOS, you can pass a test file describing a dylib instead of an
|
||||
// actual dylib file to link against a dynamic library. Such text file
|
||||
// should be in the YAML format and contains dylib's exported symbols
|
||||
// as well as the file's various attributes. The extension of the text
|
||||
// file is `.tbd`.
|
||||
//
|
||||
// .tbd files allows users to link against a library without
|
||||
// distributing the binary of the library file itself.
|
||||
//
|
||||
// This file contains functions to parse the .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
@@ -13,6 +26,15 @@ get_vector(YamlNode &node, std::string_view key) {
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::vector<std::string_view>
|
||||
get_string_vector(YamlNode &node, std::string_view key) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
vec.push_back(*val);
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::optional<std::string_view>
|
||||
get_string(YamlNode &node, std::string_view key) {
|
||||
if (auto *map = std::get_if<std::map<std::string_view, YamlNode>>(&node.data))
|
||||
@@ -24,70 +46,233 @@ get_string(YamlNode &node, std::string_view key) {
|
||||
|
||||
static bool contains(const std::vector<YamlNode> &vec, std::string_view key) {
|
||||
for (const YamlNode &mem : vec)
|
||||
if (const std::string_view *s = std::get_if<std::string_view>(&mem.data))
|
||||
if (*s == key)
|
||||
if (auto val = std::get_if<std::string_view>(&mem.data))
|
||||
if (*val == key)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::optional<TextDylib> to_tbd(YamlNode &node, std::string_view arch) {
|
||||
template <typename E>
|
||||
static std::optional<TextDylib>
|
||||
to_tbd(Context<E> &ctx, YamlNode &node, std::string_view arch) {
|
||||
if (!contains(get_vector(node, "targets"), arch))
|
||||
return {};
|
||||
|
||||
TextDylib tbd;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "uuids"))
|
||||
if (auto target = get_string(mem, "target"))
|
||||
if (*target == arch)
|
||||
if (auto value = get_string(mem, "value"))
|
||||
tbd.uuid = *value;
|
||||
|
||||
if (auto val = get_string(node, "install-name"))
|
||||
tbd.install_name = *val;
|
||||
|
||||
if (auto val = get_string(node, "current-version"))
|
||||
tbd.current_version = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "parent-umbrella"))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
if (auto val = get_string(mem, "umbrella"))
|
||||
tbd.parent_umbrella = *val;
|
||||
|
||||
for (YamlNode &mem : get_vector(node, "reexported-libraries"))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "libraries"))
|
||||
if (auto *lib = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.reexported_libs.push_back(*lib);
|
||||
append(tbd.reexported_libs, get_string_vector(mem, "libraries"));
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"})
|
||||
for (YamlNode &mem : get_vector(node, key))
|
||||
if (contains(get_vector(mem, "targets"), arch))
|
||||
for (YamlNode &mem : get_vector(mem, "symbols"))
|
||||
if (auto *sym = std::get_if<std::string_view>(&mem.data))
|
||||
tbd.exports.push_back(*sym);
|
||||
auto concat = [&](const std::string &x, std::string_view y) {
|
||||
return save_string(ctx, x + std::string(y));
|
||||
};
|
||||
|
||||
for (std::string_view key : {"exports", "reexports"}) {
|
||||
for (YamlNode &mem : get_vector(node, key)) {
|
||||
if (contains(get_vector(mem, "targets"), arch)) {
|
||||
append(tbd.exports, get_string_vector(mem, "symbols"));
|
||||
append(tbd.weak_exports, get_string_vector(mem, "weak-symbols"));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-classes")) {
|
||||
tbd.exports.push_back(concat("_OBJC_CLASS_$_", s));
|
||||
tbd.exports.push_back(concat("_OBJC_METACLASS_$_", s));
|
||||
}
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-eh-types"))
|
||||
tbd.exports.push_back(concat("_OBJC_EHTYPE_$_", s));
|
||||
|
||||
for (std::string_view s : get_string_vector(mem, "objc-ivars"))
|
||||
tbd.exports.push_back(concat("_OBJC_IVAR_$_", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tbd;
|
||||
}
|
||||
|
||||
static i64 parse_version(const std::string &arg) {
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
static std::regex re(R"((\d+)(?:\.(\d+))?(?:\.(\d+))?)", flags);
|
||||
|
||||
std::smatch m;
|
||||
bool ok = std::regex_match(arg, m, re);
|
||||
assert(ok);
|
||||
|
||||
i64 major = (m[1].length() == 0) ? 0 : stoi(m[1]);
|
||||
i64 minor = (m[2].length() == 0) ? 0 : stoi(m[2]);
|
||||
i64 patch = (m[3].length() == 0) ? 0 : stoi(m[3]);
|
||||
return (major << 16) | (minor << 8) | patch;
|
||||
}
|
||||
|
||||
// Dylib can contain special symbols whose name starts with "$ld$".
|
||||
// Such symbols aren't actually symbols but linker directives.
|
||||
// We interpret such symbols in this function.
|
||||
template <typename E>
|
||||
static TextDylib squash(Context<E> &ctx, std::span<TextDylib> tbds) {
|
||||
static void interpret_ld_symbols(Context<E> &ctx, TextDylib &tbd) {
|
||||
std::vector<std::string_view> syms;
|
||||
syms.reserve(tbd.exports.size());
|
||||
|
||||
std::unordered_set<std::string_view> hidden_syms;
|
||||
|
||||
auto string_view = [](const std::csub_match &sub) {
|
||||
return std::string_view{sub.first, (size_t)sub.length()};
|
||||
};
|
||||
|
||||
for (std::string_view s : tbd.exports) {
|
||||
if (!s.starts_with("$ld$"))
|
||||
continue;
|
||||
|
||||
auto flags = std::regex_constants::ECMAScript | std::regex_constants::optimize;
|
||||
std::cmatch m;
|
||||
|
||||
// $ld$previous$ symbol replaces the default install name with a
|
||||
// specified one if the platform OS version is in a specified range.
|
||||
static std::regex previous_re(
|
||||
R"(\$ld\$previous\$([^$]+)\$([\d.]*)\$(\d+)\$([\d.]+)\$([\d.]+)\$(.*)\$)",
|
||||
flags);
|
||||
|
||||
if (std::regex_match(s.begin(), s.end(), m, previous_re)) {
|
||||
std::string_view install_name = string_view(m[1]);
|
||||
i64 platform = std::stoi(m[3].str());
|
||||
i64 min_version = parse_version(m[4]);
|
||||
i64 max_version = parse_version(m[5]);
|
||||
std::string_view symbol_name = string_view(m[6]);
|
||||
|
||||
if (!symbol_name.empty()) {
|
||||
// ld64 source seems to have implemented a feature to give an
|
||||
// alternative install name for a matching symbol, but it didn't
|
||||
// work in practice (or I may be using the feature in a wrong way.)
|
||||
// Ignore such symbol for now.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (platform == ctx.arg.platform &&
|
||||
min_version <= ctx.arg.platform_min_version &&
|
||||
ctx.arg.platform_min_version < max_version) {
|
||||
tbd.install_name = install_name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$add$os_version$symbol adds a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex add_re(R"(\$ld\$add\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.begin(), s.end(), m, add_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
syms.push_back(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$hide$os_version$symbol hides a symbol if the given OS version
|
||||
// matches.
|
||||
static std::regex hidden_re(R"(\$ld\$hide\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.begin(), s.end(), m, hidden_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
hidden_syms.insert(string_view(m[2]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// $ld$install_name$os_version$name changes the install name to a
|
||||
// given name.
|
||||
static std::regex
|
||||
install_name_re(R"(\$ld\$install_name\$os([\d.]+)\$(.+))", flags);
|
||||
|
||||
if (std::regex_match(s.begin(), s.end(), m, install_name_re)) {
|
||||
if (ctx.arg.platform_min_version == parse_version(m[1]))
|
||||
tbd.install_name = string_view(m[2]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::string_view s : tbd.exports)
|
||||
if (!s.starts_with("$ld$") && !hidden_syms.contains(std::string(s)))
|
||||
syms.push_back(s);
|
||||
|
||||
std::erase_if(syms, [](std::string_view s) { return s.starts_with("$ld$"); });
|
||||
tbd.exports = syms;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string_view arch);
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *
|
||||
find_external_lib(Context<E> &ctx, std::string_view parent, std::string path) {
|
||||
if (!path.starts_with('/'))
|
||||
Fatal(ctx) << parent << ": contains an invalid reexported path: " << path;
|
||||
|
||||
for (const std::string &root : ctx.arg.syslibroot) {
|
||||
if (path.ends_with(".tbd")) {
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.ends_with(".dylib")) {
|
||||
std::string stem(path.substr(0, path.size() - 6));
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + stem + ".tbd"))
|
||||
return file;
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path))
|
||||
return file;
|
||||
}
|
||||
|
||||
for (std::string extn : {".tbd", ".dylib"})
|
||||
if (auto *file = MappedFile<Context<E>>::open(ctx, root + path + extn))
|
||||
return file;
|
||||
}
|
||||
|
||||
Fatal(ctx) << parent << ": cannot open reexported library " << path;
|
||||
}
|
||||
|
||||
// A single YAML file may contain multiple text dylibs. The first text
|
||||
// dylib is the main file followed by optional other text dylibs for
|
||||
// re-exported libraries.
|
||||
//
|
||||
// This fucntion squashes multiple text dylibs into a single text dylib
|
||||
// by copying symbols of re-exported text dylibs to the main text dylib.
|
||||
template <typename E>
|
||||
static TextDylib
|
||||
squash(Context<E> &ctx, std::span<TextDylib> tbds, std::string_view arch) {
|
||||
std::unordered_map<std::string_view, TextDylib> map;
|
||||
|
||||
TextDylib main = std::move(tbds[0]);
|
||||
for (TextDylib &tbd : tbds.subspan(1))
|
||||
map[tbd.install_name] = std::move(tbd);
|
||||
|
||||
std::vector<std::string_view> libs;
|
||||
std::function<void(TextDylib &)> visit = [&](TextDylib &tbd) {
|
||||
for (std::string_view lib : tbd.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
|
||||
for (std::string_view lib : main.reexported_libs) {
|
||||
auto it = map.find(lib);
|
||||
if (it != map.end())
|
||||
append(main.exports, it->second.exports);
|
||||
else
|
||||
libs.push_back(lib);
|
||||
}
|
||||
if (it != map.end()) {
|
||||
// The referenced reexported library is in the same .tbd file.
|
||||
TextDylib &child = it->second;
|
||||
append(main.exports, child.exports);
|
||||
append(main.weak_exports, child.weak_exports);
|
||||
visit(child);
|
||||
} else {
|
||||
// The referenced reexported library is a separate file.
|
||||
MappedFile<Context<E>> *mf =
|
||||
find_external_lib(ctx, tbd.install_name, std::string(lib));
|
||||
TextDylib child = parse(ctx, mf, arch);
|
||||
append(main.exports, child.exports);
|
||||
append(main.weak_exports, child.weak_exports);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
main.reexported_libs = std::move(libs);
|
||||
visit(main);
|
||||
|
||||
sort(main.exports);
|
||||
sort(main.weak_exports);
|
||||
remove_duplicates(main.exports);
|
||||
remove_duplicates(main.weak_exports);
|
||||
return main;
|
||||
}
|
||||
|
||||
@@ -109,9 +294,13 @@ static TextDylib parse(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
|
||||
std::vector<TextDylib> vec;
|
||||
for (YamlNode &node : nodes)
|
||||
if (std::optional<TextDylib> dylib = to_tbd(node, arch))
|
||||
if (std::optional<TextDylib> dylib = to_tbd(ctx, node, arch))
|
||||
vec.push_back(*dylib);
|
||||
return squash(ctx, vec);
|
||||
|
||||
for (TextDylib &tbd : vec)
|
||||
interpret_ld_symbols(ctx, tbd);
|
||||
|
||||
return squash(ctx, vec, arch);
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
+34
-19
@@ -1,12 +1,27 @@
|
||||
// On macOS, a .tbd file can be used instead of a .dylib to link
|
||||
// against a dynamic library. A .tbd file is a text file in the YAML
|
||||
// format, so the linker has to be able to parse a YAML file. This
|
||||
// file implements a YAML parser.
|
||||
//
|
||||
// We implemented a YAML parser ourselves instead of using an external
|
||||
// library. We didn't want to add an dependency to an external library
|
||||
// just to read .tdb files.
|
||||
//
|
||||
// YAML as a format is very complicated. For example, the official
|
||||
// YAML specification allows embedding a JSON string into YAML; so it
|
||||
// is strictly larger than JSON. It has surprisingly many features
|
||||
// that most users are not aware of. Fortunately, we have to support
|
||||
// only a small portion of the spec to read a .tbd file.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mold::macho {
|
||||
|
||||
struct Token {
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
enum { STRING = 1, INDENT, DEDENT, END };
|
||||
|
||||
struct Token {
|
||||
u8 kind = 0;
|
||||
std::string_view str;
|
||||
};
|
||||
@@ -39,13 +54,13 @@ std::optional<YamlError> YamlParser::tokenize() {
|
||||
std::vector<i64> indents = {0};
|
||||
|
||||
auto indent = [&](std::string_view str, i64 depth) {
|
||||
tokens.push_back({Token::INDENT, str});
|
||||
tokens.push_back({INDENT, str});
|
||||
indents.push_back(depth);
|
||||
};
|
||||
|
||||
auto dedent = [&](std::string_view str) {
|
||||
assert(indents.size() > 1);
|
||||
tokens.push_back({Token::DEDENT, str});
|
||||
tokens.push_back({DEDENT, str});
|
||||
indents.pop_back();
|
||||
};
|
||||
|
||||
@@ -63,7 +78,7 @@ std::optional<YamlError> YamlParser::tokenize() {
|
||||
if (str.starts_with("---")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
tokens.push_back({END, str.substr(0, 3)});
|
||||
skip_line(str);
|
||||
return {};
|
||||
}
|
||||
@@ -71,7 +86,7 @@ std::optional<YamlError> YamlParser::tokenize() {
|
||||
if (str.starts_with("...")) {
|
||||
while (indents.size() > 1)
|
||||
dedent(str);
|
||||
tokens.push_back({Token::END, str.substr(0, 3)});
|
||||
tokens.push_back({END, str.substr(0, 3)});
|
||||
str = str.substr(str.size());
|
||||
return {};
|
||||
}
|
||||
@@ -217,7 +232,7 @@ YamlParser::tokenize_string(std::string_view &str, char end) {
|
||||
if (pos == str.npos)
|
||||
return YamlError{"unterminated string literal", start - input.data()};
|
||||
|
||||
tokens.push_back({Token::STRING, str.substr(1, pos - 1)});
|
||||
tokens.push_back({STRING, str.substr(1, pos - 1)});
|
||||
str = str.substr(pos + 1);
|
||||
return {};
|
||||
}
|
||||
@@ -228,7 +243,7 @@ YamlParser::tokenize_bare_string(std::string_view &str) {
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-/.");
|
||||
if (pos == str.npos)
|
||||
pos = str.size();
|
||||
tokens.push_back({Token::STRING, str.substr(0, pos)});
|
||||
tokens.push_back({STRING, str.substr(0, pos)});
|
||||
str = str.substr(pos);
|
||||
}
|
||||
|
||||
@@ -240,7 +255,7 @@ std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0].kind == Token::END) {
|
||||
if (tok[0].kind == END) {
|
||||
tok = tok.subspan(1);
|
||||
continue;
|
||||
}
|
||||
@@ -250,7 +265,7 @@ std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
return *err;
|
||||
vec.push_back(std::get<YamlNode>(elem));
|
||||
|
||||
if (tok[0].kind != Token::END)
|
||||
if (tok[0].kind != END)
|
||||
return YamlError{"stray token", tok[0].str.data() - input.data()};
|
||||
}
|
||||
return vec;
|
||||
@@ -258,11 +273,11 @@ std::variant<std::vector<YamlNode>, YamlError> YamlParser::parse() {
|
||||
|
||||
std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == Token::INDENT) {
|
||||
if (tok[0].kind == INDENT) {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
std::variant<YamlNode, YamlError> elem = parse_element(tok);
|
||||
assert(tok[0].kind == Token::DEDENT);
|
||||
assert(tok[0].kind == DEDENT);
|
||||
tok = tok.subspan(1);
|
||||
return elem;
|
||||
}
|
||||
@@ -270,7 +285,7 @@ YamlParser::parse_element(std::span<Token> &tok) {
|
||||
if (tok[0].kind == '-')
|
||||
return parse_list(tok);
|
||||
|
||||
if (tok.size() > 2 && tok[0].kind == Token::STRING && tok[1].kind == ':')
|
||||
if (tok.size() > 2 && tok[0].kind == STRING && tok[1].kind == ':')
|
||||
return parse_map(tok);
|
||||
|
||||
return parse_flow_element(tok);
|
||||
@@ -280,7 +295,7 @@ std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
while (tok[0].kind != END && tok[0].kind != DEDENT) {
|
||||
if (tok[0].kind != '-')
|
||||
return YamlError{"list element expected", tok[0].str.data() - input.data()};
|
||||
tok = tok.subspan(1);
|
||||
@@ -297,8 +312,8 @@ std::variant<YamlNode, YamlError>
|
||||
YamlParser::parse_map(std::span<Token> &tok) {
|
||||
std::map<std::string_view, YamlNode> map;
|
||||
|
||||
while (tok[0].kind != Token::END && tok[0].kind != Token::DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != Token::STRING || tok[1].kind != ':')
|
||||
while (tok[0].kind != END && tok[0].kind != DEDENT) {
|
||||
if (tok.size() < 2 || tok[0].kind != STRING || tok[1].kind != ':')
|
||||
return YamlError{"map key expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view key = tok[0].str;
|
||||
@@ -319,7 +334,7 @@ YamlParser::parse_flow_element(std::span<Token> &tok) {
|
||||
return parse_flow_list(tok);
|
||||
}
|
||||
|
||||
if (tok[0].kind != Token::STRING)
|
||||
if (tok[0].kind != STRING)
|
||||
return YamlError{"scalar expected", tok[0].str.data() - input.data()};
|
||||
|
||||
std::string_view val = tok[0].str;
|
||||
@@ -332,7 +347,7 @@ YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
std::vector<YamlNode> vec;
|
||||
const char *start = tok[0].str.data();
|
||||
|
||||
while (tok[0].kind != ']' && tok[0].kind != Token::END) {
|
||||
while (tok[0].kind != ']' && tok[0].kind != END) {
|
||||
std::variant<YamlNode, YamlError> elem = parse_flow_element(tok);
|
||||
if (YamlError *err = std::get_if<YamlError>(&elem))
|
||||
return *err;
|
||||
@@ -345,7 +360,7 @@ YamlParser::parse_flow_list(std::span<Token> &tok) {
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok[0].kind == Token::END)
|
||||
if (tok[0].kind == END)
|
||||
return YamlError{"unterminated flow list", start - input.data()};
|
||||
|
||||
tok = tok.subspan(1);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "elf/mold.h"
|
||||
#include "macho/mold.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
@@ -18,6 +18,14 @@ const std::string mold_version =
|
||||
"mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
#endif
|
||||
|
||||
namespace elf {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
namespace macho {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
@@ -25,14 +33,47 @@ void cleanup() {
|
||||
unlink(socket_tmpfile);
|
||||
}
|
||||
|
||||
static void signal_handler(int) {
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS. This
|
||||
// signal handler catches that signal and print out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
if ((signo == SIGSEGV || signo == SIGBUS) &&
|
||||
output_buffer_start <= info->si_addr &&
|
||||
info->si_addr < output_buffer_end) {
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
}
|
||||
|
||||
i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "big-endian.h"
|
||||
#include "inttypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -11,6 +13,7 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -23,12 +26,30 @@
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include <xxhash.h>
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
#else
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
|
||||
class HashCmp {
|
||||
public:
|
||||
static size_t hash(const std::string_view &k) {
|
||||
return hash_string(k);
|
||||
}
|
||||
|
||||
static bool equal(const std::string_view &k1, const std::string_view &k2) {
|
||||
return k1 == k2;
|
||||
}
|
||||
};
|
||||
|
||||
namespace mold {
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
@@ -50,11 +71,15 @@ inline char *output_tmpfile;
|
||||
inline char *socket_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
|
||||
inline u8 *output_buffer_start = nullptr;
|
||||
inline u8 *output_buffer_end = nullptr;
|
||||
|
||||
extern const std::string mold_version;
|
||||
|
||||
std::string_view errno_string();
|
||||
void cleanup();
|
||||
void install_signal_handler();
|
||||
i64 get_default_thread_count();
|
||||
|
||||
//
|
||||
// Error output
|
||||
@@ -159,25 +184,43 @@ private:
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
// Some C++ libraries haven't implemented std::has_single_bit yet.
|
||||
inline bool has_single_bit(u64 val) {
|
||||
return std::popcount(val) == 1;
|
||||
}
|
||||
|
||||
// Some C++ libraries haven't implemented std::bit_ceil yet.
|
||||
inline u64 bit_ceil(u64 val) {
|
||||
if (has_single_bit(val))
|
||||
return val;
|
||||
return (u64)1 << (64 - std::countl_zero(val));
|
||||
}
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(std::popcount(align) == 1);
|
||||
assert(has_single_bit(align));
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(std::popcount(align) == 1);
|
||||
assert(has_single_bit(align));
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 next_power_of_two(u64 val) {
|
||||
assert(val >> 63 == 0);
|
||||
if (val == 0 || val == 1)
|
||||
return 1;
|
||||
return (u64)1 << (64 - std::countl_zero(val - 1));
|
||||
inline u64 bit(u64 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
};
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
inline i64 sign_extend(u64 val, i64 size) {
|
||||
return (i64)(val << (63 - size)) >> (63 - size);
|
||||
};
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic;
|
||||
@@ -215,14 +258,19 @@ inline void sort(T &vec, U less) {
|
||||
std::stable_sort(vec.begin(), vec.end(), less);
|
||||
}
|
||||
|
||||
inline i64 write_string(u8 *buf, std::string_view str) {
|
||||
template <typename T>
|
||||
inline void remove_duplicates(std::vector<T> &vec) {
|
||||
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||
}
|
||||
|
||||
inline i64 write_string(void *buf, std::string_view str) {
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
*((u8 *)buf + str.size()) = '\0';
|
||||
return str.size() + 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline i64 write_vector(u8 *buf, const std::vector<T> &vec) {
|
||||
inline i64 write_vector(void *buf, const std::vector<T> &vec) {
|
||||
i64 sz = vec.size() * sizeof(T);
|
||||
memcpy(buf, vec.data(), sz);
|
||||
return sz;
|
||||
@@ -258,13 +306,16 @@ inline u64 read_uleb(u8 *&buf) {
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 const*&buf) {
|
||||
return read_uleb(const_cast<u8 *&>(buf));
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
i64 i = 0;
|
||||
do {
|
||||
i++;
|
||||
val >>= 7;
|
||||
} while (val);
|
||||
return i;
|
||||
#pragma unroll
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < ((u64)1 << (7 * i)))
|
||||
return i;
|
||||
return 9;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
@@ -305,7 +356,7 @@ public:
|
||||
void resize(i64 nbuckets) {
|
||||
this->~ConcurrentMap();
|
||||
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, next_power_of_two(nbuckets));
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
@@ -317,7 +368,7 @@ public:
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(std::popcount<u64>(nbuckets) == 1);
|
||||
assert(has_single_bit(nbuckets));
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
@@ -376,10 +427,28 @@ private:
|
||||
};
|
||||
|
||||
//
|
||||
// threads.cc
|
||||
// output-file.h
|
||||
//
|
||||
|
||||
void set_thread_count(i64 n);
|
||||
template <typename C>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile<C>>
|
||||
open(C &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(C &ctx) = 0;
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
OutputFile(std::string path, i64 filesize, bool is_mmapped)
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
@@ -407,6 +476,65 @@ private:
|
||||
std::vector<std::atomic_uint8_t> buckets;
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class Glob {
|
||||
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
|
||||
|
||||
struct Element {
|
||||
Element(Kind k) : kind(k) {}
|
||||
Kind kind;
|
||||
std::string str;
|
||||
std::bitset<256> bitset;
|
||||
};
|
||||
|
||||
public:
|
||||
static std::optional<Glob> compile(std::string_view pat);
|
||||
bool match(std::string_view str);
|
||||
|
||||
private:
|
||||
Glob(std::vector<Element> &&vec) : elements(vec) {}
|
||||
static bool do_match(std::string_view str, std::span<Element> elements);
|
||||
|
||||
std::vector<Element> elements;
|
||||
};
|
||||
|
||||
//
|
||||
// multi-glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, u32 val);
|
||||
void compile();
|
||||
bool empty() const { return strings.empty(); }
|
||||
std::optional<u32> find(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
u32 value = -1;
|
||||
TrieNode *suffix_link = nullptr;
|
||||
std::unique_ptr<TrieNode> children[256];
|
||||
};
|
||||
|
||||
void fix_suffix_links(TrieNode &node);
|
||||
void fix_values();
|
||||
|
||||
std::vector<std::string> strings;
|
||||
std::unique_ptr<TrieNode> root;
|
||||
std::vector<std::pair<Glob, u32>> globs;
|
||||
std::vector<u32> values;
|
||||
bool compiled = false;
|
||||
};
|
||||
|
||||
//
|
||||
// uuid.cc
|
||||
//
|
||||
|
||||
std::array<u8, 16> get_uuid_v4();
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
@@ -432,7 +560,7 @@ std::string_view demangle(std::string_view name);
|
||||
|
||||
class ZlibCompressor {
|
||||
public:
|
||||
ZlibCompressor(std::string_view input);
|
||||
ZlibCompressor(u8 *buf, i64 size);
|
||||
void write_to(u8 *buf);
|
||||
i64 size() const;
|
||||
|
||||
@@ -590,6 +718,12 @@ public:
|
||||
// because archive members may have the same name.
|
||||
return parent->name + ":" + std::to_string(get_offset());
|
||||
}
|
||||
|
||||
if (thin_parent) {
|
||||
// If this is a thin archive member, the filename part is
|
||||
// guaranteed to be unique.
|
||||
return thin_parent->name + ":" + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -599,6 +733,7 @@ public:
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
int fd = -1;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
// This file implements the Aho-Corasick algorithm to match version
|
||||
// script patterns to symbol strings as quickly as possible.
|
||||
// This file implements the Aho-Corasick algorithm to match multiple
|
||||
// glob patterns to symbol strings as quickly as possible.
|
||||
//
|
||||
// Here are some examples of version script patterns:
|
||||
// Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// The pattern is a glob pattern, so `*` is a wildcard that matches
|
||||
// any substring. We sometimes have hundreds of version script
|
||||
// patterns and have to match them against millions of symbol strings.
|
||||
// `*` is a wildcard that matches any substring. We sometimes have
|
||||
// hundreds of glob patterns and have to match them against millions
|
||||
// of symbol strings.
|
||||
//
|
||||
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
|
||||
// We handle such patterns with GlobPattern. GlobPattern is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be OK.
|
||||
// We handle such patterns with the Glob class. Glob is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
assert(compiled);
|
||||
u32 idx = UINT32_MAX;
|
||||
|
||||
if (root) {
|
||||
std::call_once(once_flag, [&] { compile(); });
|
||||
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
@@ -53,13 +53,13 @@ std::optional<u16> VersionMatcher::find(std::string_view str) {
|
||||
}
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<GlobPattern, u32> &glob : globs)
|
||||
for (std::pair<Glob, u32> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
idx = std::min(idx, glob.second);
|
||||
|
||||
if (idx == UINT32_MAX)
|
||||
return {};
|
||||
return versions[idx];
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
@@ -82,17 +82,17 @@ static std::string handle_stars(std::string_view pat) {
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
assert(!compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
u32 idx = strings.size();
|
||||
strings.push_back(std::string(pat));
|
||||
versions.push_back(ver);
|
||||
values.push_back(val);
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<GlobPattern> glob = GlobPattern::compile(pat)) {
|
||||
if (std::optional<Glob> glob = Glob::compile(pat)) {
|
||||
globs.push_back({std::move(*glob), idx});
|
||||
return true;
|
||||
}
|
||||
@@ -114,13 +114,17 @@ bool VersionMatcher::add(std::string_view pat, u16 ver) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void VersionMatcher::compile() {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
void MultiGlob::compile() {
|
||||
assert(!compiled);
|
||||
compiled = true;
|
||||
|
||||
if (root) {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
}
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_suffix_links(TrieNode &node) {
|
||||
void MultiGlob::fix_suffix_links(TrieNode &node) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
if (!node.children[i])
|
||||
continue;
|
||||
@@ -146,7 +150,7 @@ void VersionMatcher::fix_suffix_links(TrieNode &node) {
|
||||
}
|
||||
}
|
||||
|
||||
void VersionMatcher::fix_values() {
|
||||
void MultiGlob::fix_values() {
|
||||
std::queue<TrieNode *> queue;
|
||||
queue.push(root.get());
|
||||
|
||||
@@ -163,4 +167,4 @@ void VersionMatcher::fix_values() {
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -1,11 +1,12 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
inline u32 get_umask() {
|
||||
u32 orig_umask = umask(0);
|
||||
@@ -23,8 +24,10 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
|
||||
// Reuse an existing file if exists and writable.
|
||||
if (rename(path.c_str(), path2) == 0) {
|
||||
// Reuse an existing file if exists and writable because on Linux,
|
||||
// writing to an existing file is much faster than creating a fresh
|
||||
// file and writing to it.
|
||||
if (ctx.overwrite_output_file && rename(path.c_str(), path2) == 0) {
|
||||
::close(fd);
|
||||
fd = ::open(path2, O_RDWR | O_CREAT, perm);
|
||||
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
|
||||
@@ -44,11 +47,11 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
template <typename C>
|
||||
class MemoryMappedOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
MemoryMappedOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, true) {
|
||||
i64 fd;
|
||||
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
@@ -57,9 +60,12 @@ public:
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
::close(fd);
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
void close(C &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
@@ -71,18 +77,18 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
class MallocOutputFile : public OutputFile<E> {
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<E>(path, filesize, false), perm(perm) {
|
||||
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << "mmap failed: " << errno_string();
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
void close(C &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (this->path == "-") {
|
||||
@@ -104,9 +110,9 @@ private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
template <typename C>
|
||||
std::unique_ptr<OutputFile<C>>
|
||||
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
@@ -121,24 +127,15 @@ OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OutputFile<E>> file;
|
||||
OutputFile<C> *file;
|
||||
if (is_special)
|
||||
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
|
||||
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
else
|
||||
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
|
||||
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return file;
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template class OutputFile<E>;
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
INSTANTIATE(ARM64);
|
||||
INSTANTIATE(ARM32);
|
||||
INSTANTIATE(RISCV64);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -10,6 +10,6 @@ test: $(TESTS)
|
||||
# command output. `tail -r` outputs an input from the last line to the
|
||||
# first.
|
||||
$(TESTS):
|
||||
@./$@ 2>&1 | tail -r | tail -r
|
||||
@set -o pipefail; ./$@ 2>&1 | tail -r | tail -r
|
||||
|
||||
.PHONY: test $(TESTS)
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
echo 'int main() { return 0; }' > $t/a.c
|
||||
echo 'int main() { return 0; }' > $t/b.c
|
||||
echo 'int main() { return 0; }' | $CC -c -o $t/a.o -xc -
|
||||
echo 'int main() { return 1; }' | $CC -c -o $t/b.o -xc -
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.c $t/b.c 2> /dev/null || false
|
||||
$CC -B. -o $t/exe $t/a.c $t/b.c -Wl,-allow-multiple-definition
|
||||
$CC -B. -o $t/exe $t/a.c $t/b.c -Wl,-z,muldefs
|
||||
! $CC -B. -o $t/exe $t/a.o $t/b.o 2> /dev/null || false
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-allow-multiple-definition
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o -Wl,-z,muldefs
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
@@ -21,7 +20,7 @@ _start:
|
||||
nop
|
||||
EOF
|
||||
|
||||
"$mold" -o $t/b.so $t/a.o -auxiliary foo -f bar -shared
|
||||
./mold -o $t/b.so $t/a.o -auxiliary foo -f bar -shared
|
||||
|
||||
readelf --dynamic $t/b.so > $t/log
|
||||
fgrep -q 'Auxiliary library: [foo]' $t/log
|
||||
|
||||
+5
-6
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
@@ -18,7 +17,7 @@ mkdir -p $t
|
||||
|
||||
echo '.globl _start; _start: jmp loop' | $CC -o $t/a.o -c -x assembler -
|
||||
echo '.globl loop; loop: jmp loop' | $CC -o $t/b.o -c -x assembler -
|
||||
"$mold" -static -o $t/exe $t/a.o $t/b.o
|
||||
./mold -static -o $t/exe $t/a.o $t/b.o
|
||||
$OBJDUMP -d $t/exe > /dev/null
|
||||
file $t/exe | grep -q ELF
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+5
-6
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
@@ -26,6 +25,6 @@ EOF
|
||||
rm -f $t/a.a
|
||||
ar rcs $t/a.a $t/a.o
|
||||
|
||||
"$mold" -o $t/exe $t/a.a
|
||||
./mold -o $t/exe $t/a.a
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+8
-9
@@ -1,22 +1,21 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
(! "$mold" -zfoo) 2>&1 | grep -q 'unknown command line option: -zfoo'
|
||||
(! "$mold" -z foo) 2>&1 | grep -q 'unknown command line option: -z foo'
|
||||
(! "$mold" -abcdefg) 2>&1 | grep -q 'unknown command line option: -abcdefg'
|
||||
(! "$mold" --abcdefg) 2>&1 | grep -q 'unknown command line option: --abcdefg'
|
||||
{ ./mold -zfoo || true; } 2>&1 | grep -q 'unknown command line option: -zfoo'
|
||||
{ ./mold -z foo || true; } 2>&1 | grep -q 'unknown command line option: -z foo'
|
||||
{ ./mold -abcdefg || true; } 2>&1 | grep -q 'unknown command line option: -abcdefg'
|
||||
{ ./mold --abcdefg || true; } 2>&1 | grep -q 'unknown command line option: --abcdefg'
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
@@ -19,16 +18,16 @@ int foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics 2> $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=always 2> $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=never 2> $t/log
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics 2> $t/log
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
! "$mold" -o $t/exe $t/a.o --color-diagnostics=auto 2> $t/log
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics=always 2> $t/log
|
||||
grep -q $'\033' $t/log
|
||||
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics=never 2> $t/log
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
! ./mold -o $t/exe $t/a.o --color-diagnostics=auto 2> $t/log
|
||||
! grep -q $'\033' $t/log || false
|
||||
|
||||
echo OK
|
||||
+4
-5
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+4
-5
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
which dwarfdump >& /dev/null || { echo skipped; exit; }
|
||||
|
||||
# Clang may not support -gz=zlib-gnu
|
||||
echo 'int main() {}' | $CXX -o /dev/null -gz=zlib-gnu -xc++ ->& /dev/null ||
|
||||
{ echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -g -gz=zlib-gnu -xc++ -
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/b.o -g -gz=zlib-gnu -xc++ -
|
||||
int foo() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o $t/b.o
|
||||
dwarfdump $t/exe > /dev/null
|
||||
readelf --sections $t/exe | fgrep -q .debug_info
|
||||
|
||||
echo ' OK'
|
||||
@@ -1,22 +1,21 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
which dwarfdump >& /dev/null || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -g -gz=zlib-gnu -xc++ -
|
||||
cat <<EOF | $CXX -c -o $t/a.o -g -gz=zlib -xc++ -
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+4
-5
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
which dwarfdump >& /dev/null || { echo skipped; exit; }
|
||||
|
||||
cat <<EOF | $CXX -c -o $t/a.o -g -xc++ -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
cat <<EOF > $t/a.h
|
||||
#define A 23
|
||||
#define B 99
|
||||
EOF
|
||||
|
||||
cat <<EOF | $GCC -o $t/b.o -c -xc - -I$t -g3
|
||||
#include "a.h"
|
||||
extern int z();
|
||||
int main () { return z() - 122; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $GCC -o $t/c.o -c -xc - -I$t -g3
|
||||
#include "a.h"
|
||||
int z() { return A + B; }
|
||||
EOF
|
||||
|
||||
$GCC -B. -o $t/exe $t/b.o $t/c.o
|
||||
$OBJDUMP --dwarf=macro $t/exe > $t/log
|
||||
! grep 'DW_MACRO_import -.* 0x0$' $t/log || false
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+4
-5
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
@@ -22,13 +21,13 @@ int main() {
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o -Wl,-no-demangle 2> $t/log || false
|
||||
grep -q 'undefined symbol: .*: _Z3fooii' $t/log
|
||||
grep -q 'undefined symbol: _Z3fooii$' $t/log
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o -Wl,-demangle 2> $t/log || false
|
||||
grep -Eq 'undefined symbol: .*: foo\(int, int\)' $t/log
|
||||
grep -Eq 'undefined symbol: foo\(int, int\)$' $t/log
|
||||
|
||||
! $CC -B. -o $t/exe $t/a.o 2> $t/log || false
|
||||
grep -Eq 'undefined symbol: .*: foo\(int, int\)' $t/log
|
||||
grep -Eq 'undefined symbol: foo\(int, int\)$' $t/log
|
||||
|
||||
cat <<EOF | $CC -c -o $t/b.o -xc -
|
||||
extern int Pi;
|
||||
@@ -38,6 +37,6 @@ int main() {
|
||||
EOF
|
||||
|
||||
! $CC -B. -o $t/exe $t/b.o -Wl,-demangle 2> $t/log || false
|
||||
grep -q 'undefined symbol: .*: Pi' $t/log
|
||||
grep -q 'undefined symbol: Pi$' $t/log
|
||||
|
||||
echo OK
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+8
-9
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
@@ -27,25 +26,25 @@ foo:
|
||||
nop
|
||||
EOF
|
||||
|
||||
"$mold" -o $t/exe $t/a.o
|
||||
./mold -o $t/exe $t/a.o
|
||||
readelf --symbols $t/exe > $t/log
|
||||
fgrep -q _start $t/log
|
||||
fgrep -q foo $t/log
|
||||
fgrep -q .Lbar $t/log
|
||||
|
||||
"$mold" -o $t/exe $t/a.o --discard-locals
|
||||
./mold -o $t/exe $t/a.o --discard-locals
|
||||
readelf --symbols $t/exe > $t/log
|
||||
fgrep -q _start $t/log
|
||||
fgrep -q foo $t/log
|
||||
! fgrep -q .Lbar $t/log || false
|
||||
|
||||
"$mold" -o $t/exe $t/a.o --discard-all
|
||||
./mold -o $t/exe $t/a.o --discard-all
|
||||
readelf --symbols $t/exe > $t/log
|
||||
fgrep -q _start $t/log
|
||||
! fgrep -q foo $t/log || false
|
||||
! fgrep -q .Lbar $t/log || false
|
||||
|
||||
"$mold" -o $t/exe $t/a.o --strip-all
|
||||
./mold -o $t/exe $t/a.o --strip-all
|
||||
readelf --symbols $t/exe > $t/log
|
||||
! fgrep -q _start $t/log || false
|
||||
! fgrep -q foo $t/log || false
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
+4
-5
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
CC="${CC:-cc}"
|
||||
CXX="${CXX:-c++}"
|
||||
GCC="${GCC:-gcc}"
|
||||
GXX="${GXX:-g++}"
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
GXX="${TEST_GXX:-g++}"
|
||||
OBJDUMP="${OBJDUMP:-objdump}"
|
||||
MACHINE="${MACHINE:-$(uname -m)}"
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
cd "$(dirname "$0")"/../..
|
||||
mold="$(pwd)/mold"
|
||||
t=out/test/elf/$testname
|
||||
mkdir -p $t
|
||||
|
||||
|
||||
Loaded 100 of 422 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user