mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad942cb27 | ||
|
|
eed4496af2 | ||
|
|
17c292c2b4 | ||
|
|
ab1ab78f8a | ||
|
|
8506065fd2 | ||
|
|
a291d0d448 | ||
|
|
690fefb97d | ||
|
|
c7c1779ad7 | ||
|
|
ec095e7513 | ||
|
|
69a91e8aa0 | ||
|
|
af46000645 | ||
|
|
b1658c4702 | ||
|
|
9f85267b44 | ||
|
|
f8d20cd3ce | ||
|
|
a396fa400f | ||
|
|
d8a4f14fd0 | ||
|
|
c8b5fae2d0 | ||
|
|
0ce32d36d5 | ||
|
|
01cd5d1a32 | ||
|
|
39a25e2394 | ||
|
|
bfdd75ed38 | ||
|
|
5411f4216e | ||
|
|
3cf00e30d5 | ||
|
|
0f714710f0 | ||
|
|
83ec409426 | ||
|
|
e6ae83fbf9 | ||
|
|
00702773f4 | ||
|
|
1739cfbfb4 | ||
|
|
ff689a1253 | ||
|
|
ae427d133e | ||
|
|
d92af2456d | ||
|
|
b3e1092879 | ||
|
|
46ef71eb75 | ||
|
|
871ef8cf14 | ||
|
|
c1b1caa854 | ||
|
|
dcab76697e | ||
|
|
cf11efb147 | ||
|
|
a688b73f8e | ||
|
|
9bb50ae7a5 | ||
|
|
ead6235470 | ||
|
|
967006479f | ||
|
|
f843daee9b | ||
|
|
c7129916ee | ||
|
|
e234ce25b6 | ||
|
|
02e1b9de10 | ||
|
|
effde5ffa9 | ||
|
|
1d3706285e | ||
|
|
085a1148fd | ||
|
|
5cf3e53ab9 | ||
|
|
a5aa387cb4 | ||
|
|
f99cb33c2d | ||
|
|
e55703405b | ||
|
|
4c71174d5d | ||
|
|
2ea08ac4b3 | ||
|
|
82ace062db | ||
|
|
17f0d85efb | ||
|
|
a994d71fa1 | ||
|
|
0eea8f4015 | ||
|
|
2e3b56ee3f | ||
|
|
b8bcb8a2a3 | ||
|
|
ac3ee91cbd | ||
|
|
18df821625 | ||
|
|
79b3fc4b6a | ||
|
|
9e09e74db3 | ||
|
|
9ee10ba4bd | ||
|
|
fe7c8c26b7 | ||
|
|
23576b6e48 | ||
|
|
cfa9e404d1 | ||
|
|
641360b0aa | ||
|
|
4ccc9eb89a | ||
|
|
a801dde186 | ||
|
|
e2d53f26e2 | ||
|
|
742cdeec66 | ||
|
|
c2116aa7ca | ||
|
|
fed950837a | ||
|
|
508890d33d | ||
|
|
141556db44 | ||
|
|
8589403fe0 | ||
|
|
cd2224742f | ||
|
|
0b1d11ed01 | ||
|
|
22443b1490 | ||
|
|
a3a53e9a0c | ||
|
|
dd3d045396 | ||
|
|
673d790372 | ||
|
|
6cac0aba6a | ||
|
|
1a13c5076e | ||
|
|
c504911bf6 | ||
|
|
db5fa8a8cd | ||
|
|
c03400303f | ||
|
|
7f91a134ef | ||
|
|
d4891d5ff1 | ||
|
|
bdc2336265 | ||
|
|
2a0f65084e | ||
|
|
db146cfc37 | ||
|
|
49f38828d1 | ||
|
|
0c972cf59d | ||
|
|
f057fdadba | ||
|
|
3fb2571255 | ||
|
|
619f741ca9 | ||
|
|
713d81947a | ||
|
|
f76d3e0d0c | ||
|
|
488cd5d933 | ||
|
|
55bcb72be7 | ||
|
|
7f8872b2f1 | ||
|
|
139807080b | ||
|
|
86b37b112f | ||
|
|
d56f528c27 | ||
|
|
a1b81b249f | ||
|
|
fa0de2f03b | ||
|
|
a357445654 | ||
|
|
8f5c49c655 | ||
|
|
03b5ce7c6f | ||
|
|
63df2a3c8b | ||
|
|
9a09ead865 | ||
|
|
c2380ff7bc | ||
|
|
c49a8b5b07 | ||
|
|
51fec5f1ff | ||
|
|
ac20d8729b | ||
|
|
7e9fedd203 | ||
|
|
576cd86ecb | ||
|
|
bb1db6cf17 | ||
|
|
3529a2fcc6 | ||
|
|
e842bee049 | ||
|
|
3c8684c528 | ||
|
|
108815dbe1 | ||
|
|
d75386b545 | ||
|
|
bb1934bb9f | ||
|
|
f01817f39e | ||
|
|
fad4c1b56c | ||
|
|
3c8868280d | ||
|
|
a665ca766d | ||
|
|
0f85278c73 | ||
|
|
085727234a | ||
|
|
83fe073e97 | ||
|
|
8a25c1bfc1 | ||
|
|
a94cd68ce1 | ||
|
|
68ac03d19c | ||
|
|
b79bc35fd9 | ||
|
|
90839d747f | ||
|
|
1b1ab744e5 | ||
|
|
f02db0fa0f | ||
|
|
fbcee0b716 | ||
|
|
c105cfd35a | ||
|
|
bb402480a5 | ||
|
|
c983321fed | ||
|
|
43ebc96bfe | ||
|
|
8ae1ad57a4 | ||
|
|
bd39bfdc5b | ||
|
|
a11566795d | ||
|
|
3676c596df | ||
|
|
0947836a44 | ||
|
|
2383e42a5c | ||
|
|
049dbb73fe | ||
|
|
95894ef2e2 | ||
|
|
7f7a744ebb | ||
|
|
3833a4a457 | ||
|
|
a0516a371f | ||
|
|
29491df6f2 | ||
|
|
599ed1dde0 | ||
|
|
143111ae5a | ||
|
|
d753ea5bdb | ||
|
|
b205eb7fa6 | ||
|
|
d8baee9605 | ||
|
|
6dacc5192a | ||
|
|
4f5f88258c | ||
|
|
f5abedad01 | ||
|
|
02aee3b43e | ||
|
|
c1406860fa | ||
|
|
53759731cf | ||
|
|
b018baec8a | ||
|
|
28ddea5371 | ||
|
|
82989a8b7c | ||
|
|
d78b748696 | ||
|
|
77968a7fc1 | ||
|
|
00a68c5fcf | ||
|
|
6f9752a1bf | ||
|
|
6a32828dd8 | ||
|
|
b5e0d7a7c9 | ||
|
|
3b0b28ff97 | ||
|
|
6ce3730083 | ||
|
|
d3e7d88753 | ||
|
|
9dee80eb4f | ||
|
|
eb99c87f75 | ||
|
|
e45f2a8efb | ||
|
|
86ab3890d6 | ||
|
|
3b5ccb9342 | ||
|
|
9441170d39 | ||
|
|
ffb83247f4 | ||
|
|
13f42071b9 | ||
|
|
032931fa3b | ||
|
|
472b325349 | ||
|
|
b852d925c2 | ||
|
|
56cdba50cf | ||
|
|
cfec68e054 | ||
|
|
d9f850b032 | ||
|
|
1f012337c0 | ||
|
|
90cf642ca7 | ||
|
|
d12c48ce63 | ||
|
|
67b9d6cb8d | ||
|
|
2e12e635a0 | ||
|
|
1b5631990c | ||
|
|
acf846acc0 | ||
|
|
e1347d8f7d | ||
|
|
6d809b56e6 | ||
|
|
4528507155 | ||
|
|
da07871416 | ||
|
|
354d0254ee | ||
|
|
b711bf2009 | ||
|
|
94e2a71866 | ||
|
|
f467ad1add | ||
|
|
f631477a68 | ||
|
|
56479e724f | ||
|
|
d752bac24a | ||
|
|
a50258fdf8 | ||
|
|
710dfd01d6 | ||
|
|
03b1a1cc68 | ||
|
|
1c73fd57fc | ||
|
|
24078a4395 | ||
|
|
270cbf42df | ||
|
|
ea75dae8ec | ||
|
|
6d290babe2 | ||
|
|
428faa75b2 | ||
|
|
829c9a980e | ||
|
|
76af9907fd | ||
|
|
9893bb331f | ||
|
|
6108e60936 | ||
|
|
9e2e376f5a | ||
|
|
2db8b3fa48 | ||
|
|
e2c6f0c171 | ||
|
|
eba7cdade2 | ||
|
|
ca88e45bf3 | ||
|
|
19776686ec | ||
|
|
e0a150a22c | ||
|
|
0df6063d8a | ||
|
|
5199414933 | ||
|
|
85f1dec81d | ||
|
|
97c937a56d | ||
|
|
f1113f7c76 | ||
|
|
0ced83597f | ||
|
|
1f5fbb808b | ||
|
|
5fb6091fb7 | ||
|
|
e764557007 | ||
|
|
961cc10e0d | ||
|
|
49fc7da32e | ||
|
|
dd56734fda | ||
|
|
2015347b0c | ||
|
|
4daa9aad36 | ||
|
|
084ca55a36 | ||
|
|
e06d886252 | ||
|
|
9fe3d750cc | ||
|
|
13bb00bfa2 | ||
|
|
79a72011b7 | ||
|
|
f2b278ce10 | ||
|
|
ec10452b67 | ||
|
|
ba3fd65bcc | ||
|
|
f816c45b88 | ||
|
|
ca14cdf9b6 | ||
|
|
6d667fad45 | ||
|
|
aa644914fe | ||
|
|
1cbe4d44ae | ||
|
|
91805d1e26 | ||
|
|
9212f2ebd7 | ||
|
|
784fd27e9e | ||
|
|
b78f237a68 | ||
|
|
1582b720d5 | ||
|
|
710127977e | ||
|
|
8e07496e08 | ||
|
|
163f5db0d1 | ||
|
|
be2e1b17cd | ||
|
|
5f5e73360b | ||
|
|
b0b015bc2e | ||
|
|
7f66689e21 | ||
|
|
17a5c3e0ab | ||
|
|
3ed5b8ddf7 | ||
|
|
de991b519a | ||
|
|
a066fe25c6 | ||
|
|
6e8a9dfec1 | ||
|
|
31eeb44d4f | ||
|
|
44b15cbb35 | ||
|
|
b5b3ffa6fe | ||
|
|
83a924ddac | ||
|
|
2e8bd0b7a6 | ||
|
|
b685f3cab7 | ||
|
|
2d25a93126 | ||
|
|
a8eb52f61a | ||
|
|
761c341c19 | ||
|
|
000e395c07 | ||
|
|
4b06f1e9f6 | ||
|
|
29dc0cf53f | ||
|
|
f8b55680f8 | ||
|
|
cb08466693 | ||
|
|
72671df483 | ||
|
|
83524e3690 | ||
|
|
0125d9c254 | ||
|
|
1ac5fe7c1e | ||
|
|
4b42f38257 | ||
|
|
d2b153c522 | ||
|
|
ab126ad1d9 | ||
|
|
b04aba89d3 | ||
|
|
2cb41d8a2b | ||
|
|
1b1a142a89 | ||
|
|
dcfe76bc80 | ||
|
|
d4d93d7fb7 | ||
|
|
ebd780e8ba | ||
|
|
65bd36ba40 | ||
|
|
bc86ab514f | ||
|
|
8ee7773260 | ||
|
|
e011889436 | ||
|
|
16e22ec067 | ||
|
|
d2ee47a4ac | ||
|
|
dce8ebe526 | ||
|
|
23de4be59f | ||
|
|
76e3f87775 | ||
|
|
fca6fad720 | ||
|
|
ceffcfcadd | ||
|
|
1ad19a4ce1 | ||
|
|
67a58c01d0 | ||
|
|
186ac191c4 | ||
|
|
34bf04bc41 | ||
|
|
e6db53fadb | ||
|
|
461001328b | ||
|
|
4bffe26bc0 | ||
|
|
290b4eaa46 | ||
|
|
0f1b828e6a | ||
|
|
46288758a0 | ||
|
|
e8789c71a4 | ||
|
|
f54203d76c | ||
|
|
a5634794b3 | ||
|
|
b403b811cd | ||
|
|
36fc065548 | ||
|
|
39b4548a2a | ||
|
|
52f0c4a06f | ||
|
|
c74fe12dd5 | ||
|
|
fe11565110 | ||
|
|
cca255e6be | ||
|
|
325d12d169 | ||
|
|
2a26a0db64 | ||
|
|
72c9425ae3 | ||
|
|
ff46c33d3f | ||
|
|
c59b3f5fa5 | ||
|
|
84a5509da2 | ||
|
|
305153b9c7 | ||
|
|
1adde7aa51 | ||
|
|
4fa2bc17b8 | ||
|
|
294a467116 | ||
|
|
67eb3069b3 | ||
|
|
ad2694445c | ||
|
|
6a1713e440 | ||
|
|
62d65371d7 | ||
|
|
c4722fe5ae | ||
|
|
13472be059 | ||
|
|
6d2dbd42a6 | ||
|
|
d2970e096d | ||
|
|
77c4795139 | ||
|
|
5434babdf3 | ||
|
|
530b3ebd88 | ||
|
|
33172a7b61 | ||
|
|
5065547c4e | ||
|
|
0870078ed2 | ||
|
|
992ad0fc08 | ||
|
|
5ae8ec9669 | ||
|
|
0bd5adde7e | ||
|
|
7e8a9f230c | ||
|
|
f7bc901c2d | ||
|
|
e1a7590db3 | ||
|
|
f592b6e167 | ||
|
|
8c0f455b97 | ||
|
|
0d08c52258 | ||
|
|
a42626b975 | ||
|
|
ec728d0cef | ||
|
|
20bddc75cf | ||
|
|
45bece4bd9 | ||
|
|
445a23fbc0 | ||
|
|
6952a2efa5 | ||
|
|
8c2ccd2f55 | ||
|
|
9ce963b480 | ||
|
|
35794cfc66 | ||
|
|
e300c6d9b2 | ||
|
|
8660db16a8 | ||
|
|
25b4f0b054 | ||
|
|
fc809c6c73 | ||
|
|
51845ac5fe | ||
|
|
8e50fad6b9 | ||
|
|
3e818cd350 | ||
|
|
4327d4547e | ||
|
|
abba8b5864 | ||
|
|
8ede25fa42 | ||
|
|
50d4bc04ab | ||
|
|
128f8b3b8d | ||
|
|
5140010a6e | ||
|
|
24c21d7ed2 | ||
|
|
e50590043c | ||
|
|
fdc592b169 | ||
|
|
82731d62f9 | ||
|
|
912a863b13 | ||
|
|
627bf7c8ef | ||
|
|
beddabe64d | ||
|
|
339b1b4704 | ||
|
|
8dff18db0e | ||
|
|
ebb9b6aff8 | ||
|
|
22af2ff328 | ||
|
|
d755793345 | ||
|
|
ec756333cb | ||
|
|
ce3aa9a752 | ||
|
|
e48b90f941 | ||
|
|
7f407fb1ae | ||
|
|
ac212c3d00 | ||
|
|
040180f933 | ||
|
|
7e6554677f | ||
|
|
dbeb10c0d2 | ||
|
|
c935d6387a | ||
|
|
acdd92e741 | ||
|
|
62660bc02a | ||
|
|
90d9fd06ae | ||
|
|
82d412cbff | ||
|
|
85efa3796a | ||
|
|
3f672919c2 | ||
|
|
a2e8f13ddd | ||
|
|
821fb07083 | ||
|
|
b9bd71ecfc | ||
|
|
9df4d6e479 | ||
|
|
29eb97fda5 | ||
|
|
67c6255c9a | ||
|
|
2d68cbbfd2 | ||
|
|
f98e0ef92a | ||
|
|
83303995c7 | ||
|
|
7d369dd9e8 | ||
|
|
9f66ea7026 | ||
|
|
67a96ae8e1 | ||
|
|
8936194ba7 | ||
|
|
00af084416 | ||
|
|
3829372a42 | ||
|
|
54ab1ec4b4 | ||
|
|
f4b0596b3a | ||
|
|
71f6b0bc32 | ||
|
|
fb8e63e498 | ||
|
|
1a133a4df5 | ||
|
|
60fc036bd6 | ||
|
|
3d96596485 | ||
|
|
0a8e90bd84 | ||
|
|
1e961000e6 | ||
|
|
3539487471 | ||
|
|
6caf0867b0 | ||
|
|
28a6c0e4f0 | ||
|
|
a4349df0a5 | ||
|
|
d2335707de | ||
|
|
1d2b08ade7 | ||
|
|
dd93988dc6 | ||
|
|
06ec355e88 | ||
|
|
c10f36c71c | ||
|
|
f32ce333dd | ||
|
|
d48c52cc2f | ||
|
|
25d02bb261 | ||
|
|
fda7657eab | ||
|
|
983fe0f10f | ||
|
|
8437097305 | ||
|
|
5a9341659c | ||
|
|
125af4c855 | ||
|
|
fa1db07ec8 | ||
|
|
f09bf0615b | ||
|
|
534cd392db | ||
|
|
5dee5ea49d | ||
|
|
957eaf0f18 | ||
|
|
12fad60eb4 | ||
|
|
404077e330 | ||
|
|
73947c56e1 | ||
|
|
63fae64e77 | ||
|
|
7f27b180ac | ||
|
|
6f4ba44946 | ||
|
|
dcc58e4f7a | ||
|
|
a68dfdb2d3 | ||
|
|
277dd7710a | ||
|
|
d03c9a1654 | ||
|
|
19bb456721 | ||
|
|
4e26d7b237 | ||
|
|
6a797a4fbc | ||
|
|
14784bbc4e | ||
|
|
6fd47dbab4 | ||
|
|
e31801f22f | ||
|
|
58e81bb0a0 | ||
|
|
d2cdce4410 | ||
|
|
7132822cc7 | ||
|
|
ca98fe843d | ||
|
|
bbfed9af3f | ||
|
|
707ddaa630 | ||
|
|
d127583c86 | ||
|
|
7128ee371d | ||
|
|
321158b99f | ||
|
|
d71a672089 | ||
|
|
e17d7daee8 | ||
|
|
d4e40a31c1 | ||
|
|
915cd23b2f | ||
|
|
994f800f8f | ||
|
|
2b3a684869 | ||
|
|
68b2b35148 | ||
|
|
da6dd7c0ac | ||
|
|
f2311b1efa | ||
|
|
b8bb78fde7 | ||
|
|
e4a1a57318 | ||
|
|
1ebaa27fc4 | ||
|
|
d6db850121 | ||
|
|
ebf732fca9 | ||
|
|
eba86d5312 | ||
|
|
648fcc60ca | ||
|
|
9f5b7e278f | ||
|
|
14fe1ba070 | ||
|
|
9d3cfd5c7d | ||
|
|
c2d5f602e7 | ||
|
|
ed42ce0d36 | ||
|
|
8201d0ed2a | ||
|
|
779a404950 | ||
|
|
52a0638dd2 | ||
|
|
73034f94fe | ||
|
|
ad0b6d0ac6 | ||
|
|
6e918eda31 | ||
|
|
3c968289f8 | ||
|
|
cb204decf4 | ||
|
|
f9fe3d621f | ||
|
|
b51d5baae6 | ||
|
|
747ba1d41e | ||
|
|
5c4ed9259e |
No files matched your search
+42
-14
@@ -26,11 +26,20 @@ jobs:
|
||||
- name: build and test
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
sudo apt-get install -y clang++-12
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ${{ matrix.target }} ..
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -42,26 +51,45 @@ jobs:
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
apt-get install -y sudo qemu-user gdb zstd dwarfdump xz-utils {gcc,g++}-10-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-10-arm-linux-gnueabihf
|
||||
|
||||
for i in i686 aarch64 riscv64 powerpc powerpc64 powerpc64le s390x sparc64 m68k sh4 alpha; do
|
||||
ln -sf /usr/bin/$i-linux-gnu-gcc-10 /usr/bin/$i-linux-gnu-gcc
|
||||
ln -sf /usr/bin/$i-linux-gnu-g++-10 /usr/bin/$i-linux-gnu-g++
|
||||
for i in {i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu arm-linux-gnueabihf; do
|
||||
ln -sf /usr/bin/$i-gcc-10 /usr/bin/$i-gcc
|
||||
ln -sf /usr/bin/$i-g++-10 /usr/bin/$i-g++
|
||||
done
|
||||
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-gcc-10 /usr/bin/arm-linux-gnueabihf-gcc
|
||||
ln -sf /usr/bin/arm-linux-gnueabihf-g++-10 /usr/bin/arm-linux-gnueabihf-g++
|
||||
|
||||
# Install a RV32 toolchain from third party since it's not available
|
||||
# as an Ubuntu package.
|
||||
mkdir /usr/local/rv32
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2022.08.26/riscv32-glibc-ubuntu-20.04-nightly-2022.08.26-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/riscv32-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar -C /usr/local/rv32 --strip-components=1 -xzf -
|
||||
|
||||
ln -sf /usr/local/rv32/sysroot /usr/riscv32-linux-gnu
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-objdump /usr/bin/riscv32-linux-gnu-objdump
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/usr/local/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-gcc /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /usr/local/rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install a LoongArch toolchain
|
||||
mkdir /usr/local/larch
|
||||
wget -O- -q https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar -C /usr/local/larch --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /usr/local/larch/target /usr/loongarch64-linux-gnu
|
||||
cp -r /usr/local/larch/loongarch64-unknown-linux-gnu/lib/* /usr/loongarch64-linux-gnu/lib64/
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /usr/local/larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
|
||||
done
|
||||
|
||||
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-gcc -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-gcc
|
||||
echo '/usr/local/larch/bin/loongarch64-unknown-linux-gnu-g++ -L/usr/local/larch/loongarch64-unknown-linux-gnu "$@"' > /usr/bin/loongarch64-linux-gnu-g++
|
||||
chmod 755 /usr/bin/loongarch64-linux-gnu-{gcc,g++}
|
||||
|
||||
wget -O/usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2023.08.08/qemu-loongarch64
|
||||
chmod 755 /usr/local/bin/qemu-loongarch64
|
||||
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
@@ -70,14 +98,14 @@ jobs:
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On ..
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results
|
||||
name: test-results-gcc
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
name: linux-packages
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: build
|
||||
run: ./dist.sh x86_64
|
||||
- name: upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: mold-*-linux.tar.gz
|
||||
+73
-41
@@ -43,8 +43,8 @@
|
||||
# of the mold linker of the same version will have the exactly same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(mold VERSION 1.8.0)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.3.0)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(GNUInstallDirs)
|
||||
@@ -52,7 +52,7 @@ include(GNUInstallDirs)
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
add_link_options(-fuse-ld=mold)
|
||||
add_link_options(-fuse-ld=mold -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
add_executable(mold)
|
||||
@@ -64,8 +64,21 @@ if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function)
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
@@ -88,7 +101,7 @@ if(MOLD_USE_TSAN)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ and libcrypto if -DMOLD_MOSTLY_STATIC=ON.
|
||||
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
@@ -96,13 +109,12 @@ endif()
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
target_link_libraries(mold PRIVATE libcrypto.a)
|
||||
endif()
|
||||
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
@@ -111,12 +123,23 @@ else()
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. Just like zlib, if libzstd.so is not
|
||||
# found, we compile a bundled one and statically-link it to mold.
|
||||
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
|
||||
# is not found, we compile a bundled one and statically-link it to mold.
|
||||
find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If libzstd.so is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
|
||||
if(HAVE_ZSTD_H)
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
@@ -142,7 +165,7 @@ check_cxx_source_compiles(
|
||||
"static_assert(sizeof(void *) == 4); int main() {}" P32)
|
||||
|
||||
cmake_dependent_option(MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"NOT APPLE; NOT ANDROID; NOT P32" OFF)
|
||||
"NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT P32" OFF)
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
@@ -198,12 +221,16 @@ endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
if(MINGW)
|
||||
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
|
||||
target_link_libraries(mold PRIVATE bcrypt)
|
||||
endif()
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
@@ -223,11 +250,6 @@ if(NOT APPLE AND NOT WIN32)
|
||||
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32 AND NOT MOLD_MOSTLY_STATIC)
|
||||
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
|
||||
target_link_libraries(mold PRIVATE OpenSSL::Crypto)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
@@ -245,7 +267,7 @@ if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
if(NOT APPLE AND NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
@@ -255,15 +277,15 @@ add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-P ${CMAKE_SOURCE_DIR}/update-git-hash.cmake
|
||||
DEPENDS update-git-hash.cmake
|
||||
-P ${CMAKE_SOURCE_DIR}/common/update-git-hash.cmake
|
||||
DEPENDS common/update-git-hash.cmake
|
||||
BYPRODUCTS git-hash.cc
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# Create config.h file
|
||||
configure_file(config.h.in config.h)
|
||||
configure_file(common/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
@@ -276,15 +298,18 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
|
||||
S390X SPARC64 M68K SH4 ALPHA HPPA32)
|
||||
S390X SPARC64 M68K SH4 ALPHA LOONGARCH32 LOONGARCH64)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/arch-loongarch.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/cmdline.cc
|
||||
elf/dwarf.cc
|
||||
elf/gc-sections.cc
|
||||
elf/gdb-index.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/jobs.cc
|
||||
elf/linker-script.cc
|
||||
elf/lto.cc
|
||||
elf/main.cc
|
||||
@@ -294,6 +319,7 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/relocatable.cc
|
||||
elf/subprocess.cc
|
||||
elf/thunks.cc
|
||||
elf/tls.cc
|
||||
)
|
||||
|
||||
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
|
||||
@@ -312,10 +338,12 @@ list(APPEND MOLD_MACHO_TEMPLATE_FILES
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
if(NOT EXISTS ${PATH})
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
endif()
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
@@ -335,33 +363,31 @@ endif()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
compress.cc
|
||||
demangle.cc
|
||||
common/compress.cc
|
||||
common/demangle.cc
|
||||
common/filepath.cc
|
||||
common/glob.cc
|
||||
common/hyperloglog.cc
|
||||
common/main.cc
|
||||
common/multi-glob.cc
|
||||
common/perf.cc
|
||||
common/tar.cc
|
||||
common/uuid.cc
|
||||
elf/arch-alpha.cc
|
||||
elf/arch-arm32.cc
|
||||
elf/arch-arm64.cc
|
||||
elf/arch-hppa32.cc
|
||||
elf/arch-i386.cc
|
||||
elf/arch-m68k.cc
|
||||
elf/arch-ppc32.cc
|
||||
elf/arch-ppc64v1.cc
|
||||
elf/arch-ppc64v2.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/arch-s390x.cc
|
||||
elf/arch-sh4.cc
|
||||
elf/arch-sparc64.cc
|
||||
elf/arch-x86-64.cc
|
||||
elf/elf.cc
|
||||
filepath.cc
|
||||
git-hash.cc
|
||||
glob.cc
|
||||
hyperloglog.cc
|
||||
main.cc
|
||||
multi-glob.cc
|
||||
perf.cc
|
||||
tar.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
uuid.cc
|
||||
)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
@@ -422,9 +448,15 @@ endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.third-party")
|
||||
install(FILES "LICENSE.third-party" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
endif()
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
@@ -437,10 +469,10 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "clang_cl_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# Contributing to mold
|
||||
|
||||
We request all patches to mold to be released under the dual license
|
||||
of the GNU AGPLv3 and the MIT license. Please read the following and
|
||||
sign-off your patch as an indication of agreement.
|
||||
|
||||
> ## Developer's Certificate of Origin
|
||||
>
|
||||
> By making a contribution to this project, I certify that:
|
||||
>
|
||||
> 1. The contribution was created in whole or in part by me and I
|
||||
> have the right to submit it under the Dual License of the GNU
|
||||
> AGPLv3 and the MIT license; or
|
||||
>
|
||||
> 2. The contribution is based upon previous work that, to the best
|
||||
> of my knowledge, is covered under the the Dual License and I
|
||||
> have the right under the Dual License to submit that work with
|
||||
> modifications, whether created in whole or in part by me, under
|
||||
> the Dual License (unless I am permitted to submit under
|
||||
> different licenses), as indicated in the file; or
|
||||
>
|
||||
> 3. The contribution was provided directly to me by some other
|
||||
> person who certified (1), (2) or (3) and I have not modified it.
|
||||
>
|
||||
> 4. I understand and agree that this project and the contribution
|
||||
> are public and that a record of the contribution (including all
|
||||
> personal information I submit with it, including my sign-off) is
|
||||
> maintained indefinitely and may be redistributed consistent with
|
||||
> this project or the open source license(s) involved.
|
||||
|
||||
If you agree, you just add a line saying:
|
||||
|
||||
```
|
||||
Signed-off-by: Random J Developer <[email protected]>
|
||||
```
|
||||
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
This will be done for you automatically if you use ``git commit -s``.
|
||||
Reverts should also include "Signed-off-by". ``git revert -s`` does that
|
||||
for you.
|
||||
|
||||
We assume that any contribution that's 10 lines or less does not meet
|
||||
the threshold of originality and therefore copyright does not apply.
|
||||
You don't need to add a "Signed-off-by" line to such small patches.
|
||||
@@ -0,0 +1,348 @@
|
||||
Mold includes a number of subcomponents with separate copyright notices
|
||||
and license terms. Your use of the source code for the these subcomponents
|
||||
is subject to the terms and conditions of the following licenses.
|
||||
|
||||
|
||||
=== For the third-party/mimalloc component:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
=== For the third-party/rust-demangle component:
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
=== For the third-party/tbb component:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
=== For the third-party/xxhash component:
|
||||
|
||||
xxHash Library
|
||||
Copyright (c) 2012-2020 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
=== For the third-party/zlib component:
|
||||
|
||||
(C) 1995-2022 Jean-loup Gailly and Mark Adler
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Jean-loup Gailly Mark Adler
|
||||
[email protected] [email protected]
|
||||
|
||||
|
||||
=== For the third-party/zstd component:
|
||||
|
||||
BSD License
|
||||
|
||||
For Zstandard software
|
||||
|
||||
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,22 +1,19 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
[](https://github.com/rui314/mold/actions/workflows/ci.yml)
|
||||
[](https://build.opensuse.org/package/show/home:marxin:mold/mold)
|
||||
|
||||
<i>This is a repo of a free, AGPL-licensed version of the linker.
|
||||
If you are looking for a commercial, non-AGPL version of the same linker,
|
||||
<i>This repository contains a free version of the mold linker.
|
||||
If you are looking for a commercial version that supports macOS
|
||||
please visit the
|
||||
[repo of the sold linker](https://github.com/bluewhalesystems/sold).</i>
|
||||
[repository of the sold linker](https://github.com/bluewhalesystems/sold).</i>
|
||||
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than the LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is designed to increase developer productivity by reducing
|
||||
build time, especially in rapid debug-edit-rebuild cycles.
|
||||
mold is a faster drop-in replacement for existing Unix linkers. It is several
|
||||
times quicker than the LLVM lld linker, the second-fastest open-source linker,
|
||||
which I initially developed a few years ago. mold aims to enhance developer
|
||||
productivity by minimizing build time, particularly in rapid
|
||||
debug-edit-rebuild cycles.
|
||||
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
on a simulated 8-core 16-threads machine.
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold when linking
|
||||
final debuginfo-enabled executables for major large programs on a simulated
|
||||
8-core, 16-thread machine.
|
||||
|
||||

|
||||
|
||||
@@ -26,48 +23,47 @@ on a simulated 8-core 16-threads machine.
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
|
||||
mold is so fast that it is only 2x _slower_ than `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
|
||||
machine. If you find that mold is not faster than other linkers, please feel
|
||||
free to [file a bug report](https://github.com/rui314/mold/issues).
|
||||
|
||||
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
|
||||
RISC-V, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian PowerPC ELFv2,
|
||||
s390x, SPARC64 and m68k.
|
||||
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
|
||||
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, SH-4, and DEC
|
||||
Alpha.
|
||||
|
||||
mold/macOS is commercial software. For mold/macOS, please visit
|
||||
https://github.com/bluewhalesystems/sold.
|
||||
|
||||
## Why does the speed of linking matter?
|
||||
## Why does linking speed 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
|
||||
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.
|
||||
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 source files into
|
||||
object files (`.o` files). In the second phase, a linker takes all object
|
||||
files and combines them into a single executable or shared library file.
|
||||
|
||||
The second phase takes a long time if your build output is large.
|
||||
mold can make it faster, saving your time and keeping you from being
|
||||
distracted while waiting for a long build to finish. The difference is
|
||||
most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
The second phase can be time-consuming if your build output is large. mold can
|
||||
speed up this process, saving you time and preventing distractions while
|
||||
waiting for a lengthy build to finish. The difference is most noticeable
|
||||
during rapid debug-edit-rebuild cycles.
|
||||
|
||||
## Install
|
||||
## Installation
|
||||
|
||||
Binary packages for the following systems are currently available.
|
||||
Binary packages for the following systems are currently available:
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
## How to build
|
||||
## How to Build
|
||||
|
||||
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 (or later) as well as libstdc++ 10 or libc++ 7 (or
|
||||
later) are recommended.
|
||||
mold is written in C++20, so if you build mold yourself, you will need a
|
||||
recent version of a C++ compiler and a C++ standard library. We recommend GCC
|
||||
10.2 or Clang 12.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
|
||||
|
||||
### Install dependencies
|
||||
### Install Dependencies
|
||||
|
||||
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.
|
||||
directory. It will detect your Linux distribution and attempt to install the
|
||||
necessary packages. You may need to run it as root.
|
||||
|
||||
### Compile mold
|
||||
|
||||
@@ -75,59 +71,58 @@ necessary packages. You may want to run it as root.
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v1.8.0
|
||||
git checkout v2.3.0
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --install .
|
||||
sudo cmake --build . --target install
|
||||
```
|
||||
|
||||
You may need to pass a C++20 compiler command name to `cmake`.
|
||||
In the above case, `c++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
example above, `c++` is passed. If that doesn't work for you, try a specific
|
||||
version of a compiler, such as `g++-10` or `clang++-12`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change
|
||||
that by passing `-DCMAKE_INSTALL_PREFIX=<directory>`. For other cmake
|
||||
options, see the comments in `CMakeLists.txt`.
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change the
|
||||
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
|
||||
If you don't use a recent enough Linux distribution, or if for any reason
|
||||
`cmake` in the above commands doesn't work for you, you can use Docker to
|
||||
build it in a Docker environment. To do so, just run `./dist.sh` in this
|
||||
directory instead of `cmake`. The shell script pulls a Docker image,
|
||||
builds mold and auxiliary files inside it, and packs them into a
|
||||
single tar file `mold-$version-$arch-linux.tar.gz`. You can extract
|
||||
the tar file anywhere and use `mold` executable in it.
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
not work for you for any reason, you can use Docker to build mold in a Docker
|
||||
environment. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a Docker image, build mold and auxiliary
|
||||
files inside it, and package them into a single tar file named
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere and
|
||||
use the mold executable within it.
|
||||
|
||||
## How to use
|
||||
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by the compiler driver (which is usually `cc`,
|
||||
`gcc` or `clang`), which is typically in turn indirectly invoked by
|
||||
`make` or some other build system command.
|
||||
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
|
||||
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
|
||||
indirectly invoked by `make` or another build system command.
|
||||
|
||||
If you can specify an additional command line option to your compiler
|
||||
driver by modifying build system's config files, add one of the
|
||||
following flags to use `mold` instead of `/usr/bin/ld`:
|
||||
If you can specify an additional command line option for your compiler driver
|
||||
by modifying the build system's config files, add one of the following flags
|
||||
to use mold instead of `/usr/bin/ld`:
|
||||
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
- For Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
- For 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
|
||||
to tell GCC where to look for external commands such as `ld`.
|
||||
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
|
||||
valid argument, so you need to use the `-B` option instead. The `-B` option
|
||||
tells GCC where to look for external commands like `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
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
If you 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 the `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.
|
||||
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
|
||||
take an absolute path as an argument for `-fuse-ld` though.
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
|
||||
accept an absolute path as an argument for `-fuse-ld`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -141,32 +136,19 @@ linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable. In the
|
||||
above example, we use `clang` as a linker driver as it can always take
|
||||
the `-fuse-ld` option. If your GCC is recent enough to recognize the
|
||||
option, you may be able to remove the `linker = "clang"` line.
|
||||
where `/path/to/mold` is an absolute path to the mold executable. In the
|
||||
example above, we use `clang` as a linker driver since it always accepts the
|
||||
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
|
||||
may be able to remove the `linker = "clang"` line.
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
|
||||
If you are using macOS, you can modify `config.toml` in a similar manner.
|
||||
Here is an example with `mold` installed via [Homebrew](https://brew.sh).
|
||||
|
||||
```toml
|
||||
[target.x86_64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Nim</summary>
|
||||
@@ -178,67 +160,46 @@ when findExe("mold").len > 0 and defined(linux):
|
||||
switch("passL", "-fuse-ld=mold")
|
||||
```
|
||||
|
||||
where `mold` must be included in the PATH environment variable. In this example
|
||||
The above example uses `gcc` as the linker driver.
|
||||
Use the `fuse-ld` option. If your GCC is recent enough to recognize this option.
|
||||
where `mold` must be included in the `PATH` environment variable. In this
|
||||
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
|
||||
GCC is recent enough to recognize this option.
|
||||
|
||||
If you want to use mold for all projects, put the above snippet to `~/.config/config.nims`.
|
||||
|
||||
If you are using macOS, you can modify config.nims in a similar manner. Here is an example with mold installed via Homebrew.
|
||||
|
||||
```nim
|
||||
when findExe("ld64.mold").len > 0 and defined(macosx):
|
||||
switch("passL", "-fuse-ld=ld64.mold")
|
||||
```
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.config/config.nims`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option
|
||||
to `cc` to specify an alternative linker. To deal with the situation,
|
||||
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
`ld.gold` and redirect it to itself. To use the feature, run `make`
|
||||
(or another build command) as a subcommand of mold as follows:
|
||||
It is sometimes very hard to pass an appropriate command line option to `cc`
|
||||
to specify an alternative linker. To address this situation, mold has a
|
||||
feature to intercept all invocations of `ld`, `ld.lld`, or `ld.gold` and
|
||||
redirect them to itself. To use this feature, run `make` (or another build
|
||||
command) as a subcommand of mold as follows:
|
||||
|
||||
```shell
|
||||
mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Internally, mold invokes a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to `exec(3)`-family functions to
|
||||
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>On macOS</summary>
|
||||
|
||||
mold/macOS is available as an alpha version. It can be used to build not
|
||||
only macOS apps but also iOS apps because their binary formats are the same.
|
||||
|
||||
The command name of mold/macOS is `ld64.mold`. If you build mold on macOS,
|
||||
it still produces `mold` and `ld.mold`, but these executables are useful
|
||||
only for cross compilation (i.e. building Linux apps on macOS.)
|
||||
|
||||
If you find any issue with mold/macOS, please file it to
|
||||
<a href=https://github.com/rui314/mold/issues>our GitHub Issues</a>.
|
||||
Internally, mold invokes the given command with the `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object file
|
||||
intercepts all function calls to `exec(3)`-family functions to replace
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
<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.
|
||||
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
|
||||
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
|
||||
run on a two-core machine, mold is still significantly faster than the default
|
||||
GNU linker, especially when linking large programs.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
mold leaves its identification string in `.comment` section in an output
|
||||
mold leaves its identification string in the `.comment` section of an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```shell
|
||||
@@ -249,70 +210,53 @@ String dump of section '.comment':
|
||||
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
|
||||
```
|
||||
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
If `mold` is present in the `.comment` section, the file was created by mold.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
|
||||
Since mold is a drop-in replacement, you should be able to use it
|
||||
without reading its manual. But just in case you need it, it's available
|
||||
online at <a href=https://rui314.github.io/mold.html>here</a>.
|
||||
You can also read the same manual by `man mold`.
|
||||
Since mold is a drop-in replacement, you should be able to use it without
|
||||
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
|
||||
is available. You can read the same manual by running `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
One reason is because it simply uses faster algorithms and efficient
|
||||
data structures than other linkers do. The other reason is that the
|
||||
new linker is highly parallelized.
|
||||
One reason is that it utilizes faster algorithms and more efficient data
|
||||
structures compared to other linkers. Another reason is that mold is highly
|
||||
parallelized.
|
||||
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
|
||||
mold (right), linking the same program, a Chromium executable.
|
||||
|
||||

|
||||
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. In this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
As you can see, mold uses all available cores throughout its execution and
|
||||
finishes quickly. In contrast, lld fails to utilize available cores most of
|
||||
the time. In this demo, the maximum parallelism is artificially capped at 16,
|
||||
so that the bars fit in the GIF.
|
||||
|
||||
For details, please read [design notes](docs/design.md).
|
||||
|
||||
## 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.
|
||||
For details, please see the [design notes](docs/design.md).
|
||||
|
||||
## Sponsors
|
||||
|
||||
We accept donations via [GitHub Sponsors](https://github.com/sponsors/rui314)
|
||||
and [OpenCollective](https://opencollective.com/mold-linker).
|
||||
We thank you to everybody who sponsors our project. In particular,
|
||||
we'd like to acknowledge the following people and organizations who
|
||||
have sponsored $128/month or more:
|
||||
and [OpenCollective](https://opencollective.com/mold-linker). We thank
|
||||
everyone who sponsors our project. In particular, we'd like to acknowledge the
|
||||
following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
### Corporate sponsors
|
||||
|
||||
<a href="https://mercury.com/"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
<a href="https://mercury.com"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
|
||||
<a href="https://cybozu-global.com"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
|
||||
|
||||
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
|
||||
|
||||
- [Uber](https://uber.com)
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(C &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
|
||||
u8 *data = mf->data;
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mf->size && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mf->size)
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
|
||||
while (i < mf->size) {
|
||||
if (data[i] == '\\' && i + 1 < mf->size) {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[i])) {
|
||||
buf.append(1, data[i++]);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mf->size;) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
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;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static inline 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
|
||||
@@ -9,15 +9,9 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends software-properties-common && \
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 && \
|
||||
apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev zlib1g-dev gcc-10 g++-10 python3 libssl-dev && \
|
||||
\
|
||||
mkdir /openssl && cd /openssl && \
|
||||
wget -O- -q https://www.openssl.org/source/openssl-3.0.7.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./Configure --prefix=/usr/local --libdir=lib && \
|
||||
make -j$(nproc) && \
|
||||
make -j$(nproc) install && \
|
||||
ldconfig && \
|
||||
mkdir /cmake && cd /cmake && \
|
||||
wget -O- -q https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2.tar.gz | tar --strip-components=1 -xzf - && \
|
||||
./bootstrap --parallel=$(nproc) && make -j$(nproc) && make -j$(nproc) install && \
|
||||
rm -rf /var/lib/apt/lists/* /cmake /openssl
|
||||
rm -rf /var/lib/apt/lists/* /cmake
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
#include "filetype.h"
|
||||
|
||||
namespace mold {
|
||||
@@ -76,12 +76,12 @@ struct ArHdr {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::vector<MappedFile *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mf->size) {
|
||||
@@ -117,19 +117,18 @@ 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.push_back(MappedFile::must_open(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile<C> *> vec;
|
||||
std::vector<MappedFile *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mf->size - data >= 2) {
|
||||
@@ -163,10 +162,9 @@ read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<MappedFile<C> *>
|
||||
read_archive_members(C &ctx, MappedFile<C> *mf) {
|
||||
switch (get_file_type(mf, !ctx.arg.plugin.empty())) {
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
std::vector<std::string_view>
|
||||
read_response_file(Context &ctx, std::string_view path, i64 depth) {
|
||||
if (depth > 10)
|
||||
Fatal(ctx) << path << ": response file nesting too deep";
|
||||
|
||||
std::vector<std::string_view> vec;
|
||||
MappedFile<Context> *mf = MappedFile<Context>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mf->data, mf->size);
|
||||
|
||||
while (!data.empty()) {
|
||||
if (isspace(data[0])) {
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto read_quoted = [&]() {
|
||||
char quote = data[0];
|
||||
data = data.substr(1);
|
||||
|
||||
std::string buf;
|
||||
while (!data.empty() && data[0] != quote) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
} else {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
}
|
||||
}
|
||||
if (data.empty())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
data = data.substr(1);
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
auto read_unquoted = [&] {
|
||||
std::string buf;
|
||||
while (!data.empty()) {
|
||||
if (data[0] == '\\' && data.size() >= 1) {
|
||||
buf.append(1, data[1]);
|
||||
data = data.substr(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isspace(data[0])) {
|
||||
buf.append(1, data[0]);
|
||||
data = data.substr(1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return save_string(ctx, buf);
|
||||
};
|
||||
|
||||
std::string_view tok;
|
||||
if (data[0] == '\'' || data[0] == '\"')
|
||||
tok = read_quoted();
|
||||
else
|
||||
tok = read_unquoted();
|
||||
|
||||
if (tok.starts_with('@'))
|
||||
append(vec, read_response_file(ctx, tok.substr(1), depth + 1));
|
||||
else
|
||||
vec.push_back(tok);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Replace "@path/to/some/text/file" with its file contents.
|
||||
template <typename Context>
|
||||
std::vector<std::string_view> expand_response_files(Context &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1, 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static inline 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
|
||||
+337
-190
@@ -8,6 +8,7 @@
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -32,7 +33,7 @@
|
||||
#endif
|
||||
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include "third-party/xxhash/xxhash.h"
|
||||
#include "../third-party/xxhash/xxhash.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
@@ -40,6 +41,23 @@
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
// __builtin_assume() is supported only by clang, and [[assume]] is
|
||||
// available only in C++23, so we use this macro when giving a hint to
|
||||
// the compiler's optimizer what's true.
|
||||
#define ASSUME(x) do { if (!(x)) __builtin_unreachable(); } while (0)
|
||||
|
||||
// This is an assert() that is enabled even in the release build.
|
||||
#define ASSERT(x) \
|
||||
do { \
|
||||
if (!(x)) { \
|
||||
std::cerr << "Assertion failed: (" << #x \
|
||||
<< "), function " << __FUNCTION__ \
|
||||
<< ", file " << __FILE__ \
|
||||
<< ", line " << __LINE__ << ".\n"; \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
@@ -60,7 +78,7 @@ namespace mold {
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
template <typename C> class OutputFile;
|
||||
template <typename Context> class OutputFile;
|
||||
|
||||
inline char *output_tmpfile;
|
||||
inline thread_local bool opt_demangle;
|
||||
@@ -86,41 +104,44 @@ static u64 combine_hash(u64 a, u64 b) {
|
||||
// Error output
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class SyncOut {
|
||||
public:
|
||||
SyncOut(C &ctx, std::ostream &out = std::cout) : out(out) {
|
||||
SyncOut(Context &ctx, std::ostream *out = &std::cout) : out(out) {
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
}
|
||||
|
||||
~SyncOut() {
|
||||
std::scoped_lock lock(mu);
|
||||
out << ss.str() << "\n";
|
||||
if (out) {
|
||||
std::scoped_lock lock(mu);
|
||||
*out << ss.str() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> SyncOut &operator<<(T &&val) {
|
||||
ss << std::forward<T>(val);
|
||||
if (out)
|
||||
ss << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
static inline std::mutex mu;
|
||||
|
||||
private:
|
||||
std::ostream &out;
|
||||
std::ostream *out;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
static std::string add_color(C &ctx, std::string msg) {
|
||||
template <typename Context>
|
||||
static std::string add_color(Context &ctx, std::string msg) {
|
||||
if (ctx.arg.color_diagnostics)
|
||||
return "mold: \033[0;1;31m" + msg + ":\033[0m ";
|
||||
return "mold: " + msg + ": ";
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Fatal {
|
||||
public:
|
||||
Fatal(C &ctx) : out(ctx, std::cerr) {
|
||||
Fatal(Context &ctx) : out(ctx, &std::cerr) {
|
||||
out << add_color(ctx, "fatal");
|
||||
}
|
||||
|
||||
@@ -136,13 +157,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
SyncOut<Context> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Error {
|
||||
public:
|
||||
Error(C &ctx) : out(ctx, std::cerr) {
|
||||
Error(Context &ctx) : out(ctx, &std::cerr) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << add_color(ctx, "warning");
|
||||
} else {
|
||||
@@ -157,13 +178,14 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
SyncOut<Context> out;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class Warn {
|
||||
public:
|
||||
Warn(C &ctx) : out(ctx, std::cerr) {
|
||||
Warn(Context &ctx)
|
||||
: out(ctx, ctx.arg.suppress_warnings ? nullptr : &std::cerr) {
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
out << add_color(ctx, "error");
|
||||
ctx.has_error = true;
|
||||
@@ -178,7 +200,149 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SyncOut<C> out;
|
||||
SyncOut<Context> out;
|
||||
};
|
||||
|
||||
//
|
||||
// Atomics
|
||||
//
|
||||
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
template <typename T>
|
||||
struct Atomic : std::atomic<T> {
|
||||
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) { store(other.load()); }
|
||||
|
||||
Atomic<T> &operator=(const Atomic<T> &other) {
|
||||
store(other.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
|
||||
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
|
||||
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
|
||||
|
||||
bool test_and_set() {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return load() || exchange(true);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled) [[unlikely]]
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename Context>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// Bit vector
|
||||
//
|
||||
|
||||
class BitVector {
|
||||
public:
|
||||
BitVector() = default;
|
||||
BitVector(u32 size) : vec((size + 7) / 8) {}
|
||||
|
||||
void resize(u32 size) { vec.resize((size + 7) / 8); }
|
||||
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
|
||||
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
|
||||
|
||||
private:
|
||||
std::vector<u8> vec;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -220,7 +384,7 @@ inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
|
||||
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 = {}) {
|
||||
@@ -238,17 +402,6 @@ void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
// An optimized "mark" operation for parallel mark-and-sweep algorithms.
|
||||
// Returns true if `visited` was false and updated to true.
|
||||
inline bool fast_mark(std::atomic<bool> &visited) {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return !visited.load(std::memory_order_relaxed) &&
|
||||
!visited.exchange(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
|
||||
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
|
||||
@@ -256,7 +409,12 @@ inline void append(std::vector<T> &vec1, std::vector<U> vec2) {
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
|
||||
i64 size = 0;
|
||||
for (std::vector<T> &v : vec)
|
||||
size += v.size();
|
||||
|
||||
std::vector<T> ret;
|
||||
ret.reserve(size);
|
||||
for (std::vector<T> &v : vec)
|
||||
append(ret, v);
|
||||
return ret;
|
||||
@@ -322,30 +480,36 @@ inline i64 write_uleb(u8 *buf, u64 val) {
|
||||
return i;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 *&buf) {
|
||||
inline u64 read_uleb(u8 **buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *buf++;
|
||||
byte = *(*buf)++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 const*&buf) {
|
||||
return read_uleb(const_cast<u8 *&>(buf));
|
||||
inline u64 read_uleb(u8 *buf) {
|
||||
u8 *tmp = buf;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view &str) {
|
||||
u8 *start = (u8 *)&str[0];
|
||||
inline u64 read_uleb(std::string_view *str) {
|
||||
u8 *start = (u8 *)str->data();
|
||||
u8 *ptr = start;
|
||||
u64 val = read_uleb(ptr);
|
||||
str = str.substr(ptr - start);
|
||||
u64 val = read_uleb(&ptr);
|
||||
*str = str->substr(ptr - start);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view str) {
|
||||
std::string_view tmp = str;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline i64 uleb_size(u64 val) {
|
||||
#if __GNUC__
|
||||
#pragma GCC unroll 8
|
||||
@@ -356,8 +520,16 @@ inline i64 uleb_size(u64 val) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::string_view save_string(C &ctx, const std::string &str) {
|
||||
inline void overwrite_uleb(u8 *loc, u64 val) {
|
||||
while (*loc & 0b1000'0000) {
|
||||
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
|
||||
val >>= 7;
|
||||
}
|
||||
*loc = val & 0b0111'1111;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
std::string_view save_string(Context &ctx, const std::string &str) {
|
||||
u8 *buf = new u8[str.size() + 1];
|
||||
memcpy(buf, str.data(), str.size());
|
||||
buf[str.size()] = '\0';
|
||||
@@ -392,53 +564,72 @@ public:
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
if (keys) {
|
||||
free((void *)keys);
|
||||
free((void *)key_sizes);
|
||||
free((void *)values);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
// to make this object to be aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
struct alignas(32) Entry {
|
||||
Atomic<const char *> key;
|
||||
T value;
|
||||
u32 keylen;
|
||||
};
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
this->~ConcurrentMap();
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
|
||||
nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
i64 sz = sizeof(Entry) * this->nbuckets;
|
||||
free(entries);
|
||||
|
||||
this->nbuckets = nbuckets;
|
||||
keys = (std::atomic<const char *> *)calloc(nbuckets, sizeof(keys[0]));
|
||||
key_sizes = (u32 *)calloc(nbuckets, sizeof(key_sizes[0]));
|
||||
values = (T *)calloc(nbuckets, sizeof(values[0]));
|
||||
#if _WIN32
|
||||
// Even though std::aligned_alloc is defined in C++17, MSVC doesn't
|
||||
// seem to provide that function.
|
||||
entries = (Entry *)_aligned_malloc(sz, alignof(Entry));
|
||||
#else
|
||||
entries = (Entry *)std::aligned_alloc(alignof(Entry), sz);
|
||||
#endif
|
||||
|
||||
memset(entries, 0, sz);
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
if (!keys)
|
||||
return {nullptr, false};
|
||||
|
||||
assert(has_single_bit(nbuckets));
|
||||
|
||||
i64 idx = hash & (nbuckets - 1);
|
||||
i64 retry = 0;
|
||||
|
||||
while (retry < MAX_RETRY) {
|
||||
const char *ptr = keys[idx].load(std::memory_order_acquire);
|
||||
if (ptr == marker) {
|
||||
pause();
|
||||
Entry &ent = entries[idx];
|
||||
const char *ptr = nullptr;
|
||||
bool claimed = ent.key.compare_exchange_weak(ptr, (char *)-1,
|
||||
std::memory_order_acquire);
|
||||
|
||||
// If we successfully claimed the ownership of an unused slot,
|
||||
// copy values to it.
|
||||
if (claimed) {
|
||||
new (&ent.value) T(val);
|
||||
ent.keylen = key.size();
|
||||
ent.key.store(key.data(), std::memory_order_release);
|
||||
return {&ent.value, true};
|
||||
}
|
||||
|
||||
// Loop on a spurious failure.
|
||||
if (ptr == nullptr)
|
||||
continue;
|
||||
|
||||
// If someone is copying values to the slot, do busy wait.
|
||||
while (ptr == (char *)-1) {
|
||||
pause();
|
||||
ptr = ent.key.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
if (ptr == nullptr) {
|
||||
if (!keys[idx].compare_exchange_weak(ptr, marker,
|
||||
std::memory_order_acq_rel))
|
||||
continue;
|
||||
new (values + idx) T(val);
|
||||
key_sizes[idx] = key.size();
|
||||
keys[idx].store(key.data(), std::memory_order_release);
|
||||
return {values + idx, true};
|
||||
}
|
||||
|
||||
if (key.size() == key_sizes[idx] &&
|
||||
memcmp(ptr, key.data(), key_sizes[idx]) == 0)
|
||||
return {values + idx, false};
|
||||
// If the same key is already present, this is the slot we are
|
||||
// looking for.
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
|
||||
// Otherwise, move on to the next slot.
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
idx = (idx & ~mask) | ((idx + 1) & mask);
|
||||
retry++;
|
||||
@@ -448,18 +639,21 @@ public:
|
||||
return {nullptr, false};
|
||||
}
|
||||
|
||||
bool has_key(i64 idx) {
|
||||
return keys[idx].load(std::memory_order_relaxed);
|
||||
const char *get_key(i64 idx) {
|
||||
return entries[idx].key;
|
||||
}
|
||||
|
||||
i64 get_idx(T *value) const {
|
||||
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
|
||||
return (Entry *)addr - entries;
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 2048;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 128;
|
||||
|
||||
Entry *entries = nullptr;
|
||||
i64 nbuckets = 0;
|
||||
std::atomic<const char *> *keys = nullptr;
|
||||
u32 *key_sizes = nullptr;
|
||||
T *values = nullptr;
|
||||
|
||||
private:
|
||||
static void pause() {
|
||||
@@ -469,27 +663,27 @@ private:
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr const char *marker = "marker";
|
||||
};
|
||||
|
||||
//
|
||||
// output-file.h
|
||||
//
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class OutputFile {
|
||||
public:
|
||||
static std::unique_ptr<OutputFile<C>>
|
||||
open(C &ctx, std::string path, i64 filesize, i64 perm);
|
||||
static std::unique_ptr<OutputFile<Context>>
|
||||
open(Context &ctx, std::string path, i64 filesize, i64 perm);
|
||||
|
||||
virtual void close(C &ctx) = 0;
|
||||
virtual void close(Context &ctx) = 0;
|
||||
virtual ~OutputFile() = default;
|
||||
|
||||
u8 *buf = nullptr;
|
||||
std::vector<u8> buf2;
|
||||
std::string path;
|
||||
i64 filesize;
|
||||
bool is_mmapped;
|
||||
i64 fd = -1;
|
||||
i64 filesize = 0;
|
||||
bool is_mmapped = false;
|
||||
bool is_unmapped = false;
|
||||
|
||||
protected:
|
||||
@@ -497,6 +691,43 @@ protected:
|
||||
: path(path), filesize(filesize), is_mmapped(is_mmapped) {}
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
class MallocOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MallocOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
~MallocOutputFile() {
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
FILE *fp;
|
||||
|
||||
if (this->path == "-") {
|
||||
fp = stdout;
|
||||
} else {
|
||||
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
|
||||
fp = fdopen(fd, "w");
|
||||
}
|
||||
|
||||
fwrite(this->buf, this->filesize, 1, fp);
|
||||
if (!this->buf2.empty())
|
||||
fwrite(this->buf2.data(), this->buf2.size(), 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
private:
|
||||
i64 perm;
|
||||
};
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
@@ -632,85 +863,6 @@ private:
|
||||
std::vector<std::vector<u8>> shards;
|
||||
};
|
||||
|
||||
//
|
||||
// perf.cc
|
||||
//
|
||||
|
||||
// Counter is used to collect statistics numbers.
|
||||
class Counter {
|
||||
public:
|
||||
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
instances.push_back(this);
|
||||
}
|
||||
|
||||
Counter &operator++(int) {
|
||||
if (enabled)
|
||||
values.local()++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter &operator+=(int delta) {
|
||||
if (enabled)
|
||||
values.local() += delta;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static void print();
|
||||
|
||||
static inline bool enabled = false;
|
||||
|
||||
private:
|
||||
i64 get_value();
|
||||
|
||||
std::string_view name;
|
||||
tbb::enumerable_thread_specific<i64> values;
|
||||
|
||||
static inline std::vector<Counter *> instances;
|
||||
};
|
||||
|
||||
// Timer and TimeRecord records elapsed time (wall clock time)
|
||||
// used by each pass of the linker.
|
||||
struct TimerRecord {
|
||||
TimerRecord(std::string name, TimerRecord *parent = nullptr);
|
||||
void stop();
|
||||
|
||||
std::string name;
|
||||
TimerRecord *parent;
|
||||
tbb::concurrent_vector<TimerRecord *> children;
|
||||
i64 start;
|
||||
i64 end;
|
||||
i64 user;
|
||||
i64 sys;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
void
|
||||
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
|
||||
|
||||
template <typename C>
|
||||
class Timer {
|
||||
public:
|
||||
Timer(C &ctx, std::string name, Timer *parent = nullptr) {
|
||||
record = new TimerRecord(name, parent ? parent->record : nullptr);
|
||||
ctx.timer_records.push_back(std::unique_ptr<TimerRecord>(record));
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
@@ -743,16 +895,16 @@ private:
|
||||
|
||||
// MappedFile represents an mmap'ed input file.
|
||||
// mold uses mmap-IO only.
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
class MappedFile {
|
||||
public:
|
||||
static MappedFile *open(C &ctx, std::string path);
|
||||
static MappedFile *must_open(C &ctx, std::string path);
|
||||
static MappedFile *open(Context &ctx, std::string path);
|
||||
static MappedFile *must_open(Context &ctx, std::string path);
|
||||
|
||||
~MappedFile() { unmap(); }
|
||||
void unmap();
|
||||
|
||||
MappedFile *slice(C &ctx, std::string name, u64 start, u64 size);
|
||||
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size);
|
||||
|
||||
std::string_view get_contents() {
|
||||
return std::string_view((char *)data, size);
|
||||
@@ -782,7 +934,6 @@ public:
|
||||
std::string name;
|
||||
u8 *data = nullptr;
|
||||
i64 size = 0;
|
||||
i64 mtime = 0;
|
||||
bool given_fullpath = true;
|
||||
MappedFile *parent = nullptr;
|
||||
MappedFile *thin_parent = nullptr;
|
||||
@@ -792,20 +943,23 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
template <typename Context>
|
||||
MappedFile<Context> *MappedFile<Context>::open(Context &ctx, std::string path) {
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
i64 fd;
|
||||
#ifdef _WIN32
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
fd = ::_open(path.c_str(), O_RDONLY);
|
||||
#else
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
fd = ::open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
|
||||
if (fd == -1)
|
||||
if (fd == -1) {
|
||||
if (errno != ENOENT)
|
||||
Fatal(ctx) << "opening " << path << " failed: " << errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
@@ -817,14 +971,6 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
#ifdef _WIN32
|
||||
mf->mtime = st.st_mtime;
|
||||
#elif defined(__APPLE__)
|
||||
mf->mtime = (u64)st.st_mtimespec.tv_sec * 1000000000 + st.st_mtimespec.tv_nsec;
|
||||
#else
|
||||
mf->mtime = (u64)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
|
||||
#endif
|
||||
|
||||
if (st.st_size > 0) {
|
||||
#ifdef _WIN32
|
||||
HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
|
||||
@@ -842,23 +988,24 @@ MappedFile<C> *MappedFile<C>::open(C &ctx, std::string path) {
|
||||
if (mf->data == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *MappedFile<C>::must_open(C &ctx, std::string path) {
|
||||
template <typename Context>
|
||||
MappedFile<Context> *
|
||||
MappedFile<Context>::must_open(Context &ctx, std::string path) {
|
||||
if (MappedFile *mf = MappedFile::open(ctx, path))
|
||||
return mf;
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
MappedFile<C> *
|
||||
MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile<C>;
|
||||
template <typename Context>
|
||||
MappedFile<Context> *
|
||||
MappedFile<Context>::slice(Context &ctx, std::string name, u64 start, u64 size) {
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = name;
|
||||
mf->data = data + start;
|
||||
mf->size = size;
|
||||
@@ -868,8 +1015,8 @@ MappedFile<C>::slice(C &ctx, std::string name, u64 start, u64 size) {
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
void MappedFile<C>::unmap() {
|
||||
template <typename Context>
|
||||
void MappedFile<Context>::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "third-party/rust-demangle/rust-demangle.h"
|
||||
#include "../third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
@@ -7,8 +7,10 @@ namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::string ret = std::filesystem::canonical(path, ec).string();
|
||||
return ec ? std::string(path) : ret;
|
||||
std::filesystem::path link = std::filesystem::read_symlink(path, ec);
|
||||
if (ec)
|
||||
return std::string(path);
|
||||
return (filepath(path) / ".." / link).lexically_normal().string();
|
||||
}
|
||||
|
||||
// Removes redundant '/..' or '/.' from a given path.
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "mold.h"
|
||||
#include "elf/elf.h"
|
||||
#include "common.h"
|
||||
#include "../elf/elf.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -23,15 +23,15 @@ enum class FileType {
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
bool is_text_file(MappedFile<C> *mf) {
|
||||
template <typename MappedFile>
|
||||
bool is_text_file(MappedFile *mf) {
|
||||
u8 *data = mf->data;
|
||||
return mf->size >= 4 && isprint(data[0]) && isprint(data[1]) &&
|
||||
isprint(data[2]) && isprint(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename C>
|
||||
inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
template <typename E, typename Context, typename MappedFile>
|
||||
inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
const char *data = mf->get_contents().data();
|
||||
@@ -45,21 +45,23 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
? sh_begin->sh_link : ehdr.e_shstrndx;
|
||||
|
||||
for (ElfShdr<E> &sec : shdrs) {
|
||||
// GCC LTO object contains only sections symbols followed by a common
|
||||
// symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1` for older
|
||||
// GCC releases).
|
||||
//
|
||||
// However, FAT LTO objects don't have any of the above mentioned symbols
|
||||
// and can identify LTO by `.gnu.lto_.symtab.` section, similarly
|
||||
// to what lto-plugin does. However, if LTO linker plug-in is not available,
|
||||
// use the emitted assembly instead.
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (opt_plugin && name.starts_with (".gnu.lto_.symtab."))
|
||||
// GCC FAT LTO objects contain both regular ELF sections and GCC-
|
||||
// specific LTO sections, so that they can be linked as LTO objects if
|
||||
// the LTO linker plugin is available and falls back as regular
|
||||
// objects otherwise. GCC FAT LTO object can be identified by the
|
||||
// presence of `.gcc.lto_.symtab` section.
|
||||
if (!ctx.arg.plugin.empty()) {
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (name.starts_with(".gnu.lto_.symtab."))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
continue;
|
||||
|
||||
// GCC non-FAT LTO object contains only sections symbols followed by
|
||||
// a common symbol whose name is `__gnu_lto_slim` (or `__gnu_lto_v1`
|
||||
// for older GCC releases).
|
||||
std::span<ElfSym<E>> elf_syms{(ElfSym<E> *)(data + sec.sh_offset),
|
||||
(size_t)sec.sh_size / sizeof(ElfSym<E>)};
|
||||
|
||||
@@ -83,8 +85,8 @@ inline bool is_gcc_lto_obj(MappedFile<C> *mf, bool opt_plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
template <typename Context, typename MappedFile>
|
||||
FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
using namespace elf;
|
||||
|
||||
std::string_view data = mf->get_contents();
|
||||
@@ -100,10 +102,10 @@ FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<I386>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<I386>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<X86_64>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<X86_64>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
@@ -116,10 +118,10 @@ FileType get_file_type(MappedFile<C> *mf, bool opt_plugin) {
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<M68K>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<M68K>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<SPARC64>(mf, opt_plugin))
|
||||
if (is_gcc_lto_obj<SPARC64>(ctx, mf))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace mold {
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += pow(2, -val);
|
||||
z += std::ldexp(1.0, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
@@ -110,14 +110,26 @@ 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";
|
||||
switch (signo) {
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
if (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);
|
||||
}
|
||||
break;
|
||||
case SIGABRT: {
|
||||
const char msg[] =
|
||||
"mold: aborted\n"
|
||||
"mold: If mold failed due to a spurious failure of pthread_create, "
|
||||
"it's likely because of https://github.com/oneapi-src/oneTBB/pull/824. "
|
||||
"You should ensure that you are using 2021.9.0 or newer version of libtbb.\n";
|
||||
(void)!write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
@@ -127,6 +139,7 @@ void install_signal_handler() {
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGABRT, &action, NULL);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
@@ -148,7 +161,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
#if MOLD_IS_SOLD
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd == "ld64.mold")
|
||||
if (cmd == "ld64" || cmd.starts_with("ld64."))
|
||||
return mold::macho::main(argc, argv);
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
@@ -14,9 +14,9 @@ inline u32 get_umask() {
|
||||
return orig_umask;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
template <typename Context>
|
||||
static std::pair<i64, char *>
|
||||
open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
char *path2 = (char *)save_string(ctx, tmpl).data();
|
||||
|
||||
@@ -47,19 +47,18 @@ open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
class MemoryMappedOutputFile : public OutputFile<C> {
|
||||
template <typename Context>
|
||||
class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
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);
|
||||
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, true) {
|
||||
std::tie(this->fd, output_tmpfile) =
|
||||
open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0);
|
||||
MAP_SHARED, this->fd, 0);
|
||||
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;
|
||||
@@ -70,12 +69,21 @@ public:
|
||||
::close(fd2);
|
||||
}
|
||||
|
||||
void close(C &ctx) override {
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (this->buf2.empty()) {
|
||||
::close(this->fd);
|
||||
} else {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
@@ -92,42 +100,9 @@ private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
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(C &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 C>
|
||||
std::unique_ptr<OutputFile<C>>
|
||||
OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
@@ -142,11 +117,11 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
OutputFile<C> *file;
|
||||
OutputFile<Context> *file;
|
||||
if (is_special)
|
||||
file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
else
|
||||
file = new MemoryMappedOutputFile<C>(ctx, path, filesize, perm);
|
||||
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Enable transparent huge page for an output memory-mapped file.
|
||||
@@ -163,7 +138,7 @@ OutputFile<C>::open(C &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
return std::unique_ptr<OutputFile>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <windows.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
OutputFile<Context> *file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<Context>>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
@@ -31,7 +31,7 @@ static i64 now_nsec() {
|
||||
#else
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_nsec;
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ static std::pair<i64, i64> get_usage() {
|
||||
return {to_nsec(user), to_nsec(kernel)};
|
||||
#else
|
||||
auto to_nsec = [](struct timeval t) -> i64 {
|
||||
return (i64)t.tv_sec * 1000000000 + t.tv_usec * 1000;
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
|
||||
};
|
||||
|
||||
struct rusage ru;
|
||||
@@ -79,9 +79,9 @@ void TimerRecord::stop() {
|
||||
|
||||
static void print_rec(TimerRecord &rec, i64 indent) {
|
||||
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
|
||||
((double)rec.user / 1000000000),
|
||||
((double)rec.sys / 1000000000),
|
||||
(((double)rec.end - rec.start) / 1000000000),
|
||||
((double)rec.user / 1'000'000'000),
|
||||
((double)rec.sys / 1'000'000'000),
|
||||
(((double)rec.end - rec.start) / 1'000'000'000),
|
||||
std::string(indent * 2, ' ').c_str(),
|
||||
rec.name.c_str());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -24,7 +24,10 @@ struct UstarHeader {
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(*this); i++)
|
||||
sum += ((u8 *)this)[i];
|
||||
assert(sum < 01000000);
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
ASSUME(sum < 01'000'000);
|
||||
snprintf(checksum, sizeof(checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
@@ -47,6 +50,8 @@ struct UstarHeader {
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UstarHeader) == 512);
|
||||
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
|
||||
@@ -78,6 +83,7 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.name[0] = '/';
|
||||
pax.typeflag[0] = 'x';
|
||||
pax.finalize();
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
#include "mold.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
@@ -7,20 +7,27 @@
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
[[ $arch = arm* ]] && arch=arm
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | ppc64le | s390x ]"
|
||||
exit 1
|
||||
fi
|
||||
esac
|
||||
|
||||
arch=$1
|
||||
echo $arch | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
set -e -x
|
||||
|
||||
docker run --platform linux/$arch -it --rm -v "$(pwd):/mold" \
|
||||
docker run --platform linux/$arch -i --rm -v "$(pwd):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" rui314/mold-builder:latest \
|
||||
bash -c "mkdir /tmp/build &&
|
||||
cd /tmp/build &&
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
+495
-1239
File diff suppressed because it is too large.
Load diff
+802
@@ -0,0 +1,802 @@
|
||||
mold(1) -- a modern linker
|
||||
==========================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`mold` [_option_...] _file_...
|
||||
|
||||
## DESCRIPTION
|
||||
`mold` is a faster drop-in replacement for the default GNU ld(1).
|
||||
|
||||
### How to use
|
||||
See <https://github.com/rui314/mold#how-to-use>.
|
||||
|
||||
### Compatibility
|
||||
|
||||
**Mold** is designed to be a drop-in replacement for the GNU linkers for
|
||||
linking user-land programs. If your user-land program cannot be built due to
|
||||
missing command-line options, please file a bug at
|
||||
<https://github.com/rui314/mold/issues>.
|
||||
|
||||
Mold supports a very limited set of linker script features, which is just
|
||||
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
|
||||
Linux, that file is contrary to its name not a shared library but an ASCII
|
||||
linker script that loads a real `libc.so` file.)
|
||||
|
||||
Beyond that, we have no plan to support any additional linker script features.
|
||||
The linker script is an ad-hoc, over-designed, complex language which we
|
||||
believe needs to be replaced by a simpler mechanism. We have a plan to add a
|
||||
replacement for the linker script to `mold` instead.
|
||||
|
||||
### Archive symbol resolution
|
||||
|
||||
Traditionally, Unix linkers are sensitive to the order in which input files
|
||||
appear on the command line. They process input files from the first (leftmost)
|
||||
file to the last (rightmost) file one-by-one. While reading input files, they
|
||||
maintain sets of defined and undefined symbols. When visiting an archive file
|
||||
(`.a` files), they pull out object files to resolve as many undefined symbols
|
||||
as possible and move on to the next input file. Object files that weren't
|
||||
pulled out will never have a chance for a second look.
|
||||
|
||||
Due to this behavior, you usually have to add archive files at the end of a
|
||||
command line, so that when a linker reaches archive files, it knows what
|
||||
symbols remain as undefined.
|
||||
|
||||
If you put archive files at the beginning of a command line, a linker doesn't
|
||||
have any undefined symbols, and thus no object files will be pulled out from
|
||||
archives. You can change the processing order by using the `--start-group` and
|
||||
`--end-group` options, though they make a linker slower.
|
||||
|
||||
`mold`, as well as the LLVM lld(1) linker, takes a different approach. They
|
||||
remember which symbols can be resolved from archive files instead of
|
||||
forgetting them after processing each archive. Therefore, `mold` and lld(1)
|
||||
can "go back" in a command line to pull out object files from archives if they
|
||||
are needed to resolve remaining undefined symbols. They are not sensitive to
|
||||
the input file order.
|
||||
|
||||
`--start-group` and `--end-group` are still accepted by `mold` and lld(1)
|
||||
for compatibility with traditional linkers, but they are silently ignored.
|
||||
|
||||
### Dynamic symbol resolution
|
||||
|
||||
Some Unix linker features are difficult to understand without comprehending
|
||||
the semantics of dynamic symbol resolution. Therefore, even though it's not
|
||||
specific to `mold`, we'll explain it here.
|
||||
|
||||
We use "ELF module" or just "module" as a collective term to refer to an
|
||||
executable or a shared library file in the ELF format.
|
||||
|
||||
An ELF module may have lists of imported symbols and exported symbols, as well
|
||||
as a list of shared library names from which imported symbols should be
|
||||
imported. The point is that imported symbols are not bound to any specific
|
||||
shared library until runtime.
|
||||
|
||||
Here is how the Unix dynamic linker resolves dynamic symbols. Upon the start
|
||||
of an ELF program, the dynamic linker constructs a list of ELF modules which,
|
||||
as a whole, consist of a complete program. The executable file is always at
|
||||
the beginning of the list followed by its dependent shared libraries. An
|
||||
imported symbol is searched from the beginning of the list to the end. If two
|
||||
or more modules define the same symbol, the one that appears first in the list
|
||||
takes precedence over the others.
|
||||
|
||||
This Unix semantics are contrary to systems such as Windows that have a
|
||||
two-level namespace for dynamic symbols. On Windows, for example, dynamic
|
||||
symbols are represented as a tuple of (`symbol-name`, `shared-library-name`),
|
||||
so that each dynamic symbol is guaranteed to be resolved from some specific
|
||||
library.
|
||||
|
||||
Typically, an ELF module that exports a symbol also imports the same symbol.
|
||||
Such a symbol is usually resolved to itself, but that's not the case if a
|
||||
module that appears before it in the symbol search list provides another
|
||||
definition of the same symbol.
|
||||
|
||||
Let's take `malloc` as an example. Assume that you define your version of
|
||||
`malloc` in your main executable file. Then, all `malloc` calls from any
|
||||
module are resolved to your function instead of the one in libc, because the
|
||||
executable is always at the beginning of the dynamic symbol search list. Note
|
||||
that even `malloc` calls within libc are resolved to your definition since
|
||||
libc exports and imports `malloc`. Therefore, by defining `malloc` yourself,
|
||||
you can overwrite a library function, and the `malloc` in libc becomes dead
|
||||
code.
|
||||
|
||||
These Unix semantics are tricky and sometimes considered harmful. For example,
|
||||
assume that you accidentally define `atoi` as a global function in your
|
||||
executable that behaves completely differently from the one in the C standard.
|
||||
Then, all `atoi` function calls from any modules (even function calls within
|
||||
libc) are redirected to your function instead of the one in libc, which will
|
||||
very likely cause a problem.
|
||||
|
||||
That is a somewhat surprising consequence for an accidental name conflict. On
|
||||
the other hand, this semantic is sometimes useful because it allows users to
|
||||
override library functions without rebuilding modules containing them.
|
||||
|
||||
Whether good or bad, you should keep these semantics in mind to understand
|
||||
Unix linkers' behaviors.
|
||||
|
||||
### Build reproducibility
|
||||
|
||||
`mold`'s output is deterministic. That is, if you pass the same object files
|
||||
and the same command-line options to the same version of `mold`, it is
|
||||
guaranteed that `mold` produces the bit-by-bit identical output. The linker's
|
||||
internal randomness, such as the timing of thread scheduling or iteration
|
||||
orders of hash tables, doesn't affect the output.
|
||||
|
||||
`mold` does not have any host-specific default settings. This is contrary to the
|
||||
GNU linkers, for which some configurable values, such as system-dependent
|
||||
library search paths, are hard-coded. `mold` depends only on its command-line
|
||||
arguments.
|
||||
|
||||
## MOLD-SPECIFIC OPTIONS
|
||||
|
||||
* `--chroot`=_dir_:
|
||||
Set _dir_ as the root directory.
|
||||
|
||||
* `--color-diagnostics`=[ _auto_ | _always_ | _never_ ]:
|
||||
Show diagnostic messages in color using ANSI escape sequences. `auto` means
|
||||
that `mold` prints out messages in color only if the standard output is
|
||||
connected to a TTY. Default is `auto`.
|
||||
|
||||
* `--color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=auto`.
|
||||
|
||||
* `--no-color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=never`.
|
||||
|
||||
* `--fork`, `--no-fork`:
|
||||
Spawn a child process and let it do the actual linking. When linking a large
|
||||
program, the OS kernel can take a few hundred milliseconds to terminate a
|
||||
`mold` process. `--fork` hides that latency. By default, it does fork.
|
||||
|
||||
* `--perf`:
|
||||
Print performance statistics.
|
||||
|
||||
* `--print-dependencies`:
|
||||
Print out dependency information for input files.
|
||||
|
||||
Each line of the output for this option shows which file depends on which
|
||||
file to use a specific symbol. This option is useful for debugging why some
|
||||
object file in a static archive got linked or why some shared library is
|
||||
kept in an output file's dependency list even with `--as-needed`.
|
||||
|
||||
* `--repro`:
|
||||
Archive input files, as well as a text file containing command line options,
|
||||
in a tar file so that you can run `mold` with the exact same inputs again.
|
||||
This is useful for reporting a bug with a reproducer. The output filename is
|
||||
`path/to/output.tar`, where `path/to/output` is an output filename specified
|
||||
by `-o`.
|
||||
|
||||
* `--reverse-sections`:
|
||||
Reverse the order of input sections before assigning them the offsets in the
|
||||
output file.
|
||||
|
||||
This option is useful for finding bugs that depend on the initialization
|
||||
order of global objects. In C++, constructors of global objects in a single
|
||||
source file are guaranteed to be executed in the source order, but there's
|
||||
no such guarantee across compilation units. Usually, constructors are
|
||||
executed in the order given to the linker, but depending on it is a mistake.
|
||||
|
||||
By reversing the order of input sections using `--reverse-sections`, you can
|
||||
easily test that your program works in the reversed initialization order.
|
||||
|
||||
* `--run` _command_ _arg_...:
|
||||
Run _command_ with `mold` `/usr/bin/ld`. Specifically, `mold` runs a given
|
||||
command with the `LD_PRELOAD` environment set to intercept exec(3) family
|
||||
functions and replaces `argv[0]` with itself if it is `ld`, `ld.gold`, or
|
||||
`ld.lld`.
|
||||
|
||||
* `--shuffle-sections`, `--shuffle-sections`=_number_:
|
||||
Randomize the output by shuffling the order of input sections before
|
||||
assigning them the offsets in the output file. If a _number_ is given, it's
|
||||
used as a seed for the random number generator, so that the linker produces
|
||||
the same output for the same seed. If no seed is given, a random number is
|
||||
used as a seed.
|
||||
|
||||
This option is useful for benchmarking. Modern CPUs are sensitive to a
|
||||
program's memory layout. A seemingly benign change in program layout, such
|
||||
as a small size increase of a function in the middle of a program, can
|
||||
affect the program's performance. Therefore, even if you write new code and
|
||||
get a good benchmark result, it is hard to say whether the new code improves
|
||||
the program's performance; it is possible that the new memory layout happens
|
||||
to perform better.
|
||||
|
||||
By running a benchmark multiple times with randomized memory layouts using
|
||||
`--shuffle-sections`, you can isolate your program's real performance number
|
||||
from the randomness caused by memory layout changes.
|
||||
|
||||
* `--stats`:
|
||||
Print input statistics.
|
||||
|
||||
* `--thread-count`=_count_:
|
||||
Use _count_ number of threads.
|
||||
|
||||
* `--threads`, `--no-threads`:
|
||||
Use multiple threads. By default, `mold` uses as many threads as the number of
|
||||
cores or 32, whichever is smaller. The reason it is capped at 32 is because
|
||||
`mold` doesn't scale well beyond that point. To use only one thread, pass
|
||||
`--no-threads` or `--thread-count=1`.
|
||||
|
||||
* `--quick-exit`, `--no-quick-exit`:
|
||||
Use or do not use `quick_exit` to exit.
|
||||
|
||||
## GNU-COMPATIBLE OPTIONS
|
||||
|
||||
* `--help`:
|
||||
Report usage information to stdout and exit.
|
||||
|
||||
* `-v`, `--version`:
|
||||
Report version information to stdout.
|
||||
|
||||
* `-V`:
|
||||
Report version and target information to stdout.
|
||||
|
||||
* `-E`, `--export-dynamic`, `--no-export-dynamic`:
|
||||
When creating an executable, using the `-E` option causes all global symbols
|
||||
to be put into the dynamic symbol table, so that the symbols are visible
|
||||
from other ELF modules at runtime.
|
||||
|
||||
By default, or if `--no-export-dynamic` is given, only symbols that are
|
||||
referenced by DSOs at link-time are exported from an executable.
|
||||
|
||||
* `-F` _libname_, `--filter`=_libname_:
|
||||
Set the `DT_FILTER` dynamic section field to _libname_.
|
||||
|
||||
* `-I` _file_, `--dynamic-linker`=_file_, `--no-dynamic-linker`:
|
||||
Set the dynamic linker path to _file_. If no `-I` option is given, or if
|
||||
`--no-dynamic-linker` is given, no dynamic linker path is set to an output
|
||||
file. This is contrary to the GNU linkers which set a default dynamic linker
|
||||
path in that case. This difference doesn't usually make any difference
|
||||
because the compiler driver always passes `-I` to the linker.
|
||||
|
||||
* `-L` _dir_, `--library-path`=_dir_:
|
||||
Add _dir_ to the list of library search paths from which `mold` searches
|
||||
libraries for the `-l` option.
|
||||
|
||||
Unlike the GNU linkers, `mold` does not have default search paths. This
|
||||
difference doesn't usually make any difference because the compiler driver
|
||||
always passes all necessary search paths to the linker.
|
||||
|
||||
* `-M`, `--print-map`:
|
||||
Write a map file to stdout.
|
||||
|
||||
* `-N`, `--omagic`, `--no-omagic`:
|
||||
Force `mold` to emit an output file with an old-fashioned memory layout.
|
||||
First, it makes the first data segment not aligned to a page boundary.
|
||||
Second, text segments are marked as writable if the option is given.
|
||||
|
||||
* `-S`, `--strip-debug`:
|
||||
Omit `.debug_*` sections from the output file.
|
||||
|
||||
* `-T` _file_, `--script`=_file_:
|
||||
Read linker script from _file_.
|
||||
|
||||
* `-X`, `--discard-locals`:
|
||||
Discard temporary local symbols to reduce the sizes of the symbol table and
|
||||
the string table. Temporary local symbols are local symbols starting with
|
||||
`.L`. Compilers usually generate such symbols for unnamed program elements
|
||||
such as string literals or floating-point literals.
|
||||
|
||||
* `-e` _symbol_, `--entry`=_symbol_:
|
||||
|
||||
Use _symbol_ as the entry point symbol instead of the default entry
|
||||
point symbol _start.
|
||||
|
||||
* `-f` _shlib_, `--auxiliary`=_shlib_:
|
||||
Set the `DT_AUXILIARY` dynamic section field to _shlib_.
|
||||
|
||||
* `-h` _libname_, `--soname`=_libname_:
|
||||
Set the `DT_SONAME` dynamic section field to _libname_. This option is used
|
||||
when creating a shared object file. Typically, when you create `libfoo.so`,
|
||||
you want to pass `--soname=foo` to a linker.
|
||||
|
||||
* `-l` _libname_:
|
||||
Search for `lib`_libname_`.so` or `lib`_libname_`.a` from library search
|
||||
paths.
|
||||
|
||||
* `-m` _target_:
|
||||
Choose a _target_.
|
||||
|
||||
* `-o` _file_, `--output`=_file_:
|
||||
Use _file_ as the output file name instead of the default name `a.out`.
|
||||
|
||||
* `-r`, `--relocatable`:
|
||||
Instead of generating an executable or a shared object file, combine input
|
||||
object files to generate another object file that can be used as an input to
|
||||
a linker.
|
||||
|
||||
* `--relocatable-merge-sections`:
|
||||
By default, `mold` doesn't merge input sections by name when merging input
|
||||
object files into a single output object file for `-r`. For example,
|
||||
`.text.foo` and `.text.bar` aren't merged for `-r` even though they are
|
||||
merged into `.text` according to the default section merging rules.
|
||||
|
||||
This option changes the behavior so that `mold` merges input sections by
|
||||
name by the default section merging rules.
|
||||
|
||||
* `-s`, `--strip-all`:
|
||||
Omit `.symtab` section from the output file.
|
||||
|
||||
* `-u` _symbol_, `--undefined`=_symbol_:
|
||||
If _symbol_ remains as an undefined symbol after reading all object files,
|
||||
and if there is a static archive that contains an object file defining
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
* `--Bstatic`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--Bsymbolic`:
|
||||
When creating a shared library, make global symbols export-only (i.e. do not
|
||||
import the same symbol). As a result, references within a shared library are
|
||||
always resolved locally, negating symbol override at runtime. See "Dynamic
|
||||
symbol resolution" for more information about symbol imports and exports.
|
||||
|
||||
* `--Bsymbolic-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for function
|
||||
symbols. Data symbols remain being both imported and exported.
|
||||
|
||||
* `--Bno-symbolic`:
|
||||
Cancel `--Bsymbolic` and `--Bsymbolic-functions`.
|
||||
|
||||
* `--Map`=_file_:
|
||||
Write map file to _file_.
|
||||
|
||||
* `--Tbss`=_address_:
|
||||
Alias for `--section-start=.bss=`_address_.
|
||||
|
||||
* `--Tdata`=_address_:
|
||||
Alias for `--section-start=.data=`_address_.
|
||||
|
||||
* `--Ttext`=_address_:
|
||||
Alias for `--section-start=.text=`_address_.
|
||||
|
||||
* `--allow-multiple-definition`:
|
||||
Normally, the linker reports an error if there are more than one definition
|
||||
of a symbol. This option changes the default behavior so that it doesn't
|
||||
report an error for duplicate definitions and instead use the first
|
||||
definition.
|
||||
|
||||
* `--as-needed`, `--no-as-needed`:
|
||||
By default, shared libraries given to the linker are unconditionally added
|
||||
to the list of required libraries in an output file. However, shared
|
||||
libraries after `--as-needed` are added to the list only when at least one
|
||||
symbol is actually used by the output file. In other words, shared libraries
|
||||
after `--as-needed` are not added to the list of needed libraries if they
|
||||
are not needed by a program.
|
||||
|
||||
The `--no-as-needed` option restores the default behavior for subsequent
|
||||
files.
|
||||
|
||||
* `--build-id`=[ `md5` | `sha1` | `sha256` | `uuid` | `0x`_hexstring_ | `none` ]:
|
||||
Create a `.note.gnu.build-id` section containing a byte string to uniquely
|
||||
identify an output file. `sha256` compute a 256-bit cryptographic hash of an
|
||||
output file and set it to build-id. `md5` and `sha1` compute the same hash
|
||||
but truncate it to 128 and 160 bits, respectively, before setting it to
|
||||
build-id. `uuid` sets a random 128-bit UUID. `0x`_hexstring_ sets
|
||||
_hexstring_.
|
||||
|
||||
* `--build-id`:
|
||||
Synonym for `--build-id=sha256`.
|
||||
|
||||
* `--no-build-id`:
|
||||
Synonym for `--build-id=none`.
|
||||
|
||||
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `none` ]:
|
||||
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
|
||||
compression algorithm. `zlib-gabi` is an alias for `zlib`.
|
||||
|
||||
* `--defsym`=_symbol_=_value_:
|
||||
Define _symbol_ as an alias for _value_.
|
||||
|
||||
_value_ is either an integer (in decimal or hexadecimal with `0x` prefix) or
|
||||
a symbol name. If an integer is given as a value, _symbol_ is defined as an
|
||||
absolute symbol with the given value.
|
||||
|
||||
* `--default-symver`:
|
||||
Use soname as a symbol version and append that version to all symbols.
|
||||
|
||||
* `--demangle`, `--no-demangle`:
|
||||
Demangle C++ and Rust symbols in log messages.
|
||||
|
||||
* `--dependency-file`=_file_:
|
||||
Write a dependency file to _file_. The contents of the written file is
|
||||
readable by make(1), which defines only one rule with the linker's output
|
||||
file as a target and all input files as its prerequisites. Users are
|
||||
expected to include the generated dependency file into a Makefile to
|
||||
automate the dependency management. This option is analogous to the
|
||||
compiler's `-MM -MF` options.
|
||||
|
||||
* `--dynamic-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_. Same as
|
||||
`--export-dynamic-symbol-list`, except that it implies `--Bsymbolic`. If
|
||||
_file_ does not exist in the current directory, it is searched from library
|
||||
search paths for the sake of compatibility with GNU ld.
|
||||
|
||||
* `--eh-frame-hdr`, `--no-eh-frame-hdr`:
|
||||
Create `.eh_frame_hdr` section.
|
||||
|
||||
* `--emit-relocs`:
|
||||
The linker usually "consumes" relocation sections. That is, the linker
|
||||
applies relocations to other sections, and relocation sections themselves
|
||||
are discarded.
|
||||
|
||||
The `--emit-relocs` instructs the linker to leave relocation sections in the
|
||||
output file. Some post-link binary analysis or optimization tools such as
|
||||
LLVM Bolt need them.
|
||||
|
||||
* `--enable-new-dtags`, `--disable-new-dtags`:
|
||||
By default, `mold` emits `DT_RUNPATH` for `--rpath`. If you pass
|
||||
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
|
||||
|
||||
* `--execute-only`:
|
||||
Traditionally, most processors require both executable and readable bits to
|
||||
1 to make the page executable, which allows machine code to be read as data
|
||||
at runtime. This is actually what an attacker often does after gaining a
|
||||
limited control of a process to find pieces of machine code they can use to
|
||||
gain the full control of the process. As a mitigation, some recent
|
||||
processors allows "execute-only" pages. If a page is execute-only, you can
|
||||
call a function there as long as you know its address but can't read it as
|
||||
data.
|
||||
|
||||
This option marks text segments execute-only. This option currently works
|
||||
only on some ARM64 processors.
|
||||
|
||||
* `--exclude-libs`=_libraries_ ...:
|
||||
Mark all symbols in the given _libraries_ hidden.
|
||||
|
||||
* `--export-dynamic-symbol`=_symbol_:
|
||||
Put symbols matching _symbol_ in the dynamic symbol table. _symbol_ may be a
|
||||
glob pattern in the same syntax as for the `--export-dynamic-symbol-list` or
|
||||
`--version-script` options.
|
||||
|
||||
* `--export-dynamic-symbol-list`=_file_:
|
||||
Read a list of dynamic symbols from _file_.
|
||||
|
||||
* `--fatal-warnings`, `--no-fatal-warnings`:
|
||||
Treat warnings as errors.
|
||||
|
||||
* `--fini`=_symbol_:
|
||||
Call _symbol_ at unload-time.
|
||||
|
||||
* `--gc-sections`, `--no-gc-sections`:
|
||||
Remove unreferenced sections.
|
||||
|
||||
* `--gdb-index`:
|
||||
Create a `.gdb_index` section to speed up GNU debugger. To use this, you
|
||||
need to compile source files with the `-ggnu-pubnames` compiler flag.
|
||||
|
||||
* `--hash-style`=[ `sysv` | `gnu` | `both` | `none` ]:
|
||||
Set hash style.
|
||||
|
||||
* `--icf`=[ `safe` | `all` | `none` ], `--no-icf`:
|
||||
It is not uncommon for a program to contain many identical functions that
|
||||
differ only in name. For example, a C++ template `std::vector` is very
|
||||
likely to be instantiated to the identical code for `std::vector<int>` and
|
||||
`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.
|
||||
|
||||
If `--icf=all` is given, `mold` tries to merge all identical functions. This
|
||||
reduces the size of the output most, but it is not a "safe" optimization. It
|
||||
is guaranteed in C and C++ that two pointers pointing two different
|
||||
functions will never be equal, but `--icf=all` breaks that assumption as two
|
||||
identical functions have the same address after merging. So a care must be
|
||||
taken when you use this flag that your program does not depend on the
|
||||
function pointer uniqueness.
|
||||
|
||||
`--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.
|
||||
|
||||
`--icf=safe` needs to be used with a compiler that supports `.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 support it
|
||||
yet, you cannot use `--icf=safe` with GCC (it doesn't do any harm but can't
|
||||
optimize at all.)
|
||||
|
||||
`--icf=none` and `--no-icf` disables ICF.
|
||||
|
||||
* `--ignore-data-address-equality`:
|
||||
Make ICF to merge not only functions but also data. This option should be
|
||||
used in combination with `--icf=all`.
|
||||
|
||||
* `--image-base`=_addr_:
|
||||
Set the base address to _addr_.
|
||||
|
||||
* `--init`=_symbol_:
|
||||
Call _symbol_ at load-time.
|
||||
|
||||
* `--no-undefined`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `--noinhibit-exec`:
|
||||
Create an output file even if errors occur.
|
||||
|
||||
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
|
||||
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
|
||||
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
|
||||
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
|
||||
flag can reduce the size of the output. This is typically most effective for
|
||||
position-independent executable.
|
||||
|
||||
Note that a runtime loader has to support `.relr.dyn` to run executables or
|
||||
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2022, only
|
||||
ChromeOS, Android and Fuchsia support it.
|
||||
|
||||
* `--package-metadata`=_string_:
|
||||
Embed _string_ to a `.note.package` section. This option is intended to be
|
||||
used by a package management command such as rpm(8) to embed metadata
|
||||
regarding a package to each executable file.
|
||||
|
||||
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
|
||||
Create a position-independent executable.
|
||||
|
||||
* `--print-gc-sections`, `--no-print-gc-sections`:
|
||||
Print removed unreferenced sections.
|
||||
|
||||
* `--print-icf-sections`, `--no-print-icf-sections`:
|
||||
Print folded identical sections.
|
||||
|
||||
* `--push-state`, `--pop-state`:
|
||||
`--push-state` saves the current values of `--as-needed`, `--whole-archive`,
|
||||
`--static`, and `--start-lib`. The saved values can be restored by
|
||||
pop-state.
|
||||
|
||||
`--push-state` and `--pop-state` pairs can nest.
|
||||
|
||||
These options are useful when you want to construct linker command line
|
||||
options programmatically. For example, if you want to link `libfoo.so` by
|
||||
as-needed basis but don't want to change the global state of `--as-needed`,
|
||||
you can append `--push-state --as-needed -lfoo --pop-state` to the linker
|
||||
command line options.
|
||||
|
||||
* `--relax, --no-relax`:
|
||||
Rewrite machine instructions with more efficient ones for some relocations.
|
||||
The feature is enabled by default.
|
||||
|
||||
* `--require-defined`=_symbol_:
|
||||
Like `--undefined`, except the new symbol must be defined by the end of the
|
||||
link.
|
||||
|
||||
* `--retain-symbols-file`=_file_:
|
||||
Keep only symbols listed in _file_. _file_ is a text file containing a
|
||||
symbol name on each line. `mold` discards all local symbols as well as
|
||||
global symbol that are not in _file_. Note that this option removes symbols
|
||||
only from `.symtab` section and does not affect `.dynsym` section, which is
|
||||
used for dynamic linking.
|
||||
|
||||
* `--rpath`=_dir_:
|
||||
Add _dir_ to runtime search paths.
|
||||
|
||||
* `--section-start`=_section_=_address_:
|
||||
Set _address_ to section. _address_ is a hexadecimal number that may start
|
||||
with an optional `0x`.
|
||||
|
||||
* `--shared`, `--Bshareable`:
|
||||
Create a share library.
|
||||
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
Reserve the given _number_ of tags in `.dynamic` section.
|
||||
|
||||
* `--start-lib`, `--end-lib`:
|
||||
Handle object files between `--start-lib` and `--end-lib` as if they were in
|
||||
an archive file. That means object files between them are linked only when
|
||||
they are needed to resolve undefined symbols. The options are useful if you
|
||||
want to link object files only when they are needed but want to avoid the
|
||||
overhead of running ar(3).
|
||||
|
||||
* `--static`:
|
||||
Do not link against shared libraries.
|
||||
|
||||
* `--sysroot`=_dir_:
|
||||
Set target system root directory to _dir_.
|
||||
|
||||
* `--trace`:
|
||||
Print name of each input file.
|
||||
|
||||
* `--undefined-version`, `--no-undefined-version`:
|
||||
By default, `mold` warns on a symbol specified by a version script or by
|
||||
`--export-dynamic-symbol` if it is not defined. You can silence the warning
|
||||
by `--undefined-version`.
|
||||
|
||||
* `--unique`=_pattern_:
|
||||
Don't merge input sections that match the given glob pattern _pattern_.
|
||||
|
||||
* `--unresolved-symbols`=[ `report-all` | `ignore-all` | `ignore-in-object-files` | `ignore-in-shared-libs` ]:
|
||||
How to handle undefined symbols.
|
||||
|
||||
* `--version-script`=_file_:
|
||||
Read version script from _file_. If _file_ does not exist in the current
|
||||
directory, it is searched from library search paths for the sake of
|
||||
compatibility with GNU ld.
|
||||
|
||||
* `--warn-common`, `--no-warn-common`:
|
||||
Warn about common symbols.
|
||||
|
||||
* `--warn-once`:
|
||||
Only warn once for each undefined symbol instead of warn for each relocation
|
||||
referring an undefined symbol.
|
||||
|
||||
* `--warn-unresolved-symbols`, `--error-unresolved-symbols`:
|
||||
Normally, the linker reports an error for unresolved symbols.
|
||||
`--warn-unresolved-symbols` option turns it into a warning.
|
||||
`--error-unresolved-symbols` option restores the default behavior.
|
||||
|
||||
* `--whole-archive`, `--no-whole-archive`:
|
||||
When archive files (`.a` files) are given to the linker, only object files
|
||||
that are needed to resolve undefined symbols are extracted from them and
|
||||
linked to an output file. `--whole-archive` changes that behavior for
|
||||
subsequent archives so that the linker extracts all object files and links
|
||||
them to an output. For example, if you are creating a shared object file and
|
||||
you want to include all archive members to the output, you should pass
|
||||
`--whole-archive`. `--no-whole-archive` restores the default behavior for
|
||||
subsequent archives.
|
||||
|
||||
* `--wrap`=_symbol_:
|
||||
Make _symbol_ be resolved to `__wrap_`_symbol_. The original symbol can be
|
||||
resolved as `__real_`_symbol_. This option is typically used for wrapping an
|
||||
existing function.
|
||||
|
||||
* `-z cet-report`=[ `warning` | `error` | `none` ]:
|
||||
Intel Control-flow Enforcement Technology (CET) is a new x86 feature
|
||||
available since Tiger Lake which is released in 2020. It defines new
|
||||
instructions to harden security to protect programs from control hijacking
|
||||
attacks. You can tell the compiler to use the feature by specifying the
|
||||
`-fcf-protection` flag.
|
||||
|
||||
`-z cet-report` flag is used to make sure that all object files were
|
||||
compiled with a correct `-fcf-protection` flag. If `warning` or `error` are
|
||||
given, `mold` prints out a warning or an error message if an object file was
|
||||
not compiled with the compiler flag.
|
||||
|
||||
`mold` looks for `GNU_PROPERTY_X86_FEATURE_1_IBT` bit and
|
||||
`GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in `.note.gnu.property` section to
|
||||
determine whether or not an object file was compiled with `-fcf-protection`.
|
||||
|
||||
* `-z now`, `-z lazy`:
|
||||
By default, functions referring to other ELF modules are resolved by the
|
||||
dynamic linker when they are called for the first time. `-z now` marks an
|
||||
executable or a shared library file so that all dynamic symbols are resolved
|
||||
when a file is loaded to memory. `-z lazy` restores the default behavior.
|
||||
|
||||
* `-z origin`:
|
||||
Mark object requiring immediate `$ORIGIN` processing at runtime.
|
||||
|
||||
* `-z ibt`:
|
||||
Turn on `GNU_PROPERTY_X86_FEATURE_1_IBT` bit in `.note.gnu.property` section
|
||||
to indicate that the output uses IBT-enabled PLT. This option implies `-z
|
||||
ibtplt`.
|
||||
|
||||
* `-z ibtplt`:
|
||||
Generate Intel Branch Tracking (IBT)-enabled PLT which is the default on
|
||||
x86-64. This is the default.
|
||||
|
||||
* `-z execstack`, `-z noexecstack`:
|
||||
By default, the pages for the stack area (i.e. the pages where local
|
||||
variables reside) are not executable for security reasons. `-z execstack`
|
||||
makes it executable. `-z noexecstack` restores the default behavior.
|
||||
|
||||
* `-z keep-text-section-prefix`, `-z nokeep-text-section-prefix`:
|
||||
Keep `.text.hot`, `.text.unknown`, `.text.unlikely`, `.text.startup`, and
|
||||
`.text.exit` as separate sections in the final binary instead of merging
|
||||
them as `.text`.
|
||||
|
||||
* `-z relro`, `-z norelro`:
|
||||
Some sections such as `.dynamic` have to be writable only during an
|
||||
executable or a shared library file is being loaded to memory. Once the
|
||||
dynamic linker finishes its job, such sections won't be mutated by anyone.
|
||||
As a security mitigation, it is preferred to make such segments read-only
|
||||
during program execution.
|
||||
|
||||
`-z relro` puts such sections into a special segment called `relro`. The
|
||||
dynamic linker makes a relro segment read-only after it finishes its job.
|
||||
|
||||
By default, `mold` generates a relro segment. `-z norelro` disables the
|
||||
feature.
|
||||
|
||||
* `-z sectionheader`, `-z nosectionheader`:
|
||||
`-z nosectionheader` tell the linker to omit the section header.
|
||||
By default, the linker does not omit the section header.
|
||||
|
||||
* `-z separate-loadable-segments`, `-z separate-code`, `-z noseparate-code`:
|
||||
If one memory page contains multiple segments, the page protection bits are
|
||||
set in such a way that the needed attributes (writable or executable) are
|
||||
satisfied for all segments. This usually happens at a boundary of two
|
||||
segments with two different attributes.
|
||||
|
||||
`separate-loadable-segments` adds paddings between segments with different
|
||||
attributes so that they do not share the same page. This is the default.
|
||||
|
||||
`separate-code` adds paddings only between executable and non-executable
|
||||
segments.
|
||||
|
||||
`noseparate-code` does not add any paddings between segments.
|
||||
|
||||
* `-z defs`, `-z nodefs`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `-z shstk`:
|
||||
Enforce shadow stack by turning `GNU_PROPERTY_X86_FEATURE_1_SHSTK` bit in
|
||||
`.note.gnu.property` output section. Shadow stack is part of Intel
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z text`, `-z notext`, `-z textoff`:
|
||||
`mold` by default reports an error if dynamic relocations are created in
|
||||
read-only sections. If `-z notext` or `-z textoff` are given, `mold` creates
|
||||
such dynamic relocations without reporting an error. `-z text` restores the
|
||||
default behavior.
|
||||
|
||||
* `-z max-page-size`=_number_:
|
||||
Some CPU ISAs support multiple different memory page sizes. This option
|
||||
specifies the maximum page size that an output binary can run on. The
|
||||
default value is 4 KiB for i386, x86-64, and RISC-V, and 64 KiB for ARM64.
|
||||
|
||||
* `-z nodefaultlib`:
|
||||
Make the dynamic loader ignore default search paths.
|
||||
|
||||
* `-z nodelete`:
|
||||
Mark DSO non-deletable at runtime.
|
||||
|
||||
* `-z nodlopen`:
|
||||
Mark DSO not available to dlopen(3). This option makes it possible for the
|
||||
linker to optimize thread-local variable accesses by rewriting instructions
|
||||
for some targets.
|
||||
|
||||
* `-z nodump`:
|
||||
Mark DSO not available to dldump(3).
|
||||
|
||||
* `-z nocopyreloc`:
|
||||
Do not create copy relocations.
|
||||
|
||||
* `-z initfirst`:
|
||||
Mark DSO to be initialized first at runtime.
|
||||
|
||||
* `-z interpose`:
|
||||
Mark object to interpose all DSOs but executable.
|
||||
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--allow-shlib-undefined`, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-allow-shlib-undefined`, `--no-copy-dt-needed-entries`, `--no-fatal-warnings`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
|
||||
Ignored
|
||||
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
* `MOLD_JOBS`:
|
||||
If this variable is set to `1`, only one process of `mold` runs actively. A
|
||||
mold process invoked while another active mold process is running will wait
|
||||
before doing anything until the active process exits.
|
||||
|
||||
The purpose of this environment variable is to reduce peak memory usage.
|
||||
Since mold is highly parallelized, there's no point in running it
|
||||
simultaneously. If you run N instances of mold in parallel, it would take N
|
||||
times more time and N times more memory. If you run them serially, it would
|
||||
still take N times more to finish, but their peak memory usage is reduced to
|
||||
normal.
|
||||
|
||||
If your build system tends to invoke multiple linker processes
|
||||
simultaneously, you may want to try to set this environment variable to
|
||||
`1` to see if it could improve overall performance.
|
||||
|
||||
Currently, any value other than 1 is silently ignored.
|
||||
|
||||
* `MOLD_DEBUG`:
|
||||
If this variable is set to a non-empty string, `mold` embeds its
|
||||
command-line options in the output file's `.comment` section.
|
||||
|
||||
* `MOLD_REPRO`:
|
||||
Setting this variable to a non-empty string has the same effect as passing
|
||||
the `--repro` option.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
gold(1), ld(1), elf(5), ld.so(8)
|
||||
|
||||
## AUTHOR
|
||||
|
||||
Rui Ueyama <ruiu@cs.stanford.edu>
|
||||
|
||||
## BUGS
|
||||
|
||||
Report bugs to <https://github.com/rui314/mold/issues>.
|
||||
+24
-36
@@ -62,8 +62,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -103,7 +103,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFQUAD:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
*(ul32 *)loc = S + A - GP;
|
||||
@@ -166,17 +166,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -213,16 +208,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
|
||||
|
||||
@@ -231,29 +221,30 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_LITERAL:
|
||||
if (rel.r_addend) {
|
||||
sym.flags.fetch_or(NEEDS_ALPHA_GOT, std::memory_order_relaxed);
|
||||
if (rel.r_addend)
|
||||
ctx.extra.got->add_symbol(sym, rel.r_addend);
|
||||
} else {
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_BRSGP:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ALPHA_TLSGD:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ALPHA_TLSLDM:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ALPHA_GOTTPREL:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ALPHA_TPRELHI:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
case R_ALPHA_TPRELLO:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
case R_ALPHA_LITUSE:
|
||||
@@ -263,7 +254,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ALPHA_GPRELLOW:
|
||||
case R_ALPHA_DTPRELHI:
|
||||
case R_ALPHA_DTPRELLO:
|
||||
case R_ALPHA_TPRELLO:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -273,16 +263,15 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
|
||||
// entry for an external symbol with a non-zero addend. This is an unusual
|
||||
// request which we cannot find in any other targets.
|
||||
// request which is not found in any other targets.
|
||||
//
|
||||
// Referring an external symbol with a non-zero addend is a bad practice.
|
||||
// To see why, assume that we have two R_ALPHA_LITERAL relocs to symbol X
|
||||
// with addend 8 and 16. To satisfy that request, we need to create not
|
||||
// only one but two GOT entries for symbol X with different addends.
|
||||
// Referring an external symbol with a non-zero addend is a bad practice
|
||||
// because we need to create as many dynamic relocations as the number of
|
||||
// distinctive addends for the same symbol.
|
||||
//
|
||||
// We don't want to mess up the implementation of the common GOT section
|
||||
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
|
||||
// entry for an R_ALPHA_LITERAL reloc with a non-zero addends is created
|
||||
// entry for an R_ALPHA_LITERAL reloc with a non-zero addend is created
|
||||
// not in .got but in .alpha_got.
|
||||
//
|
||||
// Since .alpha_got entries are accessed relative to GP, .alpha_got
|
||||
@@ -304,9 +293,9 @@ u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
|
||||
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) {
|
||||
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
i64 n = 0;
|
||||
for (Entry &e : entries)
|
||||
for (const Entry &e : entries)
|
||||
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
|
||||
n++;
|
||||
return n;
|
||||
@@ -319,9 +308,8 @@ void AlphaGotSection::finalize() {
|
||||
}
|
||||
|
||||
void AlphaGotSection::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < entries.size(); i++) {
|
||||
Entry &e = entries[i];
|
||||
|
||||
+171
-157
@@ -2,7 +2,7 @@
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// thumb is a subset of ARM, so not all ARM instructions are
|
||||
// Thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
@@ -12,14 +12,16 @@
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit an interworking thunk code to switch mode.
|
||||
// linker even has to emit interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries.
|
||||
// byte boundaries. So the least significant bit of a function address
|
||||
// is always 0.
|
||||
//
|
||||
// You can distinguish Thumb functions from ARM functions by looking
|
||||
// at the least significant bit (LSB) of its "address". If LSB is 0,
|
||||
// it's ARM; otherwise, Thumb.
|
||||
// To distinguish Thumb functions from ARM fucntions, the LSB of a
|
||||
// function address is repurposed as a boolean flag. If the LSB is 0,
|
||||
// the function referred to by the address is encoded in ARM;
|
||||
// otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
@@ -42,8 +44,6 @@ using E = ARM32;
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
return 0;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_TARGET1:
|
||||
@@ -76,8 +76,9 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_TLS_CALL:
|
||||
return sign_extend(*(ul32 *)loc & 0x00ff'ffff, 23) << 2;
|
||||
return sign_extend(*(ul32 *)loc, 23) << 2;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
@@ -100,7 +101,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
return sign_extend(val, 15);
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +168,8 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
write_thm_b_imm(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
@@ -194,14 +195,14 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
@@ -210,8 +211,8 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
static const ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
@@ -230,8 +231,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
// 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, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
@@ -247,17 +248,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks =
|
||||
output_section->thunks;
|
||||
|
||||
auto get_trampoline_addr = [&](u64 addr) {
|
||||
for (;;) {
|
||||
assert(!thunks.empty());
|
||||
i64 disp = output_section->shdr.sh_addr + thunks[0]->offset - addr;
|
||||
auto get_tls_trampoline_addr = [&, i = 0](u64 addr) mutable {
|
||||
for (; i < output_section->thunks.size(); i++) {
|
||||
i64 disp = output_section->shdr.sh_addr + output_section->thunks[i]->offset -
|
||||
addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
thunks = thunks.subspan(1);
|
||||
}
|
||||
unreachable();
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -288,15 +286,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
@@ -304,6 +299,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
i64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
@@ -332,54 +330,66 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32: {
|
||||
// Just like THM_CALL, ARM_CALL relocation refers either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers neither BL nor BLX";
|
||||
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
|
||||
|
||||
u64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T)
|
||||
*(ul32 *)loc = 0xfa00'0000 | (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
else
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(val, 25, 2);
|
||||
if (T) {
|
||||
*(ul32 *)loc = 0xfa00'0000; // BLX
|
||||
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// These relocs refers to a B (unconditional branch) instruction.
|
||||
// Unlike BL or BLX, we can't rewrite B to BX in place when the
|
||||
// processor mode switch is required because BX doesn't takes an
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
// required, we jump to a linker-synthesized thunk which does the
|
||||
// job with a longer code sequence.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
// Unlike BL and BLX, we can't rewrite B to BX because BX doesn't
|
||||
// takes an immediate; it takes only a register. So if mode switch
|
||||
// is required, we jump to a linker-synthesized thunk which constructs
|
||||
// a branch destination in a register and branches to that address.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP11: {
|
||||
case R_ARM_THM_JUMP11:
|
||||
assert(T);
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc &= 0xf800;
|
||||
*(ul16 *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 19), 1 << 19);
|
||||
@@ -398,18 +408,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
} else {
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || !T)
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
@@ -419,12 +431,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc &= 0x8000'0000;
|
||||
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
break;
|
||||
@@ -456,12 +467,33 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in r0.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: bl foo
|
||||
// R_ARM_TLS_CALL
|
||||
// .L2: .word foo + . - .L1
|
||||
// R_ARM_TLS_GOTDESC
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: ldr r0, [pc, r0]
|
||||
// ...
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: nop
|
||||
// ...
|
||||
// .L2: .word foo(tpoff)
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
if (A & 1)
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 6;
|
||||
else
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - 4;
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
@@ -469,20 +501,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_trampoline_addr(P + 8), 25, 2);
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
// BL -> NOP
|
||||
*(ul32 *)loc = 0xe320'f000;
|
||||
*(ul32 *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_trampoline_addr(P + 4), 4);
|
||||
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
|
||||
// we use two instructions instead.
|
||||
*(ul16 *)loc = 0x4478; // add r0, pc
|
||||
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
// BL -> NOP.W
|
||||
*(ul32 *)loc = 0x8000'f3af;
|
||||
*(ul32 *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -497,17 +534,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -546,18 +578,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
@@ -568,16 +595,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
@@ -585,17 +612,20 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
@@ -607,10 +637,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -620,8 +648,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
@@ -629,36 +655,44 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
const u8 entry[] = {
|
||||
// .thumb
|
||||
0xfc, 0x46, // mov ip, pc
|
||||
0x60, 0x47, // bx ip # jumps to the following `ldr` insn
|
||||
0x78, 0x47, // bx pc # jumps to 1f
|
||||
0xc0, 0x46, // nop
|
||||
// .arm
|
||||
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, 2f
|
||||
0x0f, 0xc0, 0x8c, 0xe0, // 1: add ip, ip, pc
|
||||
0x1c, 0xff, 0x2f, 0xe1, // bx ip
|
||||
0x00, 0x00, 0x00, 0x00, // 2: .word sym - 1b
|
||||
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
|
||||
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
|
||||
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
memcpy(buf, hdr, sizeof(hdr));
|
||||
buf += sizeof(hdr);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *loc = buf + sizeof(hdr) + i * sizeof(entry);
|
||||
memcpy(loc, entry, sizeof(entry));
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
|
||||
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr) + i * sizeof(entry);
|
||||
*(ul32 *)(loc + 16) = S - P - 16;
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
|
||||
buf += sizeof(entry);
|
||||
P += sizeof(entry);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return EF_ARM_EABI_VER5;
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
@@ -668,7 +702,13 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
OutputSection<E> *osec = find_section(ctx, SHT_ARM_EXIDX);
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
@@ -688,11 +728,11 @@ static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
ul32 val;
|
||||
};
|
||||
|
||||
if (osec.shdr.sh_size % sizeof(Entry))
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
|
||||
Entry *ent = (Entry *)(ctx.buf + osec.shdr.sh_offset);
|
||||
i64 num_entries = osec.shdr.sh_size / sizeof(Entry);
|
||||
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
@@ -719,38 +759,12 @@ static void sort_exidx(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
}
|
||||
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
auto find_exidx = [&]() -> OutputSection<E> * {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (OutputSection<E> *osec = chunk->to_osec())
|
||||
if (osec->shdr.sh_type == SHT_ARM_EXIDX)
|
||||
return osec;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
OutputSection<E> *exidx = find_exidx();
|
||||
if (!exidx)
|
||||
return;
|
||||
|
||||
// Sort .ARM.exidx contents
|
||||
sort_exidx(ctx, *exidx);
|
||||
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (ctx.shdr) {
|
||||
auto find_text = [&]() -> Chunk<E> * {
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->name == ".text")
|
||||
return chunk;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (Chunk<E> *text = find_text()) {
|
||||
exidx->shdr.sh_link = text->shndx;
|
||||
if (Chunk<E> *text = find_section(ctx, ".text")) {
|
||||
osec->shdr.sh_link = text->shndx;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
+202
-102
@@ -102,8 +102,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -123,6 +123,24 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_adrp(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
|
||||
}
|
||||
|
||||
static bool is_ldr(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
|
||||
}
|
||||
|
||||
static bool is_add(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -155,9 +173,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
@@ -172,57 +191,86 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(val, 31, 16) << 5;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(val, 47, 32) << 5;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
check(S + A, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE: {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
if (sym.has_got(ctx)) {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
|
||||
u32 reg = bits(*(ul32 *)loc, 4, 0);
|
||||
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
|
||||
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_PG_HI21: {
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
|
||||
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
|
||||
// :lo12: foo` instruction pair to materialize a PC-relative address
|
||||
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
|
||||
// if foo is in PC ± 1 MiB.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
i64 val = S + A - P - 4;
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (sign_extend(val, 20) == val &&
|
||||
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_addend == rel.r_addend &&
|
||||
is_adrp(loc) &&
|
||||
is_add(loc + 4)) {
|
||||
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (reg1 == reg2) {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
|
||||
write_adr(loc + 4, val);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, val);
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
@@ -238,25 +286,23 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(val, 20, 2) << 5;
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_AARCH64_PREL16:
|
||||
check(S + A - P, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
case R_AARCH64_PREL32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -309,6 +355,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
check(S + A - ctx.tp_addr, 0, 1 << 12);
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
@@ -322,38 +371,65 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
// ARM64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
//
|
||||
// adrp x0, 0
|
||||
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
|
||||
// ldr x1, [x0]
|
||||
// R_AARCH64_TLSDESC_LD64_LO12 foo
|
||||
// add x0, x0, #0
|
||||
// R_AARCH64_TLSDESC_ADD_LO12 foo
|
||||
// blr x1
|
||||
// R_AARCH64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// adrp x0, :gottprel:foo
|
||||
// ldr x0, [x0, :gottprel_lo12:foo]
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// movz x0, :tls_offset_hi:foo, lsl #16
|
||||
// movk x0, :tls_offset_lo:foo
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
|
||||
i64 val = (S + A - ctx.tp_addr);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ul32 *)loc = 0xd2a0'0000 | (bits(val, 32, 16) << 5);
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
if (sym.has_tlsdesc(ctx))
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
|
||||
u32 offset_lo = (S + A - ctx.tp_addr) & 0xffff;
|
||||
*(ul32 *)loc = 0xf280'0000 | (offset_lo << 5);
|
||||
}
|
||||
else
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
|
||||
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
|
||||
} else {
|
||||
// add x0, x0, #0 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// blr x2 -> nop
|
||||
*(ul32 *)loc = 0xd503'201f;
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -368,17 +444,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -424,48 +495,77 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
|
||||
// symbol's address from GOT. If the GOT value is a link-time
|
||||
// constant, we may be able to rewrite the ADRP+LDR instruction pair
|
||||
// with an ADRP+ADD, eliminating a GOT memory load.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
// ADRP+LDR must be consecutive and use the same register to relax.
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel.r_addend == 0 &&
|
||||
rel2.r_addend == 0 &&
|
||||
is_adrp(loc) &&
|
||||
is_ldr(loc + 4)) {
|
||||
u32 rd = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
|
||||
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (rd == rn && rn == rt) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
case R_AARCH64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
@@ -482,20 +582,18 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -505,24 +603,26 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const ul32 data[] = {
|
||||
static const ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
|
||||
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
|
||||
0xd61f'0200, // br x16
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(data));
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = output_section.shdr.sh_addr + offset + i * E::thunk_size;
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
u8 *loc = buf + i * E::thunk_size;
|
||||
memcpy(loc , data, sizeof(data));
|
||||
write_adrp(loc, page(S) - page(P));
|
||||
*(ul32 *)(loc + 4) |= bits(S, 11, 0) << 10;
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
memcpy(buf, insn, E::thunk_size);
|
||||
write_adrp(buf, page(S) - page(P));
|
||||
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,506 +0,0 @@
|
||||
// HP/PA (Hewlett-Packard Precision Architecture), also known as PA-RISC,
|
||||
// is a RISC ISA developed by HP in the '80s. It was replaced by Itanium
|
||||
// which was a joint project between Intel and HP in the early 2000s. No
|
||||
// new HP/PA processors have been created after 2005.
|
||||
//
|
||||
// Thread pointer (TP) is stored to the control register cr27, which is an
|
||||
// alias for tr3.
|
||||
//
|
||||
// $ltp (linkage table pointer) is $r19
|
||||
// $dp is $r27
|
||||
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = HPPA32;
|
||||
|
||||
static u64 RND(u64 x) { return (x + 0x1000) & ~(u64)0x1fff; }
|
||||
static u64 L(u64 x) { return x & 0xffff'f800; }
|
||||
static u64 R(u64 x) { return x & 0x0000'07ff; }
|
||||
static u64 LR(u64 x, u64 a) { return L(x + RND(a)); }
|
||||
static u64 RR(u64 x, u64 a) { return R(x + RND(a)) + a - RND(a); }
|
||||
|
||||
static u64 get_gp(Context<E> &ctx) {
|
||||
if (ctx.arg.pic)
|
||||
return ctx.gotplt->shdr.sh_addr;
|
||||
return ctx.extra.global->value;
|
||||
}
|
||||
|
||||
static u32 dis_assemble_17(u32 val) {
|
||||
return ((val & 0x10000) >> 16) |
|
||||
((val & 0x0f800) << 5) |
|
||||
((val & 0x00400) >> 8) |
|
||||
((val & 0x003ff) << 3);
|
||||
}
|
||||
|
||||
static u32 dis_assemble_21(u32 val) {
|
||||
return (bits(val, 6, 2) << 16) | (bits(val, 8, 7) << 14) |
|
||||
(bits(val, 1, 0) << 12) | (bits(val, 19, 9) << 1) |
|
||||
bit(val, 20);
|
||||
}
|
||||
|
||||
static u32 dis_low_sign_ext(u32 val, u32 len) {
|
||||
return (bits(val, len - 2, 0) << 1) | bit(val, len - 1);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_PARISC_PCREL32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_PARISC_SEGREL32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 SB = 0;
|
||||
u64 GP = get_gp(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
#define S sym.get_addr(ctx)
|
||||
#define A get_addend(loc, rel)
|
||||
#define P (get_addr() + rel.r_offset)
|
||||
#define G (sym.get_got_idx(ctx) * sizeof(Word<E>))
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_DIR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
break;
|
||||
case R_PARISC_DIR21L:
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_DIR14R:
|
||||
*(ub32 *)loc |= dis_low_sign_ext(RR(S, A), 14);
|
||||
break;
|
||||
case R_PARISC_PCREL32: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc = addr + A - P - 8;
|
||||
break;
|
||||
}
|
||||
case R_PARISC_PCREL21L: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(addr + A - P - 8, 31, 11));
|
||||
break;
|
||||
}
|
||||
case R_PARISC_PCREL17F: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc |= dis_assemble_17((addr + A - P - 8) >> 2);
|
||||
break;
|
||||
}
|
||||
case R_PARISC_PCREL14R: {
|
||||
u64 addr = has_thunk(i) ? get_thunk_addr(i) : S;
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(addr - P - 8 + A), 14);
|
||||
break;
|
||||
}
|
||||
case R_PARISC_DPREL21L:
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_DPREL14R:
|
||||
*(ub32 *)loc |= dis_low_sign_ext(RR(S - GP, A), 14);
|
||||
break;
|
||||
case R_PARISC_DLTIND21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(G + GOT - GP, 31, 11));
|
||||
break;
|
||||
case R_PARISC_DLTIND14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(G + GOT - GP), 14);
|
||||
break;
|
||||
case R_PARISC_SEGREL32:
|
||||
*(ub32 *)loc = S + A - SB;
|
||||
break;
|
||||
case R_PARISC_PLABEL32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ub32 *)loc = 0;
|
||||
} else if (ctx.arg.pic) {
|
||||
if (sym.is_imported)
|
||||
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, sym.get_dynsym_idx(ctx), 0);
|
||||
else
|
||||
*dynrel++ = ElfRel<E>(P, R_PARISC_PLABEL32, 0, sym.get_opd_addr(ctx) + 2);
|
||||
*(ub32 *)loc = 0;
|
||||
} else {
|
||||
*(ub32 *)loc = sym.get_opd_addr(ctx) + 2;
|
||||
}
|
||||
break;
|
||||
case R_PARISC_TPREL21L:
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.tp_addr, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TPREL14R:
|
||||
*(ub32 *)loc |= dis_low_sign_ext(RR(S - ctx.tp_addr, A), 14);
|
||||
break;
|
||||
case R_PARISC_LTOFF_TP21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |=
|
||||
dis_assemble_21(bits(LR(sym.get_gottp_addr(ctx) - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_LTOFF_TP14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_gottp_addr(ctx) - GP), 14);
|
||||
break;
|
||||
case R_PARISC_TLS_GD21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |=
|
||||
dis_assemble_21(bits(LR(sym.get_tlsgd_addr(ctx) - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TLS_GD14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(sym.get_tlsgd_addr(ctx) - GP), 14);
|
||||
break;
|
||||
case R_PARISC_TLS_LDM21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |=
|
||||
dis_assemble_21(bits(LR(ctx.got->get_tlsld_addr(ctx) - GP, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TLS_LDM14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(ctx.got->get_tlsld_addr(ctx) - GP), 14);
|
||||
break;
|
||||
case R_PARISC_TLS_LDO21L:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_assemble_21(bits(LR(S - ctx.dtp_addr, A), 31, 11));
|
||||
break;
|
||||
case R_PARISC_TLS_LDO14R:
|
||||
assert(A == 0);
|
||||
*(ub32 *)loc |= dis_low_sign_ext(R(S - ctx.dtp_addr), 14);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (frag ? frag_addend : get_addend(loc, rel))
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_DIR32:
|
||||
case R_PARISC_SEGREL32:
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on PA-RISC";
|
||||
|
||||
if (sym.is_func()) {
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_PCREL32:
|
||||
case R_PARISC_PCREL21L:
|
||||
case R_PARISC_PCREL17F:
|
||||
case R_PARISC_PCREL14R:
|
||||
case R_PARISC_PLABEL32:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": " << rel
|
||||
<< " may not refer to a function symbol " << sym;
|
||||
}
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PARISC_DIR32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PARISC_DIR21L:
|
||||
case R_PARISC_DIR14R:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PARISC_PCREL32:
|
||||
case R_PARISC_PCREL21L:
|
||||
case R_PARISC_PCREL17F:
|
||||
case R_PARISC_PCREL14R:
|
||||
case R_PARISC_DPREL21L:
|
||||
case R_PARISC_DPREL14R:
|
||||
if (sym.is_func() || sym.esym().is_undef_weak())
|
||||
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
|
||||
else
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PARISC_DLTIND21L:
|
||||
case R_PARISC_DLTIND14R:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_PLABEL32:
|
||||
if (!sym.is_remaining_undef_weak()) {
|
||||
if (ctx.arg.pic)
|
||||
file.num_dynrel++;
|
||||
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
|
||||
}
|
||||
break;
|
||||
case R_PARISC_LTOFF_TP21L:
|
||||
case R_PARISC_LTOFF_TP14R:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_TLS_GD21L:
|
||||
case R_PARISC_TLS_GD14R:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_TLS_LDM21L:
|
||||
case R_PARISC_TLS_LDM14R:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case R_PARISC_SEGREL32:
|
||||
case R_PARISC_TPREL21L:
|
||||
case R_PARISC_TPREL14R:
|
||||
case R_PARISC_TLS_LDO21L:
|
||||
case R_PARISC_TLS_LDO14R:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 gp = get_gp(ctx);
|
||||
|
||||
static const ub32 opd_pic_entry[] = {
|
||||
0x2a60'0000, // addil LR'<OFFSET>, r19, r1
|
||||
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
|
||||
0x0ec0'1095, // ldw 0(r22), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0ec8'1093, // ldw 4(r22), r19
|
||||
};
|
||||
|
||||
static const ub32 opd_nopic_entry[] = {
|
||||
0x2b60'0000, // addil LR'<OFFSET>, dp, r1
|
||||
0x3436'0000, // ldo RR'<OFFSET>(r1), r22
|
||||
0x0ec0'1095, // ldw 0(r22), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0ec8'1093, // ldw 4(r22), r19
|
||||
};
|
||||
|
||||
static const ub32 local_pic_entry[] = {
|
||||
0x2a60'0000, // addil L%0, r19, r1
|
||||
0x3435'0000, // ldo 0(r1), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0800'0240, // nop
|
||||
0x0800'0240, // nop
|
||||
};
|
||||
|
||||
static const ub32 local_nopic_entry[] = {
|
||||
0x2b60'0000, // addil L%0, dp, r1
|
||||
0x3435'0000, // ldo 0(r1), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0800'0240, // nop
|
||||
0x0800'0240, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(opd_pic_entry));
|
||||
static_assert(E::thunk_size == sizeof(opd_nopic_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_pic_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_nopic_entry));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
i64 val;
|
||||
|
||||
if (sym.has_opd(ctx)) {
|
||||
val = sym.get_opd_addr(ctx) - gp;
|
||||
if (ctx.arg.pic)
|
||||
memcpy(loc, opd_pic_entry, E::thunk_size);
|
||||
else
|
||||
memcpy(loc, opd_nopic_entry, E::thunk_size);
|
||||
} else {
|
||||
assert(!sym.is_imported);
|
||||
val = sym.get_addr(ctx) - gp;
|
||||
if (ctx.arg.pic)
|
||||
memcpy(loc, local_pic_entry, E::thunk_size);
|
||||
else
|
||||
memcpy(loc, local_nopic_entry, E::thunk_size);
|
||||
}
|
||||
|
||||
loc[0] |= dis_assemble_21(bits(LR(val, 0), 31, 11));
|
||||
loc[1] |= dis_low_sign_ext(RR(val, 0), 14);
|
||||
}
|
||||
}
|
||||
|
||||
void HppaOpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
sym->set_opd_idx(ctx, symbols.size());
|
||||
symbols.push_back(sym);
|
||||
}
|
||||
|
||||
void HppaOpdSection::update_shdr(Context<E> &ctx) {
|
||||
if (!symbols.empty())
|
||||
this->shdr.sh_size = symbols.size() * ENTRY_SIZE + TRAILER_SIZE;
|
||||
this->shdr.sh_link = ctx.extra.opd->shndx;
|
||||
}
|
||||
|
||||
void HppaOpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
u64 gp = get_gp(ctx);
|
||||
|
||||
memset(buf, 0, this->shdr.sh_size);
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
if (!ctx.arg.pic && !sym.is_imported) {
|
||||
buf[i * 2] = sym.get_addr(ctx, NO_OPD);
|
||||
buf[i * 2 + 1] = gp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HppaRelOpdSection::update_shdr(Context<E> &ctx) {
|
||||
this->shdr.sh_link = ctx.extra.opd->shndx;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
this->shdr.sh_size = ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>);
|
||||
} else {
|
||||
this->shdr.sh_size = 0;
|
||||
for (Symbol<E> *sym : ctx.extra.opd->symbols)
|
||||
if (sym->is_imported)
|
||||
this->shdr.sh_size += sizeof(ElfRel<E>);
|
||||
}
|
||||
}
|
||||
|
||||
void HppaRelOpdSection::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
for (Symbol<E> *sym : ctx.extra.opd->symbols) {
|
||||
if (ctx.arg.pic || sym->is_imported) {
|
||||
memset(rel, 0, sizeof(*rel));
|
||||
rel->r_offset = sym->get_opd_addr(ctx);
|
||||
rel->r_type = R_PARISC_IPLT;
|
||||
if (sym->is_imported)
|
||||
rel->r_sym = sym->get_dynsym_idx(ctx);
|
||||
else
|
||||
rel->r_addend = sym->get_addr(ctx, NO_OPD);
|
||||
rel++;
|
||||
}
|
||||
}
|
||||
|
||||
// This code stub must immediately follow the OPD element that the last
|
||||
// .rela.plt relocation refers to, because that's how the runtime finds
|
||||
// this piece of code on process startup.
|
||||
//
|
||||
// The code stub loads the _dl_runtime_resolve address to $r20 call the
|
||||
// function with its GP in $r19. The magic bytes, 0xc0ffee and 0xdeadbeef,
|
||||
// are used by the runtime to identify this code stub, so they must be
|
||||
// exactly that bytes.
|
||||
//
|
||||
// HPPA's runtime assumes that this stub is immediately followed by .got.
|
||||
// The files created by mold don't actually satisfy that constraint
|
||||
// because we don't intermix executable code and data. So, instead, we
|
||||
// reserve three padding words after the code stub and set
|
||||
// _GLOBAL_OFFSET_TABLE_ there as if that particular place is the
|
||||
// beginning of a .got. This trick works because the runtime only uses
|
||||
// _GLOBAL_OFFSET_TABLE_ to access GOT[1],
|
||||
//
|
||||
// Due to the existence of this code stub, .opd must be readable,
|
||||
// writable and executable. Writable executable segment is bad from the
|
||||
// security standpoint, but we have no choice other than doing it.
|
||||
//
|
||||
// https://sourceware.org/git/?p=glibc.git;f=sysdeps/hppa/dl-machine.h;h=1d5194856601e025c#l223
|
||||
ub32 insn[] = {
|
||||
0x0e80'1095, // 1: ldw 0(r20), r21
|
||||
0xeaa0'c000, // bv r0(r21)
|
||||
0x0e88'1095, // ldw 4(r20), r21
|
||||
0xea9f'1fdd, // b,l 1b, r20
|
||||
0xd680'1c1e, // depwi 0, 31, 2, r20
|
||||
0x00c0'ffee, // (_dl_runtime_resolve's address)
|
||||
0xdead'beef, // (%r19 for _dl_runtime_resolve)
|
||||
0x0000'0000, // (_GLOBAL_OFFSET_TABLE_ is set to here)
|
||||
0x0000'0000, // (runtime uses this word to identify this ELF module)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(ctx.buf + this->shdr.sh_offset +
|
||||
ctx.extra.opd->symbols.size() * sizeof(ElfRel<E>),
|
||||
insn, sizeof(insn));
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+150
-160
@@ -31,7 +31,7 @@
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -42,8 +42,6 @@ using E = I386;
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return 0;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *loc;
|
||||
@@ -66,8 +64,9 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(ul32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -141,6 +140,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
@@ -150,10 +150,9 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -178,8 +177,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -203,6 +202,58 @@ static u32 relax_got32x(u8 *loc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Relax GD to LE
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32:
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Relax LD to LE
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
@@ -234,33 +285,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
@@ -295,72 +338,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax GD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $val, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = ctx.tp_addr - S - A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
// Relax LD to LE
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x65, 0x8b, 0x00, // mov %gs:(%eax), %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
i++;
|
||||
}
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
@@ -369,13 +356,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
// i386 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %eax.
|
||||
//
|
||||
// lea 0(%ebx), %eax
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// mov foo@GOTTPOFF(%ebx), %eax
|
||||
// nop
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x83;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
loc[-2] = 0x90;
|
||||
loc[-1] = 0xb8;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
@@ -398,17 +404,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -425,36 +426,28 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
case R_386_PC8:
|
||||
check(S + A, -(1 << 7), 1 << 7);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_PC16: {
|
||||
i64 val = S + A;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_386_PC16:
|
||||
check(S + A, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
@@ -489,19 +482,24 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
@@ -518,55 +516,47 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X: {
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_got32x(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
case R_386_GOT32X:
|
||||
// We always want to relax GOT32X even if --no-relax is given
|
||||
// because static PIE doesn't work without it.
|
||||
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_GD reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static)
|
||||
i++;
|
||||
else
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": TLS_LDM reloc must be followed by PLT or GOT32";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
|
||||
@@ -0,0 +1,686 @@
|
||||
// LoongArch is a new RISC ISA announced in 2021 by Loongson. The ISA
|
||||
// feels like a modernized MIPS with a hint of RISC-V flavor, although
|
||||
// it's not compatible with either one.
|
||||
//
|
||||
// While LoongArch is a fresh and clean ISA, its technological advantage
|
||||
// over other modern RISC ISAs such as RISC-V doesn't seem to be very
|
||||
// significant. It appears that the real selling point of LoongArch is
|
||||
// that the ISA is developed and controlled by a Chinese company,
|
||||
// reflecting a desire for domestic CPUs. Loongson is actively working on
|
||||
// bootstrapping the entire ecosystem for LoongArch, sending patches to
|
||||
// Linux, GCC, LLVM, etc.
|
||||
//
|
||||
// All instructions are 4 bytes long in LoongArch and aligned to 4-byte
|
||||
// boundaries. It has 32 general-purpose registers. Among these, $t0 - $t8
|
||||
// (aliases for $r12 - $r20) are temporary registers that we can use in
|
||||
// our PLT and range extension thunks.
|
||||
//
|
||||
// The psABI defines a few linker relaxations. We haven't supported them
|
||||
// yet.
|
||||
//
|
||||
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
|
||||
|
||||
#if MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
static u64 hi20(u64 val, u64 pc) {
|
||||
// A PC-relative address with a 32 bit offset is materialized in a
|
||||
// register with the following instructions:
|
||||
//
|
||||
// pcalau12i $rN, %pc_hi20(sym)
|
||||
// addi.d $rN, $rN, %lo12(sym)
|
||||
//
|
||||
// PCALAU12I materializes bits [63:12] by computing (pc + imm << 12)
|
||||
// and zero-clear [11:0]. ADDI.D sign-extends its 12 bit immediate and
|
||||
// add it to the register. To compensate the sign-extension, PCALAU12I
|
||||
// needs to materialize a 0x1000 larger value than the desired [63:12]
|
||||
// if [11:0] is sign-extended.
|
||||
//
|
||||
// This is similar but different from RISC-V because RISC-V's AUIPC
|
||||
// doesn't zero-clear [11:0].
|
||||
return bits(page(val + 0x800) - page(pc), 31, 12);
|
||||
}
|
||||
|
||||
static u64 hi64(u64 val, u64 pc) {
|
||||
// A PC-relative 64-bit address is materialized with the following
|
||||
// instructions for the large code model:
|
||||
//
|
||||
// pcalau12i $rN, %pc_hi20(sym)
|
||||
// addi.d $rM, $zero, %lo12(sym)
|
||||
// lu32i.d $rM, %pc64_lo20(sym)
|
||||
// lu52i.d $rM, $r12, %pc64_hi12(sym)
|
||||
// add.d $rN, $rN, $rM
|
||||
//
|
||||
// PCALAU12I computes (pc + imm << 12) to materialize a 64-bit value.
|
||||
// ADDI.D adds a sign-extended 12 bit value to a register. LU32I.D and
|
||||
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
|
||||
//
|
||||
// Compensating all the sign-extensions is a bit complicated.
|
||||
bool x = val & 0x800;
|
||||
bool y = (page(val + 0x800) - page(pc)) & 0x8000'0000;
|
||||
|
||||
if (x && !y)
|
||||
return val - 0x1'0000'0000;
|
||||
if (!x && y)
|
||||
return val + 0x1'0000'0000;
|
||||
return val;
|
||||
}
|
||||
|
||||
static u64 higher20(u64 val, u64 pc) {
|
||||
return bits(hi64(val, pc), 51, 32);
|
||||
}
|
||||
|
||||
static u64 highest12(u64 val, u64 pc) {
|
||||
return bits(hi64(val, pc), 63, 52);
|
||||
}
|
||||
|
||||
static void write_k12(u8 *loc, u32 val) {
|
||||
// opcode, [11:0], rj, rd
|
||||
*(ul32 *)loc &= 0b1111111111'000000000000'11111'11111;
|
||||
*(ul32 *)loc |= bits(val, 11, 0) << 10;
|
||||
}
|
||||
|
||||
static void write_k16(u8 *loc, u32 val) {
|
||||
// opcode, [15:0], rj, rd
|
||||
*(ul32 *)loc &= 0b111111'0000000000000000'11111'11111;
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 10;
|
||||
}
|
||||
|
||||
static void write_j20(u8 *loc, u32 val) {
|
||||
// opcode, [19:0], rd
|
||||
*(ul32 *)loc &= 0b1111111'00000000000000000000'11111;
|
||||
*(ul32 *)loc |= bits(val, 19, 0) << 5;
|
||||
}
|
||||
|
||||
static void write_d5k16(u8 *loc, u32 val) {
|
||||
// opcode, [15:0], rj, [20:16]
|
||||
*(ul32 *)loc &= 0b111111'0000000000000000'11111'00000;
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 10;
|
||||
*(ul32 *)loc |= bits(val, 20, 16);
|
||||
}
|
||||
|
||||
static void write_d10k16(u8 *loc, u32 val) {
|
||||
// opcode, [15:0], [25:16]
|
||||
*(ul32 *)loc &= 0b111111'0000000000000000'0000000000;
|
||||
*(ul32 *)loc |= bits(val, 15, 0) << 10;
|
||||
*(ul32 *)loc |= bits(val, 25, 16);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn_64[] = {
|
||||
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
|
||||
0x0011'bdad, // sub.d $t1, $t1, $t3
|
||||
0x28c0'01cf, // ld.d $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
|
||||
0x02ff'51ad, // addi.d $t1, $t1, -44 # .plt entry
|
||||
0x02c0'01cc, // addi.d $t0, $t2, %lo12(.got.plt) # &.got.plt
|
||||
0x0045'05ad, // srli.d $t1, $t1, 1 # .plt entry offset
|
||||
0x28c0'218c, // ld.d $t0, $t0, 8 # link map
|
||||
0x4c00'01e0, // jr $t3
|
||||
};
|
||||
|
||||
static const ul32 insn_32[] = {
|
||||
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
|
||||
0x0011'3dad, // sub.w $t1, $t1, $t3
|
||||
0x2880'01cf, // ld.w $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
|
||||
0x02bf'51ad, // addi.w $t1, $t1, -44 # .plt entry
|
||||
0x0280'01cc, // addi.w $t0, $t2, %lo12(.got.plt) # &.got.plt
|
||||
0x0044'89ad, // srli.w $t1, $t1, 2 # .plt entry offset
|
||||
0x2880'118c, // ld.w $t0, $t0, 4 # link map
|
||||
0x4c00'01e0, // jr $t3
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, E::is_64 ? insn_64 : insn_32, E::plt_hdr_size);
|
||||
write_j20(buf, hi20(gotplt, plt));
|
||||
write_k12(buf + 8, gotplt);
|
||||
write_k12(buf + 16, gotplt);
|
||||
}
|
||||
|
||||
static const ul32 plt_entry_64[] = {
|
||||
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
|
||||
0x28c0'01ef, // ld.d $t3, $t3, %lo12([email protected])
|
||||
0x4c00'01ed, // jirl $t1, $t3, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
static const ul32 plt_entry_32[] = {
|
||||
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
|
||||
0x2880'01ef, // ld.w $t3, $t3, %lo12([email protected])
|
||||
0x4c00'01ed, // jirl $t1, $t3, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
write_j20(buf, hi20(gotplt, plt));
|
||||
write_k12(buf + 4, gotplt);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
u64 got = sym.get_got_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, E::is_64 ? plt_entry_64 : plt_entry_32, E::plt_size);
|
||||
write_j20(buf, hi20(got, plt));
|
||||
write_k12(buf + 4, got);
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc + val) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_ADD8:
|
||||
*loc += val;
|
||||
break;
|
||||
case R_LARCH_ADD16:
|
||||
*(ul16 *)loc += val;
|
||||
break;
|
||||
case R_LARCH_ADD32:
|
||||
*(ul32 *)loc += val;
|
||||
break;
|
||||
case R_LARCH_ADD64:
|
||||
*(ul64 *)loc += val;
|
||||
break;
|
||||
case R_LARCH_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - val) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_SUB8:
|
||||
*loc -= val;
|
||||
break;
|
||||
case R_LARCH_SUB16:
|
||||
*(ul16 *)loc -= val;
|
||||
break;
|
||||
case R_LARCH_SUB32:
|
||||
*(ul32 *)loc -= val;
|
||||
break;
|
||||
case R_LARCH_SUB64:
|
||||
*(ul64 *)loc -= val;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_LARCH_64_PCREL:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
|
||||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
|
||||
rel.r_type == R_LARCH_ALIGN)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto check_branch = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val & 0b11)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
check(val, lo, hi);
|
||||
};
|
||||
|
||||
// Unlike other psABIs, the LoongArch ABI uses the same relocation
|
||||
// types to refer to GOT entries for thread-local symbols and regular
|
||||
// ones. Therefore, G may refer to a TLSGD or a regular GOT slot
|
||||
// depending on the symbol type.
|
||||
//
|
||||
// Note that as of August 2023, both GCC and Clang treat TLSLD relocs
|
||||
// as if they were TLSGD relocs for LoongArch, which is a clear bug.
|
||||
// We need to handle TLSLD relocs as synonyms for TLSGD relocs for the
|
||||
// sake of bug compatibility.
|
||||
auto get_got_idx = [&] {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
return sym.get_tlsgd_idx(ctx);
|
||||
return sym.get_got_idx(ctx);
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = get_got_idx() * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
if constexpr (E::is_64)
|
||||
*(ul32 *)loc = S + A;
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_LARCH_B16:
|
||||
check_branch(S + A - P, -(1 << 17), 1 << 17);
|
||||
write_k16(loc, (S + A - P) >> 2);
|
||||
break;
|
||||
case R_LARCH_B21:
|
||||
check_branch(S + A - P, -(1 << 22), 1 << 22);
|
||||
write_d5k16(loc, (S + A - P) >> 2);
|
||||
break;
|
||||
case R_LARCH_B26: {
|
||||
i64 val = S + A - P;
|
||||
if (val < -(1 << 27) || (1 << 27) <= val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
write_d10k16(loc, val >> 2);
|
||||
break;
|
||||
}
|
||||
case R_LARCH_ABS_LO12:
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_ABS_HI20:
|
||||
write_j20(loc, (S + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_ABS64_LO20:
|
||||
write_j20(loc, (S + A) >> 32);
|
||||
break;
|
||||
case R_LARCH_ABS64_HI12:
|
||||
write_k12(loc, (S + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_PCALA_LO12:
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
break;
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
write_j20(loc, higher20(S + A, P));
|
||||
break;
|
||||
case R_LARCH_PCALA64_HI12:
|
||||
write_k12(loc, highest12(S + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT_PC_LO12:
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
check(GOT + G + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
write_j20(loc, higher20(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_HI12:
|
||||
write_k12(loc, highest12(GOT + G + A, P));
|
||||
break;
|
||||
case R_LARCH_GOT_LO12:
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_HI20:
|
||||
write_j20(loc, (GOT + G + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_GOT64_LO20:
|
||||
write_j20(loc, (GOT + G + A) >> 32);
|
||||
break;
|
||||
case R_LARCH_GOT64_HI12:
|
||||
write_k12(loc, (GOT + G + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_LO12:
|
||||
write_k12(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_HI20:
|
||||
write_j20(loc, (S + A - ctx.tp_addr) >> 12);
|
||||
break;
|
||||
case R_LARCH_TLS_LE64_LO20:
|
||||
write_j20(loc, (S + A - ctx.tp_addr) >> 32);
|
||||
break;
|
||||
case R_LARCH_TLS_LE64_HI12:
|
||||
write_k12(loc, (S + A - ctx.tp_addr) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_PC_LO12:
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
check(sym.get_gottp_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_PC_LO20:
|
||||
write_j20(loc, higher20(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_PC_HI12:
|
||||
write_k12(loc, highest12(sym.get_gottp_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_IE_LO12:
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_LARCH_TLS_IE_HI20:
|
||||
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_LO20:
|
||||
write_j20(loc, (sym.get_gottp_addr(ctx) + A) >> 32);
|
||||
break;
|
||||
case R_LARCH_TLS_IE64_HI12:
|
||||
write_k12(loc, (sym.get_gottp_addr(ctx) + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_PC_HI20:
|
||||
check(sym.get_tlsgd_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(sym.get_tlsgd_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
case R_LARCH_TLS_GD_HI20:
|
||||
write_j20(loc, (sym.get_tlsgd_addr(ctx) + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc + S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD16:
|
||||
*(ul16 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD32:
|
||||
*(ul32 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD64:
|
||||
*(ul64 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB16:
|
||||
*(ul16 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB32:
|
||||
*(ul32 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB64:
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_LARCH_64_PCREL:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) + S + A);
|
||||
break;
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc + S + A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_ADD8:
|
||||
*loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD16:
|
||||
*(ul16 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD32:
|
||||
*(ul32 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_ADD64:
|
||||
*(ul64 *)loc += S + A;
|
||||
break;
|
||||
case R_LARCH_SUB6:
|
||||
*loc = (*loc & 0b1100'0000) | ((*loc - S - A) & 0b0011'1111);
|
||||
break;
|
||||
case R_LARCH_SUB8:
|
||||
*loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB16:
|
||||
*(ul16 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB32:
|
||||
*(ul32 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_SUB64:
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_TLS_DTPREL32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_LARCH_TLS_DTPREL64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) + S + A);
|
||||
break;
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
|
||||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
|
||||
rel.r_type == R_LARCH_ALIGN)
|
||||
continue;
|
||||
|
||||
if (record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
if constexpr (E::is_64)
|
||||
scan_absrel(ctx, sym, rel);
|
||||
else
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
assert(E::is_64);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B26:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_LARCH_GOT_HI20:
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_LARCH_TLS_IE_HI20:
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_PC_HI20:
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
case R_LARCH_TLS_GD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
case R_LARCH_64_PCREL:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_HI20:
|
||||
case R_LARCH_TLS_LE_LO12:
|
||||
case R_LARCH_TLS_LE64_LO20:
|
||||
case R_LARCH_TLS_LE64_HI12:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B16:
|
||||
case R_LARCH_B21:
|
||||
case R_LARCH_ABS_HI20:
|
||||
case R_LARCH_ABS_LO12:
|
||||
case R_LARCH_ABS64_LO20:
|
||||
case R_LARCH_ABS64_HI12:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
case R_LARCH_PCALA_LO12:
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
case R_LARCH_PCALA64_HI12:
|
||||
case R_LARCH_GOT_PC_LO12:
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
case R_LARCH_GOT64_PC_HI12:
|
||||
case R_LARCH_GOT_LO12:
|
||||
case R_LARCH_GOT64_LO20:
|
||||
case R_LARCH_GOT64_HI12:
|
||||
case R_LARCH_TLS_IE_PC_LO12:
|
||||
case R_LARCH_TLS_IE64_PC_LO20:
|
||||
case R_LARCH_TLS_IE64_PC_HI12:
|
||||
case R_LARCH_TLS_IE_LO12:
|
||||
case R_LARCH_TLS_IE64_LO20:
|
||||
case R_LARCH_TLS_IE64_HI12:
|
||||
case R_LARCH_ADD6:
|
||||
case R_LARCH_SUB6:
|
||||
case R_LARCH_ADD8:
|
||||
case R_LARCH_SUB8:
|
||||
case R_LARCH_ADD16:
|
||||
case R_LARCH_SUB16:
|
||||
case R_LARCH_ADD32:
|
||||
case R_LARCH_SUB32:
|
||||
case R_LARCH_ADD64:
|
||||
case R_LARCH_SUB64:
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x1a00'000c, // pcalau12i $t0, 0
|
||||
0x02c0'018c, // addi.d $t0, $t0, 0
|
||||
0x4c00'0180, // jirl $zero, $t0, 0
|
||||
0x0340'0000, // nop
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_j20(buf, hi20(S, P));
|
||||
write_k12(buf + 4, S);
|
||||
|
||||
buf += sizeof(insn);
|
||||
P += sizeof(insn);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+16
-24
@@ -12,7 +12,7 @@
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/m68k-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/m68k.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -56,8 +56,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -126,7 +126,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
@@ -221,17 +221,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -262,16 +257,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
@@ -294,35 +284,37 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_68K_GOTOFF32:
|
||||
case R_68K_GOTOFF16:
|
||||
case R_68K_GOTOFF8:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_68K_PLT32:
|
||||
case R_68K_PLT16:
|
||||
case R_68K_PLT8:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
case R_68K_TLS_GD16:
|
||||
case R_68K_TLS_GD8:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
case R_68K_TLS_LDM16:
|
||||
case R_68K_TLS_LDM8:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
case R_68K_TLS_IE16:
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
|
||||
+45
-57
@@ -30,7 +30,7 @@
|
||||
// 0x10000 (or 65536) bytes.
|
||||
//
|
||||
// Since each object file has its own .got2, %r30 refers to different
|
||||
// places in a merged .got2 for two functions that are came from different
|
||||
// places in a merged .got2 for two functions that came from different
|
||||
// input files. Therefore, %r30 makes sense only within a single function.
|
||||
//
|
||||
// Technically, we can reuse a %r30 value in our PLT if we create a PLT
|
||||
@@ -38,7 +38,7 @@
|
||||
// doesn't seems to be worth its complexity. Our PLT simply doesn't rely
|
||||
// on a %r30 value.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/ppc32-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/ppc32.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -46,15 +46,11 @@ namespace mold::elf {
|
||||
|
||||
using E = PPC32;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
@@ -130,8 +126,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -157,9 +153,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 got2 = 0;
|
||||
if (file.ppc32_got2)
|
||||
got2 = file.ppc32_got2->output_section->shdr.sh_addr + file.ppc32_got2->offset;
|
||||
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -178,7 +172,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC_ADDR14:
|
||||
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
|
||||
@@ -201,16 +195,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc |= bits(S + A, 31, 2) << 2;
|
||||
break;
|
||||
case R_PPC_PLT16_LO:
|
||||
*(ub16 *)loc = lo(G + GOT - got2 - A);
|
||||
*(ub16 *)loc = lo(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT16_HI:
|
||||
*(ub16 *)loc = hi(G + GOT - got2 - A);
|
||||
*(ub16 *)loc = hi(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT16_HA:
|
||||
*(ub16 *)loc = ha(G + GOT - got2 - A);
|
||||
*(ub16 *)loc = ha(G + GOT - A - GOT2);
|
||||
break;
|
||||
case R_PPC_PLT32:
|
||||
*(ub32 *)loc = G + GOT - got2 - A;
|
||||
*(ub32 *)loc = G + GOT - A - GOT2;
|
||||
break;
|
||||
case R_PPC_REL14:
|
||||
*(ub32 *)loc |= bits(S + A - P, 15, 2) << 2;
|
||||
@@ -299,17 +293,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -341,18 +330,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
@@ -385,30 +369,32 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC_PLT16_HI:
|
||||
case R_PPC_PLT16_HA:
|
||||
case R_PPC_PLT32:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_PLTREL24:
|
||||
case R_PPC_PLTREL32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC_GOT_TLSGD16:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC_GOT_TLSLD16:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC_GOT_TPREL16:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC_TPREL16_LO:
|
||||
case R_PPC_TPREL16_HI:
|
||||
case R_PPC_TPREL16_HA:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_LOCAL24PC:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
case R_PPC_TPREL16_LO:
|
||||
case R_PPC_TPREL16_HI:
|
||||
case R_PPC_TPREL16_HA:
|
||||
case R_PPC_DTPREL16_LO:
|
||||
case R_PPC_DTPREL16_HI:
|
||||
case R_PPC_DTPREL16_HA:
|
||||
@@ -423,8 +409,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
static const ub32 local_thunk[] = {
|
||||
// Get this thunk's address
|
||||
0x7c08'02a6, // mflr r0
|
||||
@@ -443,22 +427,26 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(plt_entry));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_entry, sizeof(plt_entry));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - get_addr(i) - 8;
|
||||
loc[4] |= higha(val);
|
||||
loc[5] |= lo(val);
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_plt(ctx)) {
|
||||
u64 got =
|
||||
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
|
||||
i64 val = got - P - 8;
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) - get_addr(i) - 8;
|
||||
loc[4] |= higha(val);
|
||||
loc[5] |= lo(val);
|
||||
i64 val = sym->get_addr(ctx) - P - 8;
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+131
-99
@@ -5,14 +5,15 @@
|
||||
//
|
||||
// Even though they are similiar, ELFv1 isn't only different from ELFv2 in
|
||||
// endianness. The most notable difference is, in ELFv1, a function
|
||||
// pointer doesn't directly refer the entry point of a function but
|
||||
// instead refers a data structure so-called "function descriptor".
|
||||
// pointer doesn't directly refer to the entry point of a function but
|
||||
// instead refers to a data structure so-called "function descriptor".
|
||||
//
|
||||
// The function descriptor is essentially a pair of a function entry point
|
||||
// address and a value that should be set to %r2 before calling that
|
||||
// function. There is actually a third member, but we don't need to know
|
||||
// what that is. In total, the function descriptor is 24 bytes long. Here
|
||||
// is why we need it.
|
||||
// function. There is also a third member for "the environment pointer for
|
||||
// languages such as Pascal and PL/1" according to the psABI, but it looks
|
||||
// like no one acutally uses it. In total, the function descriptor is 24
|
||||
// bytes long. Here is why we need it.
|
||||
//
|
||||
// PPC generally lacks PC-relative data access instructions. Position-
|
||||
// independent code sets GOT + 0x8000 to %r2 and access global variables
|
||||
@@ -25,8 +26,8 @@
|
||||
//
|
||||
// In this way, you can't call a function just by knowing the function's
|
||||
// entry point address. You also need to know a proper %r2 value for the
|
||||
// function. This is why a function pointer refers a tuple of an address
|
||||
// and a %r2 value.
|
||||
// function. This is why a function pointer refers to a tuple of an
|
||||
// address and a %r2 value.
|
||||
//
|
||||
// If a function call is made through PLT, PLT takes care of restoring %r2.
|
||||
// Therefore, the caller has to restore %r2 only for function calls
|
||||
@@ -39,8 +40,10 @@
|
||||
// few different addresses for different purposes. It may not only have an
|
||||
// entry point address but may also have PLT and/or GOT addresses.
|
||||
// In PPCV1, it may have an OPD address in addition to these. OPD address
|
||||
// is used for relocations that refers the address of a function as a
|
||||
// is used for relocations that refers to the address of a function as a
|
||||
// function pointer.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -51,15 +54,11 @@ namespace mold::elf {
|
||||
|
||||
using E = PPC64V1;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
@@ -73,29 +72,55 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
0xe84b'0024, // ld r2,36(r11)
|
||||
0x7d88'03a6, // mtlr r12
|
||||
0x7d62'5a14, // add r11,r2,r11
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
0xe98b'0000, // ld r12,0(r11)
|
||||
0xe84b'0008, // ld r2,8(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0010, // ld r11,16(r11)
|
||||
0x4e80'0420, // bctr
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub64 *)(buf + 44) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ub32 *)(buf + 16) |= higha(val);
|
||||
*(ub32 *)(buf + 20) |= lo(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
i64 offset = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4;
|
||||
*(ub32 *)(buf + 0) = 0x3800'0000 | sym.get_plt_idx(ctx); // li %r0, PLT_INDEX
|
||||
*(ub32 *)(buf + 4) = 0x4b00'0000 | (offset & 0x00ff'ffff); // b plt0
|
||||
ub32 *loc = (ub32 *)buf;
|
||||
i64 idx = sym.get_plt_idx(ctx);
|
||||
|
||||
// The PPC64 ELFv1 ABI requires PLT entries to be vary in size depending
|
||||
// on their indices. Unlike other targets, .got.plt is filled not by us
|
||||
// but by the loader, so we don't have a control over where the initial
|
||||
// call to the PLT entry jumps to. So we need to strictly follow the PLT
|
||||
// section layout as the loader expect it to be.
|
||||
if (idx < 0x8000) {
|
||||
static const ub32 insn[] = {
|
||||
0x3800'0000, // li r0, PLT_INDEX
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= idx;
|
||||
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
|
||||
} else {
|
||||
static const ub32 insn[] = {
|
||||
0x3c00'0000, // lis r0, PLT_INDEX@high
|
||||
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
loc[0] |= high(idx);
|
||||
loc[1] |= lo(idx);
|
||||
loc[2] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 8) & 0x00ff'ffff;
|
||||
}
|
||||
}
|
||||
|
||||
// .plt.got is not necessary on PPC64 because range extension thunks
|
||||
@@ -104,8 +129,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -158,10 +183,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, dynrel);
|
||||
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ub16 *)loc = ha(S + A - TOC);
|
||||
@@ -169,12 +194,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TOC16_LO:
|
||||
*(ub16 *)loc = lo(S + A - TOC);
|
||||
break;
|
||||
case R_PPC64_TOC16_DS: {
|
||||
i64 val = S + A - TOC;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= val & 0xfffc;
|
||||
case R_PPC64_TOC16_DS:
|
||||
check(S + A - TOC, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
@@ -194,6 +217,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)(loc + 4) = 0xe841'0028; // ld r2, 40(r1)
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_PPC64_REL64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -233,12 +259,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ub16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
@@ -263,17 +289,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -321,48 +342,45 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT | NEEDS_OPD,
|
||||
std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
|
||||
|
||||
// Any relocation except R_PPC64_REL24 is considered as an
|
||||
// address-taking relocation.
|
||||
if (rel.r_type != R_PPC64_REL24 && sym.get_type() == STT_FUNC)
|
||||
sym.flags.fetch_or(NEEDS_OPD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
@@ -375,8 +393,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
@@ -394,8 +410,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
@@ -450,26 +464,27 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ub32 *loc = (ub32 *)(buf + i * E::thunk_size);
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
if (sym.has_got(ctx)) {
|
||||
memcpy(loc, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
i64 val = sym.get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
} else if(sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
i64 val = sym.get_gotplt_addr(ctx) - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_got(ctx)) {
|
||||
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else if(sym->has_plt(ctx)) {
|
||||
i64 val = sym->get_gotplt_addr(ctx) - ctx.extra.TOC->value;
|
||||
memcpy(buf, plt_thunk, sizeof(plt_thunk));
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
*(ub32 *)buf |= higha(val);
|
||||
*(ub32 *)(buf + 4) |= lo(val);
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +519,7 @@ struct OpdSymbol {
|
||||
};
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
@@ -521,10 +536,10 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
// However, in reality, .opd isn't a normal input section. It needs many
|
||||
// special treatments as follows:
|
||||
//
|
||||
// 1. A function symbol refers not a .text but an .opd. Its address works
|
||||
// fine for address-taking relocations such as R_PPC64_ADDR64. However,
|
||||
// R_PPC64_REL24 (which is used for branch instruction) needs a
|
||||
// function's real address instead of the function's .opd address.
|
||||
// 1. A function symbol refers to not a .text but an .opd. Its address
|
||||
// works fine for address-taking relocations such as R_PPC64_ADDR64.
|
||||
// However, R_PPC64_REL24 (which is used for branch instruction) needs
|
||||
// a function's real address instead of the function's .opd address.
|
||||
// We need to read .opd contents to find out a function entry point
|
||||
// address to apply R_PPC64_REL24.
|
||||
//
|
||||
@@ -532,9 +547,9 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
// are taken. Just copying input .opd sections to an output would
|
||||
// produces lots of dead .opd entries.
|
||||
//
|
||||
// 3. In this design, all function symbols refer an .opd section, and that
|
||||
// doesn't work well with graph traversal optimizations such as garbage
|
||||
// collection or identical comdat folding. For example, garbage
|
||||
// 3. In this design, all function symbols refer to an .opd section, and
|
||||
// that doesn't work well with graph traversal optimizations such as
|
||||
// garbage collection or identical comdat folding. For example, garbage
|
||||
// collector would mark an .opd alive which in turn mark all functions
|
||||
// thatare referenced by .opd as alive, effectively keeping all
|
||||
// functions as alive.
|
||||
@@ -547,11 +562,11 @@ get_opd_sym_at(Context<E> &ctx, std::span<OpdSymbol> syms, u64 offset) {
|
||||
//
|
||||
// So, in this function, we undo what the compiler did to .opd. We remove
|
||||
// function symbols from .opd and reattach them to their function entry
|
||||
// points. We also rewrite relocations that directly refer an input .opd
|
||||
// section so that they refer function symbols instead. We then mark input
|
||||
// .opd sections as dead.
|
||||
// points. We also rewrite relocations that directly refer to an input
|
||||
// .opd section so that they refer to function symbols instead. We then
|
||||
// mark input .opd sections as dead.
|
||||
//
|
||||
// After this function, we mark symbols with the NEEDS_OPD flag if the
|
||||
// After this function, we mark symbols with the NEEDS_PPC_OPD flag if the
|
||||
// symbol needs an .opd entry. We then create an output .opd just like we
|
||||
// do for .plt or .got.
|
||||
void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
@@ -599,7 +614,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
if (sym.get_input_section() != opd)
|
||||
continue;
|
||||
|
||||
Symbol<E> *real_sym = get_opd_sym_at(ctx, opd_syms, r.r_addend);
|
||||
Symbol<E> *real_sym = get_opd_sym_at(opd_syms, r.r_addend);
|
||||
if (!real_sym)
|
||||
Fatal(ctx) << *isec << ": cannot find a symbol in .opd for " << r
|
||||
<< " at offset 0x" << std::hex << (u64)r.r_addend;
|
||||
@@ -612,21 +627,21 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// When a function is exported, the dynamic symbol for the function should
|
||||
// refers the function's .opd entry. This function marks such symbols with
|
||||
// NEEDS_OPD.
|
||||
// refers to the function's .opd entry. This function marks such symbols
|
||||
// with NEEDS_PPC_OPD.
|
||||
void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file && sym->is_exported)
|
||||
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
|
||||
sym->flags |= NEEDS_OPD;
|
||||
sym->flags |= NEEDS_PPC_OPD;
|
||||
});
|
||||
|
||||
// Functions referenced by the ELF header also have to have .opd entries.
|
||||
auto mark = [&](std::string_view name) {
|
||||
if (!name.empty())
|
||||
if (Symbol<E> &sym = *get_symbol(ctx, name); !sym.is_imported)
|
||||
sym.flags |= NEEDS_OPD;
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
};
|
||||
|
||||
mark(ctx.arg.entry);
|
||||
@@ -640,13 +655,30 @@ void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
this->shdr.sh_size += ENTRY_SIZE;
|
||||
}
|
||||
|
||||
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
if (ctx.arg.pic)
|
||||
return symbols.size() * 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
ElfRel<E> *rel = nullptr;
|
||||
if (ctx.arg.pic)
|
||||
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
*buf++ = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
|
||||
*buf++ = addr;
|
||||
*buf++ = ctx.extra.TOC->value;
|
||||
*buf++ = 0;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
u64 loc = sym->get_opd_addr(ctx);
|
||||
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
|
||||
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+211
-100
@@ -1,21 +1,29 @@
|
||||
// This file implements the PowerPC ELFv2 ABI which was standardized in
|
||||
// 2014. Modern little-endian PowerPC systems are based on this ABI.
|
||||
// The ABI is often referred to as "ppc64le". This shouldn't be confused
|
||||
// with "ppc64" which refers the original, big-endian PowerPC systems.
|
||||
// with "ppc64" which refers to the original, big-endian PowerPC systems.
|
||||
//
|
||||
// PPC64 is a bit tricky to support because PC-relative load/store
|
||||
// instructions are generally not available. Therefore, it's not easy
|
||||
// for position-independent code to load a value from, for example,
|
||||
// .got, as we can't do that with [PC + the offset to the .got entry].
|
||||
// instructions hadn't been available until Power10 which debuted in 2021.
|
||||
// Prior to Power10, it wasn't trivial for position-independent code (PIC)
|
||||
// to load a value from, for example, .got, as we can't do that with [PC +
|
||||
// the offset to the .got entry].
|
||||
//
|
||||
// We can get the program counter by the following four instructions
|
||||
// In the following, I'll explain how PIC is supported on pre-Power10
|
||||
// systems first and then explain what has changed with Power10.
|
||||
//
|
||||
//
|
||||
// Position-independent code on Power9 or earlier:
|
||||
//
|
||||
// We can get the program counter on older PPC64 systems with the
|
||||
// following four instructions
|
||||
//
|
||||
// mflr r1 // save the current link register to r1
|
||||
// bl .+4 // branch to the next instruction as if it were a function
|
||||
// mflr r0 // copy the return address to r0
|
||||
// mtlr r1 // restore the original link register value
|
||||
//
|
||||
// , but that's too expensive to do if we do this for each load/store.
|
||||
// , but it's too expensive to do if we do this for each load/store.
|
||||
//
|
||||
// As a workaround, most functions are compiled in such a way that r2 is
|
||||
// assumed to always contain the address of .got + 0x8000. With this, we
|
||||
@@ -27,9 +35,9 @@
|
||||
// calls are usually within the same ELF module, so this mechanism is
|
||||
// efficient.
|
||||
//
|
||||
// In PPC64, a function usually have two entry points, global and local.
|
||||
// The global entry point is usually 8 bytes precedes the local entry
|
||||
// point. In between is the following instructions:
|
||||
// A function compiled for pre-Power10 usually has two entry points,
|
||||
// global and local. The global entry point usually 8 bytes precedes
|
||||
// the local entry point. In between is the following instructions:
|
||||
//
|
||||
// addis r2, r12, .TOC.@ha
|
||||
// addi r2, r2, .TOC.@lo + 4;
|
||||
@@ -46,12 +54,31 @@
|
||||
// to that address. Then the callee computes its own TOC pointer using
|
||||
// r12.
|
||||
//
|
||||
//
|
||||
// Position-independent code on Power10:
|
||||
//
|
||||
// Power10 added 8-bytes-long instructions to the ISA. Some of them are
|
||||
// PC-relative load/store instructions that take 34 bits offsets.
|
||||
// Functions compiled with `-mcpu=power10` use these instructions for PIC.
|
||||
// r2 does not have a special meaning in such fucntions.
|
||||
//
|
||||
// When a fucntion compiled for Power10 calls a function that uses the TOC
|
||||
// pointer, we need to compute a correct value for TOC and set it to r2
|
||||
// before transferring the control to the callee. Thunks are responsible
|
||||
// for doing it.
|
||||
//
|
||||
// `_NOTOC` relocations such as `R_PPC64_REL24_NOTOC` indicate that the
|
||||
// callee does not use TOC (i.e. compiled with `-mcpu=power10`). If a
|
||||
// function using TOC is referenced via a `_NOTOC` relocation, that call
|
||||
// is made through a range extension thunk.
|
||||
//
|
||||
//
|
||||
// Note on section names: the PPC64 psABI uses a weird naming convention
|
||||
// which calls .got.plt .plt. We ignored that part because it's just
|
||||
// confusing. Since the runtime only cares about segments, we should be
|
||||
// able to name sections whatever we want.
|
||||
//
|
||||
// https://openpowerfoundation.org/specifications/64bitelfabi/
|
||||
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -59,15 +86,17 @@ namespace mold::elf {
|
||||
|
||||
using E = PPC64V2;
|
||||
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
static u64 higher(u64 x) { return (x >> 32) & 0xffff; }
|
||||
static u64 highera(u64 x) { return ((x + 0x8000) >> 32) & 0xffff; }
|
||||
static u64 highest(u64 x) { return x >> 48; }
|
||||
static u64 highesta(u64 x) { return (x + 0x8000) >> 48; }
|
||||
static u64 lo(u64 x) { return x & 0xffff; }
|
||||
static u64 hi(u64 x) { return x >> 16; }
|
||||
static u64 ha(u64 x) { return (x + 0x8000) >> 16; }
|
||||
static u64 high(u64 x) { return (x >> 16) & 0xffff; }
|
||||
static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
static void write34(u8 *loc, u64 x) {
|
||||
ul32 *buf = (ul32 *)loc;
|
||||
buf[0] = (buf[0] & 0xfffc'0000) | bits(x, 33, 16);
|
||||
buf[1] = (buf[1] & 0xffff'0000) | bits(x, 15, 0);
|
||||
}
|
||||
|
||||
// .plt is used only for lazy symbol resolution on PPC64. All PLT
|
||||
// calls are made via range extension thunks even if they are within
|
||||
@@ -85,25 +114,26 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
0x7c08'03a6, // mtlr r0
|
||||
|
||||
// Compute the PLT entry index
|
||||
0xe80b'002c, // ld r0, 44(r11)
|
||||
0x7d8b'6050, // subf r12, r11, r12
|
||||
0x7d60'5a14, // add r11, r0, r11
|
||||
0x380c'ffcc, // addi r0, r12, -52
|
||||
0x398c'ffd4, // addi r12, r12, -44
|
||||
0x7c0b'6050, // subf r0, r11, r12
|
||||
0x7800'f082, // rldicl r0, r0, 62, 2
|
||||
|
||||
// Compute the address of .got.plt
|
||||
0x3d6b'0000, // addis r11, r11, GOTPLT_OFFSET@ha
|
||||
0x396b'0000, // addi r11, r11, GOTPLT_OFFSET@lo
|
||||
|
||||
// Load .got.plt[0] and .got.plt[1] and branch to .got.plt[0]
|
||||
0xe98b'0000, // ld r12, 0(r11)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0xe96b'0008, // ld r11, 8(r11)
|
||||
0x4e80'0420, // bctr
|
||||
|
||||
// .quad .got.plt - .plt - 8
|
||||
0x0000'0000,
|
||||
0x0000'0000,
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul64 *)(buf + 52) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
|
||||
i64 val = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 8;
|
||||
*(ul32 *)(buf + 28) |= higha(val);
|
||||
*(ul32 *)(buf + 32) |= lo(val);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -120,8 +150,8 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -144,10 +174,11 @@ void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
||||
i64 val = sym.esym().ppc_local_entry;
|
||||
assert(val <= 7);
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
if (val == 7)
|
||||
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
||||
|
||||
if (val == 0 || val == 1)
|
||||
return 0;
|
||||
return 1 << val;
|
||||
}
|
||||
|
||||
@@ -168,13 +199,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
@@ -182,12 +206,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ul16 *)loc = ha(S + A - TOC);
|
||||
@@ -199,22 +226,37 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TOC16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = S + A - P + get_local_entry_offset(ctx, sym);
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
|
||||
i64 val = r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
// If a callee is an external function, PLT saves %r2 to the
|
||||
// caller's r2 save slot. We need to restore it after function
|
||||
// return. To do so, there's usually a NOP as a placeholder
|
||||
// after a BL. 0x6000'0000 is a NOP.
|
||||
if (sym.has_plt(ctx) && *(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
// The thunk saves %r2 to the caller's r2 save slot. We need to
|
||||
// restore it after function return. To do so, there's usually a
|
||||
// NOP as a placeholder after a BL. 0x6000'0000 is a NOP.
|
||||
if (*(ul32 *)(loc + 4) == 0x6000'0000)
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
} else {
|
||||
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
break;
|
||||
case R_PPC64_REL24_NOTOC:
|
||||
if (sym.has_plt(ctx) || sym.esym().uses_toc()) {
|
||||
i64 val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_REL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -236,38 +278,63 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
*(ul16 *)loc |= (G + GOT - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
write34(loc, G + GOT - P);
|
||||
break;
|
||||
case R_PPC64_PCREL34:
|
||||
write34(loc, S + A - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_gottp_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
write34(loc, sym.get_gottp_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
*(ul16 *)loc = ha(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
*(ul16 *)loc = lo(sym.get_tlsgd_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
write34(loc, sym.get_tlsgd_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
*(ul16 *)loc = ha(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
*(ul16 *)loc = lo(ctx.got->get_tlsld_addr(ctx) - TOC);
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
write34(loc, ctx.got->get_tlsld_addr(ctx) - P);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
write34(loc, S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
*(ul16 *)loc = ha(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
case R_PPC64_TPREL34:
|
||||
write34(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_PLTCALL_NOTOC:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
@@ -284,17 +351,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -342,18 +404,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
@@ -363,21 +420,38 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_PPC64_REL24_NOTOC:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
ctx.extra.is_power10 = true;
|
||||
break;
|
||||
case R_PPC64_PLT16_HA:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
case R_PPC64_PLT_PCREL34:
|
||||
case R_PPC64_PLT_PCREL34_NOTOC:
|
||||
case R_PPC64_GOT_PCREL34:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSGD16_HA:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
case R_PPC64_GOT_TLSGD_PCREL34:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_PPC64_GOT_TLSLD16_HA:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
case R_PPC64_GOT_TLSLD_PCREL34:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL34:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
case R_PPC64_TOC16_LO:
|
||||
@@ -388,10 +462,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_PLT16_HI:
|
||||
case R_PPC64_PLT16_LO:
|
||||
case R_PPC64_PLT16_LO_DS:
|
||||
case R_PPC64_PCREL34:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_PLTCALL_NOTOC:
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
@@ -400,6 +475,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL34:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -409,53 +485,88 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
// Save r2 to the r2 save slot reserved in the caller's stack frame
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
|
||||
// Jump to a PLT entry
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
0xe98c'0000, // ld r12, foo@gotplt@toc@lo(r12)
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static const ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
0xe580'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
// Jump to a local entry point
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static const ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
0x3980'0000,
|
||||
0x7d89'03a6, // mtctr r12
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk_power10));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk_power10));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
ul32 *loc = (ul32 *)(buf + i * E::thunk_size);
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
if (sym.has_plt(ctx)) {
|
||||
memcpy(loc, plt_thunk, sizeof(plt_thunk));
|
||||
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
|
||||
i64 val = got - ctx.extra.TOC->value;
|
||||
loc[1] |= higha(val);
|
||||
loc[2] |= lo(val);
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_plt(ctx)) {
|
||||
u64 got =
|
||||
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
|
||||
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, plt_thunk_power10, E::thunk_size);
|
||||
write34(buf + 8, got - P - 8);
|
||||
} else {
|
||||
memcpy(buf, plt_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 8) |= higha(got - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
||||
}
|
||||
} else {
|
||||
memcpy(loc, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx) + get_local_entry_offset(ctx, sym) -
|
||||
ctx.extra.TOC->value;
|
||||
loc[0] |= higha(val);
|
||||
loc[1] |= lo(val);
|
||||
u64 S = sym->get_addr(ctx);
|
||||
if (ctx.extra.is_power10) {
|
||||
memcpy(buf, local_thunk_power10, E::thunk_size);
|
||||
write34(buf + 8, S - P - 8);
|
||||
} else {
|
||||
memcpy(buf, local_thunk, E::thunk_size);
|
||||
*(ul32 *)(buf + 8) |= higha(S - TOC);
|
||||
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
||||
}
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+610
-257
File diff suppressed because it is too large.
Load diff
+124
-173
@@ -12,7 +12,7 @@
|
||||
// As an instruction set, s390x isn't particularly odd. It has 16 general-
|
||||
// purpose registers. Instructions are 2, 4 or 6 bytes long and always
|
||||
// aligned to 2 bytes boundaries. Despite unfamiliarty, I found that it
|
||||
// just feels like a 64-bit i386 in a parallel universe.
|
||||
// just feels like an x86-64 in a parallel universe.
|
||||
//
|
||||
// Here is the register usage in this ABI:
|
||||
//
|
||||
@@ -25,15 +25,15 @@
|
||||
// a1: upper 32 bits of TP (thread pointer)
|
||||
// a2: lower 32 bits of TP (thread pointer)
|
||||
//
|
||||
// TLS is supported on s390x in the same way as it is on other targets
|
||||
// with one exeption. On other targets, __tls_get_addr is used to get an
|
||||
// address of a thread-local variable. On s390x, __tls_get_offset is used
|
||||
// instead. The difference is __tls_get_offset returns an address of a
|
||||
// thread-local variable as an offset from TP. So we need to add TP to a
|
||||
// return value before use. I don't know why it is different, but that is
|
||||
// the way it is.
|
||||
// Thread-local storage (TLS) is supported on s390x in the same way as it
|
||||
// is on other targets with one exeption. On other targets, __tls_get_addr
|
||||
// is used to get an address of a thread-local variable. On s390x,
|
||||
// __tls_get_offset is used instead. The difference is __tls_get_offset
|
||||
// returns an address of a thread-local variable as an offset from TP. So
|
||||
// we need to add TP to a return value before use. I don't know why it is
|
||||
// different, but that is the way it is.
|
||||
//
|
||||
// https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/s390x.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -48,16 +48,23 @@ static void write_mid20(u8 *loc, u64 val) {
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static u8 insn[] = {
|
||||
// Compute PLT_INDEX
|
||||
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
|
||||
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
|
||||
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
|
||||
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
|
||||
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
|
||||
// Branch to _dl_runtime_resolve
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
|
||||
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
|
||||
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x07, 0x00, 0x07, 0x00, // nopr; nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 8) = (ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 6) >> 1;
|
||||
*(ub32 *)(buf + 26) =
|
||||
(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 24) >> 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -65,15 +72,12 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
|
||||
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
|
||||
0xc0, 0x01, 0, 0, 0, 0, // lgfi %r0, PLT_INDEX
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x07, 0x00, 0x07, 0x00, 0x07, 0x00, // nopr; nopr; nopr
|
||||
0x0d, 0x01, // basr %r0, %r1
|
||||
0x07, 0x00, // nopr
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = (sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx)) >> 1;
|
||||
*(ub32 *)(buf + 14) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -90,8 +94,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -149,136 +153,97 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_390_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_390_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_12: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 12);
|
||||
*(ul16 *)loc = bits(val, 11, 0);
|
||||
case R_390_12:
|
||||
check(S + A, 0, 1 << 12);
|
||||
*(ul16 *)loc |= bits(S + A, 11, 0);
|
||||
break;
|
||||
}
|
||||
case R_390_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_20: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
case R_390_20:
|
||||
check(S + A, 0, 1 << 20);
|
||||
write_mid20(loc, S + A);
|
||||
break;
|
||||
}
|
||||
case R_390_32:
|
||||
case R_390_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_PLT32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PLT12DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 12), 1 << 12);
|
||||
*(ul16 *)loc = (val >> 1) & 0x0fff;
|
||||
case R_390_PLT12DBL:
|
||||
check_dbl(S + A - P, -(1 << 12), 1 << 12);
|
||||
*(ul16 *)loc |= bits(S + A - P, 12, 1);
|
||||
break;
|
||||
}
|
||||
case R_390_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_390_PC32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_PC32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_390_PC64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PLT16DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = val >> 1;
|
||||
case R_390_PLT16DBL:
|
||||
check_dbl(S + A - P, -(1 << 16), 1 << 16);
|
||||
*(ub16 *)loc = (S + A - P) >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_PC24DBL:
|
||||
case R_390_PLT24DBL: {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1 << 24), 1 << 24);
|
||||
*(ub32 *)loc |= bits(val, 24, 1);
|
||||
case R_390_PLT24DBL:
|
||||
check_dbl(S + A - P, -(1 << 24), 1 << 24);
|
||||
*(ub32 *)loc |= bits(S + A - P, 24, 1);
|
||||
break;
|
||||
}
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PLT32DBL:
|
||||
if (ctx.is_static && &sym == ctx.tls_get_offset) {
|
||||
// __tls_get_offset() in libc.a is stub code that calls abort().
|
||||
// So we provide a replacement function.
|
||||
*(ub32 *)loc = (ctx.extra.tls_get_offset->shdr.sh_addr - P) >> 1;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
}
|
||||
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (S + A - P) >> 1;
|
||||
break;
|
||||
case R_390_GOT12:
|
||||
case R_390_GOTPLT12: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 12);
|
||||
*(ul16 *)loc = bits(val, 11, 0);
|
||||
case R_390_GOTPLT12:
|
||||
check(G + A, 0, 1 << 12);
|
||||
*(ul16 *)loc |= bits(G + A, 11, 0);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT16:
|
||||
case R_390_GOTPLT16: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_GOTPLT16:
|
||||
check(G + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = G + A;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT20:
|
||||
case R_390_GOTPLT20: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1 << 20);
|
||||
write_mid20(loc, val);
|
||||
case R_390_GOTPLT20:
|
||||
check(G + A, 0, 1 << 20);
|
||||
write_mid20(loc, G + A);
|
||||
break;
|
||||
}
|
||||
case R_390_GOT32:
|
||||
case R_390_GOTPLT32: {
|
||||
i64 val = G + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_GOTPLT32:
|
||||
check(G + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = G + A;
|
||||
break;
|
||||
}
|
||||
case R_390_GOT64:
|
||||
case R_390_GOTPLT64:
|
||||
*(ub64 *)loc = G + A;
|
||||
break;
|
||||
case R_390_GOTOFF16:
|
||||
case R_390_PLTOFF16: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
case R_390_PLTOFF16:
|
||||
check(S + A - GOT, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - GOT;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF32:
|
||||
case R_390_PLTOFF32: {
|
||||
i64 val = S + A - GOT;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_PLTOFF32:
|
||||
check(S + A - GOT, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - GOT;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTOFF64:
|
||||
case R_390_PLTOFF64:
|
||||
*(ub64 *)loc = S + A - GOT;
|
||||
@@ -286,18 +251,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_GOTPC:
|
||||
*(ub64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_390_GOTPCDBL: {
|
||||
i64 val = GOT + A - P;
|
||||
check_dbl(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
case R_390_GOTPCDBL:
|
||||
check_dbl(GOT + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + A - P) >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_GOTENT: {
|
||||
i64 val = GOT + G + A - P;
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
case R_390_GOTENT:
|
||||
check(GOT + G + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + G + A - P) >> 1;
|
||||
break;
|
||||
}
|
||||
case R_390_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
@@ -313,19 +274,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_TLS_GD32:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else if (sym.has_gottp(ctx))
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GD64:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ub64 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else if (sym.has_gottp(ctx))
|
||||
*(ub64 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_GDCALL:
|
||||
if (!sym.has_tlsgd(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
// do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// lg %r2, 0(%r2, %r12)
|
||||
static u8 insn[] = { 0xe3, 0x22, 0xc0, 0x00, 0x00, 0x04 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
} else {
|
||||
// nop
|
||||
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
@@ -350,8 +322,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_390_TLS_LDCALL:
|
||||
if (!ctx.got->has_tlsld(ctx)) {
|
||||
static u8 nop[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, nop, sizeof(nop));
|
||||
// nop
|
||||
static u8 insn[] = { 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00 };
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -366,17 +339,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -392,12 +360,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_390_32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_390_64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub64 *)loc = *val;
|
||||
@@ -426,18 +392,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
@@ -473,7 +434,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_GOTPC:
|
||||
case R_390_GOTPCDBL:
|
||||
case R_390_GOTENT:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_390_PLT12DBL:
|
||||
case R_390_PLT16DBL:
|
||||
@@ -485,57 +446,47 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_PLTOFF32:
|
||||
case R_390_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_390_TLS_GOTIE20:
|
||||
case R_390_TLS_IEENT:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_390_TLS_GD32:
|
||||
case R_390_TLS_GD64:
|
||||
if (!relax_tlsgd(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
// We always want to relax calls to __tls_get_offset() in statically-
|
||||
// linked executables because __tls_get_offset() in libc.a just calls
|
||||
// abort().
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static) {
|
||||
// Do nothing
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
case R_390_TLS_LDM64:
|
||||
if (!relax_tlsld(ctx))
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
ctx.needs_tlsld = true;
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LE32:
|
||||
case R_390_TLS_LE64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
case R_390_TLS_LDO64:
|
||||
case R_390_TLS_GDCALL:
|
||||
case R_390_TLS_LDCALL:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __tls_get_offset() in libc.a just calls abort(), assuming that the
|
||||
// linker always relaxes TLS calls for statically-linkd executables.
|
||||
// We don't always do that because we believe --relax and --static
|
||||
// should be orthogonal.
|
||||
//
|
||||
// This section provides a replacement for __tls_get_offset() in libc.a.
|
||||
void S390XTlsGetOffsetSection::copy_buf(Context<E> &ctx) {
|
||||
static const u8 insn[] = {
|
||||
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT
|
||||
0xb9, 0x08, 0x00, 0x21, // agr %r2, %r1
|
||||
0xe3, 0x20, 0x20, 0x08, 0x00, 0x04, // lg %r2, 8(%r2)
|
||||
0xc0, 0x11, 0, 0, 0, 0, // lgfi %r1, TLS_BLOCK_SIZE
|
||||
0xb9, 0x09, 0x00, 0x21, // sgr %r2, %r1
|
||||
0x07, 0xfe, // br %r14
|
||||
};
|
||||
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset;
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
*(ub32 *)(loc + 2) = (ctx.got->shdr.sh_addr - this->shdr.sh_addr) >> 1;
|
||||
*(ub32 *)(loc + 18) = ctx.tp_addr - ctx.tls_begin;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
+20
-21
@@ -26,6 +26,13 @@
|
||||
// small to contain large immediates. On SH-4, large immediates are
|
||||
// loaded from memory using `mov.l` PC-relative load instruction.
|
||||
//
|
||||
// - Many RISC ISAs are, despite their name, actually fairly complex.
|
||||
// They tend to have hundreds if not thousands of different instructions.
|
||||
// SH-4 doesn't really have that many instructions because its 16-bit
|
||||
// machine code simply can't encode many different opcodes. As a
|
||||
// result, the number of relocations the linker has to support is also
|
||||
// small.
|
||||
//
|
||||
// Beside these, SH-4 has a delay branch slot just like contemporary MIPS
|
||||
// and SPARC. That is, one instruction after a branch instruction will
|
||||
// always be executed even if the branch is taken. Delay branch slot allows
|
||||
@@ -179,8 +186,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -222,7 +229,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
case R_SH_PLT32:
|
||||
@@ -264,17 +271,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -305,16 +307,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
|
||||
|
||||
@@ -326,25 +323,27 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_GOT32:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_SH_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SH_TLS_GD_32:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_SH_TLS_LD_32:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_SH_TLS_IE_32:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_SH_TLS_LE_32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_TLS_LDO_32:
|
||||
case R_SH_TLS_LE_32:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
|
||||
+92
-132
@@ -54,8 +54,7 @@
|
||||
// This scheme is very similar to i386. That may not be a coincidence
|
||||
// because the i386 ELF psABI is created by Sun Microsystems too.
|
||||
//
|
||||
// https://docs.oracle.com/cd/E36784_01/html/E36857/chapter6-62988.html
|
||||
// https://docs.oracle.com/cd/E19120-01/open.solaris/819-0690/chapter8-40/index.html
|
||||
// https://github.com/rui314/psabi/blob/main/sparc.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -120,8 +119,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -171,99 +170,73 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_SPARC_5: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(val, 4, 0);
|
||||
case R_SPARC_5:
|
||||
check(S + A, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(S + A, 4, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_6: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(val, 5, 0);
|
||||
case R_SPARC_6:
|
||||
check(S + A, 0, 1 << 6);
|
||||
*(ub32 *)loc |= bits(S + A, 5, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_7: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(val, 6, 0);
|
||||
case R_SPARC_7:
|
||||
check(S + A, 0, 1 << 7);
|
||||
*(ub32 *)loc |= bits(S + A, 6, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*(u8 *)loc = val;
|
||||
case R_SPARC_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*(u8 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_10: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(val, 9, 0);
|
||||
case R_SPARC_10:
|
||||
check(S + A, 0, 1 << 10);
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_LO10:
|
||||
case R_SPARC_LOPLT10:
|
||||
*(ub32 *)loc |= bits(S + A, 9, 0);
|
||||
break;
|
||||
case R_SPARC_11: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(val, 10, 0);
|
||||
case R_SPARC_11:
|
||||
check(S + A, 0, 1 << 11);
|
||||
*(ub32 *)loc |= bits(S + A, 10, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_13: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(val, 12, 0);
|
||||
case R_SPARC_13:
|
||||
check(S + A, 0, 1 << 13);
|
||||
*(ub32 *)loc |= bits(S + A, 12, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_16:
|
||||
case R_SPARC_UA16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
case R_SPARC_UA16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ub16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_22: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(val, 21, 0);
|
||||
case R_SPARC_22:
|
||||
check(S + A, 0, 1 << 22);
|
||||
*(ub32 *)loc |= bits(S + A, 21, 0);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32:
|
||||
case R_SPARC_PLT32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_SPARC_PLT32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_PLT64:
|
||||
case R_SPARC_UA64:
|
||||
case R_SPARC_REGISTER:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_DISP8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*(u8 *)loc = val;
|
||||
case R_SPARC_DISP8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*(u8 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
case R_SPARC_DISP16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP32:
|
||||
case R_SPARC_PCPLT32: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = val;
|
||||
case R_SPARC_PCPLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_DISP64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
@@ -273,25 +246,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub16 *)loc |= (bit(val, 16) << 21) | bits(val, 15, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(val, 20, 2);
|
||||
case R_SPARC_WDISP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
*(ub32 *)loc |= bits(S + A - P, 20, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP22: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(val, 23, 2);
|
||||
case R_SPARC_WDISP22:
|
||||
check(S + A - P, -(1 << 23), 1 << 23);
|
||||
*(ub32 *)loc |= bits(S + A - P, 23, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_WDISP30:
|
||||
case R_SPARC_WPLT30: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(val, 31, 2);
|
||||
case R_SPARC_WPLT30:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ub32 *)loc |= bits(S + A - P, 31, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_HI22:
|
||||
case R_SPARC_HIPLT22:
|
||||
case R_SPARC_LM22:
|
||||
@@ -321,36 +288,32 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// We always have to relax a GOT load to a load immediate if a
|
||||
// symbol is local, because R_SPARC_GOTDATA_OP cannot represent
|
||||
// an addend for a local symbol.
|
||||
if (sym.is_imported || sym.is_ifunc()) {
|
||||
if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(G, 31, 10);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val < 0 ? ~val : val, 31, 10);
|
||||
}
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10: {
|
||||
if (sym.is_imported || sym.is_ifunc()) {
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(G, 9, 0);
|
||||
} else if (sym.is_absolute()) {
|
||||
i64 val = S + A;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
} else {
|
||||
i64 val = S + A - GOT;
|
||||
*(ub32 *)loc |= bits(val, 9, 0) | (val < 0 ? 0b1'1100'0000'0000 : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
if (sym.is_imported || sym.is_ifunc())
|
||||
break;
|
||||
|
||||
if (sym.is_absolute()) {
|
||||
// ldx [ %g2 + %g1 ], %g1 → nop
|
||||
*(ub32 *)loc = 0x0100'0000;
|
||||
} else {
|
||||
} else if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
|
||||
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
|
||||
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
|
||||
@@ -402,12 +365,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL: {
|
||||
u64 addr;
|
||||
if (ctx.arg.is_static)
|
||||
*(ub32 *)loc |= bits(ctx.extra.tls_get_addr->shdr.sh_addr + A - P, 31, 2);
|
||||
addr = ctx.extra.tls_get_addr_sec->shdr.sh_addr;
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tls_get_addr->get_addr(ctx) + A - P, 31, 2);
|
||||
addr = ctx.extra.tls_get_addr_sym->get_addr(ctx);
|
||||
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
break;
|
||||
}
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
break;
|
||||
@@ -454,17 +421,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -516,18 +478,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
@@ -571,17 +528,17 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_PCPLT10:
|
||||
case R_SPARC_PLT64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_GOT13:
|
||||
case R_SPARC_GOT10:
|
||||
case R_SPARC_GOT22:
|
||||
case R_SPARC_GOTDATA_HIX22:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_HIX22:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_SPARC_DISP16:
|
||||
case R_SPARC_DISP32:
|
||||
@@ -597,18 +554,23 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (!ctx.arg.is_static && ctx.tls_get_addr->is_imported)
|
||||
ctx.tls_get_addr->flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
if (!ctx.arg.is_static)
|
||||
if (Symbol<E> &sym = *ctx.extra.tls_get_addr_sym; sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
@@ -624,12 +586,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
case R_SPARC_TLS_IE_LO10:
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
case R_SPARC_SIZE32:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": scan_relocations: " << rel;
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+316
-306
@@ -23,8 +23,8 @@
|
||||
// TLS block as TP (with some addend). As a result, offsets from TP to
|
||||
// thread-local variables (TLVs) in the main executable are all negative.
|
||||
//
|
||||
// https://github.com/rui314/mold/wiki/i386-psabi.pdf
|
||||
// https://github.com/rui314/mold/wiki/x86-64-psabi.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/x86-64.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
@@ -64,33 +64,46 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
// Only a canonical PLT can be address-taken; there's no way to take
|
||||
// an address of a non-canonical PLT. Therefore, a non-canonical PLT
|
||||
// doesn't have to start with an endbr64.
|
||||
if (sym.is_canonical) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 16;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x41, 0xbb, 0, 0, 0, 0, // mov $index_in_relplt, %r11d
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_plt_idx(ctx);
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 10;
|
||||
*(ul32 *)(buf + 2) = sym.get_got_addr(ctx) - sym.get_plt_addr(ctx) - 6;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
@@ -165,26 +178,143 @@ static u32 relax_gottpoff(u8 *loc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gotpc32_tlsdesc(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. We can do this when we know the thread-local variable's TP-
|
||||
// relative address at link-time.
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// or
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val - 12;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val - 13;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. The difference from relax_gd_to_le is that we are
|
||||
// materializing a Dynamic Thread Pointer for the current ELF module
|
||||
// instead of an address for a particular thread-local variable.
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// The instructions are so short that we cannot rewrite them with
|
||||
// "mov %fs:0, %rax" which is 9 bytes long. We use a shorter code
|
||||
// sequence instead. Since "xor %eax, %eax" zero-clears %rax, the
|
||||
// meaning is equivalent.
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = tls_size;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
@@ -228,21 +358,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOTPLT = ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_X86_64_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_X86_64_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
@@ -250,20 +376,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, dynrel);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
case R_X86_64_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC16: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = val;
|
||||
case R_X86_64_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32:
|
||||
write32s(S + A - P);
|
||||
@@ -272,168 +394,70 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_X86_64_GOT32:
|
||||
write32s(G + A);
|
||||
write32(G + A);
|
||||
break;
|
||||
case R_X86_64_GOT64:
|
||||
*(ul64 *)loc = G + A;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_PLTOFF64:
|
||||
*(ul64 *)loc = S + A - GOTPLT;
|
||||
*(ul64 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
write32s(GOTPLT + A - P);
|
||||
write32s(GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
*(ul64 *)loc = GOTPLT + A - P;
|
||||
*(ul64 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
write32s(G + GOTPLT + A - P);
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPCREL64:
|
||||
*(ul64 *)loc = G + GOTPLT + A - P;
|
||||
*(ul64 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOTPLT + A - P);
|
||||
} else {
|
||||
// We always want to relax GOTPCRELX relocs even if --no-relax
|
||||
// was given because some static PIE runtime code depends on these
|
||||
// relaxations.
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.has_got(ctx)) {
|
||||
write32s(G + GOTPLT + A - P);
|
||||
} else {
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S + A - P);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
write32s(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax GD to LE. If we are creating an exectuable, the offset of
|
||||
// a thread-local variable from TP is a link-time constant. So we
|
||||
// don't need to call __tls_get_addr to obtain the address of a TLV.
|
||||
i64 val = S - ctx.tp_addr;
|
||||
assert(A == -4);
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 66 48 e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// or
|
||||
//
|
||||
// 66 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 66 48 ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 8) = val;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsgd(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $val, %rax
|
||||
0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 9) = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
else if (sym.has_gottp(ctx))
|
||||
relax_gd_to_ie(loc, rels[++i], sym.get_gottp_addr(ctx) - P);
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
} else {
|
||||
// Relax LD to LE. If we are creating an executable, we don't need
|
||||
// to call __tls_get_addr to obtain the address of the beginning
|
||||
// of the current TLS block. TP points past the end of the TLS
|
||||
// block, and we know the exact size of the TLS block we have
|
||||
// created, so we can just subtract it from TP.
|
||||
switch (rels[i + 1].r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
// The original instructions are the following:
|
||||
//
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// 48 b8 00 00 00 00 00 00 00 00 movabs __tls_get_addr@GOTOFF, %rax
|
||||
// 48 01 d8 add %rbx, %rax
|
||||
// ff d0 call *%rax
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
0x0f, 0x1f, 0x44, 0x00, 0x00, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
*(ul32 *)(loc + 5) = ctx.tp_addr - ctx.tls_begin;
|
||||
assert(A == -4);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
write32s(S + A - ctx.dtp_addr);
|
||||
@@ -460,15 +484,46 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
// x86-64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %rax.
|
||||
//
|
||||
// lea 0(%rip), %rax
|
||||
// R_X86_64_GOTPC32_TLSDESC foo
|
||||
// call *(%rax)
|
||||
// R_X86_64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// mov foo@GOTTPOFF(%rip), %rax
|
||||
// nop
|
||||
//
|
||||
// or to the following for executable.
|
||||
//
|
||||
// mov $foo@TPOFF, %rax
|
||||
// nop
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// mov foo@gottpoff(%rip), %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x05;
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gotpc32_tlsdesc(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
// mov $foo@tpoff, %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0xc7;
|
||||
loc[-1] = 0xc0;
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
@@ -477,13 +532,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_X86_64_SIZE64:
|
||||
*(ul64 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -508,17 +556,12 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
@@ -544,18 +587,14 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
case R_X86_64_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_16: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1 << 16);
|
||||
*(ul16 *)loc = val;
|
||||
case R_X86_64_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_32:
|
||||
write32(S + A);
|
||||
break;
|
||||
@@ -580,6 +619,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
else
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_X86_64_GOTOFF64:
|
||||
*(ul64 *)loc = S + A - ctx.gotplt->shdr.sh_addr;
|
||||
break;
|
||||
case R_X86_64_GOTPC64:
|
||||
// PC-relative relocation doesn't make sense for non-memory-allocated
|
||||
// section, but GCC 6.3.0 seems to create this reloc for
|
||||
// _GLOBAL_OFFSET_TABLE_.
|
||||
*(ul64 *)loc = ctx.gotplt->shdr.sh_addr + A;
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
write32(sym.esym().st_size + A);
|
||||
break;
|
||||
@@ -609,19 +657,27 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
record_undef_error(ctx, rel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags.fetch_or(NEEDS_GOT | NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_X86_64_TLSGD || rel.r_type == R_X86_64_TLSLD) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel
|
||||
<< " must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": " << rel
|
||||
<< " must be followed by PLT or GOTPCREL";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
@@ -645,103 +701,57 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_gotpcrelx(loc - 2);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
bool do_relax = ctx.arg.relax && !sym.is_imported &&
|
||||
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOT, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PLTOFF64:
|
||||
if (sym.is_imported)
|
||||
sym.flags.fetch_or(NEEDS_PLT, std::memory_order_relaxed);
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_X86_64_TLSGD: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSGD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSGD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (relax_tlsgd(ctx, sym))
|
||||
case R_X86_64_TLSGD:
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
i++;
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
i++;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSLD:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
sym.flags.fetch_or(NEEDS_TLSGD, std::memory_order_relaxed);
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_TLSLD";
|
||||
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_X86_64_PLT32 && ty != R_X86_64_PC32 &&
|
||||
ty != R_X86_64_PLTOFF64 && ty != R_X86_64_GOTPCREL &&
|
||||
ty != R_X86_64_GOTPCRELX)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc must be followed by PLT or GOTPCREL";
|
||||
|
||||
if (relax_tlsld(ctx))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld.store(true, std::memory_order_relaxed);
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (ctx.arg.relax && relax_gottpoff(loc - 3) &&
|
||||
sym.is_tprel_linktime_const(ctx)) {
|
||||
// do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTTPOFF: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTTPOFF";
|
||||
|
||||
ctx.has_gottp_rel.store(true, std::memory_order_relaxed);
|
||||
|
||||
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
|
||||
!sym.is_imported && relax_gottpoff(loc - 3);
|
||||
if (!do_relax)
|
||||
sym.flags.fetch_or(NEEDS_GOTTP, std::memory_order_relaxed);
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTPC32_TLSDESC: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPC32_TLSDESC";
|
||||
|
||||
if (relax_gotpc32_tlsdesc(loc - 3) == 0)
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (!relax_tlsdesc(ctx, sym))
|
||||
sym.flags.fetch_or(NEEDS_TLSDESC, std::memory_order_relaxed);
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
|
||||
+56
-32
@@ -1,6 +1,7 @@
|
||||
#include "mold.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../common/cmdline.h"
|
||||
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
@@ -98,7 +99,7 @@ Options:
|
||||
--gc-sections Remove unreferenced sections
|
||||
--no-gc-sections
|
||||
--gdb-index Create .gdb_index for faster gdb startup
|
||||
--hash-style [sysv,gnu,both]
|
||||
--hash-style [sysv,gnu,both,none]
|
||||
Set hash style
|
||||
--icf=[all,safe,none] Fold identical code
|
||||
--no-icf
|
||||
@@ -187,18 +188,21 @@ Options:
|
||||
-z origin Mark object requiring immediate $ORIGIN processing at runtime
|
||||
-z pack-relative-relocs Alias for --pack-dyn-relocs=relr
|
||||
-z nopack-relative-relocs
|
||||
-z sectionheader Do not omit section header (default)
|
||||
-z nosectionheader Omit section header
|
||||
-z separate-loadable-segments
|
||||
Separate all loadable segments to different pages
|
||||
-z separate-code Separate code and data into different pages
|
||||
-z noseparate-code Allow overlap in pages
|
||||
-z stack-size=VALUE Set size of stack segment
|
||||
-z relro Make some sections read-only after relocation (default)
|
||||
-z norelro
|
||||
-z text Report error if DT_TEXTREL is set
|
||||
-z notext
|
||||
-z textoff
|
||||
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf32-hppa-linux
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha hppalinux)";
|
||||
mold: supported targets: elf32-i386 elf64-x86-64 elf32-littlearm elf64-littleaarch64 elf32-littleriscv elf32-bigriscv elf64-littleriscv elf64-bigriscv elf32-powerpc elf64-powerpc elf64-powerpc elf64-powerpcle elf64-s390 elf64-sparc elf32-m68k elf32-sh-linux elf64-alpha elf64-loongarch elf32-loongarch
|
||||
mold: supported emulations: elf_i386 elf_x86_64 armelf_linux_eabi aarch64linux aarch64elf elf32lriscv elf32briscv elf64lriscv elf64briscv elf32ppc elf32ppclinux elf64ppc elf64lppc elf64_s390 elf64_sparc m68kelf shlelf_linux elf64alpha elf64loongarch elf32loongarch)";
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Single-letter option
|
||||
@@ -398,13 +402,13 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
std::vector<std::string> remaining;
|
||||
std::string_view arg;
|
||||
|
||||
ctx.page_size = E::page_size;
|
||||
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
|
||||
|
||||
bool version_shown = false;
|
||||
bool warn_shared_textrel = false;
|
||||
std::optional<SeparateCodeKind> z_separate_code;
|
||||
std::optional<bool> z_relro;
|
||||
std::optional<u64> shuffle_sections_seed;
|
||||
std::unordered_set<std::string_view> rpaths;
|
||||
|
||||
auto add_rpath = [&](std::string_view arg) {
|
||||
@@ -423,7 +427,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
// It looks like the SPARC's dynamic linker takes both RELA's r_addend
|
||||
// and the value at the relocated place. So we don't want to write
|
||||
// values to relocated places.
|
||||
if (is_sparc<E>)
|
||||
if constexpr (is_sparc<E>)
|
||||
ctx.arg.apply_dynamic_relocs = false;
|
||||
|
||||
auto read_arg = [&](std::string name) {
|
||||
@@ -521,7 +525,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
<< " elf64briscv\n elf32lriscv\n elf32briscv\n"
|
||||
<< " elf32ppc\n elf64ppc\n elf64lppc\n elf64_s390\n"
|
||||
<< " elf64_sparc\n m68kelf\n shlelf_linux\n"
|
||||
<< " elf64alpha\n hppalinux";
|
||||
<< " elf64alpha\n elf64loongarch\n elf32loongarch";
|
||||
version_shown = true;
|
||||
} else if (read_arg("m")) {
|
||||
if (arg == "elf_x86_64") {
|
||||
@@ -556,8 +560,10 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.emulation = SH4::target_name;
|
||||
} else if (arg == "elf64alpha") {
|
||||
ctx.arg.emulation = ALPHA::target_name;
|
||||
} else if (arg == "hppalinux") {
|
||||
ctx.arg.emulation = HPPA32::target_name;
|
||||
} else if (arg == "elf64loongarch") {
|
||||
ctx.arg.emulation = LOONGARCH64::target_name;
|
||||
} else if (arg == "elf32loongarch") {
|
||||
ctx.arg.emulation = LOONGARCH32::target_name;
|
||||
} else {
|
||||
Fatal(ctx) << "unknown -m argument: " << arg;
|
||||
}
|
||||
@@ -585,9 +591,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag("print-dependencies")) {
|
||||
ctx.arg.print_dependencies = 1;
|
||||
} else if (read_flag("print-dependencies=full")) {
|
||||
ctx.arg.print_dependencies = 2;
|
||||
ctx.arg.print_dependencies = true;
|
||||
} else if (read_flag("print-map") || read_flag("M")) {
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag("Bstatic") || read_flag("dn") || read_flag("static")) {
|
||||
@@ -628,7 +632,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
} else if (read_eq("shuffle-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_SHUFFLE;
|
||||
ctx.arg.shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
|
||||
shuffle_sections_seed = parse_number(ctx, "shuffle-sections", arg);
|
||||
} else if (read_flag("reverse-sections")) {
|
||||
ctx.arg.shuffle_sections = SHUFFLE_SECTIONS_REVERSE;
|
||||
} else if (read_flag("rosegment")) {
|
||||
@@ -655,7 +659,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg("u") || read_arg("undefined")) {
|
||||
} else if (read_arg("undefined") || read_arg("u")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
} else if (read_arg("require-defined")) {
|
||||
ctx.arg.require_defined.push_back(arg);
|
||||
@@ -673,6 +677,9 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (arg == "both") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
ctx.arg.hash_style_gnu = true;
|
||||
} else if (arg == "none") {
|
||||
ctx.arg.hash_style_sysv = false;
|
||||
ctx.arg.hash_style_gnu = false;
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --hash-style argument: " << arg;
|
||||
}
|
||||
@@ -694,7 +701,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("pie") || read_flag("pic-executable")) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.pie = true;
|
||||
} else if (read_flag("no-pie") || read_flag("no-pic-executable")) {
|
||||
} else if (read_flag("no-pie") || read_flag("no-pic-executable") ||
|
||||
read_flag("nopie")) {
|
||||
ctx.arg.pic = false;
|
||||
ctx.arg.pie = false;
|
||||
} else if (read_flag("relax")) {
|
||||
@@ -713,9 +721,11 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.relocatable_merge_sections = true;
|
||||
} else if (read_flag("perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag("pack-dyn-relocs=relr")) {
|
||||
} else if (read_flag("pack-dyn-relocs=relr") ||
|
||||
read_z_flag("pack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_flag("pack-dyn-relocs=none")) {
|
||||
} else if (read_flag("pack-dyn-relocs=none") ||
|
||||
read_z_flag("nopack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_arg("package-metadata")) {
|
||||
ctx.arg.package_metadata = arg;
|
||||
@@ -857,20 +867,24 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
ctx.arg.z_origin = true;
|
||||
} else if (read_z_flag("nodefaultlib")) {
|
||||
ctx.arg.z_nodefaultlib = true;
|
||||
} else if (read_z_flag("pack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = true;
|
||||
} else if (read_z_flag("nopack-relative-relocs")) {
|
||||
ctx.arg.pack_dyn_relocs_relr = false;
|
||||
} else if (read_z_flag("separate-loadable-segments")) {
|
||||
z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
|
||||
} else if (read_z_flag("separate-code")) {
|
||||
z_separate_code = SEPARATE_CODE;
|
||||
} else if (read_z_flag("noseparate-code")) {
|
||||
z_separate_code = NOSEPARATE_CODE;
|
||||
} else if (read_z_arg("stack-size")) {
|
||||
ctx.arg.z_stack_size = parse_number(ctx, "-z stack-size", arg);
|
||||
} else if (read_z_flag("dynamic-undefined-weak")) {
|
||||
ctx.arg.z_dynamic_undefined_weak = true;
|
||||
} else if (read_z_flag("nodynamic-undefined-weak")) {
|
||||
ctx.arg.z_dynamic_undefined_weak = false;
|
||||
} else if (read_z_flag("sectionheader")) {
|
||||
ctx.arg.z_sectionheader = true;
|
||||
} else if (read_z_flag("nosectionheader")) {
|
||||
ctx.arg.z_sectionheader = false;
|
||||
} else if (read_z_flag("rewrite-endbr")) {
|
||||
ctx.arg.z_rewrite_endbr = true;
|
||||
} else if (read_flag("no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag("fatal-warnings")) {
|
||||
@@ -996,7 +1010,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_flag("strip-all") || read_flag("s")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag("strip-debug") || read_flag("S")) {
|
||||
ctx.arg.strip_all = true;
|
||||
ctx.arg.strip_debug = true;
|
||||
} else if (read_flag("warn-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
|
||||
} else if (read_flag("error-unresolved-symbols")) {
|
||||
@@ -1049,6 +1063,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("filter") || read_arg("F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_arg("O")) {
|
||||
} else if (read_flag("EB")) {
|
||||
} else if (read_flag("EL")) {
|
||||
} else if (read_flag("O0")) {
|
||||
} else if (read_flag("O1")) {
|
||||
} else if (read_flag("O2")) {
|
||||
@@ -1089,7 +1105,7 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
} else if (read_arg("max-cache-size")) {
|
||||
} else if (read_arg("version-script")) {
|
||||
// --version-script, --dynamic-list and --export-dynamic-symbol[-list]
|
||||
// are treated as positional arguments even if they are actually not
|
||||
// are treated as positional arguments even though they are actually not
|
||||
// positional. This is because linker scripts (a positional argument)
|
||||
// can also specify a version script, and it's better to consolidate
|
||||
// parsing in read_input_files. In particular, version scripts can
|
||||
@@ -1126,10 +1142,8 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Warn(ctx) << "unknown command line option: -z " << args[1];
|
||||
args = args.subspan(2);
|
||||
} else if (args[0] == "-dynamic") {
|
||||
Fatal(ctx) << "unknown command line option: -dynamic;"
|
||||
<< " -dynamic is a macOS linker's option. If you are trying"
|
||||
<< " to build a binary for an Apple platform, you need to use"
|
||||
<< " ld64.mold instead of mold or ld.mold.";
|
||||
Fatal(ctx) << "unknown command line option: -dynamic; -dynamic is a "
|
||||
<< "macOS linker's option. mold does not support macOS.";
|
||||
} else {
|
||||
if (args[0][0] == '-')
|
||||
Fatal(ctx) << "unknown command line option: " << args[0];
|
||||
@@ -1166,6 +1180,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
if (ctx.arg.relocatable)
|
||||
ctx.arg.is_static = true;
|
||||
|
||||
if (ctx.arg.shuffle_sections == SHUFFLE_SECTIONS_SHUFFLE) {
|
||||
if (shuffle_sections_seed)
|
||||
ctx.arg.shuffle_sections_seed = *shuffle_sections_seed;
|
||||
else
|
||||
ctx.arg.shuffle_sections_seed =
|
||||
((u64)std::random_device()() << 32) | std::random_device()();
|
||||
}
|
||||
|
||||
// --section-order implies `-z separate-loadable-segments`
|
||||
if (z_separate_code)
|
||||
ctx.arg.z_separate_code = *z_separate_code;
|
||||
@@ -1185,12 +1207,14 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if (!ctx.arg.apply_dynamic_relocs && !E::is_rela)
|
||||
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
|
||||
<< E::target_name;
|
||||
if constexpr (!E::is_rela)
|
||||
if (!ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--no-apply-dynamic-relocs may not be used on "
|
||||
<< E::target_name;
|
||||
|
||||
if (is_sparc<E> && ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
|
||||
if constexpr (is_sparc<E>)
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
Fatal(ctx) << "--apply-dynamic-relocs may not be used on SPARC64";
|
||||
|
||||
if (!ctx.arg.section_start.empty() && !ctx.arg.section_order.empty())
|
||||
Fatal(ctx) << "--section-start may not be used with --section-order";
|
||||
|
||||
-554
@@ -1,554 +0,0 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, which often abbreviated as compunit
|
||||
// or cu, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using `gdb-add-index`
|
||||
// post-processing tool that comes with gdb.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name (with the same definition), while there
|
||||
// should be no two functions that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contains attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -fucntion-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4/5 and
|
||||
// .debug_rnglists/.debug_addr in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#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 (*(U32<E> *)data.data() == 0xffffffff)
|
||||
Fatal(ctx) << *file.debug_info << ": --gdb-index: DWARF64 not supported";
|
||||
i64 len = *(U32<E> *)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 = *(U32<E> *)contents.data() + 4;
|
||||
u32 debug_info_offset = *(U32<E> *)(contents.data() + 6);
|
||||
u32 cu_idx = get_cu_idx(isec, debug_info_offset);
|
||||
|
||||
std::string_view data = contents.substr(14, len - 14);
|
||||
contents = contents.substr(len);
|
||||
|
||||
while (!data.empty()) {
|
||||
u32 offset = *(U32<E> *)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 = *(U16<E> *)(cu + 4);
|
||||
u32 abbrev_offset;
|
||||
|
||||
// Skip a header.
|
||||
switch (dwarf_version) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
abbrev_offset = *(U32<E> *)(cu + 6);
|
||||
if (u32 address_size = cu[10]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
cu += 11;
|
||||
break;
|
||||
case 5: {
|
||||
abbrev_offset = *(U32<E> *)(cu + 8);
|
||||
if (u32 address_size = cu[7]; address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << file << ": --gdb-index: unsupported address size "
|
||||
<< address_size;
|
||||
|
||||
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 / sizeof(Word<E>) / 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 = *(U16<E> *)cu;
|
||||
cu += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)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 = *(U32<E> *)cu;
|
||||
cu += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)cu;
|
||||
cu += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)cu;
|
||||
cu += sizeof(Word<E>);
|
||||
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.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_debug_range(Context<E> &ctx, ObjectFile<E> &file, Word<E> *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.
|
||||
template <typename E>
|
||||
static std::vector<u64>
|
||||
read_rnglist_range(Context<E> &ctx, ObjectFile<E> &file, u8 *rnglist,
|
||||
Word<E> *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 = *(Word<E> *)rnglist;
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_length:
|
||||
vec.push_back(*(Word<E> *)rnglist);
|
||||
rnglist += sizeof(Word<E>);
|
||||
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;
|
||||
Word<E> *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 = (Word<E> *)(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) {
|
||||
Word<E> *range_begin =
|
||||
(Word<E> *)(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 + *(U32<E> *)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 {};
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<std::string_view> read_compunits(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<GdbIndexName> read_pubnames(Context<E> &, ObjectFile<E> &);
|
||||
template i64 estimate_address_areas(Context<E> &, ObjectFile<E> &);
|
||||
template std::vector<u64> read_address_areas(Context<E> &, ObjectFile<E> &, i64);
|
||||
|
||||
} // namespace mold::elf
|
||||
+941
-940
File diff suppressed because it is too large.
Load diff
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../integers.h"
|
||||
#include "../common/integers.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <concepts>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -25,7 +25,8 @@ struct SPARC64;
|
||||
struct M68K;
|
||||
struct SH4;
|
||||
struct ALPHA;
|
||||
struct HPPA32;
|
||||
struct LOONGARCH64;
|
||||
struct LOONGARCH32;
|
||||
|
||||
template <typename E> struct ElfSym;
|
||||
template <typename E> struct ElfShdr;
|
||||
@@ -55,9 +56,9 @@ enum : u32 {
|
||||
|
||||
enum : u32 {
|
||||
SHN_UNDEF = 0,
|
||||
SHN_LORESERVE = 0xff00,
|
||||
SHN_ABS = 0xfff1,
|
||||
SHN_COMMON = 0xfff2,
|
||||
SHN_LORESERVE = 0xff00,
|
||||
SHN_XINDEX = 0xffff,
|
||||
};
|
||||
|
||||
@@ -88,6 +89,7 @@ enum : u32 {
|
||||
SHT_X86_64_UNWIND = 0x70000001,
|
||||
SHT_ARM_EXIDX = 0x70000001,
|
||||
SHT_ARM_ATTRIBUTES = 0x70000003,
|
||||
SHT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -119,7 +121,6 @@ enum : u32 {
|
||||
STT_TLS = 6,
|
||||
STT_GNU_IFUNC = 10,
|
||||
STT_SPARC_REGISTER = 13,
|
||||
STT_HPPA_MILLI = 13,
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
@@ -139,10 +140,6 @@ inline std::string stt_to_string(u32 st_type) {
|
||||
if (st_type == STT_SPARC_REGISTER)
|
||||
return "STT_SPARC_REGISTER";
|
||||
|
||||
if constexpr (std::is_same_v<E, HPPA32>)
|
||||
if (st_type == STT_HPPA_MILLI)
|
||||
return "STT_HPPA_MILLI";
|
||||
|
||||
return "unknown st_type (" + std::to_string(st_type) + ")";
|
||||
}
|
||||
|
||||
@@ -188,7 +185,9 @@ enum : u32 {
|
||||
PT_GNU_EH_FRAME = 0x6474e550,
|
||||
PT_GNU_STACK = 0x6474e551,
|
||||
PT_GNU_RELRO = 0x6474e552,
|
||||
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
|
||||
PT_ARM_EXIDX = 0x70000001,
|
||||
PT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -223,7 +222,6 @@ enum : u32 {
|
||||
EM_NONE = 0,
|
||||
EM_386 = 3,
|
||||
EM_68K = 4,
|
||||
EM_PARISC = 15,
|
||||
EM_PPC = 20,
|
||||
EM_PPC64 = 21,
|
||||
EM_S390X = 22,
|
||||
@@ -233,6 +231,7 @@ enum : u32 {
|
||||
EM_X86_64 = 62,
|
||||
EM_AARCH64 = 183,
|
||||
EM_RISCV = 243,
|
||||
EM_LOONGARCH = 258,
|
||||
EM_ALPHA = 0x9026,
|
||||
};
|
||||
|
||||
@@ -244,11 +243,6 @@ enum : u32 {
|
||||
EI_ABIVERSION = 8,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ELFOSABI_NONE = 0,
|
||||
ELFOSABI_GNU = 3,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
DT_NULL = 0,
|
||||
DT_NEEDED = 1,
|
||||
@@ -297,6 +291,7 @@ enum : u32 {
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_PPC_GOT = 0x70000000,
|
||||
DT_PPC64_GLINK = 0x70000000,
|
||||
DT_AARCH64_VARIANT_PCS = 0x70000005,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff,
|
||||
};
|
||||
@@ -382,6 +377,24 @@ enum : u32 {
|
||||
STO_ALPHA_STD_GPLOAD = 0x22,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ELF_TAG_FILE = 1,
|
||||
ELF_TAG_SECTION = 2,
|
||||
ELF_TAG_SYMBOL = 3,
|
||||
ELF_TAG_RISCV_STACK_ALIGN = 4,
|
||||
ELF_TAG_RISCV_ARCH = 5,
|
||||
ELF_TAG_RISCV_UNALIGNED_ACCESS = 6,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
EF_LOONGARCH_ABI_SOFT_FLOAT = 0x1,
|
||||
EF_LOONGARCH_ABI_SINGLE_FLOAT = 0x2,
|
||||
EF_LOONGARCH_ABI_DOUBLE_FLOAT = 0x3,
|
||||
EF_LOONGARCH_ABI_MODIFIER_MASK = 0x7,
|
||||
EF_LOONGARCH_OBJABI_V1 = 0x40,
|
||||
EF_LOONGARCH_OBJABI_MASK = 0xC0,
|
||||
};
|
||||
|
||||
//
|
||||
// Relocation types
|
||||
//
|
||||
@@ -732,6 +745,7 @@ enum : u32 {
|
||||
R_RISCV_TLS_DTPREL64 = 9,
|
||||
R_RISCV_TLS_TPREL32 = 10,
|
||||
R_RISCV_TLS_TPREL64 = 11,
|
||||
R_RISCV_TLSDESC = 12,
|
||||
R_RISCV_BRANCH = 16,
|
||||
R_RISCV_JAL = 17,
|
||||
R_RISCV_CALL = 18,
|
||||
@@ -761,6 +775,9 @@ enum : u32 {
|
||||
R_RISCV_RVC_BRANCH = 44,
|
||||
R_RISCV_RVC_JUMP = 45,
|
||||
R_RISCV_RVC_LUI = 46,
|
||||
R_RISCV_GPREL_LO12_I = 47,
|
||||
R_RISCV_GPREL_LO12_S = 48,
|
||||
R_RISCV_GPREL_HI20 = 49,
|
||||
R_RISCV_RELAX = 51,
|
||||
R_RISCV_SUB6 = 52,
|
||||
R_RISCV_SET6 = 53,
|
||||
@@ -769,6 +786,13 @@ enum : u32 {
|
||||
R_RISCV_SET32 = 56,
|
||||
R_RISCV_32_PCREL = 57,
|
||||
R_RISCV_IRELATIVE = 58,
|
||||
R_RISCV_PLT32 = 59,
|
||||
R_RISCV_SET_ULEB128 = 60,
|
||||
R_RISCV_SUB_ULEB128 = 61,
|
||||
R_RISCV_TLSDESC_HI20 = 62,
|
||||
R_RISCV_TLSDESC_LOAD_LO12 = 63,
|
||||
R_RISCV_TLSDESC_ADD_LO12 = 64,
|
||||
R_RISCV_TLSDESC_CALL = 65,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -945,9 +969,13 @@ enum : u32 {
|
||||
R_PPC64_REL24_NOTOC = 116,
|
||||
R_PPC64_PLTSEQ = 119,
|
||||
R_PPC64_PLTCALL = 120,
|
||||
R_PPC64_PLTSEQ_NOTOC = 121,
|
||||
R_PPC64_PLTCALL_NOTOC = 122,
|
||||
R_PPC64_PCREL_OPT = 123,
|
||||
R_PPC64_PCREL34 = 132,
|
||||
R_PPC64_GOT_PCREL34 = 133,
|
||||
R_PPC64_PLT_PCREL34 = 134,
|
||||
R_PPC64_PLT_PCREL34_NOTOC = 135,
|
||||
R_PPC64_TPREL34 = 146,
|
||||
R_PPC64_DTPREL34 = 147,
|
||||
R_PPC64_GOT_TLSGD_PCREL34 = 148,
|
||||
@@ -1230,127 +1258,104 @@ enum : u32 {
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_PARISC_NONE = 0,
|
||||
R_PARISC_DIR32 = 1,
|
||||
R_PARISC_DIR21L = 2,
|
||||
R_PARISC_DIR17R = 3,
|
||||
R_PARISC_DIR17F = 4,
|
||||
R_PARISC_DIR14R = 6,
|
||||
R_PARISC_DIR14F = 7,
|
||||
R_PARISC_PCREL12F = 8,
|
||||
R_PARISC_PCREL32 = 9,
|
||||
R_PARISC_PCREL21L = 10,
|
||||
R_PARISC_PCREL17R = 11,
|
||||
R_PARISC_PCREL17F = 12,
|
||||
R_PARISC_PCREL17C = 13,
|
||||
R_PARISC_PCREL14R = 14,
|
||||
R_PARISC_PCREL14F = 15,
|
||||
R_PARISC_DPREL21L = 18,
|
||||
R_PARISC_DPREL14WR = 19,
|
||||
R_PARISC_DPREL14DR = 20,
|
||||
R_PARISC_DPREL14R = 22,
|
||||
R_PARISC_DPREL14F = 23,
|
||||
R_PARISC_DLTREL21L = 26,
|
||||
R_PARISC_DLTREL14R = 30,
|
||||
R_PARISC_DLTREL14F = 31,
|
||||
R_PARISC_DLTIND21L = 34,
|
||||
R_PARISC_DLTIND14R = 38,
|
||||
R_PARISC_DLTIND14F = 39,
|
||||
R_PARISC_SETBASE = 40,
|
||||
R_PARISC_SECREL32 = 41,
|
||||
R_PARISC_BASEREL21L = 42,
|
||||
R_PARISC_BASEREL17R = 43,
|
||||
R_PARISC_BASEREL17F = 44,
|
||||
R_PARISC_BASEREL14R = 46,
|
||||
R_PARISC_BASEREL14F = 47,
|
||||
R_PARISC_SEGBASE = 48,
|
||||
R_PARISC_SEGREL32 = 49,
|
||||
R_PARISC_PLTOFF21L = 50,
|
||||
R_PARISC_PLTOFF14R = 54,
|
||||
R_PARISC_PLTOFF14F = 55,
|
||||
R_PARISC_LTOFF_FPTR32 = 57,
|
||||
R_PARISC_LTOFF_FPTR21L = 58,
|
||||
R_PARISC_LTOFF_FPTR14R = 62,
|
||||
R_PARISC_FPTR64 = 64,
|
||||
R_PARISC_PLABEL32 = 65,
|
||||
R_PARISC_PLABEL21L = 66,
|
||||
R_PARISC_PLABEL14R = 70,
|
||||
R_PARISC_PCREL64 = 72,
|
||||
R_PARISC_PCREL22C = 73,
|
||||
R_PARISC_PCREL22F = 74,
|
||||
R_PARISC_PCREL14WR = 75,
|
||||
R_PARISC_PCREL14DR = 76,
|
||||
R_PARISC_PCREL16F = 77,
|
||||
R_PARISC_PCREL16WF = 78,
|
||||
R_PARISC_PCREL16DF = 79,
|
||||
R_PARISC_DIR64 = 80,
|
||||
R_PARISC_DIR14WR = 83,
|
||||
R_PARISC_DIR14DR = 84,
|
||||
R_PARISC_DIR16F = 85,
|
||||
R_PARISC_DIR16WF = 86,
|
||||
R_PARISC_DIR16DF = 87,
|
||||
R_PARISC_GPREL64 = 88,
|
||||
R_PARISC_DLTREL14WR = 91,
|
||||
R_PARISC_DLTREL14DR = 92,
|
||||
R_PARISC_GPREL16F = 93,
|
||||
R_PARISC_GPREL16WF = 94,
|
||||
R_PARISC_GPREL16DF = 95,
|
||||
R_PARISC_LTOFF64 = 96,
|
||||
R_PARISC_DLTIND14WR = 99,
|
||||
R_PARISC_DLTIND14DR = 100,
|
||||
R_PARISC_LTOFF16F = 101,
|
||||
R_PARISC_LTOFF16WF = 102,
|
||||
R_PARISC_LTOFF16DF = 103,
|
||||
R_PARISC_SECREL64 = 104,
|
||||
R_PARISC_BASEREL14WR = 107,
|
||||
R_PARISC_BASEREL14DR = 108,
|
||||
R_PARISC_SEGREL64 = 112,
|
||||
R_PARISC_PLTOFF14WR = 115,
|
||||
R_PARISC_PLTOFF14DR = 116,
|
||||
R_PARISC_PLTOFF16F = 117,
|
||||
R_PARISC_PLTOFF16WF = 118,
|
||||
R_PARISC_PLTOFF16DF = 119,
|
||||
R_PARISC_LTOFF_FPTR64 = 120,
|
||||
R_PARISC_LTOFF_FPTR14WR = 123,
|
||||
R_PARISC_LTOFF_FPTR14DR = 124,
|
||||
R_PARISC_LTOFF_FPTR16F = 125,
|
||||
R_PARISC_LTOFF_FPTR16WF = 126,
|
||||
R_PARISC_LTOFF_FPTR16DF = 127,
|
||||
R_PARISC_COPY = 128,
|
||||
R_PARISC_IPLT = 129,
|
||||
R_PARISC_EPLT = 130,
|
||||
R_PARISC_TPREL32 = 153,
|
||||
R_PARISC_TPREL21L = 154,
|
||||
R_PARISC_TPREL14R = 158,
|
||||
R_PARISC_LTOFF_TP21L = 162,
|
||||
R_PARISC_LTOFF_TP14R = 166,
|
||||
R_PARISC_LTOFF_TP14F = 167,
|
||||
R_PARISC_TPREL64 = 216,
|
||||
R_PARISC_TPREL14WR = 219,
|
||||
R_PARISC_TPREL14DR = 220,
|
||||
R_PARISC_TPREL16F = 221,
|
||||
R_PARISC_TPREL16WF = 222,
|
||||
R_PARISC_TPREL16DF = 223,
|
||||
R_PARISC_LTOFF_TP64 = 224,
|
||||
R_PARISC_LTOFF_TP14WR = 227,
|
||||
R_PARISC_LTOFF_TP14DR = 228,
|
||||
R_PARISC_LTOFF_TP16F = 229,
|
||||
R_PARISC_LTOFF_TP16WF = 230,
|
||||
R_PARISC_LTOFF_TP16DF = 231,
|
||||
R_PARISC_GNU_VTENTRY = 232,
|
||||
R_PARISC_GNU_VTINHERIT = 233,
|
||||
R_PARISC_TLS_GD21L = 234,
|
||||
R_PARISC_TLS_GD14R = 235,
|
||||
R_PARISC_TLS_GDCALL = 236,
|
||||
R_PARISC_TLS_LDM21L = 237,
|
||||
R_PARISC_TLS_LDM14R = 238,
|
||||
R_PARISC_TLS_LDMCALL = 239,
|
||||
R_PARISC_TLS_LDO21L = 240,
|
||||
R_PARISC_TLS_LDO14R = 241,
|
||||
R_PARISC_TLS_DTPMOD32 = 242,
|
||||
R_PARISC_TLS_DTPMOD64 = 243,
|
||||
R_PARISC_TLS_DTPOFF32 = 244,
|
||||
R_PARISC_TLS_DTPOFF64 = 245,
|
||||
R_LARCH_NONE = 0,
|
||||
R_LARCH_32 = 1,
|
||||
R_LARCH_64 = 2,
|
||||
R_LARCH_RELATIVE = 3,
|
||||
R_LARCH_COPY = 4,
|
||||
R_LARCH_JUMP_SLOT = 5,
|
||||
R_LARCH_TLS_DTPMOD32 = 6,
|
||||
R_LARCH_TLS_DTPMOD64 = 7,
|
||||
R_LARCH_TLS_DTPREL32 = 8,
|
||||
R_LARCH_TLS_DTPREL64 = 9,
|
||||
R_LARCH_TLS_TPREL32 = 10,
|
||||
R_LARCH_TLS_TPREL64 = 11,
|
||||
R_LARCH_IRELATIVE = 12,
|
||||
R_LARCH_MARK_LA = 20,
|
||||
R_LARCH_MARK_PCREL = 21,
|
||||
R_LARCH_SOP_PUSH_PCREL = 22,
|
||||
R_LARCH_SOP_PUSH_ABSOLUTE = 23,
|
||||
R_LARCH_SOP_PUSH_DUP = 24,
|
||||
R_LARCH_SOP_PUSH_GPREL = 25,
|
||||
R_LARCH_SOP_PUSH_TLS_TPREL = 26,
|
||||
R_LARCH_SOP_PUSH_TLS_GOT = 27,
|
||||
R_LARCH_SOP_PUSH_TLS_GD = 28,
|
||||
R_LARCH_SOP_PUSH_PLT_PCREL = 29,
|
||||
R_LARCH_SOP_ASSERT = 30,
|
||||
R_LARCH_SOP_NOT = 31,
|
||||
R_LARCH_SOP_SUB = 32,
|
||||
R_LARCH_SOP_SL = 33,
|
||||
R_LARCH_SOP_SR = 34,
|
||||
R_LARCH_SOP_ADD = 35,
|
||||
R_LARCH_SOP_AND = 36,
|
||||
R_LARCH_SOP_IF_ELSE = 37,
|
||||
R_LARCH_SOP_POP_32_S_10_5 = 38,
|
||||
R_LARCH_SOP_POP_32_U_10_12 = 39,
|
||||
R_LARCH_SOP_POP_32_S_10_12 = 40,
|
||||
R_LARCH_SOP_POP_32_S_10_16 = 41,
|
||||
R_LARCH_SOP_POP_32_S_10_16_S2 = 42,
|
||||
R_LARCH_SOP_POP_32_S_5_20 = 43,
|
||||
R_LARCH_SOP_POP_32_S_0_5_10_16_S2 = 44,
|
||||
R_LARCH_SOP_POP_32_S_0_10_10_16_S2 = 45,
|
||||
R_LARCH_SOP_POP_32_U = 46,
|
||||
R_LARCH_ADD8 = 47,
|
||||
R_LARCH_ADD16 = 48,
|
||||
R_LARCH_ADD24 = 49,
|
||||
R_LARCH_ADD32 = 50,
|
||||
R_LARCH_ADD64 = 51,
|
||||
R_LARCH_SUB8 = 52,
|
||||
R_LARCH_SUB16 = 53,
|
||||
R_LARCH_SUB24 = 54,
|
||||
R_LARCH_SUB32 = 55,
|
||||
R_LARCH_SUB64 = 56,
|
||||
R_LARCH_GNU_VTINHERIT = 57,
|
||||
R_LARCH_GNU_VTENTRY = 58,
|
||||
R_LARCH_B16 = 64,
|
||||
R_LARCH_B21 = 65,
|
||||
R_LARCH_B26 = 66,
|
||||
R_LARCH_ABS_HI20 = 67,
|
||||
R_LARCH_ABS_LO12 = 68,
|
||||
R_LARCH_ABS64_LO20 = 69,
|
||||
R_LARCH_ABS64_HI12 = 70,
|
||||
R_LARCH_PCALA_HI20 = 71,
|
||||
R_LARCH_PCALA_LO12 = 72,
|
||||
R_LARCH_PCALA64_LO20 = 73,
|
||||
R_LARCH_PCALA64_HI12 = 74,
|
||||
R_LARCH_GOT_PC_HI20 = 75,
|
||||
R_LARCH_GOT_PC_LO12 = 76,
|
||||
R_LARCH_GOT64_PC_LO20 = 77,
|
||||
R_LARCH_GOT64_PC_HI12 = 78,
|
||||
R_LARCH_GOT_HI20 = 79,
|
||||
R_LARCH_GOT_LO12 = 80,
|
||||
R_LARCH_GOT64_LO20 = 81,
|
||||
R_LARCH_GOT64_HI12 = 82,
|
||||
R_LARCH_TLS_LE_HI20 = 83,
|
||||
R_LARCH_TLS_LE_LO12 = 84,
|
||||
R_LARCH_TLS_LE64_LO20 = 85,
|
||||
R_LARCH_TLS_LE64_HI12 = 86,
|
||||
R_LARCH_TLS_IE_PC_HI20 = 87,
|
||||
R_LARCH_TLS_IE_PC_LO12 = 88,
|
||||
R_LARCH_TLS_IE64_PC_LO20 = 89,
|
||||
R_LARCH_TLS_IE64_PC_HI12 = 90,
|
||||
R_LARCH_TLS_IE_HI20 = 91,
|
||||
R_LARCH_TLS_IE_LO12 = 92,
|
||||
R_LARCH_TLS_IE64_LO20 = 93,
|
||||
R_LARCH_TLS_IE64_HI12 = 94,
|
||||
R_LARCH_TLS_LD_PC_HI20 = 95,
|
||||
R_LARCH_TLS_LD_HI20 = 96,
|
||||
R_LARCH_TLS_GD_PC_HI20 = 97,
|
||||
R_LARCH_TLS_GD_HI20 = 98,
|
||||
R_LARCH_32_PCREL = 99,
|
||||
R_LARCH_RELAX = 100,
|
||||
R_LARCH_DELETE = 101,
|
||||
R_LARCH_ALIGN = 102,
|
||||
R_LARCH_PCREL20_S2 = 103,
|
||||
R_LARCH_CFA = 104,
|
||||
R_LARCH_ADD6 = 105,
|
||||
R_LARCH_SUB6 = 106,
|
||||
R_LARCH_ADD_ULEB128 = 107,
|
||||
R_LARCH_SUB_ULEB128 = 108,
|
||||
R_LARCH_64_PCREL = 109,
|
||||
};
|
||||
|
||||
//
|
||||
@@ -1589,14 +1594,8 @@ struct ElfPhdr<E> {
|
||||
// with a newly computed value.
|
||||
//
|
||||
// We don't want to have too many `if (REL)`s and `if (RELA)`s in our
|
||||
// codebase, so we write dynamic relocations in the following manner:
|
||||
//
|
||||
// - We always create a dynamic relocation with an addend. If it's REL,
|
||||
// the addend will be discarded.
|
||||
//
|
||||
// - We also always write an addend to the relocated place by default
|
||||
// even though it's redundant for RELA. If RELA, the written value
|
||||
// will be ovewritten by the dynamic linker at load-time.
|
||||
// codebase, so ElfRel always takes r_addend as a constructor argument.
|
||||
// If it's REL, the argument will simply be ignored.
|
||||
template <typename E> requires E::is_le && E::is_rela
|
||||
struct ElfRel<E> {
|
||||
ElfRel() = default;
|
||||
@@ -1643,6 +1642,16 @@ struct ElfRel<E> {
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
inline bool is_func_call_rel(const ElfRel<E> &r) {
|
||||
for (u32 r_type : E::R_FUNCALL)
|
||||
if (r.r_type == r_type)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct ElfDyn {
|
||||
Word<E> d_tag;
|
||||
@@ -1710,6 +1719,35 @@ struct ElfNhdr {
|
||||
// Target-specific ELF data types
|
||||
//
|
||||
|
||||
template <>
|
||||
struct ElfSym<ARM64> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 5;
|
||||
u8 arm64_variant_pcs : 1; // ARM64-specific
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfSym<PPC64V2> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
@@ -1718,6 +1756,9 @@ struct ElfSym<PPC64V2> {
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
bool preserves_r2() const { return ppc_local_entry != 1; }
|
||||
bool uses_toc() const { return ppc_local_entry > 1; }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
@@ -1784,39 +1825,38 @@ struct ElfRel<SPARC64> {
|
||||
// Machine descriptions
|
||||
//
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
|
||||
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
|
||||
template <typename E> concept needs_thunk = requires { E::thunk_size; };
|
||||
|
||||
template <typename E>
|
||||
static constexpr bool needs_thunk = requires { E::thunk_size; };
|
||||
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
|
||||
template <typename E> concept is_i386 = std::same_as<E, I386>;
|
||||
template <typename E> concept is_arm64 = std::same_as<E, ARM64>;
|
||||
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
|
||||
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
|
||||
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
|
||||
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
|
||||
template <typename E> concept is_rv32be = std::same_as<E, RV32BE>;
|
||||
template <typename E> concept is_ppc32 = std::same_as<E, PPC32>;
|
||||
template <typename E> concept is_ppc64v1 = std::same_as<E, PPC64V1>;
|
||||
template <typename E> concept is_ppc64v2 = std::same_as<E, PPC64V2>;
|
||||
template <typename E> concept is_s390x = std::same_as<E, S390X>;
|
||||
template <typename E> concept is_sparc64 = std::same_as<E, SPARC64>;
|
||||
template <typename E> concept is_m68k = std::same_as<E, M68K>;
|
||||
template <typename E> concept is_sh4 = std::same_as<E, SH4>;
|
||||
template <typename E> concept is_alpha = std::same_as<E, ALPHA>;
|
||||
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
|
||||
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
|
||||
|
||||
template <typename E> static constexpr bool is_x86_64 = std::is_same_v<E, X86_64>;
|
||||
template <typename E> static constexpr bool is_i386 = std::is_same_v<E, I386>;
|
||||
template <typename E> static constexpr bool is_arm64 = std::is_same_v<E, ARM64>;
|
||||
template <typename E> static constexpr bool is_arm32 = std::is_same_v<E, ARM32>;
|
||||
template <typename E> static constexpr bool is_rv64le = std::is_same_v<E, RV64LE>;
|
||||
template <typename E> static constexpr bool is_rv64be = std::is_same_v<E, RV64BE>;
|
||||
template <typename E> static constexpr bool is_rv32le = std::is_same_v<E, RV32LE>;
|
||||
template <typename E> static constexpr bool is_rv32be = std::is_same_v<E, RV32BE>;
|
||||
template <typename E> static constexpr bool is_ppc32 = std::is_same_v<E, PPC32>;
|
||||
template <typename E> static constexpr bool is_ppc64v1 = std::is_same_v<E, PPC64V1>;
|
||||
template <typename E> static constexpr bool is_ppc64v2 = std::is_same_v<E, PPC64V2>;
|
||||
template <typename E> static constexpr bool is_s390x = std::is_same_v<E, S390X>;
|
||||
template <typename E> static constexpr bool is_sparc64 = std::is_same_v<E, SPARC64>;
|
||||
template <typename E> static constexpr bool is_m68k = std::is_same_v<E, M68K>;
|
||||
template <typename E> static constexpr bool is_sh4 = std::is_same_v<E, SH4>;
|
||||
template <typename E> static constexpr bool is_alpha = std::is_same_v<E, ALPHA>;
|
||||
template <typename E> static constexpr bool is_hppa32 = std::is_same_v<E, HPPA32>;
|
||||
|
||||
template <typename E> static constexpr bool is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> static constexpr bool is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> static constexpr bool is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
template <typename E> static constexpr bool is_rv32 = is_rv32le<E> || is_rv32be<E>;
|
||||
template <typename E> static constexpr bool is_riscv = is_rv64<E> || is_rv32<E>;
|
||||
template <typename E> static constexpr bool is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
|
||||
template <typename E> static constexpr bool is_ppc = is_ppc64<E> || is_ppc32<E>;
|
||||
template <typename E> static constexpr bool is_sparc = is_sparc64<E>;
|
||||
template <typename E> static constexpr bool is_hppa = is_hppa32<E>;
|
||||
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
template <typename E> concept is_rv32 = is_rv32le<E> || is_rv32be<E>;
|
||||
template <typename E> concept is_riscv = is_rv64<E> || is_rv32<E>;
|
||||
template <typename E> concept is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
|
||||
template <typename E> concept is_ppc = is_ppc64<E> || is_ppc32<E>;
|
||||
template <typename E> concept is_sparc = is_sparc64<E>;
|
||||
template <typename E> concept is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr std::string_view target_name = "x86_64";
|
||||
@@ -1827,7 +1867,7 @@ struct X86_64 {
|
||||
static constexpr u32 e_machine = EM_X86_64;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
@@ -1839,6 +1879,7 @@ struct X86_64 {
|
||||
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
|
||||
};
|
||||
|
||||
struct I386 {
|
||||
@@ -1862,6 +1903,7 @@ struct I386 {
|
||||
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
@@ -1875,7 +1917,7 @@ struct ARM64 {
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 12;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
@@ -1887,6 +1929,7 @@ struct ARM64 {
|
||||
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
@@ -1899,8 +1942,8 @@ struct ARM32 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 12;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
static constexpr u32 thunk_hdr_size = 16;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
@@ -1912,34 +1955,46 @@ struct ARM32 {
|
||||
static constexpr u32 R_TPOFF = R_ARM_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_ARM_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_ARM_TLS_DESC;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_ARM_JUMP24, R_ARM_THM_JUMP24, R_ARM_CALL, R_ARM_THM_CALL, R_ARM_PLT32,
|
||||
};
|
||||
};
|
||||
|
||||
struct RV64LE {
|
||||
struct RV64 {
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV64LE : RV64 {
|
||||
static constexpr std::string_view target_name = "riscv64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
};
|
||||
|
||||
struct RV64BE {
|
||||
struct RV64BE : RV64 {
|
||||
static constexpr std::string_view target_name = "riscv64be";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct RV32 {
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
@@ -1948,58 +2003,26 @@ struct RV64BE {
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_64;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD64;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
static constexpr u32 R_TLSDESC = R_RISCV_TLSDESC;
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV32LE {
|
||||
struct RV32LE : RV32 {
|
||||
static constexpr std::string_view target_name = "riscv32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
};
|
||||
|
||||
struct RV32BE {
|
||||
struct RV32BE : RV32 {
|
||||
static constexpr std::string_view target_name = "riscv32be";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_RISCV_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_RISCV_32;
|
||||
static constexpr u32 R_RELATIVE = R_RISCV_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_RISCV_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_RISCV_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_RISCV_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_RISCV_TLS_DTPMOD32;
|
||||
};
|
||||
|
||||
struct PPC32 {
|
||||
@@ -2015,6 +2038,7 @@ struct PPC32 {
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 36;
|
||||
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC_JMP_SLOT;
|
||||
@@ -2024,54 +2048,47 @@ struct PPC32 {
|
||||
static constexpr u32 R_DTPOFF = R_PPC_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_PPC_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_PPC_DTPMOD32;
|
||||
|
||||
static constexpr u32 R_FUNCALL[] = {
|
||||
R_PPC_REL24, R_PPC_PLTREL24, R_PPC_LOCAL24PC,
|
||||
};
|
||||
};
|
||||
|
||||
struct PPC64V1 {
|
||||
static constexpr std::string_view target_name = "ppc64v1";
|
||||
struct PPC64 {
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_PPC64;
|
||||
static constexpr u32 plt_hdr_size = 52;
|
||||
static constexpr u32 plt_size = 8;
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_PPC64_REL24, R_PPC64_REL24_NOTOC };
|
||||
};
|
||||
|
||||
struct PPC64V1 : PPC64 {
|
||||
static constexpr std::string_view target_name = "ppc64v1";
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr u32 plt_hdr_size = 44;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
};
|
||||
|
||||
struct PPC64V2 {
|
||||
struct PPC64V2 : PPC64 {
|
||||
static constexpr std::string_view target_name = "ppc64v2";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_PPC64;
|
||||
static constexpr u32 plt_hdr_size = 60;
|
||||
static constexpr u32 plt_hdr_size = 52;
|
||||
static constexpr u32 plt_size = 4;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC64_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PPC64_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_PPC64_ADDR64;
|
||||
static constexpr u32 R_RELATIVE = R_PPC64_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_PPC64_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_PPC64_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_PPC64_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_PPC64_DTPMOD64;
|
||||
static constexpr u32 thunk_size = 24;
|
||||
};
|
||||
|
||||
struct S390X {
|
||||
@@ -2081,8 +2098,8 @@ struct S390X {
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_S390X;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_390_COPY;
|
||||
@@ -2094,6 +2111,7 @@ struct S390X {
|
||||
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
|
||||
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
|
||||
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
|
||||
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
|
||||
};
|
||||
|
||||
struct SPARC64 {
|
||||
@@ -2116,6 +2134,7 @@ struct SPARC64 {
|
||||
static constexpr u32 R_DTPOFF = R_SPARC_TLS_DTPOFF64;
|
||||
static constexpr u32 R_TPOFF = R_SPARC_TLS_TPOFF64;
|
||||
static constexpr u32 R_DTPMOD = R_SPARC_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SPARC_WPLT30, R_SPARC_WDISP30 };
|
||||
};
|
||||
|
||||
struct M68K {
|
||||
@@ -2134,10 +2153,10 @@ struct M68K {
|
||||
static constexpr u32 R_JUMP_SLOT = R_68K_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_68K_32;
|
||||
static constexpr u32 R_RELATIVE = R_68K_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
static constexpr u32 R_DTPOFF = R_68K_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_68K_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_68K_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
|
||||
};
|
||||
|
||||
struct SH4 {
|
||||
@@ -2156,10 +2175,10 @@ struct SH4 {
|
||||
static constexpr u32 R_JUMP_SLOT = R_SH_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_SH_DIR32;
|
||||
static constexpr u32 R_RELATIVE = R_SH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
static constexpr u32 R_DTPOFF = R_SH_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_SH_TLS_TPOFF32;
|
||||
static constexpr u32 R_DTPMOD = R_SH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
|
||||
};
|
||||
|
||||
struct ALPHA {
|
||||
@@ -2178,34 +2197,60 @@ struct ALPHA {
|
||||
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = {};
|
||||
};
|
||||
|
||||
struct HPPA32 {
|
||||
static constexpr std::string_view target_name = "hppa32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = false;
|
||||
struct LOONGARCH64 {
|
||||
static constexpr std::string_view target_name = "loongarch64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_PARISC;
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 0;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 e_machine = EM_LOONGARCH;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 20;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_PARISC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PARISC_DIR32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_PARISC_IPLT;
|
||||
static constexpr u32 R_ABS = R_PARISC_DIR32;
|
||||
static constexpr u32 R_RELATIVE = R_PARISC_DIR32;
|
||||
static constexpr u32 R_IRELATIVE = R_NONE;
|
||||
static constexpr u32 R_DTPOFF = R_PARISC_TLS_DTPOFF32;
|
||||
static constexpr u32 R_TPOFF = R_PARISC_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_PARISC_TLS_DTPMOD32;
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_64;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
struct LOONGARCH32 {
|
||||
static constexpr std::string_view target_name = "loongarch32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 e_machine = EM_LOONGARCH;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
|
||||
static constexpr u32 R_JUMP_SLOT = R_LARCH_JUMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_LARCH_32;
|
||||
static constexpr u32 R_RELATIVE = R_LARCH_RELATIVE;
|
||||
static constexpr u32 R_IRELATIVE = R_LARCH_IRELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
};
|
||||
|
||||
} // namespace mold::elf
|
||||
+11
-29
@@ -30,7 +30,7 @@ static bool should_keep(const InputSection<E> &isec) {
|
||||
|
||||
template <typename E>
|
||||
static bool mark_section(InputSection<E> *isec) {
|
||||
return isec && isec->is_alive && fast_mark(isec->is_visited);
|
||||
return isec && isec->is_alive && !isec->is_visited.test_and_set();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -53,19 +53,18 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
frag->is_alive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mark_section(sym.get_input_section()))
|
||||
continue;
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
if (mark_section(sym.get_input_section())) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ static void collect_root_set(Context<E> &ctx,
|
||||
auto enqueue_symbol = [&](Symbol<E> *sym) {
|
||||
if (sym) {
|
||||
if (SectionFragment<E> *frag = sym->get_frag())
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
frag->is_alive = true;
|
||||
else
|
||||
enqueue_section(sym->get_input_section());
|
||||
}
|
||||
@@ -94,10 +93,10 @@ static void collect_root_set(Context<E> &ctx,
|
||||
if (!isec || !isec->is_alive)
|
||||
continue;
|
||||
|
||||
// -gc-sections discards only SHF_ALLOC sections. If you want to
|
||||
// --gc-sections discards only SHF_ALLOC sections. If you want to
|
||||
// reduce the amount of non-memory-mapped segments, you should
|
||||
// use `strip` command, compile without debug info or use
|
||||
// -strip-all linker option.
|
||||
// --strip-all linker option.
|
||||
u32 flags = isec->shdr().sh_flags;
|
||||
if (!(flags & SHF_ALLOC))
|
||||
isec->is_visited = true;
|
||||
@@ -163,27 +162,10 @@ static void sweep(Context<E> &ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
// Non-alloc section fragments are not subject of garbage collection.
|
||||
// This function marks such fragments.
|
||||
template <typename E>
|
||||
static void mark_nonalloc_fragments(Context<E> &ctx) {
|
||||
Timer t(ctx, "mark_nonalloc_fragments");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
|
||||
if (m)
|
||||
for (SectionFragment<E> *frag : m->fragments)
|
||||
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
|
||||
frag->is_alive.store(true, std::memory_order_relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
mark_nonalloc_fragments(ctx);
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
collect_root_set(ctx, rootset);
|
||||
mark(ctx, rootset);
|
||||
|
||||
@@ -0,0 +1,808 @@
|
||||
// This file contains code to read DWARF debug info to create .gdb_index.
|
||||
//
|
||||
// .gdb_index is an optional section to speed up GNU debugger. It contains
|
||||
// two maps: 1) a map from function/variable/type names to compunits, and
|
||||
// 2) a map from function address ranges to compunits. gdb uses these
|
||||
// maps to quickly find a compunit given a name or an instruction pointer.
|
||||
//
|
||||
// (Terminology: a compilation unit, often abbreviated as compunit or
|
||||
// CU, is a unit of debug info. An input .debug_info section usually
|
||||
// contains one compunit, and thus an output .debug_info contains as
|
||||
// many compunits as the number of input files.)
|
||||
//
|
||||
// .gdb_index is not mandatory. All the information in .gdb_index is
|
||||
// also in other debug info sections. You can actually create an
|
||||
// executable without .gdb_index and later add it using the
|
||||
// `gdb-add-index` post-processing tool that comes with gdb.
|
||||
//
|
||||
// Post-relocated debug section contents are needed to create a
|
||||
// .gdb_index. Therefore, we create it after relocating all the other
|
||||
// sections. The size of the section is also hard to estimate before
|
||||
// applying relocations to debug info sections, so a .gdb_index is
|
||||
// placed at the very end of the output file, even after the section
|
||||
// header.
|
||||
//
|
||||
// The mapping from names to compunits is 1:n while the mapping from
|
||||
// address ranges to compunits is 1:1. That is, two object files may
|
||||
// define the same type name, while there should be no two functions
|
||||
// that overlap with each other in memory.
|
||||
//
|
||||
// .gdb_index contains an on-disk hash table for names, so gdb can
|
||||
// lookup names without loading all strings into memory and construct an
|
||||
// in-memory hash table.
|
||||
//
|
||||
// Names are in .debug_gnu_pubnames and .debug_gnu_pubtypes input
|
||||
// sections. These sections are created if `-ggnu-pubnames` is given.
|
||||
// Besides names, these sections contain attributes for each name so
|
||||
// that gdb can distinguish type names from function names, for example.
|
||||
//
|
||||
// A compunit contains one or more function address ranges. If an
|
||||
// object file is compiled without -ffunction-sections, it contains
|
||||
// only one .text section and therefore contains a single address range.
|
||||
// Such range is typically stored directly to the compunit.
|
||||
//
|
||||
// If an object file is compiled with -ffunction-sections, it contains
|
||||
// more than one .text section, and it has as many address ranges as
|
||||
// the number of .text sections. Such discontiguous address ranges are
|
||||
// stored to .debug_ranges in DWARF 2/3/4 and .debug_rnglists/.debug_addr
|
||||
// in DWARF 5.
|
||||
//
|
||||
// .debug_info section contains DWARF debug info. Although we don't need
|
||||
// to parse the whole .debug_info section to read address ranges, we
|
||||
// have to do a little bit. DWARF is complicated and often handled using
|
||||
// a library such as libdwarf. But we don't use any library because we
|
||||
// don't want to add an extra run-time dependency just for --gdb-index.
|
||||
//
|
||||
// This page explains the format of .gdb_index:
|
||||
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
|
||||
|
||||
#include "mold.h"
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U32<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf2_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> abbrev_offset;
|
||||
u8 address_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct CuHdrDwarf5_64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
u8 unit_type;
|
||||
u8 address_size;
|
||||
U64<E> abbrev_offset;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr32 {
|
||||
U32<E> size;
|
||||
U16<E> version;
|
||||
U32<E> debug_info_offset;
|
||||
U32<E> debug_info_size;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
struct PubnamesHdr64 {
|
||||
U32<E> magic;
|
||||
U64<E> size;
|
||||
U16<E> version;
|
||||
U64<E> debug_info_offset;
|
||||
U64<E> debug_info_size;
|
||||
};
|
||||
|
||||
struct SectionHeader {
|
||||
ul32 version = 7;
|
||||
ul32 cu_list_offset = 0;
|
||||
ul32 cu_types_offset = 0;
|
||||
ul32 ranges_offset = 0;
|
||||
ul32 symtab_offset = 0;
|
||||
ul32 const_pool_offset = 0;
|
||||
};
|
||||
|
||||
struct NameType {
|
||||
bool operator==(const NameType &) const = default;
|
||||
|
||||
bool operator<(const NameType &other) const {
|
||||
return std::tuple(hash, type, name) <
|
||||
std::tuple(other.hash, other.type, other.name);
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
u32 hash;
|
||||
u8 type;
|
||||
};
|
||||
|
||||
struct MapValue {
|
||||
std::string_view name;
|
||||
u32 hash = 0;
|
||||
Atomic<u32> count = 0;
|
||||
u32 name_offset = 0;
|
||||
u32 type_offset = 0;
|
||||
};
|
||||
|
||||
struct Compunit {
|
||||
DwarfKind kind;
|
||||
i64 offset;
|
||||
i64 size;
|
||||
std::vector<std::pair<u64, u64>> ranges;
|
||||
std::vector<NameType> nametypes;
|
||||
std::vector<MapValue *> entries;
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static DwarfKind get_dwarf_kind(Context<E> &ctx, u8 *p) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff) {
|
||||
CuHdrDwarf2_64<E> &hdr = *(CuHdrDwarf2_64<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_64 : DWARF2_64;
|
||||
}
|
||||
|
||||
CuHdrDwarf2_32<E> &hdr = *(CuHdrDwarf2_32<E> *)p;
|
||||
if (hdr.version > 5)
|
||||
Fatal(ctx) << "--gdb-index: DWARF version " << hdr.version
|
||||
<< " is not supported";
|
||||
return (hdr.version == 5) ? DWARF5_32 : DWARF2_32;
|
||||
}
|
||||
|
||||
template <typename E, typename CuHdr>
|
||||
u8 *find_cu_abbrev(Context<E> &ctx, u8 **p, const CuHdr &hdr) {
|
||||
if (hdr.address_size != sizeof(Word<E>))
|
||||
Fatal(ctx) << "--gdb-index: unsupported address size " << hdr.address_size;
|
||||
|
||||
if constexpr (requires { hdr.unit_type; }) {
|
||||
switch (hdr.unit_type) {
|
||||
case DW_UT_compile:
|
||||
case DW_UT_partial:
|
||||
break;
|
||||
case DW_UT_skeleton:
|
||||
case DW_UT_split_compile:
|
||||
*p += 8;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unknown unit type: 0x"
|
||||
<< std::hex << hdr.unit_type;
|
||||
}
|
||||
}
|
||||
|
||||
i64 abbrev_code = read_uleb(p);
|
||||
|
||||
// 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 = &ctx.debug_abbrev[0] + hdr.abbrev_offset;
|
||||
|
||||
for (;;) {
|
||||
u32 code = read_uleb(&abbrev);
|
||||
if (code == 0)
|
||||
Fatal(ctx) << "--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) << "--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 abbrev;
|
||||
}
|
||||
|
||||
// .debug_info contains variable-length fields.
|
||||
// This function reads one scalar value from a given location.
|
||||
template <typename E, typename Offset>
|
||||
u64 read_scalar(Context<E> &ctx, u8 **p, 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 *(*p)++;
|
||||
case DW_FORM_data2:
|
||||
case DW_FORM_strx2:
|
||||
case DW_FORM_addrx2:
|
||||
case DW_FORM_ref2: {
|
||||
u64 val = *(U16<E> *)*p;
|
||||
*p += 2;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strx3:
|
||||
case DW_FORM_addrx3: {
|
||||
u64 val = *(U24<E> *)*p;
|
||||
*p += 3;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data4:
|
||||
case DW_FORM_strx4:
|
||||
case DW_FORM_addrx4:
|
||||
case DW_FORM_ref4: {
|
||||
u64 val = *(U32<E> *)*p;
|
||||
*p += 4;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_data8:
|
||||
case DW_FORM_ref8: {
|
||||
u64 val = *(U64<E> *)*p;
|
||||
*p += 8;
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_strp:
|
||||
case DW_FORM_sec_offset:
|
||||
case DW_FORM_line_strp: {
|
||||
u64 val = *(Offset *)*p;
|
||||
*p += sizeof(Offset);
|
||||
return val;
|
||||
}
|
||||
case DW_FORM_addr:
|
||||
case DW_FORM_ref_addr: {
|
||||
u64 val = *(Word<E> *)*p;
|
||||
*p += sizeof(Word<E>);
|
||||
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(p);
|
||||
case DW_FORM_string:
|
||||
*p += strlen((char *)*p) + 1;
|
||||
return 0;
|
||||
default:
|
||||
Fatal(ctx) << "--gdb-index: unhandled debug info form: 0x"
|
||||
<< std::hex << form;
|
||||
}
|
||||
}
|
||||
|
||||
// Read a range list from .debug_ranges starting at the given offset.
|
||||
template <typename E>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_debug_range(Word<E> *range, u64 base) {
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
|
||||
for (i64 i = 0; range[i] || range[i + 1]; i += 2) {
|
||||
if (range[i] + 1 == 0)
|
||||
base = range[i + 1];
|
||||
else
|
||||
vec.emplace_back(range[i] + base, range[i + 1] + base);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Read a range list from .debug_rnglists starting at the given offset.
|
||||
template <typename E>
|
||||
static void
|
||||
read_rnglist_range(std::vector<std::pair<u64, u64>> &vec, u8 *p,
|
||||
Word<E> *addrx, u64 base) {
|
||||
for (;;) {
|
||||
switch (*p++) {
|
||||
case DW_RLE_end_of_list:
|
||||
return;
|
||||
case DW_RLE_base_addressx:
|
||||
base = addrx[read_uleb(&p)];
|
||||
break;
|
||||
case DW_RLE_startx_endx: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val2]);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_startx_length: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(addrx[val1], addrx[val1] + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_offset_pair: {
|
||||
u64 val1 = read_uleb(&p);
|
||||
u64 val2 = read_uleb(&p);
|
||||
|
||||
// If the base is 0, this address range is for an eliminated
|
||||
// section. We only emit it if it's alive.
|
||||
if (base)
|
||||
vec.emplace_back(base + val1, base + val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_base_address:
|
||||
base = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
break;
|
||||
case DW_RLE_start_end: {
|
||||
u64 val1 = ((Word<E> *)p)[0];
|
||||
u64 val2 = ((Word<E> *)p)[1];
|
||||
p += sizeof(Word<E>) * 2;
|
||||
vec.emplace_back(val1, val2);
|
||||
break;
|
||||
}
|
||||
case DW_RLE_start_length: {
|
||||
u64 val1 = *(Word<E> *)p;
|
||||
p += sizeof(Word<E>);
|
||||
u64 val2 = read_uleb(&p);
|
||||
vec.emplace_back(val1, val1 + val2);
|
||||
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, typename CuHdr>
|
||||
static std::vector<std::pair<u64, u64>>
|
||||
read_address_ranges(Context<E> &ctx, const Compunit &cu) {
|
||||
// Read .debug_info to find the record at a given offset.
|
||||
u8 *p = &ctx.debug_info[0] + cu.offset;
|
||||
CuHdr &hdr = *(CuHdr *)p;
|
||||
p += sizeof(hdr);
|
||||
|
||||
u8 *abbrev = find_cu_abbrev(ctx, &p, hdr);
|
||||
|
||||
// Now, read debug info records.
|
||||
struct Record {
|
||||
u64 form = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
Record low_pc;
|
||||
Record high_pc;
|
||||
Record ranges;
|
||||
u64 rnglists_base = -1;
|
||||
Word<E> *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 = read_scalar<E, Offset>(ctx, &p, 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 = (Word<E> *)(&ctx.debug_addr[0] + val);
|
||||
break;
|
||||
case DW_AT_ranges:
|
||||
ranges = {form, val};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle non-contiguous address ranges.
|
||||
if (ranges.form) {
|
||||
if (hdr.version <= 4) {
|
||||
Word<E> *p = (Word<E> *)(&ctx.debug_ranges[0] + ranges.value);
|
||||
return read_debug_range<E>(p, low_pc.value);
|
||||
}
|
||||
|
||||
assert(hdr.version == 5);
|
||||
|
||||
std::vector<std::pair<u64, u64>> vec;
|
||||
u8 *buf = &ctx.debug_rnglists[0];
|
||||
|
||||
if (ranges.form == DW_FORM_sec_offset) {
|
||||
read_rnglist_range<E>(vec, buf + ranges.value, addrx, low_pc.value);
|
||||
} else {
|
||||
if (rnglists_base == -1)
|
||||
Fatal(ctx) << "--gdb-index: missing DW_AT_rnglists_base";
|
||||
|
||||
u8 *base = buf + rnglists_base;
|
||||
i64 num_offsets = *(U32<E> *)(base - 4);
|
||||
Offset *offsets = (Offset *)base;
|
||||
|
||||
for (i64 i = 0; i < num_offsets; i++)
|
||||
read_rnglist_range<E>(vec, base + offsets[i], addrx, low_pc.value);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// 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) << "--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) << "--gdb-index: unhandled form for DW_AT_high_pc: 0x"
|
||||
<< std::hex << high_pc.form;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename E, typename PubnamesHdr>
|
||||
static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
|
||||
std::vector<Compunit> &cus, ObjectFile<E> &file) {
|
||||
using Offset = decltype(hdr.size);
|
||||
|
||||
auto get_cu = [&](i64 offset) {
|
||||
for (i64 i = 0; i < cus.size(); i++)
|
||||
if (cus[i].offset == offset)
|
||||
return &cus[i];
|
||||
Fatal(ctx) << file << ": corrupted debug_info_offset";
|
||||
};
|
||||
|
||||
Compunit *cu = get_cu(file.debug_info->offset + hdr.debug_info_offset);
|
||||
i64 size = hdr.size + offsetof(PubnamesHdr, size) + sizeof(hdr.size);
|
||||
u8 *p = (u8 *)&hdr + sizeof(hdr);
|
||||
u8 *end = (u8 *)&hdr + size;
|
||||
|
||||
while (p < end) {
|
||||
if (*(Offset *)p == 0)
|
||||
break;
|
||||
p += sizeof(Offset);
|
||||
|
||||
u8 type = *p++;
|
||||
std::string_view name = (char *)p;
|
||||
p += name.size() + 1;
|
||||
cu->nametypes.push_back({name, gdb_hash(name), type});
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// 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>
|
||||
static void read_pubnames(Context<E> &ctx, std::vector<Compunit> &cus,
|
||||
ObjectFile<E> &file) {
|
||||
for (InputSection<E> *isec : { file.debug_pubnames, file.debug_pubtypes }) {
|
||||
if (!isec)
|
||||
continue;
|
||||
|
||||
isec->uncompress(ctx);
|
||||
if (isec->contents.empty())
|
||||
continue;
|
||||
|
||||
u8 *p = (u8*)&isec->contents[0];
|
||||
u8 *end = p + isec->contents.size();
|
||||
|
||||
while (p < end) {
|
||||
if (*(U32<E> *)p == 0xffff'ffff)
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr64<E> *)p, cus, file);
|
||||
else
|
||||
p += read_pubnames_cu(ctx, *(PubnamesHdr32<E> *)p, cus, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
std::vector<Compunit> cus;
|
||||
|
||||
// Read compunits from the output .debug_info section.
|
||||
u8 *begin = &ctx.debug_info[0];
|
||||
u8 *end = begin + ctx.debug_info.size();
|
||||
|
||||
for (u8 *p = begin; p < end;) {
|
||||
DwarfKind kind = get_dwarf_kind(ctx, p);
|
||||
i64 size;
|
||||
if (kind == DWARF2_32 || kind == DWARF5_32)
|
||||
size = ((CuHdrDwarf2_32<E> *)p)->size + 4;
|
||||
else
|
||||
size = ((CuHdrDwarf2_64<E> *)p)->size + 12;
|
||||
|
||||
cus.push_back(Compunit{kind, p - begin, size});
|
||||
p += size;
|
||||
}
|
||||
|
||||
// Read address ranges for each compunit.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
switch (cu.kind) {
|
||||
case DWARF2_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_32:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_32<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF2_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf2_64<E>>(ctx, cu);
|
||||
break;
|
||||
case DWARF5_64:
|
||||
cu.ranges = read_address_ranges<E, CuHdrDwarf5_64<E>>(ctx, cu);
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove empty ranges
|
||||
std::erase_if(cu.ranges, [](std::pair<u64, u64> p) {
|
||||
return p.first == 0 || p.first == p.second;
|
||||
});
|
||||
});
|
||||
|
||||
// Read symbols from .debug_gnu_pubnames and .debug_gnu_pubtypes.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
read_pubnames(ctx, cus, *file);
|
||||
});
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
sort(cu.nametypes);
|
||||
remove_duplicates(cu.nametypes);
|
||||
});
|
||||
|
||||
return cus;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (chunk->is_compressed)
|
||||
return chunk->uncompressed_data;
|
||||
return {ctx.buf + chunk->shdr.sh_offset, chunk->shdr.sh_size};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void write_gdb_index(Context<E> &ctx) {
|
||||
Timer t(ctx, "write_gdb_index");
|
||||
|
||||
// Find debug info sections
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
std::string_view name = chunk->name;
|
||||
if (name == ".debug_info")
|
||||
ctx.debug_info = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_abbrev")
|
||||
ctx.debug_abbrev = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_ranges")
|
||||
ctx.debug_ranges = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_addr")
|
||||
ctx.debug_addr = get_buffer(ctx, chunk);
|
||||
if (name == ".debug_rnglists")
|
||||
ctx.debug_rnglists = get_buffer(ctx, chunk);
|
||||
}
|
||||
|
||||
if (ctx.debug_info.empty())
|
||||
return;
|
||||
|
||||
// Read debug info
|
||||
std::vector<Compunit> cus = read_compunits(ctx);
|
||||
|
||||
// Uniquify symbols
|
||||
HyperLogLog estimator;
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
HyperLogLog e;
|
||||
for (NameType &nt : cu.nametypes)
|
||||
e.insert(nt.hash);
|
||||
estimator.merge(e);
|
||||
});
|
||||
|
||||
ConcurrentMap<MapValue> map(estimator.get_cardinality() * 3 / 2);
|
||||
Atomic<i64> num_entries;
|
||||
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
cu.entries.reserve(cu.nametypes.size());
|
||||
i64 count = 0;
|
||||
|
||||
for (NameType &nt : cu.nametypes) {
|
||||
MapValue *ent;
|
||||
bool inserted;
|
||||
MapValue value = {nt.name, nt.hash};
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, value);
|
||||
ent->count++;
|
||||
if (inserted)
|
||||
count++;
|
||||
cu.entries.push_back(ent);
|
||||
}
|
||||
num_entries += count;
|
||||
});
|
||||
|
||||
// Sort symbols for build reproducibility
|
||||
std::vector<MapValue *> entries;
|
||||
entries.reserve(num_entries);
|
||||
|
||||
for (i64 i = 0; i < map.nbuckets; i++)
|
||||
if (map.entries[i].key)
|
||||
entries.push_back(&map.entries[i].value);
|
||||
|
||||
tbb::parallel_sort(entries, [](MapValue *a, MapValue *b) {
|
||||
return std::tuple(a->hash, a->name) < std::tuple(b->hash, b->name);
|
||||
});
|
||||
|
||||
// Compute sizes of each components
|
||||
SectionHeader hdr;
|
||||
hdr.cu_list_offset = sizeof(hdr);
|
||||
hdr.cu_types_offset = hdr.cu_list_offset + cus.size() * 16;
|
||||
hdr.ranges_offset = hdr.cu_types_offset;
|
||||
|
||||
hdr.symtab_offset = hdr.ranges_offset;
|
||||
for (Compunit &cu : cus)
|
||||
hdr.symtab_offset += cu.ranges.size() * 20;
|
||||
|
||||
i64 ht_size = bit_ceil(num_entries * 5 / 4 + 1);
|
||||
hdr.const_pool_offset = hdr.symtab_offset + ht_size * 8;
|
||||
|
||||
i64 offset = 0;
|
||||
for (MapValue *ent : entries) {
|
||||
ent->type_offset = offset;
|
||||
offset += ent->count * 4 + 4;
|
||||
}
|
||||
|
||||
for (MapValue *ent : entries) {
|
||||
ent->name_offset = offset;
|
||||
offset += ent->name.size() + 1;
|
||||
}
|
||||
|
||||
i64 bufsize = hdr.const_pool_offset + offset;
|
||||
|
||||
// Allocate an output buffer
|
||||
ctx.output_file->buf2.resize(bufsize);
|
||||
u8 *buf = ctx.output_file->buf2.data();
|
||||
|
||||
// Write a section header
|
||||
memcpy(buf, &hdr, sizeof(hdr));
|
||||
|
||||
// Write a CU list
|
||||
u8 *p = buf + sizeof(hdr);
|
||||
|
||||
for (Compunit &cu : cus) {
|
||||
*(ul64 *)p = cu.offset;
|
||||
*(ul64 *)(p + 8) = cu.size;
|
||||
p += 16;
|
||||
}
|
||||
|
||||
// Write address areas
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
for (std::pair<u64, u64> range : cus[i].ranges) {
|
||||
*(ul64 *)p = range.first;
|
||||
*(ul64 *)(p + 8) = range.second;
|
||||
*(ul32 *)(p + 16) = i;
|
||||
p += 20;
|
||||
}
|
||||
}
|
||||
|
||||
// Write a symbol table
|
||||
u32 mask = ht_size - 1;
|
||||
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
|
||||
|
||||
for (MapValue *ent : entries) {
|
||||
u32 hash = ent->hash;
|
||||
u32 step = ((hash * 17) & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
while (ht[j * 2] || ht[j * 2 + 1])
|
||||
j = (j + step) & mask;
|
||||
|
||||
ht[j * 2] = ent->name_offset;
|
||||
ht[j * 2 + 1] = ent->type_offset;
|
||||
}
|
||||
|
||||
// Write types
|
||||
for (i64 i = 0; i < cus.size(); i++) {
|
||||
Compunit &cu = cus[i];
|
||||
u8 *base = buf + hdr.const_pool_offset;
|
||||
|
||||
for (i64 j = 0; j < cu.nametypes.size(); j++) {
|
||||
ul32 *p = (ul32 *)(base + cu.entries[j]->type_offset);
|
||||
i64 idx = ++p[0];
|
||||
p[idx] = (cu.nametypes[j].type << 24) | i;
|
||||
}
|
||||
}
|
||||
|
||||
// Write names
|
||||
tbb::parallel_for_each(entries, [&](MapValue *ent) {
|
||||
write_string(buf + hdr.const_pool_offset + ent->name_offset, ent->name);
|
||||
});
|
||||
|
||||
// Update the section size and rewrite the section header
|
||||
if (ctx.shdr) {
|
||||
ctx.gdb_index->shdr.sh_size = bufsize;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void write_gdb_index(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+30
-38
@@ -65,7 +65,7 @@
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "../sha.h"
|
||||
#include "blake3.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
@@ -81,7 +81,7 @@ static constexpr int64_t HASH_SIZE = 16;
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<Digest> {
|
||||
template <> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
return *(int64_t *)&k[0];
|
||||
}
|
||||
@@ -90,16 +90,6 @@ template<> struct hash<Digest> {
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
class BitVector {
|
||||
public:
|
||||
BitVector(u32 size) : vec((size + 7) / 8) {}
|
||||
bool get(u32 idx) const { return vec[idx / 8] & (1 << (idx % 8)); }
|
||||
void set(u32 idx) { vec[idx / 8] |= 1 << (idx % 8); }
|
||||
|
||||
private:
|
||||
std::vector<u8> vec;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
@@ -125,26 +115,26 @@ 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_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;
|
||||
bool is_bss = (shdr.sh_type == SHT_NOBITS);
|
||||
bool is_empty = (shdr.sh_size == 0);
|
||||
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;
|
||||
if (shdr.sh_size == 0 || !(shdr.sh_flags & SHF_ALLOC) ||
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
return false;
|
||||
|
||||
return is_alloc && is_exec && is_readonly && !is_bss && !is_empty &&
|
||||
!is_init && !is_fini && !is_enumerable && !is_addr_taken;
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
return (ctx.arg.icf_all || !isec.address_taken) &&
|
||||
name != ".init" && name != ".fini";
|
||||
} else {
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.output_section && isec.output_section->is_relro;
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
}
|
||||
|
||||
static Digest digest_final(SHA256Hash &sha) {
|
||||
u8 buf[SHA256_SIZE];
|
||||
sha.finish(buf);
|
||||
static Digest digest_final(blake3_hasher *hasher) {
|
||||
assert(HASH_SIZE <= BLAKE3_OUT_LEN);
|
||||
|
||||
u8 buf[BLAKE3_OUT_LEN];
|
||||
blake3_hasher_finalize(hasher, buf, BLAKE3_OUT_LEN);
|
||||
|
||||
Digest digest;
|
||||
memcpy(digest.data(), buf, HASH_SIZE);
|
||||
@@ -244,15 +234,16 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
SHA256Hash sha;
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
sha.update((u8 *)&val, sizeof(val));
|
||||
blake3_hasher_update(&hasher, (u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
sha.update((u8 *)str.data(), str.size());
|
||||
blake3_hasher_update(&hasher, (u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
@@ -308,7 +299,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
|
||||
return digest_final(sha);
|
||||
return digest_final(&hasher);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -421,16 +412,17 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
if (converged.get(i))
|
||||
return;
|
||||
|
||||
SHA256Hash sha;
|
||||
sha.update(digests[2][i].data(), HASH_SIZE);
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
blake3_hasher_update(&hasher, digests[2][i].data(), HASH_SIZE);
|
||||
|
||||
i64 begin = edge_indices[i];
|
||||
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
|
||||
|
||||
for (i64 j : edges.subspan(begin, end - begin))
|
||||
sha.update(digests[slot][j].data(), HASH_SIZE);
|
||||
blake3_hasher_update(&hasher, digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
digests[!slot][i] = digest_final(sha);
|
||||
digests[!slot][i] = digest_final(&hasher);
|
||||
|
||||
if (digests[slot][i] == digests[!slot][i]) {
|
||||
// This node has converged. Skip further iterations as it will
|
||||
@@ -573,7 +565,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group sections by SHA digest.
|
||||
// Group sections by BLAKE3 digest.
|
||||
{
|
||||
Timer t(ctx, "group");
|
||||
|
||||
|
||||
+187
-288
@@ -42,6 +42,12 @@ InputFile<E>::InputFile(Context<E> &ctx, MappedFile<Context<E>> *mf)
|
||||
shstrtab = this->get_string(ctx, shstrtab_idx);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<ElfPhdr<E>> InputFile<E>::get_phdrs() {
|
||||
ElfEhdr<E> &ehdr = get_ehdr();
|
||||
return {(ElfPhdr<E> *)(mf->data + ehdr.e_phoff), ehdr.e_phnum};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
for (ElfShdr<E> &sec : elf_sections)
|
||||
@@ -50,21 +56,20 @@ ElfShdr<E> *InputFile<E>::find_section(i64 type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This function is actually racy, in that "tearing" might be observed
|
||||
// during the read or write of `sym->file`. However, on most platforms
|
||||
// read/write of pointers never tears, and even if it did, it would be
|
||||
// harmless, as `sym->file` will not become equal to `sym`, or even if
|
||||
// it did by chance, we just wipe a symbol twice.
|
||||
//
|
||||
// Doing this in a standard-compliant way is too cumbersome, hence
|
||||
// here we just pretend a compiler will never optimize this into a
|
||||
// form that actually exploits the UB.
|
||||
template <typename E>
|
||||
__attribute__((no_sanitize("thread")))
|
||||
void InputFile<E>::clear_symbols() {
|
||||
for (Symbol<E> *sym : get_global_syms())
|
||||
if (sym->file == this)
|
||||
sym->clear();
|
||||
for (Symbol<E> *sym : get_global_syms()) {
|
||||
if (__atomic_load_n(&sym->file, __ATOMIC_ACQUIRE) == this) {
|
||||
sym->origin = 0;
|
||||
sym->value = -1;
|
||||
sym->sym_idx = -1;
|
||||
sym->ver_idx = 0;
|
||||
sym->is_weak = false;
|
||||
sym->is_imported = false;
|
||||
sym->is_exported = false;
|
||||
__atomic_store_n(&sym->file, nullptr, __ATOMIC_RELEASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the source filename. It should be listed in symtab as STT_FILE.
|
||||
@@ -99,7 +104,7 @@ static bool is_debug_section(const ElfShdr<E> &shdr, std::string_view name) {
|
||||
|
||||
template <typename E>
|
||||
void
|
||||
ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
ObjectFile<E>::parse_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
std::string_view data = this->get_string(ctx, shdr);
|
||||
|
||||
while (!data.empty()) {
|
||||
@@ -135,6 +140,57 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
|
||||
}
|
||||
}
|
||||
|
||||
// <format-version>
|
||||
// [ <section-length> "vendor-name" <file-tag> <size> <attribute>*]+ ]*
|
||||
template <typename E>
|
||||
static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view data) {
|
||||
if (data.empty())
|
||||
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
|
||||
|
||||
if (u8 format_version = data[0]; format_version != 'A')
|
||||
return;
|
||||
data = data.substr(1);
|
||||
|
||||
while (!data.empty()) {
|
||||
i64 sz = *(U32<E> *)data.data();
|
||||
if (data.size() < sz)
|
||||
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
|
||||
|
||||
std::string_view p(data.data() + 4, sz - 4);
|
||||
data = data.substr(sz);
|
||||
|
||||
if (!p.starts_with("riscv\0"sv))
|
||||
continue;
|
||||
p = p.substr(6);
|
||||
|
||||
if (!p.starts_with(ELF_TAG_FILE))
|
||||
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
|
||||
p = p.substr(5); // skip the tag and the sub-sub-section size
|
||||
|
||||
while (!p.empty()) {
|
||||
i64 tag = read_uleb(&p);
|
||||
|
||||
switch (tag) {
|
||||
case ELF_TAG_RISCV_STACK_ALIGN:
|
||||
file.extra.stack_align = read_uleb(&p);
|
||||
break;
|
||||
case ELF_TAG_RISCV_ARCH: {
|
||||
i64 pos = p.find_first_of('\0');
|
||||
file.extra.arch = p.substr(0, pos);
|
||||
p = p.substr(pos + 1);
|
||||
break;
|
||||
}
|
||||
case ELF_TAG_RISCV_UNALIGNED_ACCESS:
|
||||
file.extra.unaligned_access = read_uleb(&p);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
// Read sections
|
||||
@@ -145,6 +201,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
shdr.sh_type != SHT_LLVM_ADDRSIG && !ctx.arg.relocatable)
|
||||
continue;
|
||||
|
||||
if constexpr (is_arm<E>)
|
||||
if (shdr.sh_type == SHT_ARM_ATTRIBUTES)
|
||||
continue;
|
||||
|
||||
if constexpr (is_riscv<E>) {
|
||||
if (shdr.sh_type == SHT_RISCV_ATTRIBUTES) {
|
||||
read_riscv_attributes(ctx, *this, this->get_string(ctx, shdr));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (shdr.sh_type) {
|
||||
case SHT_GROUP: {
|
||||
// Get the signature of this section group.
|
||||
@@ -155,7 +222,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
std::string_view signature;
|
||||
if (esym.st_type == STT_SECTION) {
|
||||
signature = this->shstrtab.data() +
|
||||
this->elf_sections[esym.st_shndx].sh_name;
|
||||
this->elf_sections[get_shndx(esym)].sh_name;
|
||||
} else {
|
||||
signature = this->symbol_strtab.data() + esym.st_name;
|
||||
}
|
||||
@@ -181,15 +248,20 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
comdat_groups.push_back({group, (u32)i, entries.subspan(1)});
|
||||
break;
|
||||
}
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, shdr);
|
||||
case SHT_REL:
|
||||
if constexpr (E::is_rela)
|
||||
Fatal(ctx) << *this << ": REL-type relocation table is not supported"
|
||||
<< " for this target";
|
||||
break;
|
||||
case SHT_RELA:
|
||||
if constexpr (!E::is_rela)
|
||||
Fatal(ctx) << *this <<": RELA-type relocation table is not supported"
|
||||
<< " for this target";
|
||||
break;
|
||||
case SHT_SYMTAB:
|
||||
case SHT_SYMTAB_SHNDX:
|
||||
case SHT_STRTAB:
|
||||
case SHT_REL:
|
||||
case SHT_RELA:
|
||||
case SHT_NULL:
|
||||
case SHT_ARM_ATTRIBUTES:
|
||||
break;
|
||||
default: {
|
||||
std::string_view name = this->shstrtab.data() + shdr.sh_name;
|
||||
@@ -211,7 +283,7 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
if (name == ".note.gnu.property") {
|
||||
read_note_gnu_property(ctx, shdr);
|
||||
parse_note_gnu_property(ctx, shdr);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -235,12 +307,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
is_debug_section(shdr, name))
|
||||
continue;
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
llvm_addrsig = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If an output file doesn't have a section header (i.e.
|
||||
// --oformat=binary is given), we discard all non-memory-allocated
|
||||
// sections. This is because without a section header, we can't find
|
||||
@@ -248,11 +314,20 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
if (ctx.arg.oformat_binary && !(shdr.sh_flags & SHF_ALLOC))
|
||||
continue;
|
||||
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, name, i);
|
||||
this->sections[i] = std::make_unique<InputSection<E>>(ctx, *this, i);
|
||||
|
||||
// Save .llvm_addrsig for --icf=safe.
|
||||
if (shdr.sh_type == SHT_LLVM_ADDRSIG && !ctx.arg.relocatable) {
|
||||
llvm_addrsig = std::move(this->sections[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name == ".eh_frame")
|
||||
eh_frame_section = this->sections[i].get();
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
ppc32_got2 = this->sections[i].get();
|
||||
extra.got2 = this->sections[i].get();
|
||||
|
||||
// Save debug sections for --gdb-index.
|
||||
if (ctx.arg.gdb_index) {
|
||||
@@ -260,10 +335,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
|
||||
if (name == ".debug_info")
|
||||
debug_info = 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
|
||||
@@ -313,17 +384,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_ehframe_sections(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < sections.size(); i++) {
|
||||
std::unique_ptr<InputSection<E>> &isec = sections[i];
|
||||
if (isec && isec->is_alive && isec->name() == ".eh_frame") {
|
||||
read_ehframe(ctx, *isec);
|
||||
isec->is_alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .eh_frame contains data records explaining how to handle exceptions.
|
||||
// When an exception is thrown, the runtime searches a record from
|
||||
// .eh_frame with the current program counter as a key. A record that
|
||||
@@ -354,7 +414,11 @@ 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) {
|
||||
void ObjectFile<E>::parse_ehframe(Context<E> &ctx) {
|
||||
if (!eh_frame_section)
|
||||
return;
|
||||
|
||||
InputSection<E> &isec = *eh_frame_section;
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
i64 cies_begin = cies.size();
|
||||
i64 fdes_begin = fdes.size();
|
||||
@@ -490,7 +554,7 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
this->symbols.resize(this->elf_syms.size());
|
||||
|
||||
i64 num_globals = this->elf_syms.size() - this->first_global;
|
||||
symvers.resize(num_globals);
|
||||
has_symver.resize(num_globals);
|
||||
|
||||
for (i64 i = 0; i < this->first_global; i++)
|
||||
this->symbols[i] = &this->local_syms[i];
|
||||
@@ -505,14 +569,13 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
|
||||
|
||||
// Parse symbol version after atsign
|
||||
if (i64 pos = name.find('@'); pos != name.npos) {
|
||||
std::string_view ver = name.substr(pos + 1);
|
||||
std::string_view ver = name.substr(pos);
|
||||
name = name.substr(0, pos);
|
||||
|
||||
if (!ver.empty() && ver != "@") {
|
||||
if (ver.starts_with('@'))
|
||||
if (ver != "@" && ver != "@@") {
|
||||
if (ver.starts_with("@@"))
|
||||
key = name;
|
||||
if (!esym.is_undef())
|
||||
symvers[i - this->first_global] = ver.data();
|
||||
has_symver.set(i - this->first_global);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,7 +590,7 @@ 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 constexpr (is_riscv<E>) {
|
||||
if constexpr (is_riscv<E> || is_loongarch<E>) {
|
||||
auto less = [&](const ElfRel<E> &a, const ElfRel<E> &b) {
|
||||
return a.r_offset < b.r_offset;
|
||||
};
|
||||
@@ -575,7 +638,7 @@ static size_t find_null(std::string_view data, u64 entsize) {
|
||||
template <typename E>
|
||||
static std::unique_ptr<MergeableSection<E>>
|
||||
split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
if (!sec.is_alive || sec.sh_size == 0 || sec.relsec_idx != -1)
|
||||
if (!sec.is_alive || sec.relsec_idx != -1)
|
||||
return nullptr;
|
||||
|
||||
const ElfShdr<E> &shdr = sec.shdr();
|
||||
@@ -587,6 +650,9 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
shdr.sh_flags);
|
||||
rec->p2align = sec.p2align;
|
||||
|
||||
if (sec.sh_size == 0)
|
||||
return rec;
|
||||
|
||||
// If thes section contents are compressed, uncompress them.
|
||||
sec.uncompress(ctx);
|
||||
|
||||
@@ -656,13 +722,14 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
// atomic unit of mergeable section "section pieces".
|
||||
//
|
||||
// This feature is typically used for string literals. String literals
|
||||
// are usually put into a mergeable section by a compiler. If the same
|
||||
// string literal happen to occur in two different translation units,
|
||||
// a linker merges them into a single instance of a string, so that
|
||||
// a linker's output doesn't contain duplicate string literals.
|
||||
// are usually put into a mergeable section by the compiler. If the same
|
||||
// string literal happens to occur in two different translation units,
|
||||
// the linker merges them into a single instance of a string, so that
|
||||
// the linker's output doesn't contain duplicate string literals.
|
||||
//
|
||||
// Handling symbols in mergeable sections is a bit tricky. Assume that
|
||||
// we have a mergeable section with the following contents and symbols:
|
||||
// Handling symbols in the mergeable sections is a bit tricky. Assume
|
||||
// that we have a mergeable section with the following contents and
|
||||
// symbols:
|
||||
//
|
||||
// Hello world\0foo bar\0
|
||||
// ^ ^
|
||||
@@ -671,23 +738,23 @@ split_section(Context<E> &ctx, InputSection<E> &sec) {
|
||||
//
|
||||
// '\0' represents a NUL byte. This mergeable section contains two
|
||||
// section pieces, "Hello world" and "foo bar". The first string is
|
||||
// referred by two symbols, .rodata and .L.str0, and the second by
|
||||
// referred to by two symbols, .rodata and .L.str0, and the second by
|
||||
// .L.str1. .rodata is a section symbol and therefore a local symbol
|
||||
// and refers the begining of the section.
|
||||
// and refers to the beginning of the section.
|
||||
//
|
||||
// In this example, there are actually two different ways to point to
|
||||
// string "foo bar", because .rodata+12 and .L.str1+0 refer the same
|
||||
// string "foo bar", because .rodata+12 and .L.str1+0 refer to the same
|
||||
// place in the section. This kind of "out-of-bound" reference occurs
|
||||
// only when a symbol is a section symbol. In other words, compiler
|
||||
// may use an offset from the beginning of a section to refer any
|
||||
// only when a symbol is a section symbol. In other words, the compiler
|
||||
// may use an offset from the beginning of a section to refer to any
|
||||
// section piece in a section, but it doesn't do for any other types
|
||||
// of symbols.
|
||||
//
|
||||
// In mold, we attach section pieces symbols. If a relocation refers a
|
||||
// section symbol whose section is a mergeable section, we create a
|
||||
// new dummy symbol with a section piece and redirect the relocation
|
||||
// to the symbol. If a non-section symbol refers a section piece, the
|
||||
// section piece is attached to the symbol.
|
||||
// In mold, we attach symbols to section pieces. If a relocation refers
|
||||
// to a section symbol, and that symbol's section is a mergeable one,
|
||||
// we create a new dummy symbol for a section piece and redirect the
|
||||
// relocation to this new symbol. If a non-section symbol refers to a
|
||||
// section piece, the section piece is attached to the symbol.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::initialize_mergeable_sections(Context<E> &ctx) {
|
||||
mergeable_sections.resize(sections.size());
|
||||
@@ -708,7 +775,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
if (m) {
|
||||
m->fragments.reserve(m->strings.size());
|
||||
for (i64 i = 0; i < m->strings.size(); i++)
|
||||
m->fragments.push_back(m->parent->insert(m->strings[i], m->hashes[i],
|
||||
m->fragments.push_back(m->parent->insert(ctx, m->strings[i], m->hashes[i],
|
||||
m->p2align));
|
||||
|
||||
// Shrink vectors that we will never use again to reclaim memory.
|
||||
@@ -726,7 +793,7 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
continue;
|
||||
|
||||
std::unique_ptr<MergeableSection<E>> &m = mergeable_sections[get_shndx(esym)];
|
||||
if (!m)
|
||||
if (!m || m->fragments.empty())
|
||||
continue;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
@@ -795,33 +862,6 @@ void ObjectFile<E>::resolve_section_pieces(Context<E> &ctx) {
|
||||
this->symbols.push_back(&sym);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::mark_addrsig(Context<E> &ctx) {
|
||||
// Parse a .llvm_addrsig section.
|
||||
if (llvm_addrsig) {
|
||||
u8 *cur = (u8 *)llvm_addrsig->contents.data();
|
||||
u8 *end = cur + llvm_addrsig->contents.size();
|
||||
|
||||
while (cur != end) {
|
||||
Symbol<E> &sym = *this->symbols[read_uleb(cur)];
|
||||
if (sym.file == this)
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
isec->address_significant = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We treat a symbol's address as significant if
|
||||
//
|
||||
// 1. we have no address significance information for the symbol, or
|
||||
// 2. the symbol can be referenced from the outside in an address-
|
||||
// significant manner.
|
||||
for (Symbol<E> *sym : this->symbols)
|
||||
if (sym->file == this)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (!llvm_addrsig || sym->is_exported)
|
||||
isec->address_significant = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
sections.resize(this->elf_sections.size());
|
||||
@@ -833,12 +873,15 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
this->first_global = symtab_sec->sh_info;
|
||||
this->elf_syms = this->template get_data<ElfSym<E>>(ctx, *symtab_sec);
|
||||
this->symbol_strtab = this->get_string(ctx, symtab_sec->sh_link);
|
||||
|
||||
if (ElfShdr<E> *shdr = this->find_section(SHT_SYMTAB_SHNDX))
|
||||
symtab_shndx_sec = this->template get_data<U32<E>>(ctx, *shdr);
|
||||
}
|
||||
|
||||
initialize_sections(ctx);
|
||||
initialize_symbols(ctx);
|
||||
sort_relocations(ctx);
|
||||
initialize_ehframe_sections(ctx);
|
||||
parse_ehframe(ctx);
|
||||
}
|
||||
|
||||
// Symbols with higher priorities overwrites symbols with lower priorities.
|
||||
@@ -854,22 +897,22 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
|
||||
//
|
||||
// Ties are broken by file priority.
|
||||
template <typename E>
|
||||
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_lazy) {
|
||||
if (esym.is_common()) {
|
||||
assert(!file->is_dso);
|
||||
if (is_lazy)
|
||||
return (6 << 24) + file->priority;
|
||||
return (5 << 24) + file->priority;
|
||||
}
|
||||
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_in_archive) {
|
||||
auto get_sym_rank = [&] {
|
||||
if (esym.is_common()) {
|
||||
assert(!file->is_dso);
|
||||
return is_in_archive ? 6 : 5;
|
||||
}
|
||||
|
||||
if (file->is_dso || is_in_archive)
|
||||
return (esym.st_bind == STB_WEAK) ? 4 : 3;
|
||||
|
||||
if (file->is_dso || is_lazy) {
|
||||
if (esym.st_bind == STB_WEAK)
|
||||
return (4 << 24) + file->priority;
|
||||
return (3 << 24) + file->priority;
|
||||
}
|
||||
if (esym.st_bind == STB_WEAK)
|
||||
return (2 << 24) + file->priority;
|
||||
return (1 << 24) + file->priority;
|
||||
return 2;
|
||||
return 1;
|
||||
};
|
||||
|
||||
return (get_sym_rank() << 24) + file->priority;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -943,8 +986,6 @@ void ObjectFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = esym.is_weak();
|
||||
sym.is_imported = false;
|
||||
sym.is_exported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -974,7 +1015,7 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
continue;
|
||||
|
||||
bool keep = esym.is_undef() || (esym.is_common() && !sym.esym().is_common());
|
||||
if (keep && fast_mark(sym.file->is_alive)) {
|
||||
if (keep && !sym.file->is_alive.test_and_set()) {
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
@@ -984,135 +1025,6 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Comdat groups are used to de-duplicate functions and data that may
|
||||
// be included into multiple object files. C++ compiler uses comdat
|
||||
// groups to de-duplicate instantiated templates.
|
||||
//
|
||||
// For example, if a compiler decides to instantiate `std::vector<int>`,
|
||||
// it generates code and data for `std::vector<int>` and put them into a
|
||||
// comdat group whose name is the mangled name of `std::vector<int>`.
|
||||
// The instantiation may happen multiple times for different translation
|
||||
// units. Then linker de-duplicates them so that the resulting executable
|
||||
// contains only a single copy of `std::vector<int>`.
|
||||
template <typename E>
|
||||
void ObjectFile<E>::resolve_comdat_groups() {
|
||||
for (ComdatGroupRef<E> &ref : comdat_groups)
|
||||
update_minimum(ref.group->owner, this->priority);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::eliminate_duplicate_comdat_groups() {
|
||||
for (ComdatGroupRef<E> &ref : comdat_groups)
|
||||
if (ref.group->owner != this->priority)
|
||||
for (u32 i : ref.members)
|
||||
if (sections[i])
|
||||
sections[i]->kill();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = this->elf_syms[i];
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (!esym.is_undef())
|
||||
continue;
|
||||
|
||||
std::scoped_lock lock(sym.mu);
|
||||
|
||||
// If a protected/hidden undefined symbol is resolved to an
|
||||
// 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(sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.file &&
|
||||
(!sym.esym().is_undef() || sym.file->priority <= this->priority))
|
||||
continue;
|
||||
|
||||
// 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 = 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 &&
|
||||
sym2->get_version() == key.substr(pos + 1)) {
|
||||
this->symbols[i] = sym2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto claim = [&](bool is_imported) {
|
||||
if (sym.is_traced)
|
||||
SyncOut(ctx) << "trace-symbol: " << *this << ": unresolved"
|
||||
<< (esym.is_weak() ? " weak" : "")
|
||||
<< " symbol " << sym;
|
||||
|
||||
sym.file = this;
|
||||
sym.origin = 0;
|
||||
sym.value = 0;
|
||||
sym.sym_idx = i;
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = is_imported;
|
||||
sym.is_exported = false;
|
||||
sym.ver_idx = is_imported ? 0 : ctx.default_version;
|
||||
};
|
||||
|
||||
if (esym.is_undef_weak()) {
|
||||
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
|
||||
ctx.arg.z_dynamic_undefined_weak) {
|
||||
// Global weak undefined symbols are promoted to dynamic symbols
|
||||
// when when linking a DSO, unless `-z nodynamic_undefined_weak`
|
||||
// was given.
|
||||
claim(true);
|
||||
} else {
|
||||
// Otherwise, weak undefs are converted to absolute symbols with value 0.
|
||||
claim(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN)
|
||||
report_undef(sym);
|
||||
|
||||
// Traditionally, remaining undefined symbols cause a link failure
|
||||
// only when we are creating an executable. Undefined symbols in
|
||||
// shared objects are promoted to dynamic symbols, so that they'll
|
||||
// get another chance to be resolved at run-time. You can change the
|
||||
// behavior by passing `-z defs` to the linker.
|
||||
//
|
||||
// Even if `-z defs` is given, weak undefined symbols are still
|
||||
// promoted to dynamic symbols for compatibility with other linkers.
|
||||
// Some major programs, notably Firefox, depend on the behavior
|
||||
// (they use this loophole to export symbols from libxul.so).
|
||||
if (ctx.arg.shared && sym.visibility != STV_HIDDEN &&
|
||||
(!ctx.arg.z_defs || ctx.arg.unresolved_symbols != UNRESOLVED_ERROR)) {
|
||||
claim(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert remaining undefined symbols to absolute symbols with value 0.
|
||||
if (ctx.arg.unresolved_symbols != UNRESOLVED_ERROR || ctx.arg.noinhibit_exec)
|
||||
claim(false);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
// Scan relocations against seciton contents
|
||||
@@ -1125,6 +1037,11 @@ void ObjectFile<E>::scan_relocations(Context<E> &ctx) {
|
||||
for (ElfRel<E> &rel : cie.get_rels()) {
|
||||
Symbol<E> &sym = *this->symbols[rel.r_sym];
|
||||
|
||||
if (ctx.arg.pic && rel.r_type == E::R_ABS)
|
||||
Error(ctx) << *this << ": relocation " << rel << " in .eh_frame can"
|
||||
<< " not be used when making a position-independent output;"
|
||||
<< " recompile with -fPIE or -fPIC";
|
||||
|
||||
if (sym.is_imported) {
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
Fatal(ctx) << *this << ": " << sym
|
||||
@@ -1176,13 +1093,9 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
ElfShdr<E> &shdr = elf_sections2.back();
|
||||
memset(&shdr, 0, sizeof(shdr));
|
||||
|
||||
std::string_view name;
|
||||
|
||||
if (sym.get_type() == STT_TLS) {
|
||||
name = ".tls_common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE | SHF_TLS;
|
||||
} else {
|
||||
name = ".common";
|
||||
shdr.sh_flags = SHF_ALLOC | SHF_WRITE;
|
||||
}
|
||||
|
||||
@@ -1192,7 +1105,7 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
|
||||
i64 idx = this->elf_sections.size() + elf_sections2.size() - 1;
|
||||
std::unique_ptr<InputSection<E>> isec =
|
||||
std::make_unique<InputSection<E>>(ctx, *this, name, idx);
|
||||
std::make_unique<InputSection<E>>(ctx, *this, idx);
|
||||
|
||||
sym.file = this;
|
||||
sym.set_input_section(isec.get());
|
||||
@@ -1200,8 +1113,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = ctx.default_version;
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = false;
|
||||
sym.is_exported = false;
|
||||
|
||||
sections.push_back(std::move(isec));
|
||||
}
|
||||
@@ -1238,9 +1149,6 @@ void ObjectFile<E>::compute_symtab_size(Context<E> &ctx) {
|
||||
this->output_sym_indices.resize(this->elf_syms.size(), -1);
|
||||
|
||||
auto is_alive = [&](Symbol<E> &sym) -> bool {
|
||||
if (!ctx.arg.gc_sections)
|
||||
return true;
|
||||
|
||||
if (SectionFragment<E> *frag = sym.get_frag())
|
||||
return frag->is_alive;
|
||||
if (InputSection<E> *isec = sym.get_input_section())
|
||||
@@ -1286,31 +1194,29 @@ void ObjectFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
u8 *strtab_base = ctx.buf + ctx.strtab->shdr.sh_offset;
|
||||
i64 strtab_off = this->strtab_offset;
|
||||
|
||||
auto write_sym = [&](Symbol<E> &sym, i64 &symtab_idx) {
|
||||
auto write_sym = [&](Symbol<E> &sym, i64 idx) {
|
||||
U32<E> *xindex = nullptr;
|
||||
if (ctx.symtab_shndx)
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
|
||||
symtab_idx * 4);
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) + idx;
|
||||
|
||||
symtab_base[symtab_idx++] = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
symtab_base[idx] = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab_base + strtab_off, sym.name());
|
||||
};
|
||||
|
||||
i64 local_symtab_idx = this->local_symtab_idx;
|
||||
i64 global_symtab_idx = this->global_symtab_idx;
|
||||
for (i64 i = 1; i < this->first_global; i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.write_to_symtab)
|
||||
write_sym(sym, local_symtab_idx);
|
||||
}
|
||||
|
||||
for (i64 i = 1; i < this->first_global; i++)
|
||||
if (Symbol<E> &sym = *this->symbols[i]; sym.write_to_symtab)
|
||||
write_sym(sym, local_symtab_idx++);
|
||||
|
||||
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
|
||||
Symbol<E> &sym = *this->symbols[i];
|
||||
if (sym.file == this && sym.write_to_symtab) {
|
||||
if (sym.is_local(ctx))
|
||||
write_sym(sym, local_symtab_idx);
|
||||
write_sym(sym, local_symtab_idx++);
|
||||
else
|
||||
write_sym(sym, global_symtab_idx);
|
||||
write_sym(sym, global_symtab_idx++);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1341,7 +1247,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1481,8 +1386,6 @@ void SharedFile<E>::resolve_symbols(Context<E> &ctx) {
|
||||
sym.sym_idx = i;
|
||||
sym.ver_idx = versyms[i];
|
||||
sym.is_weak = false;
|
||||
sym.is_imported = false;
|
||||
sym.is_exported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1498,8 +1401,8 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
if (sym.is_traced)
|
||||
print_trace_symbol(ctx, *this, esym, sym);
|
||||
|
||||
if (esym.is_undef() && sym.file && !sym.file->is_dso &&
|
||||
fast_mark(sym.file->is_alive)) {
|
||||
if (esym.is_undef() && !esym.is_weak() && sym.file && !sym.file->is_dso &&
|
||||
!sym.file->is_alive.test_and_set()) {
|
||||
feeder(sym.file);
|
||||
|
||||
if (sym.is_traced)
|
||||
@@ -1510,7 +1413,7 @@ SharedFile<E>::mark_live_objects(Context<E> &ctx,
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
std::span<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
assert(sym->file == this);
|
||||
|
||||
std::call_once(init_aliases, [&] {
|
||||
@@ -1526,16 +1429,12 @@ std::vector<Symbol<E> *> SharedFile<E>::find_aliases(Symbol<E> *sym) {
|
||||
});
|
||||
});
|
||||
|
||||
struct Cmp {
|
||||
bool operator()(Symbol<E> *sym, u64 val) { return sym->esym().st_value < val; }
|
||||
bool operator()(u64 val, Symbol<E> *sym) { return val < sym->esym().st_value; }
|
||||
};
|
||||
auto [begin, end] = std::equal_range(aliases.begin(), aliases.end(), sym,
|
||||
[&](Symbol<E> *x, Symbol<E> *y) {
|
||||
return x->esym().st_value < y->esym().st_value;
|
||||
});
|
||||
|
||||
auto [begin, end] =
|
||||
std::equal_range(aliases.begin(), aliases.end(), sym->esym().st_value, Cmp{});
|
||||
std::vector<Symbol<E> *> vec{begin, end};
|
||||
std::erase(vec, sym);
|
||||
return vec;
|
||||
return {&*begin, &*end};
|
||||
}
|
||||
|
||||
// Infer an alignment of a DSO symbol. An alignment of a symbol in other
|
||||
@@ -1555,13 +1454,13 @@ i64 SharedFile<E>::get_alignment(Symbol<E> *sym) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool SharedFile<E>::is_readonly(Context<E> &ctx, Symbol<E> *sym) {
|
||||
ElfPhdr<E> *phdr = this->get_phdr();
|
||||
bool SharedFile<E>::is_readonly(Symbol<E> *sym) {
|
||||
u64 val = sym->esym().st_value;
|
||||
|
||||
for (i64 i = 0; i < this->get_ehdr().e_phnum; i++)
|
||||
if (phdr[i].p_type == PT_LOAD && !(phdr[i].p_flags & PF_W) &&
|
||||
phdr[i].p_vaddr <= val && val < phdr[i].p_vaddr + phdr[i].p_memsz)
|
||||
for (ElfPhdr<E> &phdr : this->get_phdrs())
|
||||
if ((phdr.p_type == PT_LOAD || phdr.p_type == PT_GNU_RELRO) &&
|
||||
!(phdr.p_flags & PF_W) &&
|
||||
phdr.p_vaddr <= val && val < phdr.p_vaddr + phdr.p_memsz)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -1601,8 +1500,8 @@ void SharedFile<E>::populate_symtab(Context<E> &ctx) {
|
||||
|
||||
U32<E> *xindex = nullptr;
|
||||
if (ctx.symtab_shndx)
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset +
|
||||
(this->global_symtab_idx + i) * 4);
|
||||
xindex = (U32<E> *)(ctx.buf + ctx.symtab_shndx->shdr.sh_offset) +
|
||||
this->global_symtab_idx + i;
|
||||
|
||||
*symtab++ = to_output_esym(ctx, sym, strtab_off, xindex);
|
||||
strtab_off += write_string(strtab + strtab_off, sym.name());
|
||||
|
||||
+180
-101
@@ -37,8 +37,7 @@ static i64 to_p2align(u64 alignment) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
std::string_view name, i64 shndx)
|
||||
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
|
||||
: file(file), shndx(shndx) {
|
||||
if (shndx < file.elf_sections.size())
|
||||
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
|
||||
@@ -57,7 +56,10 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
if constexpr (!E::is_rela)
|
||||
//
|
||||
// SH-4 stores addends to sections despite being RELA, which is a
|
||||
// special (and buggy) case.
|
||||
if constexpr (!E::is_rela || is_sh4<E>)
|
||||
uncompress(ctx);
|
||||
}
|
||||
|
||||
@@ -104,30 +106,6 @@ void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_rel_action(Context<E> &ctx, Symbol<E> &sym,
|
||||
const Action table[3][4]) {
|
||||
auto get_output_type = [&] {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto get_sym_type = [&] {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
};
|
||||
|
||||
return table[get_output_type()][get_sym_type()];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, Action action) {
|
||||
@@ -171,78 +149,125 @@ static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR:
|
||||
// Print out the "recompile with -fPIC" error message.
|
||||
error();
|
||||
break;
|
||||
case COPYREL:
|
||||
// Create a copy relocation.
|
||||
if (!ctx.arg.z_copyreloc)
|
||||
error();
|
||||
copyrel();
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
// Same as COPYREL but try to avoid creating a copy relocation by
|
||||
// creating a dynamic relocation instead if the relocation is in
|
||||
// a writable section.
|
||||
//
|
||||
// GHC (Glasgow Haskell Compiler) places a small amount of data in
|
||||
// .text before each function and access that data with a fixed
|
||||
// offset. The function breaks if we copy-relocate the data. For such
|
||||
// programs, we should avoid copy relocations if possible.
|
||||
//
|
||||
// Besides GHC, copy relocation is a hacky solution, so if we can
|
||||
// represent a relocation either with copyrel or dynrel, we prefer
|
||||
// dynamic relocation.
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
dynrel();
|
||||
else
|
||||
copyrel();
|
||||
break;
|
||||
case PLT:
|
||||
// Create a PLT entry.
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case CPLT:
|
||||
// Create a canonical PLT entry.
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
// Same as CPLT but try to avoid creating a canonical PLT creating by
|
||||
// creating a dynamic relocation instead if the relocation is in a
|
||||
// writable section. The motivation behind it is hte same as DYN_COPYREL.
|
||||
if (writable)
|
||||
dynrel();
|
||||
else
|
||||
sym.flags |= NEEDS_CPLT;
|
||||
break;
|
||||
case DYNREL:
|
||||
// Create a dynamic relocation.
|
||||
dynrel();
|
||||
break;
|
||||
case BASEREL:
|
||||
// Create a base relocation.
|
||||
check_textrel();
|
||||
if (!isec.is_relr_reloc(ctx, rel))
|
||||
isec.file.num_dynrel++;
|
||||
break;
|
||||
case IFUNC:
|
||||
// Create an IRELATIVE relocation for a GNU ifunc symbol.
|
||||
//
|
||||
// We usually create an IRELATIVE relocation in .got for each ifunc.
|
||||
// However, if a statically-initialized pointer is initialized to an
|
||||
// ifunc's address, we have no choice other than emitting an IRELATIVE
|
||||
// relocation for each such pointer.
|
||||
dynrel();
|
||||
ctx.num_ifunc_dynrels.fetch_add(1, std::memory_order_relaxed);
|
||||
ctx.num_ifunc_dynrels++;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static inline i64 get_output_type(Context<E> &ctx) {
|
||||
if (ctx.arg.shared)
|
||||
return 0;
|
||||
if (ctx.arg.pie)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static inline i64 get_sym_type(Symbol<E> &sym) {
|
||||
if (sym.is_absolute())
|
||||
return 0;
|
||||
if (!sym.is_imported)
|
||||
return 1;
|
||||
if (sym.get_type() != STT_FUNC)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_pcrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
|
||||
// We cannot promote them to dynamic relocations because the dynamic
|
||||
// linker generally does not support PC-relative relocations.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, PLT }, // Shared object
|
||||
{ ERROR, NONE, COPYREL, PLT }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the word size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// word size, we need to report an error if a relocation cannot be
|
||||
// pointer size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Shared object
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -250,17 +275,17 @@ static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC;
|
||||
|
||||
// This is a decision table for absolute relocations for the word
|
||||
// This is a decision table for absolute relocations for the pointer
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
// a dynamic relocation if we cannot resolve an address at link-time.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -272,14 +297,14 @@ static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
// GOT-like section, and no user-generated code directly uses values
|
||||
// in it.
|
||||
constexpr static Action table[3][4] = {
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return get_rel_action(ctx, sym, table);
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -306,6 +331,38 @@ void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (ctx.arg.is_static ||
|
||||
(ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// Relax TLSDESC to Local Exec. In this case, we directly materialize
|
||||
// a TP-relative offset, so no dynamic relocation is needed.
|
||||
//
|
||||
// TLSDESC relocs must always be relaxed for statically-linked
|
||||
// executables even if -no-relax is given. It is because a
|
||||
// statically-linked executable doesn't contain a trampoline
|
||||
// function needed for TLSDESC.
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
// In this condition, TP-relative offset of a thread-local variable
|
||||
// is known at process startup time, so we can relax TLSDESC to the
|
||||
// code that reads the TP-relative offset from GOT and add TP to it.
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
// If no relaxation is doable, we simply create a TLSDESC dynamic
|
||||
// relocation.
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::check_tlsle(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
if (ctx.arg.shared)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against `" << sym
|
||||
<< "` can not be used when making a shared object;"
|
||||
<< " recompile with -fPIC";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel, u8 *loc,
|
||||
@@ -313,7 +370,7 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto apply_dynrel = [&] {
|
||||
auto emit_abs_dynrel = [&] {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
@@ -336,26 +393,29 @@ static void apply_absrel(Context<E> &ctx, InputSection<E> &isec,
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
apply_dynrel();
|
||||
emit_abs_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
if (writable)
|
||||
apply_dynrel();
|
||||
emit_abs_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYNREL:
|
||||
apply_dynrel();
|
||||
emit_abs_dynrel();
|
||||
break;
|
||||
case IFUNC: {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = addr;
|
||||
case IFUNC:
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = addr;
|
||||
} else {
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -365,8 +425,8 @@ template <typename E>
|
||||
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
|
||||
ElfRel<E> **dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
|
||||
get_dyn_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
@@ -374,8 +434,8 @@ template <typename E>
|
||||
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> *&dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, dynrel,
|
||||
ElfRel<E> **dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
|
||||
get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
@@ -385,11 +445,10 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (is_riscv<E>) {
|
||||
if constexpr (is_riscv<E>)
|
||||
copy_contents_riscv(ctx, buf);
|
||||
} else {
|
||||
else
|
||||
uncompress_to(ctx, buf);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (!ctx.arg.relocatable) {
|
||||
@@ -397,12 +456,24 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
if constexpr (is_x86_64<E>) {
|
||||
if (ctx.arg.z_rewrite_endbr) {
|
||||
// Rewrite the leading endbr instruction with a nop if the section
|
||||
// is not address-taken.
|
||||
u8 endbr[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
if (!address_taken && (shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
sh_size >= 4 && memcmp(buf, endbr, 4) == 0)
|
||||
memcpy(buf, nop, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
|
||||
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) {
|
||||
@@ -415,61 +486,69 @@ std::string_view InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Record an undefined symbol error which will be displayed all at
|
||||
// once by report_undef_errors().
|
||||
// Test if the symbol a given relocation refers to has already been resolved.
|
||||
// If not, record that error and returns true.
|
||||
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 << ")";
|
||||
bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
|
||||
// If a relocation refers to a linker-synthesized symbol for a
|
||||
// section fragment, it's always been resolved.
|
||||
if (file.elf_syms.size() <= rel.r_sym)
|
||||
return false;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
const ElfSym<E> &esym = file.elf_syms[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();
|
||||
// If a symbol is defined in a comdat group, and the comdat group is
|
||||
// discarded, the symbol may not have an owner. It is technically an
|
||||
// violation of the One Definition Rule, so it is a programmer's fault.
|
||||
if (!sym.file) {
|
||||
Error(ctx) << *this << ": " << sym << " refers to a discarded COMDAT section"
|
||||
<< " probably due to an ODR violation";
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx.checkpoint();
|
||||
auto record = [&] {
|
||||
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 << ")";
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {sym.name(), {}});
|
||||
acc->second.push_back(ss.str());
|
||||
};
|
||||
|
||||
// A non-weak undefined symbol must be promoted to an imported
|
||||
// symbol or resolved to an defined symbol. Otherwise, it's an
|
||||
// undefined symbol error.
|
||||
//
|
||||
// Every ELF file has an absolute local symbol as its first symbol.
|
||||
// Referring to that symbol is always valid.
|
||||
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
|
||||
if (!sym.is_imported && is_undef && sym.esym().is_undef()) {
|
||||
record();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a protected/hidden undefined symbol is resolved to other .so,
|
||||
// it's handled as if no symbols were found.
|
||||
if (sym.file->is_dso &&
|
||||
(sym.visibility == STV_PROTECTED || sym.visibility == STV_HIDDEN)) {
|
||||
record();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template struct CieRecord<E>;
|
||||
template class InputSection<E>;
|
||||
template void report_undef_errors(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#include "mold.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <pwd.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || std::string(jobs) != "1")
|
||||
return;
|
||||
|
||||
char *home = getenv("HOME");
|
||||
if (!home)
|
||||
home = getpwuid(getuid())->pw_dir;
|
||||
|
||||
std::string path = std::string(home) + "/.mold-lock";
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0600);
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
if (lockf(fd, F_LOCK, 0) == -1)
|
||||
return;
|
||||
|
||||
ctx.global_lock_fd = fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void release_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
if (ctx.global_lock_fd)
|
||||
close(*ctx.global_lock_fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void acquire_global_lock(Context<E> &);
|
||||
template void release_global_lock(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -279,9 +279,8 @@ static bool read_label(std::span<std::string_view> &tok,
|
||||
template <typename E>
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
std::string_view ver_str, u16 ver_idx, bool is_cpp) {
|
||||
bool is_global = true;
|
||||
|
||||
std::string_view ver_str, u16 ver_idx,
|
||||
bool is_global, bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (read_label(tok, "global")) {
|
||||
is_global = true;
|
||||
@@ -299,11 +298,11 @@ read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, false);
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, is_global, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true);
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, is_global, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
@@ -349,7 +348,7 @@ void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, false);
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
if (!tok.empty() && tok[0] != ";")
|
||||
tok = tok.subspan(1);
|
||||
|
||||
+33
-13
@@ -641,14 +641,34 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
// Initialize object symbols
|
||||
std::vector<ElfSym<E>> *esyms = new std::vector<ElfSym<E>>(1);
|
||||
obj->has_symver.resize(plugin_symbols.size());
|
||||
obj->lto_symbol_versions.resize(plugin_symbols.size());
|
||||
|
||||
for (PluginSymbol &psym : plugin_symbols) {
|
||||
for (i64 i = 0; i < plugin_symbols.size(); i++) {
|
||||
PluginSymbol &psym = plugin_symbols[i];
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->symbols.push_back(get_symbol(ctx, save_string(ctx, psym.name)));
|
||||
|
||||
std::string_view key = save_string(ctx, psym.name);
|
||||
std::string_view name = key;
|
||||
|
||||
// Parse symbol version after atsign
|
||||
if (i64 pos = name.find('@'); pos != name.npos) {
|
||||
std::string_view ver = name.substr(pos);
|
||||
name = name.substr(0, pos);
|
||||
|
||||
if (ver != "@" && ver != "@@") {
|
||||
if (ver.starts_with("@@"))
|
||||
key = name;
|
||||
obj->has_symver.set(i);
|
||||
obj->lto_symbol_versions[i] = ver.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
obj->symbols.push_back(get_symbol(ctx, key, name));
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->symvers.resize(esyms->size());
|
||||
obj->has_symver.resize(esyms->size());
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
@@ -666,16 +686,13 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (file->is_lto_obj)
|
||||
return;
|
||||
|
||||
for (i64 i = file->first_global; i < file->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
|
||||
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;
|
||||
if (!file->is_lto_obj) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -692,6 +709,9 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
get_symbol(ctx, y)->referenced_by_regular_obj = true;
|
||||
}
|
||||
|
||||
// Handle --entry
|
||||
get_symbol(ctx, ctx.arg.entry)->referenced_by_regular_obj = true;
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
if (PluginStatus st = all_symbols_read_hook(); st != LDPS_OK)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../integers.h"
|
||||
#include "../common/integers.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -55,9 +55,7 @@ enum PluginApiVersion {
|
||||
|
||||
struct PluginTagValue {
|
||||
PluginTagValue(PluginTag tag, int val) : tag(tag), val(val) {}
|
||||
|
||||
template <typename T>
|
||||
PluginTagValue(PluginTag tag, T *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
PluginTagValue(PluginTag tag, auto *ptr) : tag(tag), ptr((void *)ptr) {}
|
||||
|
||||
PluginTag tag;
|
||||
union {
|
||||
|
||||
+36
-99
@@ -1,7 +1,7 @@
|
||||
#include "mold.h"
|
||||
#include "../archive-file.h"
|
||||
#include "../cmdline.h"
|
||||
#include "../output-file.h"
|
||||
#include "../common/archive-file.h"
|
||||
#include "../common/cmdline.h"
|
||||
#include "../common/output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
@@ -70,28 +70,26 @@ std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
return SH4::target_name;
|
||||
case EM_ALPHA:
|
||||
return ALPHA::target_name;
|
||||
case EM_PARISC:
|
||||
return HPPA32::target_name;
|
||||
case EM_LOONGARCH:
|
||||
return is_64 ? LOONGARCH64::target_name : LOONGARCH32::target_name;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
bool opt_plugin = !ctx.arg.plugin.empty();
|
||||
|
||||
switch (get_file_type(mf, opt_plugin)) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
|
||||
if (get_file_type(child, opt_plugin) == FileType::ELF_OBJ)
|
||||
if (get_file_type(ctx, child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
@@ -165,9 +163,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
bool opt_plugin = !ctx.arg.plugin.empty();
|
||||
FileType type = get_file_type(mf, opt_plugin);
|
||||
switch (type) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, mf, ""));
|
||||
return;
|
||||
@@ -178,7 +174,7 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(child, opt_plugin)) {
|
||||
switch (get_file_type(ctx, child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
break;
|
||||
@@ -187,11 +183,16 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
case FileType::ELF_DSO:
|
||||
Warn(ctx) << mf->name << "(" << child->name
|
||||
<< "): shared object file in an archive is ignored";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ctx.visited.insert(mf->name);
|
||||
if (!ctx.whole_archive)
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
@@ -338,46 +339,6 @@ static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
ctx.tg.wait();
|
||||
}
|
||||
|
||||
// Since elf_main is a template, we can't run it without a type parameter.
|
||||
// We speculatively run elf_main with X86_64, and if the speculation was
|
||||
// wrong, re-run it with an actual machine type.
|
||||
template <typename E>
|
||||
static int redo_main(int argc, char **argv, std::string_view target) {
|
||||
if (target == I386::target_name)
|
||||
return elf_main<I386>(argc, argv);
|
||||
if (target == ARM64::target_name)
|
||||
return elf_main<ARM64>(argc, argv);
|
||||
if (target == ARM32::target_name)
|
||||
return elf_main<ARM32>(argc, argv);
|
||||
if (target == RV64LE::target_name)
|
||||
return elf_main<RV64LE>(argc, argv);
|
||||
if (target == RV64BE::target_name)
|
||||
return elf_main<RV64BE>(argc, argv);
|
||||
if (target == RV32LE::target_name)
|
||||
return elf_main<RV32LE>(argc, argv);
|
||||
if (target == RV32BE::target_name)
|
||||
return elf_main<RV32BE>(argc, argv);
|
||||
if (target == PPC32::target_name)
|
||||
return elf_main<PPC32>(argc, argv);
|
||||
if (target == PPC64V1::target_name)
|
||||
return elf_main<PPC64V1>(argc, argv);
|
||||
if (target == PPC64V2::target_name)
|
||||
return elf_main<PPC64V2>(argc, argv);
|
||||
if (target == S390X::target_name)
|
||||
return elf_main<S390X>(argc, argv);
|
||||
if (target == SPARC64::target_name)
|
||||
return elf_main<SPARC64>(argc, argv);
|
||||
if (target == M68K::target_name)
|
||||
return elf_main<M68K>(argc, argv);
|
||||
if (target == SH4::target_name)
|
||||
return elf_main<SH4>(argc, argv);
|
||||
if (target == ALPHA::target_name)
|
||||
return elf_main<ALPHA>(argc, argv);
|
||||
if (target == HPPA32::target_name)
|
||||
return elf_main<HPPA32>(argc, argv);
|
||||
unreachable();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int elf_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
@@ -401,7 +362,7 @@ int elf_main(int argc, char **argv) {
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.emulation != X86_64::target_name)
|
||||
return redo_main<E>(argc, argv, ctx.arg.emulation);
|
||||
return redo_main(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
|
||||
@@ -420,6 +381,8 @@ int elf_main(int argc, char **argv) {
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
|
||||
acquire_global_lock(ctx);
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
@@ -446,7 +409,6 @@ int elf_main(int argc, char **argv) {
|
||||
});
|
||||
}
|
||||
|
||||
Timer t_total(ctx, "total");
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
@@ -468,6 +430,9 @@ int elf_main(int argc, char **argv) {
|
||||
// put together in a single phase.
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// "Kill" .eh_frame input sections after symbol resolution.
|
||||
kill_eh_frame_sections(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
resolve_section_pieces(ctx);
|
||||
|
||||
@@ -491,9 +456,9 @@ int elf_main(int argc, char **argv) {
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Read address-significant section information.
|
||||
if (ctx.arg.icf && !ctx.arg.icf_all)
|
||||
mark_addrsig(ctx);
|
||||
// Set "address-taken" bits for input sections.
|
||||
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
|
||||
compute_address_significance(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
@@ -550,10 +515,8 @@ 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)
|
||||
if (ctx.arg.print_dependencies)
|
||||
print_dependencies(ctx);
|
||||
else if (ctx.arg.print_dependencies == 2)
|
||||
print_dependencies_full(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
@@ -634,10 +597,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Here, we construct output .eh_frame contents.
|
||||
ctx.eh_frame->construct(ctx);
|
||||
|
||||
// Handle --gdb-index.
|
||||
if (ctx.arg.gdb_index)
|
||||
ctx.gdb_index->construct(ctx);
|
||||
|
||||
// If --emit-relocs is given, we'll copy relocation sections from input
|
||||
// files to an output file.
|
||||
if (ctx.arg.emit_relocs)
|
||||
@@ -683,12 +642,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
// Some part of .gdb_index couldn't be computed until other debug
|
||||
// sections are complete. We have complete debug sections now, so
|
||||
// write the rest of .gdb_index.
|
||||
if (ctx.gdb_index)
|
||||
ctx.gdb_index->write_address_areas(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
@@ -696,6 +649,12 @@ int elf_main(int argc, char **argv) {
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
@@ -715,7 +674,6 @@ int elf_main(int argc, char **argv) {
|
||||
if (ctx.has_lto_object)
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_total.stop();
|
||||
t_all.stop();
|
||||
|
||||
if (ctx.arg.print_map)
|
||||
@@ -733,6 +691,8 @@ int elf_main(int argc, char **argv) {
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
release_global_lock(ctx);
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
|
||||
@@ -742,37 +702,14 @@ int elf_main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void read_file(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
|
||||
extern template int elf_main<I386>(int, char **);
|
||||
extern template int elf_main<ARM32>(int, char **);
|
||||
extern template int elf_main<ARM64>(int, char **);
|
||||
extern template int elf_main<RV32BE>(int, char **);
|
||||
extern template int elf_main<RV32LE>(int, char **);
|
||||
extern template int elf_main<RV64LE>(int, char **);
|
||||
extern template int elf_main<RV64BE>(int, char **);
|
||||
extern template int elf_main<PPC32>(int, char **);
|
||||
extern template int elf_main<PPC64V1>(int, char **);
|
||||
extern template int elf_main<PPC64V2>(int, char **);
|
||||
extern template int elf_main<S390X>(int, char **);
|
||||
extern template int elf_main<SPARC64>(int, char **);
|
||||
extern template int elf_main<M68K>(int, char **);
|
||||
extern template int elf_main<SH4>(int, char **);
|
||||
extern template int elf_main<ALPHA>(int, char **);
|
||||
extern template int elf_main<HPPA32>(int, char **);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
+2
-1
@@ -61,7 +61,8 @@ static bool is_ld(const char *path) {
|
||||
ptr--;
|
||||
|
||||
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd");
|
||||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd") ||
|
||||
!strcmp(ptr, "ld.mold");
|
||||
}
|
||||
|
||||
int execvpe(const char *file, char *const *argv, char *const *envp) {
|
||||
|
||||
+282
-327
File diff suppressed because it is too large.
Load diff
+434
-721
File diff suppressed because it is too large.
Load diff
+504
-329
File diff suppressed because it is too large.
Load diff
+4
-1
@@ -40,7 +40,7 @@ namespace mold::elf {
|
||||
// Create linker-synthesized sections
|
||||
template <typename E>
|
||||
static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
auto push = [&]<typename T>(T *x) {
|
||||
auto push = [&](auto *x) {
|
||||
ctx.chunks.push_back(x);
|
||||
ctx.chunk_pool.emplace_back(x);
|
||||
return x;
|
||||
@@ -54,6 +54,9 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.symtab = push(new SymtabSection<E>);
|
||||
ctx.shstrtab = push(new ShstrtabSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (is_riscv<E>)
|
||||
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
|
||||
}
|
||||
|
||||
// Create SHT_GROUP (i.e. comdat group) sections. We uniquify comdat
|
||||
|
||||
+118
-104
@@ -20,6 +20,9 @@
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || \
|
||||
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
@@ -27,66 +30,43 @@
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Branch reach in bytes.
|
||||
//
|
||||
// ARM64's branch has 26 bits immediate, and it's scaled by 4 because all
|
||||
// instructions are 4 bytes aligned, so it's effectively 28 bits long.
|
||||
// That means the range is [-2^27, 2^27).
|
||||
//
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. ARM32's non-Thumb branches
|
||||
// have twice longer range than its Thumb counterparts, but we
|
||||
// conservatively use the Thumb's limitation.
|
||||
//
|
||||
// PPC's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore 26.
|
||||
//
|
||||
// Here is the summary of branch instructions reaches:
|
||||
//
|
||||
// ARM64: PC ± 128 MiB
|
||||
// ARM32: PC ± 16 MiB
|
||||
// PPC: PC ± 32 MiB
|
||||
template <typename E>
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
// Returns a branch reach in bytes for a given target.
|
||||
static consteval i64 max_distance() {
|
||||
if constexpr (is_arm64<E>) {
|
||||
return 1LL << 27;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return 1LL << 24;
|
||||
} else if constexpr (is_ppc<E>) {
|
||||
return 1LL << 25;
|
||||
} else {
|
||||
static_assert(is_hppa<E>);
|
||||
return 1LL << 18;
|
||||
}
|
||||
// ARM64's branch has 26 bits immediate. The immediate is padded with
|
||||
// implicit two-bit zeros because all instructions are 4 bytes aligned
|
||||
// and therefore the least two bits are always zero. So the branch
|
||||
// operand is effectively 28 bits long. That means the branch range is
|
||||
// [-2^27, 2^27) or PC ± 128 MiB.
|
||||
//
|
||||
// LoongArch's BR instruction also takes a 26 bit immediate.
|
||||
if (is_arm64<E> || is_loongarch<E>)
|
||||
return 1 << 27;
|
||||
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. It's [-2^24, 2^24) or PC ±
|
||||
// 16 MiB.
|
||||
//
|
||||
// ARM32's non-Thumb branches have twice longer range than its Thumb
|
||||
// counterparts, but we conservatively use the Thumb's limitation.
|
||||
if (is_arm32<E>)
|
||||
return 1 << 24;
|
||||
|
||||
// PPC's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore the reach is [-2^25, 2^25) or PC ± 32 MiB.
|
||||
assert(is_ppc<E>);
|
||||
return 1 << 25;
|
||||
}
|
||||
|
||||
// We create thunks for each 12.8/1.6/3.2 MiB code block for
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
template <typename E>
|
||||
static constexpr i64 batch_size = max_distance<E>() / 10;
|
||||
static constexpr i64 batch_size = max_distance() / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
// Returns true if a given relocation is of type used for function calls.
|
||||
template <typename E>
|
||||
static bool needs_thunk_rel(const ElfRel<E> &r) {
|
||||
u32 ty = r.r_type;
|
||||
|
||||
if constexpr (is_arm64<E>) {
|
||||
return ty == R_AARCH64_JUMP26 || ty == R_AARCH64_CALL26;
|
||||
} else if constexpr (is_arm32<E>) {
|
||||
return ty == R_ARM_JUMP24 || ty == R_ARM_THM_JUMP24 ||
|
||||
ty == R_ARM_CALL || ty == R_ARM_THM_CALL;
|
||||
} else if constexpr (is_ppc32<E>) {
|
||||
return ty == R_PPC_REL24 || ty == R_PPC_PLTREL24 || ty == R_PPC_LOCAL24PC;
|
||||
} else if constexpr (is_ppc64<E>) {
|
||||
return ty == R_PPC64_REL24;
|
||||
} else {
|
||||
static_assert(is_hppa<E>);
|
||||
return ty == R_PARISC_PCREL17F || ty == R_PARISC_PCREL32;
|
||||
}
|
||||
}
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
@@ -114,14 +94,21 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
if constexpr (is_arm32<E>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb))
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb))
|
||||
return false;
|
||||
}
|
||||
|
||||
// (TODO)
|
||||
if constexpr (is_hppa<E>)
|
||||
if (sym.has_opd(ctx))
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need to a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>) {
|
||||
if (rel.r_type == R_PPC64_REL24 && !sym.esym().preserves_r2())
|
||||
return false;
|
||||
if (rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().uses_toc())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
@@ -129,10 +116,9 @@ static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
i64 A = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -max_distance<E>() <= val && val < max_distance<E>();
|
||||
return -max_distance() <= val && val < max_distance();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
@@ -142,16 +128,15 @@ static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
}
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
template <typename E>
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
RangeExtensionThunk<E> &thunk) {
|
||||
RangeExtensionThunk<E> &thunk, i64 thunk_idx) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RangeExtensionRef> &range_extn = isec.extra.range_extn;
|
||||
range_extn.resize(rels.size());
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (!needs_thunk_rel(rel))
|
||||
if (!is_func_call_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
@@ -173,7 +158,7 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk.thunk_idx;
|
||||
range_extn[i].thunk_idx = thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
@@ -183,9 +168,9 @@ static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
std::span<InputSection<E> *> m = osec.members;
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
@@ -201,75 +186,103 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
//
|
||||
// Input sections between B and C are in the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// anywhere from the current batch.
|
||||
//
|
||||
// D is the input section with the largest address such that the thunk
|
||||
// is reachable from the current batch if it's inserted right before D.
|
||||
//
|
||||
// ................................ <input sections> ............
|
||||
// A B C D
|
||||
// ^ We insert a thunk for the current batch just before D
|
||||
// <---> The current batch, which is smaller than batch_size
|
||||
// <--------> Smaller than max_distance
|
||||
// <--------> Smaller than max_distance
|
||||
// <-------------> Reachable from the current batch
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk after D.
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance<E>()) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
// The smallest thunk index that is reachable from the current batch.
|
||||
i64 t = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move C forward so that C is apart from B by BATCH_SIZE. We want
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < m.size() &&
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size<E>)
|
||||
m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
c++;
|
||||
|
||||
// Move D foward as far as we can jump from B to anywhere in a thunk at D.
|
||||
d = c;
|
||||
while (d < m.size() &&
|
||||
align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size + max_thunk_size <
|
||||
m[b]->offset + max_distance())
|
||||
d++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < osec.thunks.size() &&
|
||||
osec.thunks[a]->offset + max_distance<E>() < c_offset)
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
while (a < b && a < m.size() && m[a]->offset + max_distance() < c_offset)
|
||||
a++;
|
||||
|
||||
// Create a thunk for input sections between B and C and place it at D.
|
||||
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
|
||||
// Assign offsets to all sections before D.
|
||||
for (i64 i = b; i < d; i++) {
|
||||
offset = align_to(offset, 1 << m[i]->p2align);
|
||||
m[i]->offset = offset;
|
||||
offset += m[i]->sh_size;
|
||||
}
|
||||
|
||||
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
|
||||
thunk.thunk_idx = osec.thunks.size() - 1;
|
||||
offset = align_to(offset, thunk.alignment);
|
||||
thunk.offset = offset;
|
||||
// Erase references to out-of-range thunks.
|
||||
while (t < thunks.size() && thunks[t]->offset < m[a]->offset)
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need thunks.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, thunk);
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, RangeExtensionThunk<E>::alignment);
|
||||
i64 thunk_idx = thunks.size();
|
||||
RangeExtensionThunk<E> *thunk = new RangeExtensionThunk<E>(*this, offset);
|
||||
thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
// entries in the new thunk.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, *thunk, thunk_idx);
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// its size.
|
||||
assert(thunk.size() < max_thunk_size);
|
||||
offset += thunk.size();
|
||||
// the thunk's size.
|
||||
assert(thunk->size() < max_thunk_size);
|
||||
offset += thunk->size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
sort(thunk->symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = thunk.thunk_idx;
|
||||
sym->extra.thunk_sym_idx = i++;
|
||||
for (i64 i = 0; i < thunk->symbols.size(); i++) {
|
||||
Symbol<E> &sym = *thunk->symbols[i];
|
||||
sym.extra.thunk_idx = thunk_idx;
|
||||
sym.extra.thunk_sym_idx = i;
|
||||
}
|
||||
|
||||
// Scan relocations again to fix symbol offsets in the last thunk.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c, [&](InputSection<E> *isec) {
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
std::span<Symbol<E> *> syms = isec->file.symbols;
|
||||
std::span<const ElfRel<E>> rels = isec->get_rels(ctx);
|
||||
std::span<RangeExtensionRef> range_extn = isec->extra.range_extn;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++)
|
||||
if (range_extn[i].thunk_idx == thunk.thunk_idx)
|
||||
if (range_extn[i].thunk_idx == thunk_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
@@ -277,15 +290,16 @@ void create_range_extension_thunks(Context<E> &ctx, OutputSection<E> &osec) {
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (a < osec.thunks.size())
|
||||
reset_thunk(*osec.thunks[a++]);
|
||||
while (t < thunks.size())
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
osec.shdr.sh_size = offset;
|
||||
this->shdr.sh_size = offset;
|
||||
|
||||
for (InputSection<E> *isec : members)
|
||||
this->shdr.sh_addralign =
|
||||
std::max<u32>(this->shdr.sh_addralign, 1 << isec->p2align);
|
||||
}
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || MOLD_HPPA32
|
||||
using E = MOLD_TARGET;
|
||||
template void create_range_extension_thunks(Context<E> &, OutputSection<E> &);
|
||||
#endif
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
// This file contains helper functions for thread-local storage (TLS).
|
||||
// TLS is probably the most obscure feature the linker has to support,
|
||||
// so I'll explain it in detail in this comment.
|
||||
//
|
||||
// TLS is a per-thread storage. Thread-local variables (TLVs) are in a TLS
|
||||
// so that each thread has its own set of thread-local variables. Taking
|
||||
// an address of a TLV returns a unique value for each thread. For example,
|
||||
// `&foo` for the following code returns different pointer values for
|
||||
// different threads.
|
||||
//
|
||||
// thread_local int foo;
|
||||
//
|
||||
// TLV is a relatively new feature. C for example didn't provide the
|
||||
// official support for it through the keyword `thread_local` until C11.
|
||||
// TLV needs a coordination between the compiler, the linker and the
|
||||
// runtime to work correctly.
|
||||
//
|
||||
// An ELF exectuable or a shared library using TLV contains a "TLS template
|
||||
// image" in the PT_TLS segment. For each newly created thread including the
|
||||
// initial one, the runtime allocates a contiguous memory for an executable
|
||||
// and its depending shared libraries and copies template images there. That
|
||||
// per-thread memory is called the "initial TLS block". After allocating and
|
||||
// initializing the initial TLS block, the runtime sets a register to refer
|
||||
// to the initial TLS block, so that the thread-local variables are
|
||||
// accessible relative to the register.
|
||||
//
|
||||
// The register referring to the per-thread storage is called the Thread
|
||||
// Pointer (TP). TP is part of the thread's context. When the kernel
|
||||
// scheduler switches threads, TP is saved and restored automatically just
|
||||
// like other registers are.
|
||||
//
|
||||
// The TLS template image is read-only. It contains TLVs' initial values
|
||||
// for new threads, and no one writes to it at runtime.
|
||||
//
|
||||
// Now, let's think about how to access a TLV. We need to know the TLV's
|
||||
// address to access it which can be done in several different ways as
|
||||
// follows:
|
||||
//
|
||||
// 1. If we are creating an executable, we know the exact size of the TLS
|
||||
// template image we are creating, and we know where the TP will be set
|
||||
// to after the template is copied to the initial TLS block. Therefore,
|
||||
// the TP-relative address of a TLV in the main executable is known at
|
||||
// link-time. That means, computing a TLV's address can be as easy as
|
||||
// `add %dst, %tp, <link-time constant>`.
|
||||
//
|
||||
// 2. If we are creating a shared library, we don't exactly know where
|
||||
// its TLS template image will be copied to in terms of the
|
||||
// TP-relative address, because we don't know how large the main
|
||||
// executable's and other libraries' TLS template images are. Only the
|
||||
// runtime knows the exact TP-relative address.
|
||||
//
|
||||
// We can solve the problem with an indirection. Specifically, for
|
||||
// each TLV whose TP-relative address is known only at process startup
|
||||
// time, we create a GOT entry to store its TP-relative address. We
|
||||
// then emit a dynamic relocation to let the runtime to fill the GOT
|
||||
// entry with a TP-relative address.
|
||||
//
|
||||
// Computing a TLV address in this scheme needs at least two machine
|
||||
// instructions in most ISAs; the first instruction loads a value from
|
||||
// the GOT entry, and the second one adds the loaded value to TP.
|
||||
//
|
||||
// 3. Now, think about libraries that are dynamically loaded with dlopen.
|
||||
// The TLS block for such library may not be allocated next to the
|
||||
// initial TLS block, so we can have two or more discontiguous TLS
|
||||
// blocks. There's no easy formula to compute an address of a TLV in a
|
||||
// separate TLS block.
|
||||
//
|
||||
// The address of a TLV in a separate TLS block can be obtained by
|
||||
// calling the libc-provided function, __tls_get_addr(). The function
|
||||
// takes two arguments; a module ID to identify the ELF file and the
|
||||
// TLV's offset within the ELF file's TLS template image. Accessing a
|
||||
// TLV is sometimes compiled to a function call! The module ID and the
|
||||
// offset are usually stored to GOT as two consecutive words.
|
||||
//
|
||||
// The last access method is the most generic, so the compiler emits such
|
||||
// code by default. But that's the most expensive one, so the linker
|
||||
// rewrites instructions if possible so that 3) is relaxed to 2) or even
|
||||
// to 1).
|
||||
//
|
||||
// 1) is called the Local Exec access model. 2) is Initial Exec, and 3) is
|
||||
// General Dynamic.
|
||||
//
|
||||
// There's another little trick that the compiler can use if it knows two
|
||||
// TLVs are in the same ELF file (usually in the same file as the code is).
|
||||
// In this case, we can call __tls_get_addr() only once with a module ID and
|
||||
// the offset 0 to obtain the base address of the ELF file's TLS block. The
|
||||
// base address obtained this way is sometimes called Dynamic Thread Pointer
|
||||
// or DTP. We can then compute TLVs' addresses by adding their DTP-relative
|
||||
// addresses to DTP. This access model is called the Local Dynamic.
|
||||
//
|
||||
// === TLS Descriptor access model ===
|
||||
//
|
||||
// As described above, there are arguably too many different TLS access
|
||||
// models from the most generic one you can use in any ELF file to the most
|
||||
// efficient one you can use only when building a main executable. Compiling
|
||||
// source code with an appropriate TLS access model is bothersome. To solve
|
||||
// the problem, a new TLS access model was proposed. That is called the TLS
|
||||
// Descriptor (TLSDESC) model.
|
||||
//
|
||||
// For a TLV compiled with TLSDESC, we allocate two consecutive GOT slots
|
||||
// and create a TLSDESC dynamic relocation for them. The dynamic linker
|
||||
// sets a function pointer to the first GOT slot and its argument to the
|
||||
// second slot.
|
||||
//
|
||||
// To access the TLV, we call the function pointer with the argument we
|
||||
// read from the second GOT slot. The function returns the TLV's
|
||||
// TP-relative address.
|
||||
//
|
||||
// The runtime chooses the best access method depending on the situation
|
||||
// and sets a pointer to the most efficient code to the first GOT slot.
|
||||
// For example, if a TLV's TP-relative address is known at process startup
|
||||
// time, the runtime sets that address to the second GOT slot and set a
|
||||
// function that just returns its argument to the first GOT slot.
|
||||
//
|
||||
// With TLSDECS, the compiler can always emit the same code for TLVs
|
||||
// without sacrificing runtime performance.
|
||||
//
|
||||
// TLSDESC is better than the traditional, non-TLSDESC TLS access models.
|
||||
// It's the default on ARM64, but on other targets, TLSDESC is
|
||||
// unfortunately either optional or even not supported at all. So we still
|
||||
// need to support both the traditional TLS models and the TLSDESC model.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static ElfPhdr<E> *get_tls_segment(Context<E> &ctx) {
|
||||
if (ctx.phdr)
|
||||
for (ElfPhdr<E> &phdr : ctx.phdr->phdrs)
|
||||
if (phdr.p_type == PT_TLS)
|
||||
return &phdr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
u64 get_tls_begin(Context<E> &ctx) {
|
||||
if (ElfPhdr<E> *phdr = get_tls_segment(ctx))
|
||||
return phdr->p_vaddr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the TP address which can be used for efficient TLV accesses in
|
||||
// the main executable. TP at runtime refers to a per-process TLS block
|
||||
// whose address is not known at link-time. So the address returned from
|
||||
// this function is the TP if the TLS template image were a TLS block.
|
||||
template <typename E>
|
||||
u64 get_tp_addr(Context<E> &ctx) {
|
||||
ElfPhdr<E> *phdr = get_tls_segment(ctx);
|
||||
if (!phdr)
|
||||
return 0;
|
||||
|
||||
if constexpr (is_x86<E> || is_sparc<E> || is_s390x<E>) {
|
||||
// On x86, SPARC and s390x, TP (%gs on i386, %fs on x86-64, %g7 on SPARC
|
||||
// and %a0/%a1 on s390x) refers to past the end of the TLS block for
|
||||
// historical reasons. TLVs are accessed with negative offsets from TP.
|
||||
return align_to(phdr->p_vaddr + phdr->p_memsz, phdr->p_align);
|
||||
} else if constexpr (is_arm<E> || is_sh4<E> || is_alpha<E>) {
|
||||
// On ARM, SH4 and Alpha, the runtime appends two words at the beginning
|
||||
// of TLV template image when copying TLVs to the TLS block, so we need
|
||||
// to offset it.
|
||||
return align_down(phdr->p_vaddr - sizeof(Word<E>) * 2, phdr->p_align);
|
||||
} else if constexpr (is_ppc<E> || is_m68k<E>) {
|
||||
// On PowerPC and m68k, TP is 0x7000 (28 KiB) past the beginning
|
||||
// of the TLV block to maximize the addressable range of load/store
|
||||
// instructions with 16-bits signed immediates. It's not exactly 0x8000
|
||||
// (32 KiB) off because there's a small implementation-defined piece of
|
||||
// data before the initial TLV block, and the runtime wants to access
|
||||
// them efficiently too.
|
||||
return phdr->p_vaddr + 0x7000;
|
||||
} else {
|
||||
// RISC-V and LoongArch just uses the beginning of the main executable's
|
||||
// TLV block as TP. Their load/store instructions usually take 12-bits
|
||||
// signed immediates, so the beginning of the TLS block ± 2 KiB is
|
||||
// accessible with a single load/store instruction.
|
||||
static_assert(is_riscv<E> || is_loongarch<E>);
|
||||
return phdr->p_vaddr;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the address __tls_get_addr() would return if it's called
|
||||
// with offset 0.
|
||||
template <typename E>
|
||||
u64 get_dtp_addr(Context<E> &ctx) {
|
||||
ElfPhdr<E> *phdr = get_tls_segment(ctx);
|
||||
if (!phdr)
|
||||
return 0;
|
||||
|
||||
if constexpr (is_ppc<E> || is_m68k<E>) {
|
||||
// On PowerPC and m68k, R_DTPOFF is resolved to the address 0x8000
|
||||
// (32 KiB) past the start of the TLS block. The bias maximizes the
|
||||
// accessible range of load/store instructions with 16-bits signed
|
||||
// immediates. That is, if the offset were right at the beginning of the
|
||||
// start of the TLS block, the half of addressible space (negative
|
||||
// immediates) would have been wasted.
|
||||
return phdr->p_vaddr + 0x8000;
|
||||
} else if constexpr (is_riscv<E>) {
|
||||
// On RISC-V, the bias is 0x800 as the load/store instructions in the
|
||||
// ISA usually have a 12-bit immediate.
|
||||
return phdr->p_vaddr + 0x800;
|
||||
} else {
|
||||
// On other targets, DTP simply refers to the beginning of the TLS block.
|
||||
return phdr->p_vaddr;
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template u64 get_tls_begin<E>(Context<E> &);
|
||||
template u64 get_tp_addr<E>(Context<E> &);
|
||||
template u64 get_dtp_addr<E>(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
+19
-10
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
source /etc/os-release
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
@@ -12,33 +12,42 @@ set -x
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++ g++-10
|
||||
apt-get install -y cmake gcc g++ g++-10
|
||||
apt-get install -y file
|
||||
;;
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get update
|
||||
apt-get install -y cmake libssl-dev zlib1g-dev gcc g++
|
||||
apt-get install -y cmake gcc g++
|
||||
apt-get install -y file
|
||||
;;
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake openssl-devel zlib-devel
|
||||
dnf install -y gcc-g++ cmake
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++ gcc11-c++
|
||||
zypper install -y make cmake gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake zlib-devel libopenssl-devel gcc-c++
|
||||
zypper install -y make cmake gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake sys-libs/zlib
|
||||
emerge dev-util/cmake
|
||||
;;
|
||||
arch-*)
|
||||
arch-* | archarm-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel zlib openssl cmake util-linux
|
||||
pacman -S --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void-*)
|
||||
xbps-install -Sy xbps
|
||||
xbps-install -Sy bash make cmake gcc
|
||||
xbps-install -Sy tar diffutils util-linux
|
||||
;;
|
||||
alpine-*)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
|
||||
# to enable cross-target tests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <windows.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename C>
|
||||
class MallocOutputFile : public OutputFile<C> {
|
||||
public:
|
||||
MallocOutputFile(C &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<C>(path, filesize, false), perm(perm) {
|
||||
this->buf = (u8 *)malloc(filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << "malloc failed";
|
||||
}
|
||||
|
||||
void close(C &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);
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
private:
|
||||
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())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
OutputFile<C> *file = new MallocOutputFile<C>(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<C>>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
typedef uint8_t u8;
|
||||
static constexpr int64_t SHA256_SIZE = 32;
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, we use Microsoft CNG.
|
||||
|
||||
#include <Windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <ntstatus.h>
|
||||
|
||||
inline static BCRYPT_ALG_HANDLE get_sha256_handle() {
|
||||
static std::once_flag once;
|
||||
static BCRYPT_ALG_HANDLE alg;
|
||||
|
||||
std::call_once(once, [&] {
|
||||
BCryptOpenAlgorithmProvider(&alg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
|
||||
});
|
||||
return alg;
|
||||
}
|
||||
|
||||
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
|
||||
BCryptHash(get_sha256_handle(), nullptr, 0, in, len, out, SHA256_SIZE);
|
||||
}
|
||||
|
||||
class SHA256Hash {
|
||||
public:
|
||||
SHA256Hash() {
|
||||
BCryptCreateHash(get_sha256_handle(), &handle, nullptr, 0, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
void update(u8 *data, size_t len) {
|
||||
BCryptHashData(handle, data, len, 0);
|
||||
}
|
||||
|
||||
void finish(u8 *out) {
|
||||
BCryptFinishHash(handle, out, SHA256_SIZE, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
BCRYPT_HASH_HANDLE handle;
|
||||
};
|
||||
|
||||
#else
|
||||
// On Unix, we use OpenSSL or the Apple's OpenSSL-compatible API.
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define COMMON_DIGEST_FOR_OPENSSL
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
# define SHA256(data, len, md) CC_SHA256(data, len, md)
|
||||
#else
|
||||
# define OPENSSL_SUPPRESS_DEPRECATED 1
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
inline void sha256_hash(u8 *in, size_t len, u8 *out) {
|
||||
SHA256(in, len, out);
|
||||
}
|
||||
|
||||
class SHA256Hash {
|
||||
public:
|
||||
SHA256Hash() {
|
||||
SHA256_Init(&ctx);
|
||||
}
|
||||
|
||||
void update(u8 *data, size_t len) {
|
||||
SHA256_Update(&ctx, data, len);
|
||||
}
|
||||
|
||||
void finish(u8 *out) {
|
||||
SHA256_Final(out, &ctx);
|
||||
}
|
||||
|
||||
private:
|
||||
SHA256_CTX ctx;
|
||||
};
|
||||
|
||||
#endif
|
||||
+24
-4
@@ -1,10 +1,16 @@
|
||||
option(MOLD_ENABLE_QEMU_TESTS "Enable tests on non-native targets" OFF)
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
|
||||
function(add_target TRIPLE)
|
||||
set(HOST ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
if(${HOST} MATCHES "arm.*")
|
||||
|
||||
if(${HOST} MATCHES "amd64")
|
||||
set(HOST x86_64)
|
||||
elseif(${HOST} MATCHES "arm.*")
|
||||
set(HOST arm)
|
||||
elseif(${HOST} STREQUAL "ppc64")
|
||||
set(HOST powerpc64)
|
||||
elseif(${HOST} STREQUAL "ppc64le")
|
||||
set(HOST powerpc64le)
|
||||
endif()
|
||||
|
||||
if(${TRIPLE} MATCHES "${HOST}-.*")
|
||||
@@ -54,11 +60,25 @@ add_target(powerpc64-linux-gnu)
|
||||
add_target(powerpc64le-linux-gnu)
|
||||
add_target(sparc64-linux-gnu)
|
||||
add_target(s390x-linux-gnu)
|
||||
add_target(m68k-linux-gnu)
|
||||
add_target(sh4-linux-gnu)
|
||||
add_target(alpha-linux-gnu)
|
||||
add_target(hppa-linux-gnu)
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_RV32 "Enable tests for RV32" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_RV32)
|
||||
add_target(riscv32-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_POWER10 "Enable tests for Power10" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_POWER10)
|
||||
add_target(powerpc64le_power10-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_M68K "Enable tests for m68k" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_M68K)
|
||||
add_target(m68k-linux-gnu)
|
||||
endif()
|
||||
|
||||
option(MOLD_ENABLE_QEMU_TESTS_LOONGARCH "Enable tests for LoongArch" OFF)
|
||||
if(MOLD_ENABLE_QEMU_TESTS_LOONGARCH)
|
||||
add_target(loongarch64-linux-gnu)
|
||||
endif()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = aarch64 ] || skip
|
||||
|
||||
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
|
||||
.global foo
|
||||
.type foo, %function
|
||||
.variant_pcs foo
|
||||
foo:
|
||||
ret
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
readelf -W --dyn-syms $t/b.so | grep -Eq '\[VARIANT_PCS\].* foo$'
|
||||
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.so
|
||||
readelf -W --dynamic $t/exe | grep -q AARCH64_VARIANT_PCS
|
||||
@@ -6,6 +6,7 @@
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = s390x ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler -
|
||||
.globl foo
|
||||
|
||||
@@ -22,6 +22,5 @@ $CC -B. -o $t/exe $t/a.o \
|
||||
|
||||
$OBJDUMP -dr $t/exe | grep -F -A7 '<fn1$thunk>:' > $t/log
|
||||
|
||||
grep -Eq 'mov\s+ip, pc' $t/log
|
||||
grep -Eq 'bx\s+ip' $t/log
|
||||
grep -Eq 'add\s+ip, ip, pc' $t/log
|
||||
grep -Eq 'bx\s+pc' $t/log
|
||||
grep -Eq 'add\s+pc, ip, pc' $t/log
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
int fn1() { return 42; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc -
|
||||
int fn1();
|
||||
int fn2() { return fn1(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
int fn2();
|
||||
int main() { fn2(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -L$t -Wl,--as-needed -lbar -lfoo
|
||||
readelf -W --dynamic $t/exe > $t/log2
|
||||
grep -q libbar $t/log2
|
||||
! grep -q libfoo $t/log2 || false
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
int foo() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/b.o -xc -
|
||||
__attribute__((weak)) int foo();
|
||||
|
||||
int bar() {
|
||||
if (foo) return foo();
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -xc -c -o $t/c.o -
|
||||
int bar();
|
||||
|
||||
int main() {
|
||||
return bar();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -o $t/libfoo.so $t/a.o
|
||||
$CC -B. -shared -o $t/libbar.so $t/b.o
|
||||
$CC -B. -o $t/exe $t/c.o -L$t -Wl,--as-needed -lfoo -lbar
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
! grep libfoo.so $t/log || false
|
||||
grep -q libbar.so $t/log
|
||||
+12
-11
@@ -5,26 +5,27 @@ cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
|
||||
__attribute__((weak)) int fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
if (fn1)
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/b.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
cat <<EOF | $CC -o $t/libfoo.so -shared -fPIC -Wl,-soname,libfoo.so -xc -
|
||||
int fn1() { return 42; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -o $t/c.so -shared -fPIC -Wl,-soname,libbar.so -xc -
|
||||
cat <<EOF | $CC -o $t/libbar.so -shared -fPIC -Wl,-soname,libbar.so -xc -
|
||||
int fn2() { return 42; }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-no-as-needed $t/b.so $t/c.so
|
||||
$CC -o $t/exe1 $t/a.o -Wl,-no-as-needed -L$t -lbar -lfoo
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/readelf
|
||||
grep -Fq 'Shared library: [libbar.so]' $t/readelf
|
||||
readelf --dynamic $t/exe1 > $t/log1
|
||||
grep -Fq 'Shared library: [libfoo.so]' $t/log1
|
||||
grep -Fq 'Shared library: [libbar.so]' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe $t/a.o -Wl,-as-needed $t/b.so $t/c.so
|
||||
$CC -o $t/exe2 $t/a.o -Wl,-as-needed -L$t -lbar -lfoo
|
||||
|
||||
readelf --dynamic $t/exe > $t/readelf
|
||||
! grep -Fq 'Shared library: [libfoo.so]' $t/readelf || false
|
||||
! grep -Fq 'Shared library: [libbar.so]' $t/readelf || false
|
||||
readelf --dynamic $t/exe2 > $t/log2
|
||||
! grep -Fq 'Shared library: [libfoo.so]' $t/log2 || false
|
||||
! grep -Fq 'Shared library: [libbar.so]' $t/log2 || false
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/a.so -xc -
|
||||
int foo() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -shared -fPIC -o $t/b.so -xc -
|
||||
int bar() { return 3; }
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/c.o -xc -
|
||||
int foo();
|
||||
int baz() { return foo(); }
|
||||
EOF
|
||||
|
||||
$CC -shared -o $t/c.so $t/c.o $t/a.so
|
||||
|
||||
cat <<EOF | $CC -c -o $t/d.o -xc -
|
||||
#include <stdio.h>
|
||||
int baz();
|
||||
int main() {
|
||||
printf("%d\n", baz());
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/d.o -Wl,--as-needed $t/c.so $t/b.so $t/a.so
|
||||
$QEMU $t/exe | grep -q '^3$'
|
||||
|
||||
readelf --dynamic $t/exe > $t/log
|
||||
! grep -q /a.so $t/log || false
|
||||
grep -q /c.so $t/log || false
|
||||
! grep -q /b.so $t/log || false
|
||||
@@ -5,13 +5,6 @@
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
|
||||
|
||||
# This test does not pass even with GNU ld, which means function pointer
|
||||
# equality is not guaranteed on HP/PA. I'm not sure if this is an bug in
|
||||
# QEMU or this test really fails on a real HP/PA machine, but it's quite
|
||||
# surprising. Many programs could silently break if function pointer
|
||||
# equality is not guaranteed.
|
||||
[ $MACHINE = hppa ] && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o$t/a.o -xc -
|
||||
int foo = 4;
|
||||
|
||||
@@ -26,7 +19,7 @@ EOF
|
||||
|
||||
$CC -B. -shared -fPIC -o $t/b.so $t/a.o -Wl,-Bsymbolic -Wl,-Bno-symbolic
|
||||
|
||||
cat <<EOF | $CC -B. -c -o $t/c.o -xc - -fno-PIE
|
||||
cat <<EOF | $CC -c -o $t/c.o -xc - -fno-PIE
|
||||
#include <stdio.h>
|
||||
|
||||
extern int foo;
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
# https://sourceware.org/bugzilla/show_bug.cgi?id=29655
|
||||
[ $MACHINE = s390x ] && $CC -v 2>&1 | grep -E '^gcc version 1[0-3]\.' && skip
|
||||
|
||||
# Pointer equality does not seem to be guaranteed on HP/PA
|
||||
[ $MACHINE = hppa ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.so -fPIC -shared -xc -
|
||||
void *foo() {
|
||||
return foo;
|
||||
|
||||
+51
-17
@@ -16,23 +16,10 @@ canonical_name() {
|
||||
|
||||
HOST=$(canonical_name $(uname -m))
|
||||
|
||||
if [ -n "$TRIPLE" ]; then
|
||||
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
|
||||
else
|
||||
MACHINE=$HOST
|
||||
fi
|
||||
|
||||
# Set tool names
|
||||
if [ -n "$TRIPLE" ]; then
|
||||
CC="${TEST_CC:-$TRIPLE-gcc}"
|
||||
CXX="${TEST_CXX:-$TRIPLE-g++}"
|
||||
GCC="${TEST_GCC:-$TRIPLE-gcc}"
|
||||
GXX="${TEST_GXX:-$TRIPLE-g++}"
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
else
|
||||
if [ "$TRIPLE" = "" ]; then
|
||||
MACHINE=$HOST
|
||||
TESTDIR=out/test/elf/$HOST
|
||||
CC="${TEST_CC:-cc}"
|
||||
CXX="${TEST_CXX:-c++}"
|
||||
GCC="${TEST_GCC:-gcc}"
|
||||
@@ -41,6 +28,42 @@ else
|
||||
OBJCOPY=objcopy
|
||||
STRIP=strip
|
||||
QEMU=
|
||||
elif [ "$TRIPLE" = powerpc64le_power10-linux-gnu ]; then
|
||||
TRIPLE=powerpc64le-linux-gnu
|
||||
MACHINE=ppc64le
|
||||
TESTDIR=out/test/elf/ppc64le-power10
|
||||
CC="${TEST_CC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
CXX="${TEST_CXX:-$TRIPLE-g++} -mcpu=power10"
|
||||
GCC="${TEST_GCC:-$TRIPLE-gcc} -mcpu=power10"
|
||||
GXX="${TEST_GXX:-$TRIPLE-g++} -mcpu=power10"
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-ppc64le -L /usr/$TRIPLE -cpu power10"
|
||||
else
|
||||
MACHINE=$(canonical_name $(echo $TRIPLE | sed 's/-.*//'))
|
||||
TESTDIR=out/test/elf/$MACHINE
|
||||
CC="${TEST_CC:-$TRIPLE-gcc}"
|
||||
CXX="${TEST_CXX:-$TRIPLE-g++}"
|
||||
GCC="${TEST_GCC:-$TRIPLE-gcc}"
|
||||
GXX="${TEST_GXX:-$TRIPLE-g++}"
|
||||
OBJDUMP="$TRIPLE-objdump"
|
||||
OBJCOPY="$TRIPLE-objcopy"
|
||||
STRIP="$TRIPLE-strip"
|
||||
QEMU="qemu-$MACHINE -L /usr/$TRIPLE"
|
||||
fi
|
||||
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = i386 -o $MACHINE = arm ]; then
|
||||
tlsdesc_opt=-mtls-dialect=gnu2
|
||||
elif [ $MACHINE = aarch64 ]; then
|
||||
tlsdesc_opt=-mtls-dialect=desc
|
||||
fi
|
||||
|
||||
if [ $MACHINE = ppc64le -a -e /proc/cpuinfo ] && grep -q POWER10 /proc/cpuinfo; then
|
||||
CC="$CC -mcpu=power10"
|
||||
CXX="$CXX -mcpu=power10"
|
||||
GCC="$GCC -mcpu=power10"
|
||||
GXX="$GXX -mcpu=power10"
|
||||
fi
|
||||
|
||||
# Common functions
|
||||
@@ -53,6 +76,17 @@ supports_ifunc() {
|
||||
$CC -c -o /dev/null -xc - >& /dev/null
|
||||
}
|
||||
|
||||
is_musl() {
|
||||
ldd --help 2>&1 | grep -q musl
|
||||
}
|
||||
|
||||
supports_tlsdesc() {
|
||||
# musl's tlsdesc on arm32 seems to be broken
|
||||
[ $MACHINE = arm ] && is_musl && return 1
|
||||
|
||||
[ -n "$tlsdesc_opt" ]
|
||||
}
|
||||
|
||||
skip() {
|
||||
echo skipped
|
||||
trap - EXIT
|
||||
@@ -77,5 +111,5 @@ trap on_exit EXIT
|
||||
# Print out the startup message
|
||||
testname=$(basename "$0" .sh)
|
||||
echo -n "Testing $testname ... "
|
||||
t=out/test/elf/$MACHINE/$testname
|
||||
t=$TESTDIR/$testname
|
||||
mkdir -p $t
|
||||
@@ -4,6 +4,7 @@
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/a.so -xc -
|
||||
__attribute__((aligned(32))) int foo = 5;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
[ $MACHINE = ppc64le ] && skip
|
||||
[ $MACHINE = alpha ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
extern int foo;
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -fno-PIE -
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern char readonly[100];
|
||||
extern char readwrite[100];
|
||||
|
||||
static int segv = 0;
|
||||
static jmp_buf buf;
|
||||
|
||||
void handler(int sig) {
|
||||
segv = 1;
|
||||
longjmp(buf, 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
signal(SIGSEGV, handler);
|
||||
|
||||
readwrite[0] = 5;
|
||||
int x = segv;
|
||||
|
||||
if (setjmp(buf) == 0)
|
||||
*(char *)readonly = 5;
|
||||
int y = segv;
|
||||
|
||||
printf("sigsegv %d %d\n", x, y);
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -fPIC -shared -o $t/b.so -xc -
|
||||
__attribute__((section (".data.rel.ro"))) char readonly[100] = "abc";
|
||||
char readwrite[100] = "abc";
|
||||
EOF
|
||||
|
||||
$CC -B. $t/a.o $t/b.so -o $t/exe -no-pie
|
||||
$QEMU $t/exe | grep -q '^sigsegv 0 1$'
|
||||
@@ -6,8 +6,8 @@ void foo() {}
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@b\.so'
|
||||
|
||||
$CC -B. -o $t/b.so -shared $t/a.o \
|
||||
-Wl,--soname=bar -Wl,-default-symver
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' foo@@bar'
|
||||
@@ -2,6 +2,11 @@
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 ] && skip
|
||||
[[ $MACHINE = loongarch* ]] && skip
|
||||
|
||||
# musl libc does not support init/fini on ARM
|
||||
# https://github.com/rui314/mold/issues/951
|
||||
[ $MACHINE = arm -o $MACHINE = aarch64 ] && is_musl && skip
|
||||
|
||||
cat <<EOF | $CC -c -fPIC -o $t/a.o -xc -
|
||||
void keep();
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
rm -f $t/b.a
|
||||
ar rcs $t/b.a $t/a.o
|
||||
|
||||
cat <<EOF | $CC -o $t/c.o -c -xc -
|
||||
void foo();
|
||||
int main() { foo(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/exe $t/c.o $t/b.a $t/b.a
|
||||
|
||||
! $CC -B. -o $t/exe $t/c.o -Wl,--push-state,--whole-archive \
|
||||
$t/b.a $t/b.a -Wl,--pop-state 2> $t/log || false
|
||||
|
||||
grep -q 'duplicate symbol:.* foo' $t/log
|
||||
@@ -10,8 +10,8 @@ EOF
|
||||
$CC -B. -o $t/exe $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; bar; };
|
||||
@@ -20,17 +20,17 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
grep -q ' bar$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
grep -q ' bar' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
grep -q ' bar$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
grep -q ' bar' $t/log2
|
||||
|
||||
$CC -B. -o $t/exe3 $t/a.o -Wl,--export-dynamic-symbol=foo,--export-dynamic-symbol=bar
|
||||
|
||||
readelf --dyn-syms $t/exe3 > $t/log3
|
||||
grep -q ' foo$' $t/log3
|
||||
grep -q ' bar$' $t/log3
|
||||
grep -q ' foo' $t/log3
|
||||
grep -q ' bar' $t/log3
|
||||
@@ -14,8 +14,8 @@ EOF
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe > $t/log
|
||||
! grep -q ' foo$' $t/log || false
|
||||
! grep -q ' bar$' $t/log || false
|
||||
! grep -q ' foo' $t/log || false
|
||||
! grep -q ' bar' $t/log || false
|
||||
|
||||
cat <<EOF > $t/dyn
|
||||
{ foo; extern "C++" { "baz(int)"; }; };
|
||||
@@ -24,13 +24,13 @@ EOF
|
||||
$CC -B. -o $t/exe1 $t/a.o $t/b.o -Wl,-dynamic-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' foo$' $t/log1
|
||||
! grep -q ' bar$' $t/log1 || false
|
||||
grep -q ' _Z3bazi$' $t/log1
|
||||
grep -q ' foo' $t/log1
|
||||
! grep -q ' bar' $t/log1 || false
|
||||
grep -q ' _Z3bazi' $t/log1
|
||||
|
||||
$CC -B. -o $t/exe2 $t/a.o $t/b.o -Wl,--export-dynamic-symbol-list=$t/dyn
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' foo$' $t/log2
|
||||
! grep -q ' bar$' $t/log2 || false
|
||||
grep -q ' _Z3bazi$' $t/log2
|
||||
grep -q ' foo' $t/log2
|
||||
! grep -q ' bar' $t/log2 || false
|
||||
grep -q ' _Z3bazi' $t/log2
|
||||
+12
-12
@@ -21,19 +21,19 @@ EOF
|
||||
$CC -B. -Wl,--dynamic-list=$t/dyn -o $t/exe1 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe1 > $t/log1
|
||||
grep -q ' xyz$' $t/log1
|
||||
! grep -q ' foobarzx$' $t/log1 || false
|
||||
grep -q ' foobarcx$' $t/log1
|
||||
grep -q ' foo123bar456bx$' $t/log1
|
||||
! grep -q ' foo123bar456c$' $t/log1 || false
|
||||
! grep -q ' foo123bar456x$' $t/log1 || false
|
||||
grep -q ' xyz' $t/log1
|
||||
! grep -q ' foobarzx' $t/log1 || false
|
||||
grep -q ' foobarcx' $t/log1
|
||||
grep -q ' foo123bar456bx' $t/log1
|
||||
! grep -q ' foo123bar456c' $t/log1 || false
|
||||
! grep -q ' foo123bar456x' $t/log1 || false
|
||||
|
||||
$CC -B. -Wl,--export-dynamic-symbol-list=$t/dyn -o $t/exe2 $t/b.o
|
||||
|
||||
readelf --dyn-syms $t/exe2 > $t/log2
|
||||
grep -q ' xyz$' $t/log2
|
||||
! grep -q ' foobarzx$' $t/log2 || false
|
||||
grep -q ' foobarcx$' $t/log2
|
||||
grep -q ' foo123bar456bx$' $t/log2
|
||||
! grep -q ' foo123bar456c$' $t/log2 || false
|
||||
! grep -q ' foo123bar456x$' $t/log2 || false
|
||||
grep -q ' xyz' $t/log2
|
||||
! grep -q ' foobarzx' $t/log2 || false
|
||||
grep -q ' foobarcx' $t/log2
|
||||
grep -q ' foo123bar456bx' $t/log2
|
||||
! grep -q ' foo123bar456c' $t/log2 || false
|
||||
! grep -q ' foo123bar456x' $t/log2 || false
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
cat <<EOF | $CXX -o $t/a.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
Foo() { std::cout << "Hello world\n"; }
|
||||
};
|
||||
|
||||
Foo x;
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CXX -o $t/b.o -c -fPIC -xc++ -g -ffunction-sections -
|
||||
#include <iostream>
|
||||
struct Foo {
|
||||
Foo() { std::cout << "Hello world\n"; }
|
||||
};
|
||||
|
||||
Foo y;
|
||||
int main() {}
|
||||
EOF
|
||||
|
||||
$CXX -B. -o $t/exe $t/a.o $t/b.o -Wl,-emit-relocs
|
||||
$QEMU $t/exe | grep -q 'Hello world'
|
||||
@@ -11,5 +11,5 @@ EOF
|
||||
|
||||
$CC -B. -shared -o $t/b.so $t/a.o
|
||||
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar1$'
|
||||
readelf --dyn-syms $t/b.so | grep -q 'bar2$'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar1'
|
||||
readelf --dyn-syms $t/b.so | grep -q ' bar2'
|
||||
+15
-22
@@ -3,8 +3,11 @@
|
||||
|
||||
[ $MACHINE = sh4 ] && skip
|
||||
|
||||
static=1
|
||||
test_cflags -static || static=0
|
||||
static=
|
||||
test_cflags -static && static=-static
|
||||
|
||||
# I don't know why, but we need -pthread on m68k
|
||||
static="$static -pthread"
|
||||
|
||||
cat <<EOF > $t/a.cc
|
||||
int main() {
|
||||
@@ -20,15 +23,11 @@ EOF
|
||||
$CXX -c -o $t/b.o $t/a.cc -fPIC
|
||||
$CXX -c -o $t/c.o $t/a.cc -fno-PIC
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe1 $t/b.o -static
|
||||
$QEMU $t/exe1
|
||||
fi
|
||||
$CXX -B. -o $t/exe1 $t/b.o $static
|
||||
$QEMU $t/exe1
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe2 $t/c.o -static
|
||||
$QEMU $t/exe2
|
||||
fi
|
||||
$CXX -B. -o $t/exe2 $t/c.o -no-pie $static
|
||||
$QEMU $t/exe2
|
||||
|
||||
$CXX -B. -o $t/exe3 $t/b.o -pie
|
||||
$QEMU $t/exe3
|
||||
@@ -39,18 +38,14 @@ $QEMU $t/exe4
|
||||
$CXX -B. -o $t/exe5 $t/b.o -pie -Wl,--gc-sections
|
||||
$QEMU $t/exe5
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe6 $t/c.o -static -Wl,--gc-sections
|
||||
$QEMU $t/exe6
|
||||
fi
|
||||
$CXX -B. -o $t/exe6 $t/c.o -no-pie $static -Wl,--gc-sections
|
||||
$QEMU $t/exe6
|
||||
|
||||
if [ $MACHINE = x86_64 ]; then
|
||||
$CXX -c -o $t/d.o $t/a.cc -mcmodel=large -fPIC
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe7 $t/d.o -static
|
||||
$QEMU $t/exe7
|
||||
fi
|
||||
$CXX -B. -o $t/exe7 $t/d.o $static
|
||||
$QEMU $t/exe7
|
||||
|
||||
$CXX -B. -o $t/exe8 $t/d.o -pie
|
||||
$QEMU $t/exe8
|
||||
@@ -59,10 +54,8 @@ fi
|
||||
if [ $MACHINE = x86_64 -o $MACHINE = aarch64 ]; then
|
||||
$CXX -c -o $t/e.o $t/a.cc -mcmodel=large -fno-PIC
|
||||
|
||||
if [ $static = 1 ]; then
|
||||
$CXX -B. -o $t/exe9 $t/e.o -static
|
||||
$QEMU $t/exe9
|
||||
fi
|
||||
$CXX -B. -o $t/exe9 $t/e.o -no-pie $static
|
||||
$QEMU $t/exe9
|
||||
|
||||
$CXX -B. -o $t/exe10 $t/e.o -no-pie
|
||||
$QEMU $t/exe10
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
# .text is writable on sparc, which is not compatible with --execute-only
|
||||
[ $MACHINE = sparc64 ] && skip
|
||||
|
||||
# GCC emits data to .text for PPC64, so PPC64 is not compatible with -execute-only
|
||||
[ $MACHINE = ppc64 ] && skip
|
||||
|
||||
cat <<EOF | $CC -o $t/a.o -c -xc -
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
[ $MACHINE = $HOST ] || skip
|
||||
[ $MACHINE = riscv64 -o $MACHINE = riscv32 -o $MACHINE = sparc64 ] && skip
|
||||
|
||||
command -v gdb >& /dev/null || skip
|
||||
|
||||
test_cflags -gdwarf-5 -g -gdwarf64 || skip
|
||||
|
||||
cat <<EOF > $t/a.c
|
||||
void fn3();
|
||||
|
||||
static void fn2() {
|
||||
fn3();
|
||||
}
|
||||
|
||||
void fn1() {
|
||||
fn2();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
void fn5();
|
||||
|
||||
static void fn4() {
|
||||
fn5();
|
||||
}
|
||||
|
||||
void fn3() {
|
||||
fn4();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/c.c
|
||||
void fn7();
|
||||
|
||||
static void fn6() {
|
||||
fn7();
|
||||
}
|
||||
|
||||
void fn5() {
|
||||
fn6();
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/d.c
|
||||
#include <stdio.h>
|
||||
void trap() {}
|
||||
|
||||
static void fn8() {
|
||||
printf("Hello world\n");
|
||||
trap();
|
||||
}
|
||||
|
||||
void fn7() {
|
||||
fn8();
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -c -o $t/a.o $t/a.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/b.o $t/b.c -fPIC -g -ggnu-pubnames -gdwarf-4 -gdwarf64 -ffunction-sections
|
||||
$CC -c -o $t/c.o $t/c.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64
|
||||
$CC -c -o $t/d.o $t/d.c -fPIC -g -ggnu-pubnames -gdwarf-5 -gdwarf64 -ffunction-sections
|
||||
|
||||
$CC -B. -shared -o $t/e.so $t/a.o $t/b.o $t/c.o $t/d.o
|
||||
|
||||
$CC -B. -shared -o $t/f.so $t/a.o $t/b.o $t/c.o $t/d.o -Wl,--gdb-index
|
||||
readelf -WS $t/f.so 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
cat <<EOF | $CC -c -o $t/g.o -fPIC -g -ggnu-pubnames -gdwarf-5 -xc - -gz
|
||||
void fn1();
|
||||
|
||||
int main() {
|
||||
fn1();
|
||||
}
|
||||
EOF
|
||||
|
||||
# Older versions of gdb are buggy that they complain DWARF64 debug sections
|
||||
# even without .gdb_index. Skip if such version.
|
||||
$CC -B. -o $t/exe1 $t/e.so $t/g.o
|
||||
DEBUGINFOD_URLS= gdb $t/exe1 -nx -batch -ex 'b main' -ex r -ex quit >& $t/log1
|
||||
grep -q 'DW_FORM_line_strp pointing outside of .debug_line_str' $t/log1 && skip
|
||||
|
||||
# We are using a recent version of gdb.
|
||||
$CC -B. -o $t/exe2 $t/f.so $t/g.o -Wl,--gdb-index
|
||||
readelf -WS $t/exe2 2> /dev/null | grep -Fq .gdb_index
|
||||
|
||||
$QEMU $t/exe2 | grep -q 'Hello world'
|
||||
|
||||
DEBUGINFOD_URLS= gdb $t/exe2 -nx -batch -ex 'b main' -ex r -ex 'b trap' \
|
||||
-ex c -ex bt -ex quit >& $t/log2
|
||||
|
||||
grep -q 'fn8 () at .*/d.c:6' $t/log2
|
||||
grep -q 'fn7 () at .*/d.c:10' $t/log2
|
||||
grep -q 'fn6 () at .*/c.c:4' $t/log2
|
||||
grep -q 'fn5 () at .*/c.c:8' $t/log2
|
||||
grep -q 'fn4 () at .*/b.c:4' $t/log2
|
||||
grep -q 'fn3 () at .*/b.c:8' $t/log2
|
||||
grep -q 'fn2 () at .*/a.c:4' $t/log2
|
||||
grep -q 'fn1 () at .*/a.c:8' $t/log2
|
||||
Loaded 100 of 1184 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user