mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e61093dfd6 | ||
|
|
a01599cb4d | ||
|
|
87e4c7f552 | ||
|
|
fa74740703 | ||
|
|
121c0ed23d | ||
|
|
ed7ccccf54 | ||
|
|
23789a7936 | ||
|
|
df91548a62 | ||
|
|
bc7e4af733 | ||
|
|
2f6cf922e9 | ||
|
|
949e72307f | ||
|
|
b3778207fc | ||
|
|
8eafdfbd0a | ||
|
|
72c0340ca5 | ||
|
|
934ca5bb5b | ||
|
|
c9fa7111a2 | ||
|
|
a3682ddbfd | ||
|
|
75772401cc | ||
|
|
36f8ead511 | ||
|
|
30303d716a | ||
|
|
281d78caea | ||
|
|
e9afa21adc | ||
|
|
52c698d625 | ||
|
|
7eedb949b7 | ||
|
|
85408b4976 | ||
|
|
4cb46531d0 | ||
|
|
ea98b0cb71 | ||
|
|
27edd50815 | ||
|
|
18ecf979b8 | ||
|
|
47f6c2839c | ||
|
|
da144e9d23 | ||
|
|
f556d2b4ae | ||
|
|
f2a62f70ef | ||
|
|
1956ae6ac8 | ||
|
|
96c798d7be | ||
|
|
6214f7be34 | ||
|
|
9b8b666da8 | ||
|
|
6465232f0c | ||
|
|
cc32a6d559 | ||
|
|
6214e132f8 | ||
|
|
6ca242b8f0 | ||
|
|
3721c52d34 | ||
|
|
341c88e426 | ||
|
|
8f3f71663e | ||
|
|
1012a4804c | ||
|
|
5962014fd8 | ||
|
|
dac20fa243 | ||
|
|
8fa6fc86d3 | ||
|
|
1c64d40ab8 | ||
|
|
58cf042386 | ||
|
|
d2f8bffdba | ||
|
|
60a9e013d3 | ||
|
|
a8893494ae | ||
|
|
2479b81f8a | ||
|
|
4dd133a31e | ||
|
|
02678c8af0 | ||
|
|
29e8b83886 | ||
|
|
f0b933820d | ||
|
|
9a2f31164b | ||
|
|
8ef93b14d6 | ||
|
|
8d57a4419d | ||
|
|
0ef77ebd98 | ||
|
|
00d45d317f | ||
|
|
cdb63bb2a1 | ||
|
|
bcd77395d4 | ||
|
|
4074707891 | ||
|
|
079f914898 | ||
|
|
d59c55959c | ||
|
|
5e19601a17 | ||
|
|
3de3dcc001 | ||
|
|
0d8334e5a2 | ||
|
|
a731b4c58c | ||
|
|
971317f3a9 | ||
|
|
0a55058042 | ||
|
|
32746ebbb3 | ||
|
|
1f44438b63 | ||
|
|
b53197300b | ||
|
|
3e9321b9a0 | ||
|
|
142eeaba86 | ||
|
|
15f6b9f966 | ||
|
|
a310cd27d4 | ||
|
|
2d178eb1ed | ||
|
|
0fdffad57f | ||
|
|
d05272c4ee | ||
|
|
1683826f2f | ||
|
|
9ddec785b1 | ||
|
|
e4f867ad77 | ||
|
|
b7ce902e52 | ||
|
|
133613a493 | ||
|
|
b026d7119b | ||
|
|
fc0dd85d87 | ||
|
|
6577232458 | ||
|
|
b504cacf25 | ||
|
|
159ce3ba67 | ||
|
|
4c2dae0a2d | ||
|
|
275c53d615 | ||
|
|
fdd959880a | ||
|
|
26d827d6e7 | ||
|
|
858fd8c495 | ||
|
|
c13ecc99eb | ||
|
|
122268d147 | ||
|
|
b990467c16 | ||
|
|
3d866bea61 | ||
|
|
19a1bc651f | ||
|
|
18c813a21c | ||
|
|
af36625a02 | ||
|
|
53c1758503 | ||
|
|
67ceb41b79 | ||
|
|
7d99824c3d | ||
|
|
344dcff8e8 | ||
|
|
d0dffd5985 | ||
|
|
251041e371 | ||
|
|
4dd872ee8d | ||
|
|
bb1b9dda64 | ||
|
|
78ec306cf4 | ||
|
|
163c62f491 | ||
|
|
026fb54b9d | ||
|
|
cd162ba9f6 | ||
|
|
e08e7f6aa4 | ||
|
|
6be2e676d2 | ||
|
|
b6565025a3 | ||
|
|
118dc5de8e | ||
|
|
f3475d9f09 | ||
|
|
299904e56f | ||
|
|
fded2d8a77 | ||
|
|
f0e726024e | ||
|
|
3d29f1678d | ||
|
|
559f23ff81 | ||
|
|
3274bcbdf0 | ||
|
|
83dd353979 | ||
|
|
8bae43b842 | ||
|
|
4038cfd64d | ||
|
|
41079c64f5 | ||
|
|
02e410133b | ||
|
|
e70236a45a | ||
|
|
285c5bb509 | ||
|
|
975da12333 | ||
|
|
157b16adb5 | ||
|
|
564757a0f5 | ||
|
|
216b4a2325 | ||
|
|
954107e29c | ||
|
|
21e20e01da | ||
|
|
161cb6c99c | ||
|
|
21cfee556a | ||
|
|
06203c4a72 | ||
|
|
232067634f | ||
|
|
3f9aab7066 | ||
|
|
8b88595f26 | ||
|
|
fa09c562c9 | ||
|
|
9457d993c4 | ||
|
|
43810dfecd | ||
|
|
2d6061a233 | ||
|
|
3d6aad65b0 | ||
|
|
f7b1920f84 | ||
|
|
b42f978212 | ||
|
|
ac6f1ec18e | ||
|
|
d19f822ec0 | ||
|
|
a97a628ee8 | ||
|
|
2888aade4c | ||
|
|
0172f22ab5 | ||
|
|
418d4ff62a | ||
|
|
5249edc57f | ||
|
|
1870845f88 | ||
|
|
d15c41a8dd | ||
|
|
b7c8a843b2 | ||
|
|
9b77a7ea86 | ||
|
|
af396ad5d9 | ||
|
|
a2ee7371d5 | ||
|
|
c9812cf09f | ||
|
|
e9c070a3c2 | ||
|
|
02e75be9fa | ||
|
|
d5609a9bb8 | ||
|
|
5d24db5a64 | ||
|
|
aeec8baee5 | ||
|
|
b74a771886 | ||
|
|
0124058ef7 | ||
|
|
e302a2509c | ||
|
|
f70db66373 | ||
|
|
9c0cbc8e6c | ||
|
|
a9acbd6b6f | ||
|
|
b9d65a5787 | ||
|
|
50766f1ad4 | ||
|
|
3496ad1291 | ||
|
|
8a3d374251 | ||
|
|
96b2710849 | ||
|
|
a43f3953f0 | ||
|
|
2bc6ddc41c | ||
|
|
f6575d53c1 | ||
|
|
bf1ec5ac8f | ||
|
|
b291100cfe | ||
|
|
c43a859a90 | ||
|
|
6e3d0e3a3c | ||
|
|
790ce3f64d | ||
|
|
cbd7f74c2e | ||
|
|
3fe25fc69d | ||
|
|
823ef09585 | ||
|
|
f20f57060a | ||
|
|
0061d53409 | ||
|
|
82affb94d8 | ||
|
|
4f782fb7dc | ||
|
|
e9eb6020a6 | ||
|
|
23a662cbb9 | ||
|
|
32c8f6dd61 | ||
|
|
c95476d2fe | ||
|
|
3981f4f3d4 | ||
|
|
d4ad627919 | ||
|
|
befe780cf8 | ||
|
|
879fe682ee | ||
|
|
805a8b2b62 | ||
|
|
ec360760d6 | ||
|
|
30541fc1e8 | ||
|
|
9ebd889a73 | ||
|
|
f15bd7537e | ||
|
|
f2ac49824c | ||
|
|
716fb9c06d | ||
|
|
50231607f9 | ||
|
|
fb7a650186 | ||
|
|
a399548dfa | ||
|
|
11a62121e3 | ||
|
|
a067b0106b | ||
|
|
7607ad6371 | ||
|
|
eab8217962 | ||
|
|
f46959819b | ||
|
|
a17202d2b5 | ||
|
|
83152acc8b | ||
|
|
88ea3a1aa5 | ||
|
|
0302affc11 | ||
|
|
34986d26df | ||
|
|
1fbbcec3da | ||
|
|
9ae7e700fa | ||
|
|
f496c31008 | ||
|
|
48a84176ac | ||
|
|
7fe4bee499 | ||
|
|
015d5388a0 | ||
|
|
9306f69a61 | ||
|
|
b82028d45d | ||
|
|
123faa82dc | ||
|
|
93f540a10a | ||
|
|
143d447a66 | ||
|
|
8b089859ee | ||
|
|
4b754fd944 | ||
|
|
b9f938e801 | ||
|
|
6e50f3a5e7 | ||
|
|
c2df589e05 | ||
|
|
247ae9500c | ||
|
|
85e8d4179e | ||
|
|
9185ee4b05 | ||
|
|
16f75990f9 | ||
|
|
34053cc52e | ||
|
|
810946130d | ||
|
|
33b6f3dee9 | ||
|
|
14a276ecde | ||
|
|
245d265b4b | ||
|
|
0619541234 | ||
|
|
8f57d2affd | ||
|
|
501f66b24c | ||
|
|
6ef9ffb81a | ||
|
|
9ab2c31086 | ||
|
|
182bd7b68c | ||
|
|
09e205d11e | ||
|
|
7e5e396aae | ||
|
|
b8914d0a94 | ||
|
|
359e5134c9 | ||
|
|
4ed6f48f55 | ||
|
|
48beed9fdf | ||
|
|
42cc55e6d9 | ||
|
|
04103ae60a | ||
|
|
0966f1858c | ||
|
|
56748ed199 | ||
|
|
11387b3c19 | ||
|
|
04d71affd1 | ||
|
|
6cb2f31236 | ||
|
|
01392bd53a | ||
|
|
7cb0b817f5 | ||
|
|
ea38c1544f | ||
|
|
80a2d24948 | ||
|
|
ded9292ba9 | ||
|
|
6d1c96f34b | ||
|
|
0849789fd5 | ||
|
|
7f771d6ed9 | ||
|
|
b8a46ede02 | ||
|
|
bab7dd1b2e | ||
|
|
ebe887f0ae | ||
|
|
df30516630 | ||
|
|
e8101a4d8a | ||
|
|
6291f1b86c | ||
|
|
a570e8953c | ||
|
|
5cc0e9d0a1 | ||
|
|
4bb8e03b24 | ||
|
|
bcadb4e30f | ||
|
|
a2eda6acc4 | ||
|
|
e60e04feb2 | ||
|
|
3651dba6ef | ||
|
|
c20d027441 | ||
|
|
66b5ed66c6 | ||
|
|
0b747d8334 | ||
|
|
73f86aeadd | ||
|
|
ceee495584 | ||
|
|
3d89c1181b | ||
|
|
f014a1d015 | ||
|
|
f25e450f2b | ||
|
|
1fbc6f3dec | ||
|
|
4679573654 | ||
|
|
af608f5651 | ||
|
|
5a9c465210 | ||
|
|
1822e47a43 | ||
|
|
06f20e9d69 | ||
|
|
8929bd4a8d | ||
|
|
3b1e2a9aa3 | ||
|
|
721eafc7dd | ||
|
|
4de8daf34f | ||
|
|
f7624ee1f9 | ||
|
|
500416f8f6 | ||
|
|
c6c12d43f5 | ||
|
|
7fc2a6e935 | ||
|
|
9fe8831db3 | ||
|
|
2bb42badde | ||
|
|
48077dd91a | ||
|
|
cf0d750734 | ||
|
|
48dd767ad3 | ||
|
|
3c89ad66a2 | ||
|
|
8cd5574bfb | ||
|
|
0ca543e104 | ||
|
|
e5f80fd822 | ||
|
|
fe8e061427 | ||
|
|
05ceb7da0d | ||
|
|
3cec2e68be | ||
|
|
12a141b03b | ||
|
|
562361345b | ||
|
|
432ffeabf5 | ||
|
|
be0870719f | ||
|
|
a66750b1ec | ||
|
|
c3a957c4dc | ||
|
|
f193d2b603 | ||
|
|
d2859f597c | ||
|
|
06a21efbdb | ||
|
|
13aa1685c9 | ||
|
|
55fcd3facf | ||
|
|
5d454308f2 | ||
|
|
a01fe171d0 | ||
|
|
207c4e99f0 | ||
|
|
0dfdc014e2 | ||
|
|
eec3f6b716 | ||
|
|
a483530a3c | ||
|
|
0d1068e460 | ||
|
|
fd0fc9a3cf | ||
|
|
40b881b7bb | ||
|
|
35cde381b7 | ||
|
|
6d8c5ca90b | ||
|
|
aa4d065fde | ||
|
|
0af930ae28 | ||
|
|
d29fabf084 | ||
|
|
2ee1d47ca8 | ||
|
|
8e27616934 | ||
|
|
a7b8c6061a | ||
|
|
aef5f3231c | ||
|
|
38ae2990a0 | ||
|
|
e6345d5b14 | ||
|
|
ba35344bd0 | ||
|
|
c33ca0b001 | ||
|
|
77935f1807 | ||
|
|
3234d88600 | ||
|
|
1e8cee85bb | ||
|
|
2352ec647d | ||
|
|
88d5e74825 | ||
|
|
d884620f9d | ||
|
|
e55363ec8a | ||
|
|
a297859c53 | ||
|
|
0a112d0aa9 | ||
|
|
6a342f2c64 | ||
|
|
d0bcd35bf5 | ||
|
|
f609510e3c | ||
|
|
9fc0ace817 | ||
|
|
89f3ba89da | ||
|
|
09e7616186 | ||
|
|
2342938cea | ||
|
|
047675b762 | ||
|
|
531e43eddf | ||
|
|
50a75c25e3 | ||
|
|
7a69bd45ac | ||
|
|
006de74706 | ||
|
|
952fdde7ae | ||
|
|
dfce2fc8fa | ||
|
|
50fd4beb3e | ||
|
|
0b8090994b | ||
|
|
37e6d2f4cb | ||
|
|
0c06bef82b | ||
|
|
53dbfd96de | ||
|
|
938f145150 | ||
|
|
930ba5a7f1 | ||
|
|
4107c336a8 | ||
|
|
d4db31ceaf | ||
|
|
30f68106db | ||
|
|
e56fc35820 | ||
|
|
b8fa19bf24 | ||
|
|
d9bd1468d0 | ||
|
|
b5dfab17da | ||
|
|
7d1c970a39 | ||
|
|
e6e1cfac31 | ||
|
|
38bca63747 | ||
|
|
606ce43c5b | ||
|
|
41413f9d7c | ||
|
|
ec1d7120aa | ||
|
|
bcce7091e7 | ||
|
|
e3cb2aa09e | ||
|
|
7e7d75fb4b | ||
|
|
e333e435bd | ||
|
|
afd2fab630 | ||
|
|
26493c0ca8 | ||
|
|
9402ca357c | ||
|
|
5572679c2d | ||
|
|
179d5d4b5c | ||
|
|
f169f1c45c | ||
|
|
e05f3b5362 | ||
|
|
a5b3a22517 | ||
|
|
82263dce4d | ||
|
|
1a012dda4d | ||
|
|
dfe02f41cb | ||
|
|
b582ad3589 | ||
|
|
2d7b6b23e3 | ||
|
|
7ddc8f4654 | ||
|
|
4bbd93f06f | ||
|
|
a76d259c0b | ||
|
|
a557ea1012 | ||
|
|
06f6a56043 | ||
|
|
ac9568f17b | ||
|
|
9c9145eb6e | ||
|
|
3cb551424b | ||
|
|
ee8e50a3f7 | ||
|
|
4bbf85c2ea | ||
|
|
6257f12c1f | ||
|
|
ea460af09c | ||
|
|
a0f9641ae1 | ||
|
|
9204e1dc12 | ||
|
|
396666fd48 | ||
|
|
74024ea0df | ||
|
|
79aac78581 | ||
|
|
04066d1c1a | ||
|
|
2a78b1b333 | ||
|
|
27162691b7 | ||
|
|
fe6090efff | ||
|
|
c7042f416e | ||
|
|
e56b649968 | ||
|
|
0f56a792c3 | ||
|
|
5bb914553f | ||
|
|
26d898e9bd | ||
|
|
eec3bb4478 | ||
|
|
fa3026611b | ||
|
|
a649156e0f | ||
|
|
e2e1146a0b | ||
|
|
baf4f76f53 | ||
|
|
85be067813 | ||
|
|
8e4f7b5383 | ||
|
|
a066efa995 | ||
|
|
f184021eca | ||
|
|
0841ffcb20 | ||
|
|
9cf1165a34 | ||
|
|
9aa4ded614 | ||
|
|
e7e764ccf0 | ||
|
|
5ba5b4960f | ||
|
|
bb57289385 | ||
|
|
0e28522b5c | ||
|
|
882e7eb15b | ||
|
|
3a0bb87dfe | ||
|
|
832973cef5 | ||
|
|
0cb9fc613c | ||
|
|
8931cf81f4 | ||
|
|
ecdd0f71b6 | ||
|
|
cb9344fab8 | ||
|
|
382e45ea30 | ||
|
|
fdaa13c374 | ||
|
|
fa2411cc08 | ||
|
|
a69b332f86 | ||
|
|
20dccf2b77 | ||
|
|
ffd10dd447 | ||
|
|
ce9cdb1bae | ||
|
|
979084b593 | ||
|
|
c562c733ce | ||
|
|
3c6f57cc27 | ||
|
|
c16d6e8cc0 | ||
|
|
91e2daf3bf | ||
|
|
7204c6dec8 | ||
|
|
3441cbb636 | ||
|
|
3070e798f3 | ||
|
|
754354991b | ||
|
|
86fe020558 | ||
|
|
40e7b983d6 | ||
|
|
f69ecd616d | ||
|
|
d683f62e01 | ||
|
|
205a091924 | ||
|
|
7b4d2e77ee | ||
|
|
2c9ba071bc | ||
|
|
de65f6bd75 | ||
|
|
186272a425 | ||
|
|
5fcbf25260 | ||
|
|
16eb5139f1 | ||
|
|
35be7ba887 | ||
|
|
55f0289a41 | ||
|
|
36dd6babe1 | ||
|
|
91b6818b62 | ||
|
|
9e9539144e | ||
|
|
feb9b3eb4e | ||
|
|
4b3fc4e82a | ||
|
|
ed7cc1b85a | ||
|
|
3711ddb95e | ||
|
|
0ee12e4115 | ||
|
|
87c9929df9 | ||
|
|
f5f14d3faa | ||
|
|
5be5ea9ff5 | ||
|
|
f1739bffd0 | ||
|
|
1efbe3f141 | ||
|
|
5ba1c36fbb | ||
|
|
c217134d97 | ||
|
|
fea866d910 | ||
|
|
3b3bc7b41e | ||
|
|
0612ea41f1 | ||
|
|
940ac8125c | ||
|
|
583ba8ade1 | ||
|
|
82feeed693 | ||
|
|
e7e41329ef | ||
|
|
63d990e49a | ||
|
|
8d5b753337 | ||
|
|
431fc39db0 | ||
|
|
7734a93217 | ||
|
|
806d7409da | ||
|
|
2e326cc006 | ||
|
|
e1ace5ea00 | ||
|
|
8e010c2095 | ||
|
|
6758b53bff | ||
|
|
f6822fb0f3 | ||
|
|
f0d39b2460 | ||
|
|
a3e69502b0 | ||
|
|
4645248106 | ||
|
|
80648584cf | ||
|
|
d4c2073cda | ||
|
|
fe7237e454 | ||
|
|
4da257e6fb | ||
|
|
ed617fef05 | ||
|
|
1c321022d0 | ||
|
|
ed4cae9354 | ||
|
|
2d390565c1 | ||
|
|
5e72d88940 | ||
|
|
213f2ecec5 | ||
|
|
23b3778383 | ||
|
|
b9776af9b2 | ||
|
|
25794c6003 | ||
|
|
7fa561699e | ||
|
|
127ba25dab | ||
|
|
6a1742ea90 | ||
|
|
e013d27b54 | ||
|
|
37e175b4a4 | ||
|
|
982b901077 | ||
|
|
f0797bb8f2 | ||
|
|
18f8c425dc | ||
|
|
194239f8c6 | ||
|
|
401e019b05 | ||
|
|
0c74e8274e | ||
|
|
cf5d054eb4 | ||
|
|
d9bf693fb8 | ||
|
|
cf38f81c39 | ||
|
|
2eeb7b411e | ||
|
|
ecc7ce022b | ||
|
|
a6b182b065 | ||
|
|
e30f01fe93 | ||
|
|
ac84d2cdbb | ||
|
|
742ea87d41 | ||
|
|
fa546965ec | ||
|
|
f68657d201 | ||
|
|
23bc92c66f | ||
|
|
c2956484ca | ||
|
|
3f593c62ae | ||
|
|
5b8594979b | ||
|
|
7571305cd1 | ||
|
|
0631f9750e | ||
|
|
b51d6ab5cb | ||
|
|
93c4f35615 | ||
|
|
510b2d68c0 | ||
|
|
d6d8d2e494 | ||
|
|
9e0c1c81dd | ||
|
|
ac5031198c | ||
|
|
11d5426781 | ||
|
|
afc52eea70 | ||
|
|
6248d964fb | ||
|
|
addb5f1675 | ||
|
|
ed22d76ef0 | ||
|
|
a73e9b2dac | ||
|
|
5dfa1cf07c | ||
|
|
4164285a13 | ||
|
|
1f53bd5bfa | ||
|
|
a32d294df8 | ||
|
|
dbaa6d7341 | ||
|
|
23a9e7261d | ||
|
|
5a9919b433 | ||
|
|
1d24b70996 | ||
|
|
f18d1c1ffc | ||
|
|
1ab637cea4 | ||
|
|
573b8a45df | ||
|
|
0bf00a4247 | ||
|
|
43d9536a05 | ||
|
|
893ec1b362 | ||
|
|
1aa966c2ad | ||
|
|
121f917884 | ||
|
|
17a9481522 | ||
|
|
06e8bf8933 | ||
|
|
f7af61f80c | ||
|
|
25b34bb472 | ||
|
|
22f85c1b6f | ||
|
|
dd8d9714cb | ||
|
|
982f07168d | ||
|
|
218d89b005 | ||
|
|
bada67bb39 | ||
|
|
db03248e72 | ||
|
|
47c092aac0 | ||
|
|
4e6b4c43cd | ||
|
|
2c6f379d69 | ||
|
|
bf79ae1190 | ||
|
|
96fda95624 | ||
|
|
b71bfccb46 | ||
|
|
a4dc015c0d | ||
|
|
74b359fe62 | ||
|
|
197a74ded6 | ||
|
|
9aa4fcc6cd | ||
|
|
36915416dd | ||
|
|
411dcfd0b1 | ||
|
|
35564d5f2c | ||
|
|
bba20b3fe7 | ||
|
|
55e516da6f | ||
|
|
2c3dcc4370 | ||
|
|
1fb6dd3b46 | ||
|
|
ed7eec5969 | ||
|
|
3f7236d268 | ||
|
|
766ce80297 | ||
|
|
61af6f3c59 | ||
|
|
464f7655d6 | ||
|
|
d1674d86ac | ||
|
|
36e5b4b949 | ||
|
|
98a7cff0c5 | ||
|
|
34ed39c873 | ||
|
|
2bbba15068 | ||
|
|
a478db77c2 | ||
|
|
d829e425df | ||
|
|
f5b2b82295 | ||
|
|
e3876ddbd4 | ||
|
|
b8556e37bb | ||
|
|
bbfaa0bb30 | ||
|
|
5606087776 | ||
|
|
b919548b27 | ||
|
|
ed7414e1c7 | ||
|
|
fa1237fc08 | ||
|
|
1dc99aaade | ||
|
|
3001f02b55 | ||
|
|
39442dbdf0 | ||
|
|
6c4ca4e40c | ||
|
|
bdca85e15a | ||
|
|
1ccc77ac7f | ||
|
|
b99ed6e7ae | ||
|
|
6ee6410948 | ||
|
|
d187dac602 | ||
|
|
2b4c365161 | ||
|
|
70ea7ade7f | ||
|
|
56a94d2693 | ||
|
|
14a4b05950 | ||
|
|
14d525c1a1 | ||
|
|
5b7ae06bde | ||
|
|
e67f5e62ae | ||
|
|
bae9598190 | ||
|
|
9acda6f939 | ||
|
|
7b52f34c38 | ||
|
|
9874a9daa7 | ||
|
|
aecf39d459 | ||
|
|
9eacc638f3 | ||
|
|
0c34ec1f50 | ||
|
|
a778f9c5f4 | ||
|
|
d7b079c4fb | ||
|
|
cfe60991a5 | ||
|
|
2acf634340 | ||
|
|
09f1008186 | ||
|
|
baf9ae9038 | ||
|
|
cdcb428f26 | ||
|
|
36bc198b9a | ||
|
|
428a9c4228 | ||
|
|
666b18b851 | ||
|
|
f180ac21c8 | ||
|
|
0b1be96a13 | ||
|
|
1f444a21d2 | ||
|
|
4eeebf0221 | ||
|
|
b2a1e4b83e | ||
|
|
08938402c9 | ||
|
|
4c569c8c8c | ||
|
|
03a2105d39 | ||
|
|
d2870ac574 | ||
|
|
8497b7e1dc | ||
|
|
0e2b6b010d | ||
|
|
84263109b1 | ||
|
|
162bcca5df | ||
|
|
2fb3c9da57 | ||
|
|
83ee4962b1 | ||
|
|
e03f74a357 | ||
|
|
8c1b25b3aa | ||
|
|
315dfd3244 | ||
|
|
02ca830f42 | ||
|
|
18da5b654e | ||
|
|
97a1e218c5 | ||
|
|
f9e4cb1a7f | ||
|
|
60760a892a | ||
|
|
596ffa959a | ||
|
|
cd3b817f13 | ||
|
|
19de40fed4 | ||
|
|
e78e12b15b | ||
|
|
5b4377842b | ||
|
|
16ee9f48a4 | ||
|
|
8e3679e4e5 | ||
|
|
7089304046 | ||
|
|
5ab3708e9f | ||
|
|
aa939d7a6a | ||
|
|
a63bbcd7b1 | ||
|
|
339f9c485a | ||
|
|
4905f6d3f2 | ||
|
|
cb8cc1c8f0 | ||
|
|
c6b54532e9 | ||
|
|
3936134823 | ||
|
|
55ca05bab6 | ||
|
|
7305a5c5dd | ||
|
|
b145377c39 | ||
|
|
08f2a74be7 | ||
|
|
82fb10fe26 | ||
|
|
34f4d654d3 | ||
|
|
091395df33 | ||
|
|
a38be94064 | ||
|
|
cf1c02d097 | ||
|
|
3dbe2ac704 | ||
|
|
35d67a3fab | ||
|
|
f60fada438 | ||
|
|
c419dcde1b | ||
|
|
ebfe4f13c6 | ||
|
|
8f9b59cbe6 | ||
|
|
63f26708fd | ||
|
|
07b09a66d3 | ||
|
|
4cc61361f3 | ||
|
|
5535e7a6b7 | ||
|
|
06b592683c | ||
|
|
0b5e5dcbb5 | ||
|
|
b1695c6018 | ||
|
|
48ee8e8257 | ||
|
|
50bf031d49 | ||
|
|
bf05198d53 | ||
|
|
f9a37e9dd4 | ||
|
|
cf9e59954b | ||
|
|
7cce297318 | ||
|
|
1f94716fce | ||
|
|
863893057e | ||
|
|
b83b1950c0 | ||
|
|
d2774b0da9 | ||
|
|
cd7287f0de | ||
|
|
cff858bad5 | ||
|
|
7669a3ce67 | ||
|
|
3ad387f20c | ||
|
|
af6d356468 | ||
|
|
768f1dffdd | ||
|
|
2a25c81834 | ||
|
|
24b8941f60 | ||
|
|
841a186346 | ||
|
|
ec04bfda9d | ||
|
|
8cd85aaa29 | ||
|
|
01bc0b522d | ||
|
|
48e777ad7b | ||
|
|
0955c30852 | ||
|
|
02ec7156c1 | ||
|
|
c7c8583946 | ||
|
|
08b0a1629d | ||
|
|
02b439af48 | ||
|
|
a8cd2e84e0 | ||
|
|
a25226eb13 | ||
|
|
91f9c4d167 | ||
|
|
416b0d336f | ||
|
|
ca4738d5e2 | ||
|
|
4281f45f06 | ||
|
|
08be7ff2d2 | ||
|
|
47030cf6ca | ||
|
|
a5e5ad75fe | ||
|
|
0ce1c78012 | ||
|
|
a250aea2f9 | ||
|
|
1fa4e34cec | ||
|
|
1290a02a4e | ||
|
|
10b69883bd | ||
|
|
fa8e95a289 | ||
|
|
6373eb05a7 | ||
|
|
3ec4b1787e | ||
|
|
74129bfc86 | ||
|
|
595f98da78 | ||
|
|
2649b69e95 | ||
|
|
d20dc21e2a | ||
|
|
7ac0816aa9 | ||
|
|
154b6b047d | ||
|
|
9a233df7e2 | ||
|
|
7b2cb47727 | ||
|
|
d683e8aa0b | ||
|
|
5ec3d2dcc2 | ||
|
|
edcf252603 | ||
|
|
024b03791a | ||
|
|
230ef819c2 | ||
|
|
5bdf48695a | ||
|
|
78fea38b12 | ||
|
|
4510f4af81 | ||
|
|
a3480e8a2b | ||
|
|
5396667ad5 | ||
|
|
f0f61cc7ad | ||
|
|
b237fd1ae9 | ||
|
|
2b67afb8a3 | ||
|
|
20fa8d56f5 | ||
|
|
ed1e3c06a2 | ||
|
|
fdbe7eecf9 | ||
|
|
16ad212919 | ||
|
|
5c0cce3d6c | ||
|
|
ec0a9d09dd | ||
|
|
ea054ccced | ||
|
|
cbbe23aa46 | ||
|
|
990b0daae2 | ||
|
|
a16316e916 | ||
|
|
14952546a4 | ||
|
|
020b1a78d9 | ||
|
|
6b70e02bf5 | ||
|
|
31969a2756 | ||
|
|
53ebcd80d8 | ||
|
|
d714301180 | ||
|
|
002d619b11 | ||
|
|
55bb6bc318 | ||
|
|
05066e5279 | ||
|
|
131993634f | ||
|
|
40f6b17eda | ||
|
|
7a8ebc4dc7 | ||
|
|
a9346f5c82 | ||
|
|
3104ce1dd2 | ||
|
|
d213f2b25d | ||
|
|
813089047f | ||
|
|
0ed5a9842a | ||
|
|
f037cd0740 | ||
|
|
c9faf3d345 | ||
|
|
db8758060a | ||
|
|
c8ac0919a9 | ||
|
|
a2432e125a | ||
|
|
e498e2fd83 | ||
|
|
c65121a370 | ||
|
|
fdc935718d | ||
|
|
18d3b64bfb | ||
|
|
ddc63ba9b2 | ||
|
|
5576b88604 | ||
|
|
852be4a465 | ||
|
|
fb3c6029d0 | ||
|
|
9acd9ae894 | ||
|
|
d478addd2b | ||
|
|
6463a7c48a | ||
|
|
7434465334 | ||
|
|
f7b705959e | ||
|
|
1648f716f4 | ||
|
|
108b08fadb | ||
|
|
204566cd14 | ||
|
|
a3b4192e15 | ||
|
|
dfa26d0882 | ||
|
|
2683a07955 | ||
|
|
62bfc5fd8c | ||
|
|
f666282b4e | ||
|
|
77aefdf6a6 | ||
|
|
60ce1aaf01 | ||
|
|
496e2f3eb3 | ||
|
|
93bae76f49 | ||
|
|
7d17aa83eb | ||
|
|
7835e45ded | ||
|
|
0cf209002c | ||
|
|
57f2260301 | ||
|
|
76445ce73c | ||
|
|
99a5b1541e | ||
|
|
d432e987a0 | ||
|
|
71457375e4 | ||
|
|
8dc311c2d2 | ||
|
|
faa5e57913 | ||
|
|
4396784dbd | ||
|
|
81b714c4f3 | ||
|
|
cb739c62a9 | ||
|
|
58cfc6ba39 | ||
|
|
fafc315dac | ||
|
|
ffcd64792d | ||
|
|
8c24b22b2d | ||
|
|
6fe9bdc3db | ||
|
|
9d8b243e39 | ||
|
|
9884e19b7f | ||
|
|
5304a343e4 | ||
|
|
38e8f06def | ||
|
|
b93b85bcd5 | ||
|
|
587360662c | ||
|
|
95431aa7cf | ||
|
|
7de092df28 | ||
|
|
03b57c2655 | ||
|
|
a1c835b993 | ||
|
|
4ada2f52da | ||
|
|
9bb1a8207a | ||
|
|
a9e76295ff | ||
|
|
4e3364ddd3 | ||
|
|
6e5ae45c1a | ||
|
|
11c9376a50 | ||
|
|
b5f2a7b1ce | ||
|
|
3df7c8e89c | ||
|
|
0edbe29d38 | ||
|
|
8df5a76203 | ||
|
|
32c4a09deb | ||
|
|
ee6907f678 | ||
|
|
03640029db | ||
|
|
7a65ed9082 | ||
|
|
b10f818106 | ||
|
|
caad70dd6f | ||
|
|
6fe4f23074 | ||
|
|
fd8fb73492 | ||
|
|
7c8ed4d2e1 | ||
|
|
08fada7de0 | ||
|
|
163975d82a | ||
|
|
dda521e2bc | ||
|
|
e7dc90dadc | ||
|
|
8375741489 | ||
|
|
042b14653e | ||
|
|
c7f6a91da5 | ||
|
|
c26260e38d | ||
|
|
ac04d79ad2 | ||
|
|
052b89e8d6 | ||
|
|
c395da1c54 | ||
|
|
16b0564d49 | ||
|
|
d21207cc79 | ||
|
|
c4b69a1077 | ||
|
|
c85dd8881b | ||
|
|
8eae0a33b3 | ||
|
|
6356fa09e9 | ||
|
|
641e7c2b05 | ||
|
|
c60d1d0877 | ||
|
|
c45dd19d47 | ||
|
|
51f871f43b | ||
|
|
89a4720575 | ||
|
|
081e447a1b | ||
|
|
da0ee683a3 | ||
|
|
c6e5e2c0a0 | ||
|
|
fb3c166f80 | ||
|
|
39643aaf94 | ||
|
|
50bdf39ba5 | ||
|
|
1e413d4121 | ||
|
|
fcda712238 | ||
|
|
d4ff48a07e | ||
|
|
21f25545a8 | ||
|
|
37f975bf82 | ||
|
|
58558e319c | ||
|
|
48f6a08352 | ||
|
|
01e9f42c10 | ||
|
|
8c3967886c | ||
|
|
8a4fb3b445 | ||
|
|
04c9b62660 | ||
|
|
4790349f79 | ||
|
|
3e6005a1e7 | ||
|
|
9e3565027a | ||
|
|
6d8e6afbf2 | ||
|
|
5b5f431717 | ||
|
|
796e2556b6 | ||
|
|
8090737d56 | ||
|
|
d39a0c7035 | ||
|
|
de696ba5a4 | ||
|
|
cefa2d9100 | ||
|
|
9f07fd035c | ||
|
|
7ead7231c9 | ||
|
|
b0a3a5ce62 | ||
|
|
64c2084c8a | ||
|
|
4c355239ad | ||
|
|
79169ea315 | ||
|
|
4db866d5c3 | ||
|
|
83a5bfbfb1 | ||
|
|
c9651d9b2b | ||
|
|
8c99dd0f2c | ||
|
|
25351379ab | ||
|
|
ba67c5d977 | ||
|
|
7f8d77d84e | ||
|
|
91a88ee80f | ||
|
|
206fe8527f | ||
|
|
bed5b1731b | ||
|
|
c36b83fdea | ||
|
|
fede4a8102 | ||
|
|
29c0baa151 | ||
|
|
5d04d9c6cd | ||
|
|
2415b3e0bb | ||
|
|
d832547ba2 | ||
|
|
f05e32df4a | ||
|
|
fe118f6347 | ||
|
|
9554b92c86 | ||
|
|
669ada024e | ||
|
|
e1e16bf85a | ||
|
|
35516a6fc2 | ||
|
|
51ecb10b01 | ||
|
|
10ce09fe0c | ||
|
|
0702d4185a | ||
|
|
afe57e99d1 | ||
|
|
e921486008 | ||
|
|
70d59ca1cd | ||
|
|
2ccaa81aab | ||
|
|
4db2eb91e9 | ||
|
|
a4daff2d83 | ||
|
|
cc884d57ca | ||
|
|
0c8a872c37 | ||
|
|
663ac74610 | ||
|
|
f04aebf1e0 | ||
|
|
bcf3b48e98 | ||
|
|
e7cc9455c9 | ||
|
|
e909535684 | ||
|
|
cea54cb865 | ||
|
|
3bc2e9bdb1 | ||
|
|
159960c72e | ||
|
|
9d35710419 | ||
|
|
bdcf448605 | ||
|
|
72eb180028 | ||
|
|
fe427bfe7a | ||
|
|
348f5c7086 | ||
|
|
e09b877146 | ||
|
|
aeb7ba4731 | ||
|
|
421cd1c3ca | ||
|
|
5e8729bd6f | ||
|
|
76d057a418 | ||
|
|
ea9864bbd5 | ||
|
|
3d9b4d5c05 | ||
|
|
d06bfe0ac3 | ||
|
|
6540294a81 | ||
|
|
02a7640047 | ||
|
|
f4c5a8a42e | ||
|
|
0fdbace9b2 | ||
|
|
cab6014154 | ||
|
|
000ce0ee52 | ||
|
|
d720a2011c | ||
|
|
c91055271b | ||
|
|
1478acd551 | ||
|
|
b3404c07d7 | ||
|
|
94767dc0e7 | ||
|
|
f957acfe11 | ||
|
|
7fc43ccc3a | ||
|
|
dc5b9508d9 | ||
|
|
ef056d8ed1 | ||
|
|
6a63671ef5 | ||
|
|
d3867766af | ||
|
|
1ae4b9571b | ||
|
|
3fd1cec20e | ||
|
|
28b16eea19 | ||
|
|
37f32a9008 | ||
|
|
8415252322 | ||
|
|
b3984c9580 | ||
|
|
6616995804 | ||
|
|
de7d37e1fc | ||
|
|
3cb8a528a5 | ||
|
|
8bb01e1a0e | ||
|
|
0416d7ed80 | ||
|
|
c02c04f08a | ||
|
|
efb952ef12 | ||
|
|
04b145d795 | ||
|
|
1830fa1d84 | ||
|
|
a2b300b863 | ||
|
|
2d8297a562 | ||
|
|
deb7728b46 | ||
|
|
bbd6d86af4 | ||
|
|
b6361b8cad | ||
|
|
04fdae124f | ||
|
|
29df7093a8 | ||
|
|
18212a21ab | ||
|
|
e13d28bfe6 | ||
|
|
667204ac7c | ||
|
|
76ea83e791 | ||
|
|
55daf7385d | ||
|
|
0e66e4b7e9 | ||
|
|
fe485db9b4 | ||
|
|
b3dc4879fd | ||
|
|
25ce3738b9 | ||
|
|
22b8ac8470 | ||
|
|
6a4e128ef8 | ||
|
|
66d68c8a98 | ||
|
|
76867af090 | ||
|
|
5d02248a0b | ||
|
|
eb6c213f2a | ||
|
|
efdac9afdd | ||
|
|
c6bfff2a04 | ||
|
|
49066ea329 | ||
|
|
25ceab07f7 | ||
|
|
6e97393740 | ||
|
|
ff3d54d26f | ||
|
|
25c9cf3976 | ||
|
|
5f2fb08cfd | ||
|
|
60bc56848f | ||
|
|
600ca381fd | ||
|
|
da3f5dd4ec | ||
|
|
b59911b0c1 | ||
|
|
8131528305 | ||
|
|
6a71e7dd4b | ||
|
|
a63b2f70b2 | ||
|
|
3f88964527 | ||
|
|
e18dc70af0 | ||
|
|
0c6daad215 | ||
|
|
8c35c80bb7 | ||
|
|
d42ba3bae4 | ||
|
|
8c337b25df | ||
|
|
8d1c08bd03 | ||
|
|
5599a6f24f | ||
|
|
ef9a610dd1 | ||
|
|
31c3b5397b | ||
|
|
e11d64b5c7 | ||
|
|
9d91a2bc23 | ||
|
|
8c41f21d3c | ||
|
|
5c7a75b3e4 | ||
|
|
0804fbd095 | ||
|
|
0f78fa9c43 | ||
|
|
d30e970eb8 |
No files matched your search
@@ -0,0 +1,49 @@
|
||||
name: Build all tarballs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-tarballs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { target: x86_64, os: ubuntu-24.04 }
|
||||
- { target: aarch64, os: ubuntu-24.04-arm }
|
||||
- { target: arm, os: ubuntu-24.04-arm }
|
||||
- { target: riscv64, os: ubuntu-24.04 }
|
||||
- { target: ppc64le, os: ubuntu-24.04 }
|
||||
- { target: s390x, os: ubuntu-24.04 }
|
||||
- { target: loongarch64, os: ubuntu-24.04 }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
compression-level: 0
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Build native tarballs
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-tarballs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { target: x86_64, os: ubuntu-24.04 }
|
||||
- { target: aarch64, os: ubuntu-24.04-arm }
|
||||
- { target: arm, os: ubuntu-24.04-arm }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
compression-level: 0
|
||||
+103
-89
@@ -1,142 +1,156 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
jobs:
|
||||
build-clang:
|
||||
build-sanitizers:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
# Disable PCH for the default configuration. This prevents relying on implicit includes.
|
||||
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
|
||||
- ''
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: rui314/setup-mold@staging
|
||||
- name: install-build-deps
|
||||
run: sudo ./install-build-deps.sh update
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- run: sudo ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
sudo apt-get install -y clang++-12
|
||||
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++-12 ${{ matrix.target }} ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-gcc:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
build-multi-archs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
# Install cross toolchains
|
||||
dpkg --add-architecture i386
|
||||
./install-build-deps.sh update
|
||||
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}-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
|
||||
|
||||
# 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/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
|
||||
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,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
|
||||
- name: build and test
|
||||
sudo ./install-build-deps.sh
|
||||
sudo ./install-cross-tools.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DMOLD_ENABLE_QEMU_TESTS=On -DMOLD_ENABLE_QEMU_TESTS_RV32=On -DMOLD_ENABLE_QEMU_TESTS_LOONGARCH=On ..
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-gcc
|
||||
name: test-results-multi-archs
|
||||
path: |
|
||||
build/out
|
||||
build/Testing
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-11
|
||||
build-distros:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
# Disable PCH for the default configuration. This prevents relying on implicit includes.
|
||||
- '-DCMAKE_DISABLE_PRECOMPILE_HEADERS=On'
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
distro:
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
|
||||
- fedora
|
||||
- gentoo/stage3
|
||||
- opensuse/tumbleweed
|
||||
- ubuntu:22.04
|
||||
- ubuntu:25.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
container: ${{ matrix.distro }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: build and test
|
||||
- uses: actions/checkout@v4
|
||||
- run: ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ${{ matrix.target }} ..
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ matrix.distro }}
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
|
||||
|
||||
build-msys:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
|
||||
- name: build
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
|
||||
cmake --build . -j $(nproc)
|
||||
|
||||
build-freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
run: |
|
||||
./install-build-deps.sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest --output-on-failure -j$(nproc)
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
apt-get update
|
||||
apt-get install -y wget xz-utils
|
||||
|
||||
# Install a 32-bit RISC-V toolchain
|
||||
mkdir /rv32
|
||||
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
|
||||
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
|
||||
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install a LoongArch toolchain
|
||||
mkdir /larch
|
||||
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
|
||||
|
||||
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
|
||||
ln -sf /larch/target /usr/loongarch64-linux-gnu
|
||||
|
||||
for i in gcc g++ objdump objcopy strip; do
|
||||
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
|
||||
done
|
||||
|
||||
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
|
||||
chmod 755 /usr/local/bin/qemu-loongarch64
|
||||
|
||||
# Install ARM32 big-endian toolchain
|
||||
mkdir /armeb
|
||||
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
|
||||
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
|
||||
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
|
||||
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
|
||||
done
|
||||
|
||||
# Install ARM64 big-endian toolchain
|
||||
mkdir /aarch64be
|
||||
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
|
||||
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
|
||||
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
|
||||
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install SH4 big-endian toolchain
|
||||
mkdir /sh4aeb
|
||||
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
|
||||
|
||||
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
|
||||
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
|
||||
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
|
||||
chmod 755 /usr/bin/sh4aeb-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /sh4aeb/bin/sh4aeb-linux-$i /usr/bin/sh4aeb-linux-gnu-$i
|
||||
done
|
||||
|
||||
# Install Intel SDE CPU emulator for CET-related tests
|
||||
mkdir /sde
|
||||
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
|
||||
ln -s /sde/sde64 /usr/bin
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Build & attach tarballs on tag push
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v[0-9]*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { target: x86_64, os: ubuntu-24.04 }
|
||||
- { target: aarch64, os: ubuntu-24.04-arm }
|
||||
- { target: arm, os: ubuntu-24.04-arm }
|
||||
- { target: riscv64, os: ubuntu-24.04 }
|
||||
- { target: ppc64le, os: ubuntu-24.04 }
|
||||
- { target: s390x, os: ubuntu-24.04 }
|
||||
- { target: loongarch64, os: ubuntu-24.04 }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
pattern: '*'
|
||||
merge-multiple: true
|
||||
|
||||
- name: Ensure release exists (create if missing)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
if gh release view "$TAG" >& /dev/null; then
|
||||
echo "Existing release found for $TAG"
|
||||
else
|
||||
echo "Creating draft release for $TAG"
|
||||
gh release create "$TAG" --draft \
|
||||
--title "$TAG" \
|
||||
--notes "Automated draft – fill the changelog later."
|
||||
fi
|
||||
|
||||
- name: Upload tarballs
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload "$TAG" dist/* --clobber
|
||||
gh release view "$TAG" --json assets --jq '.assets[].name'
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Update manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/mold.md'
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-manpage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install ronn
|
||||
run: sudo apt-get update && sudo apt-get install -y ronn
|
||||
|
||||
- name: Generate mold.1 from mold.md
|
||||
run: ronn --roff docs/mold.md
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name 'Rui Ueyama'
|
||||
git config --global user.email '[email protected]'
|
||||
|
||||
- name: Commit and push if mold.1 is updated
|
||||
run: |
|
||||
git add docs/mold.1
|
||||
git diff --staged --quiet || (git commit -m "Update mold.1 (automated commit)" && git push)
|
||||
@@ -7,3 +7,5 @@ core
|
||||
gentoo
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
/dist
|
||||
+133
-156
@@ -1,31 +1,31 @@
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true while `OFF` or `0` means false.
|
||||
# means true, while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies a C compiler name to use. The default value is `cc`.
|
||||
# Specifies the C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies a C++ compiler name to use. The default value is `c++`.
|
||||
# Specifies the C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies an install target directory. The default value is `/usr/local`.
|
||||
# Specifies the install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# Specifies a build type. The default is `Release` which is the right
|
||||
# Specifies the build type. The default is `Release`, which is the right
|
||||
# option unless you are debugging mold.
|
||||
#
|
||||
# An example of a cmake command line is shown below:
|
||||
#
|
||||
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
|
||||
#
|
||||
# where `..` refers this directory.
|
||||
# where `..` refers to this directory.
|
||||
#
|
||||
# With cmake, you may run `cmake --install .` instead of `make install` to
|
||||
# install build artifacts to system directories. If you want to install
|
||||
@@ -39,31 +39,47 @@
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind it is build reproducibility. We want to guarantees that all builds
|
||||
# of the mold linker of the same version will have the exactly same set of
|
||||
# behind this is build reproducibility. We want to guarantee that all builds
|
||||
# of the mold linker of the same version will have the exact same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.3.2)
|
||||
project(mold VERSION 2.40.2)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
|
||||
if(MINGW)
|
||||
target_link_libraries(mold PRIVATE dl)
|
||||
else()
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
add_link_options(-fuse-ld=mold -Wl,--gdb-index)
|
||||
endif()
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Deb")
|
||||
target_link_options(mold PRIVATE -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
@@ -106,7 +122,7 @@ endif()
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and libcrypto" OFF)
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
@@ -117,6 +133,7 @@ find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
set(ZLIB_BUILD_EXAMPLES OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
target_include_directories(zlibstatic INTERFACE third-party/zlib
|
||||
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
|
||||
@@ -129,12 +146,17 @@ 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)
|
||||
function(mold_add_blake3)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endfunction()
|
||||
|
||||
mold_add_blake3()
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If libzstd.so is not found, we compile a
|
||||
# Find zstd compression library. If zstd.h is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
@@ -142,13 +164,13 @@ check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_CONTRIB OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(libzstd_static PRIVATE
|
||||
ZSTD_BUILD_STATIC=1
|
||||
ZSTD_BUILD_SHARED=0
|
||||
ZSTD_BUILD_PROGRAMS=0
|
||||
ZSTD_MULTITHREAD_SUPPORT=0
|
||||
ZSTD_BUILD_TESTS=0)
|
||||
target_include_directories(mold PUBLIC third-party/zstd/lib)
|
||||
target_link_libraries(mold PRIVATE libzstd_static)
|
||||
endif()
|
||||
@@ -162,7 +184,7 @@ endif()
|
||||
# be stable on 32-bit targets.
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD" OFF)
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
|
||||
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
@@ -175,7 +197,6 @@ if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc REQUIRED)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
target_compile_definitions(mold PRIVATE USE_SYSTEM_MIMALLOC)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
@@ -196,7 +217,7 @@ endif()
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB)
|
||||
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
@@ -212,11 +233,6 @@ else()
|
||||
mold_add_tbb()
|
||||
endif()
|
||||
|
||||
# Check if this is a commercial version of mold (a.k.a. "sold")
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/LICENSE.md")
|
||||
set(MOLD_IS_SOLD ON)
|
||||
endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
@@ -245,7 +261,7 @@ if(NOT APPLE AND NOT WIN32)
|
||||
# Remove the default `lib` prefix
|
||||
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
|
||||
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
|
||||
target_sources(mold-wrapper PRIVATE elf/mold-wrapper.c)
|
||||
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
@@ -270,20 +286,23 @@ if(NOT APPLE AND NOT MSVC)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
|
||||
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
|
||||
|
||||
# Create a .cc file containing the current git hash for `mold --version`.
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
|
||||
-P ${CMAKE_SOURCE_DIR}/common/update-git-hash.cmake
|
||||
DEPENDS common/update-git-hash.cmake
|
||||
BYPRODUCTS git-hash.cc
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
|
||||
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
|
||||
DEPENDS lib/update-git-hash.cmake
|
||||
BYPRODUCTS mold-git-hash.h
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# Create config.h file
|
||||
configure_file(common/config.h.in config.h)
|
||||
configure_file(lib/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
@@ -295,44 +314,53 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
list(APPEND MOLD_ELF_TARGETS
|
||||
X86_64 I386 ARM64 ARM32 RV32LE RV32BE RV64LE RV64BE PPC32 PPC64V1 PPC64V2
|
||||
S390X SPARC64 M68K SH4 ALPHA LOONGARCH32 LOONGARCH64)
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64)
|
||||
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
elf/arch-loongarch.cc
|
||||
elf/arch-riscv.cc
|
||||
elf/cmdline.cc
|
||||
elf/gc-sections.cc
|
||||
elf/gdb-index.cc
|
||||
elf/icf.cc
|
||||
elf/input-files.cc
|
||||
elf/input-sections.cc
|
||||
elf/jobs.cc
|
||||
elf/linker-script.cc
|
||||
elf/lto.cc
|
||||
elf/main.cc
|
||||
elf/mapfile.cc
|
||||
elf/output-chunks.cc
|
||||
elf/passes.cc
|
||||
elf/relocatable.cc
|
||||
elf/subprocess.cc
|
||||
elf/thunks.cc
|
||||
elf/tls.cc
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-riscv.cc
|
||||
src/arch-sh4.cc
|
||||
src/archive-file.cc
|
||||
src/cmdline.cc
|
||||
src/error.cc
|
||||
src/filetype.cc
|
||||
src/gc-sections.cc
|
||||
src/gdb-index.cc
|
||||
src/icf.cc
|
||||
src/input-files.cc
|
||||
src/input-sections.cc
|
||||
src/linker-script.cc
|
||||
src/main.cc
|
||||
src/mapfile.cc
|
||||
src/output-chunks.cc
|
||||
src/passes.cc
|
||||
src/relocatable.cc
|
||||
src/shrink-sections.cc
|
||||
src/thunks.cc
|
||||
src/tls.cc
|
||||
)
|
||||
|
||||
list(APPEND MOLD_MACHO_TARGETS X86_64 ARM64)
|
||||
if(WIN32 AND NOT MINGW)
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-win32.cc)
|
||||
else()
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-unix.cc)
|
||||
endif()
|
||||
|
||||
list(APPEND MOLD_MACHO_TEMPLATE_FILES
|
||||
macho/cmdline.cc
|
||||
macho/dead-strip.cc
|
||||
macho/input-files.cc
|
||||
macho/input-sections.cc
|
||||
macho/lto.cc
|
||||
macho/main.cc
|
||||
macho/mapfile.cc
|
||||
macho/output-chunks.cc
|
||||
macho/tapi.cc
|
||||
if(WIN32)
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
src/output-file-win32.cc
|
||||
src/subprocess-win32.cc
|
||||
)
|
||||
else()
|
||||
list(APPEND MOLD_ELF_TEMPLATE_FILES
|
||||
src/output-file-unix.cc
|
||||
src/subprocess-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
@@ -351,72 +379,49 @@ foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
foreach (SOURCE IN LISTS MOLD_MACHO_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_MACHO_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
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-i386.cc
|
||||
elf/arch-m68k.cc
|
||||
elf/arch-ppc32.cc
|
||||
elf/arch-ppc64v1.cc
|
||||
elf/arch-ppc64v2.cc
|
||||
elf/arch-s390x.cc
|
||||
elf/arch-sh4.cc
|
||||
elf/arch-sparc64.cc
|
||||
elf/arch-x86-64.cc
|
||||
elf/elf.cc
|
||||
git-hash.cc
|
||||
lib/aho-corasick.cc
|
||||
lib/crc32.cc
|
||||
lib/demangle.cc
|
||||
lib/filepath.cc
|
||||
lib/glob.cc
|
||||
lib/hyperloglog.cc
|
||||
lib/perf.cc
|
||||
lib/random.cc
|
||||
lib/tar.cc
|
||||
src/arch-i386.cc
|
||||
src/arch-m68k.cc
|
||||
src/arch-ppc32.cc
|
||||
src/arch-ppc64v1.cc
|
||||
src/arch-ppc64v2.cc
|
||||
src/arch-s390x.cc
|
||||
src/arch-sparc64.cc
|
||||
src/arch-x86-64.cc
|
||||
src/compress.cc
|
||||
src/elf.cc
|
||||
src/entry.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
macho/arch-arm64.cc
|
||||
macho/arch-x86-64.cc
|
||||
macho/yaml.cc
|
||||
src/jobs-win32.cc
|
||||
src/mapped-file-win32.cc
|
||||
src/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-unix.cc
|
||||
src/mapped-file-unix.cc
|
||||
src/signal-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add frequently included header files for pre-compiling.
|
||||
# target_precompile_headers is supported by CMake 3.16.0 or newer.
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
|
||||
if(MOLD_IS_SOLD)
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/macho/mold.h>")
|
||||
else()
|
||||
target_precompile_headers(mold PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/elf/mold.h>")
|
||||
endif()
|
||||
|
||||
# ccache needs this flag along with `sloppiness = pch_defines,time_macros`
|
||||
# to enable caching
|
||||
target_compile_options(mold PRIVATE -fpch-preprocess)
|
||||
endif()
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld and ld64 symlinks required for testing
|
||||
# Create the ld symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
@@ -424,36 +429,17 @@ if(BUILD_TESTING)
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld64
|
||||
BYPRODUCTS ld64
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(${APPLE})
|
||||
if(MOLD_IS_SOLD)
|
||||
add_subdirectory(test/macho)
|
||||
endif()
|
||||
elseif(${UNIX})
|
||||
add_subdirectory(test/elf)
|
||||
if(${UNIX})
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
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()
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
@@ -473,13 +459,4 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
${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)
|
||||
|
||||
if(MOLD_IS_SOLD)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.mold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld.sold)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold
|
||||
${CMAKE_INSTALL_BINDIR}/ld64.sold)
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,39 +1,30 @@
|
||||
# mold: A Modern 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
|
||||
[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 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 when linking
|
||||
final debuginfo-enabled executables for major large programs on a simulated
|
||||
8-core, 16-thread machine.
|
||||
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
|
||||
mold when linking final debuginfo-enabled executables for major large
|
||||
programs on a simulated 16-core, 32-thread machine.
|
||||
|
||||

|
||||

|
||||
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|----------|----------|--------
|
||||
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|--------|----------|----------|------
|
||||
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
|
||||
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
|
||||
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
|
||||
|
||||
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
|
||||
machine. If you find that mold is not faster than other linkers, 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, 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.
|
||||
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
|
||||
|
||||
## Why does linking speed matter?
|
||||
|
||||
@@ -68,14 +59,12 @@ necessary packages. You may need to run it as root.
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
mkdir mold/build
|
||||
cd mold/build
|
||||
git checkout v2.3.2
|
||||
../install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ ..
|
||||
cmake --build . -j $(nproc)
|
||||
sudo cmake --build . --target install
|
||||
git clone --branch stable https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
./install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
|
||||
cmake --build build -j$(nproc)
|
||||
sudo cmake --build build --target install
|
||||
```
|
||||
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
@@ -87,12 +76,12 @@ installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
not work for you for any reason, you can use Docker to build mold in a Docker
|
||||
environment. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a Docker image, build mold and auxiliary
|
||||
not work for you for any reason, you can use Podman to build mold in a
|
||||
container. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a container image, build mold and auxiliary
|
||||
files inside it, and package them into a single tar file named
|
||||
`mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere and
|
||||
use the mold executable within it.
|
||||
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use the mold executable in it.
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -100,7 +89,7 @@ use the mold executable within it.
|
||||
|
||||
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.
|
||||
indirectly invoked by `make` or other build system commands.
|
||||
|
||||
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
|
||||
@@ -131,7 +120,7 @@ accept an absolute path as an argument for `-fuse-ld`.
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```toml
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
@@ -142,8 +131,8 @@ example above, we use `clang` as a linker driver since it always accepts the
|
||||
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"]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
@@ -169,22 +158,30 @@ If you want to use mold for all projects, add the above snippet to
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Conan package manager</summary>
|
||||
|
||||
You can configure [Conan](https://github.com/conan-io) to download the latest
|
||||
version of `mold` and use it as the linker when building your dependencies and
|
||||
projects from source. Please see the instructions [here](https://conan.io/center/recipes/mold).
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option to `cc`
|
||||
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
|
||||
feature to intercept all invocations of `ld`, `ld.bfd`, `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 the given command with the `LD_PRELOAD` environment
|
||||
Internally, mold invokes a 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`.
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -192,7 +189,7 @@ intercepts all function calls to `exec(3)`-family functions to replace
|
||||
|
||||
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
|
||||
run on a 4 core machine, mold is still significantly faster than the default
|
||||
GNU linker, especially when linking large programs.
|
||||
|
||||
</details>
|
||||
@@ -218,7 +215,7 @@ If `mold` is present in the `.comment` section, the file was created by 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`.
|
||||
is available online. You can read the same manual by running `man mold`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -242,10 +239,19 @@ 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
|
||||
everyone who sponsors our project. In particular, we'd like to acknowledge the
|
||||
following people and organizations who have sponsored $128/month or more:
|
||||
It is taken for granted nowadays that compiler toolchains can be easily
|
||||
installed and used for free, and people may not think too much about the
|
||||
individuals behind these "free tools". mold supports many projects, but it
|
||||
is essentially a one-person project. This situation is similar to the one
|
||||
depicted in the following xkcd illustration.
|
||||
|
||||
[](https://xkcd.com/2347)
|
||||
|
||||
If you think that the "Nebraska guy" should be rewarded, please consider
|
||||
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
|
||||
|
||||
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
|
||||
|
||||
@@ -255,13 +261,15 @@ following people and organizations who have sponsored $128/month or more:
|
||||
|
||||
<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)
|
||||
- [GlareDB](https://github.com/GlareDB)
|
||||
|
||||
### Individual sponsors
|
||||
|
||||
- [300baud](https://github.com/300baud)
|
||||
- [Johan Andersson](https://github.com/repi)
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [kyle-elliott](https://github.com/kyle-elliott)
|
||||
- [Bryant Biggs](https://github.com/bryantbiggs)
|
||||
- [kraptor23](https://github.com/kraptor23)
|
||||
- [Jinkyu Yi](https://github.com/jincreator)
|
||||
- [Pedro Navarro](https://github.com/pedronavf)
|
||||
@@ -1,101 +0,0 @@
|
||||
#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
|
||||
-1081
File diff suppressed because it is too large.
Load diff
@@ -1,185 +0,0 @@
|
||||
// This file implements a multi-threaded zlib and zstd compression
|
||||
// routine.
|
||||
//
|
||||
// zlib-compressed data can be merged just by concatenation as long as
|
||||
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
|
||||
// split input data into multiple shards, compress them individually
|
||||
// and concatenate them. We then append a header, a trailer and a
|
||||
// checksum so that the concatenated data is valid zlib-format data.
|
||||
//
|
||||
// zstd-compressed data can be merged in the same way.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is reset on boundaries of shards, compression ratio is sacrificed
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
static std::vector<std::string_view> split(std::string_view input) {
|
||||
std::vector<std::string_view> shards;
|
||||
|
||||
while (input.size() >= SHARD_SIZE) {
|
||||
shards.push_back(input.substr(0, SHARD_SIZE));
|
||||
input = input.substr(SHARD_SIZE);
|
||||
}
|
||||
if (!input.empty())
|
||||
shards.push_back(input);
|
||||
return shards;
|
||||
}
|
||||
|
||||
static std::vector<u8> zlib_compress(std::string_view input) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, we chose compression
|
||||
// level 1.
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
strm.next_in = (u8 *)input.data();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = buf.size();
|
||||
strm.next_out = buf.data();
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
assert(strm.avail_out > 0);
|
||||
buf.resize(buf.size() - strm.avail_out);
|
||||
buf.shrink_to_fit();
|
||||
deflateEnd(&strm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
std::vector<u64> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 2; // +2 for header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
|
||||
// Write a checksum
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
static std::vector<u8> zstd_compress(std::string_view input) {
|
||||
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
|
||||
constexpr int level = 3; // compression level; must be between 1 to 22
|
||||
|
||||
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
|
||||
level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
buf.resize(sz);
|
||||
buf.shrink_to_fit();
|
||||
return buf;
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
|
||||
std::string_view input{(char *)buf, (size_t)size};
|
||||
std::vector<std::string_view> inputs = split(input);
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::vector<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,3 +0,0 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#cmakedefine01 MOLD_IS_SOLD
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string get_realpath(std::string_view path) {
|
||||
std::error_code ec;
|
||||
std::filesystem::path link = std::filesystem::read_symlink(path, ec);
|
||||
if (ec)
|
||||
return std::string(path);
|
||||
return (filepath(path) / ".." / link).lexically_normal().string();
|
||||
}
|
||||
|
||||
// Removes redundant '/..' or '/.' from a given path.
|
||||
// The transformation is done purely by lexical processing.
|
||||
// This function does not access file system.
|
||||
std::string path_clean(std::string_view path) {
|
||||
return filepath(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
std::filesystem::path to_abs_path(std::filesystem::path path) {
|
||||
if (path.is_absolute())
|
||||
return path.lexically_normal();
|
||||
return (std::filesystem::current_path() / path).lexically_normal();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<Glob> Glob::compile(std::string_view pat) {
|
||||
std::vector<Element> vec;
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat = pat.substr(1);
|
||||
|
||||
switch (c) {
|
||||
case '[': {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
vec.push_back({BRACKET});
|
||||
std::bitset<256> &bitset = vec.back().bitset;
|
||||
|
||||
bool negate = false;
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
negate = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
bool closed = false;
|
||||
|
||||
while (!pat.empty()) {
|
||||
if (pat[0] == ']') {
|
||||
pat = pat.substr(1);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pat[0] == '\\') {
|
||||
pat = pat.substr(1);
|
||||
if (pat.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pat.size() >= 3 && pat[1] == '-') {
|
||||
u8 start = pat[0];
|
||||
u8 end = pat[2];
|
||||
pat = pat.substr(3);
|
||||
|
||||
if (end == '\\') {
|
||||
if (pat.empty())
|
||||
return {};
|
||||
end = pat[0];
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
bitset[i] = true;
|
||||
} else {
|
||||
bitset[(u8)pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
bitset.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
vec.push_back({QUESTION});
|
||||
break;
|
||||
case '*':
|
||||
vec.push_back({STAR});
|
||||
break;
|
||||
default:
|
||||
if (vec.empty() || vec.back().kind != STRING)
|
||||
vec.push_back({STRING});
|
||||
vec.back().str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {Glob{std::move(vec)}};
|
||||
}
|
||||
|
||||
bool Glob::match(std::string_view str) {
|
||||
return do_match(str, elements);
|
||||
}
|
||||
|
||||
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
|
||||
while (!elements.empty()) {
|
||||
Element &e = elements[0];
|
||||
elements = elements.subspan(1);
|
||||
|
||||
switch (e.kind) {
|
||||
case STRING:
|
||||
if (str.empty() || !str.starts_with(e.str))
|
||||
return false;
|
||||
str = str.substr(e.str.size());
|
||||
break;
|
||||
case STAR:
|
||||
if (elements.empty())
|
||||
return true;
|
||||
|
||||
// Patterns like "*foo*bar*" should be much more common than more
|
||||
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
|
||||
// the former case here.
|
||||
if (elements[0].kind == STRING) {
|
||||
for (;;) {
|
||||
size_t pos = str.find(elements[0].str);
|
||||
if (pos == str.npos)
|
||||
break;
|
||||
if (do_match(str.substr(pos + elements[0].str.size()),
|
||||
elements.subspan(1)))
|
||||
return true;
|
||||
str = str.substr(pos + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other cases are handled here.
|
||||
for (i64 j = 0; j < str.size(); j++)
|
||||
if (do_match(str.substr(j), elements))
|
||||
return true;
|
||||
return false;
|
||||
case QUESTION:
|
||||
if (str.empty())
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
case BRACKET:
|
||||
if (str.empty() || !e.bitset[str[0]])
|
||||
return false;
|
||||
str = str.substr(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return str.empty();
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,221 +0,0 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. Users should be able to run mold on a big-endian
|
||||
// SPARC machine to create a little-endian RV64 binary, for example.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory. Because
|
||||
// archive file (.a file) aligns each member only to a 2 byte boundary,
|
||||
// anything larger than 2 bytes may be unaligned in an mmap'ed memory.
|
||||
// Unaligned access is an undefined behavior in C/C++, so we shouldn't
|
||||
// cast an arbitrary pointer to a uint32_t, for example, to read a
|
||||
// 32-bits value.
|
||||
//
|
||||
// The data types defined in this file don't depend on host byte order and
|
||||
// don't do unaligned access.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
|
||||
template <typename T>
|
||||
static inline T bswap(T val) {
|
||||
switch (sizeof(T)) {
|
||||
case 2: return __builtin_bswap16(val);
|
||||
case 4: return __builtin_bswap32(val);
|
||||
case 8: return __builtin_bswap64(val);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int SIZE = sizeof(T)>
|
||||
class LittleEndian {
|
||||
public:
|
||||
LittleEndian() = default;
|
||||
LittleEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
x = bswap(x);
|
||||
return x;
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
return (val[2] << 16) | (val[1] << 8) | val[0];
|
||||
}
|
||||
}
|
||||
|
||||
LittleEndian &operator=(T x) {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
x = bswap(x);
|
||||
memcpy(val, &x, sizeof(T));
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
val[2] = x >> 16;
|
||||
val[1] = x >> 8;
|
||||
val[0] = x;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LittleEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
LittleEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
LittleEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
LittleEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
LittleEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
LittleEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
LittleEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 val[SIZE];
|
||||
};
|
||||
|
||||
using il16 = LittleEndian<i16>;
|
||||
using il32 = LittleEndian<i32>;
|
||||
using il64 = LittleEndian<i64>;
|
||||
using ul16 = LittleEndian<u16>;
|
||||
using ul24 = LittleEndian<u32, 3>;
|
||||
using ul32 = LittleEndian<u32>;
|
||||
using ul64 = LittleEndian<u64>;
|
||||
|
||||
template <typename T, int SIZE = sizeof(T)>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian() = default;
|
||||
BigEndian(T x) { *this = x; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
T x;
|
||||
memcpy(&x, val, sizeof(T));
|
||||
if constexpr (std::endian::native == std::endian::little)
|
||||
x = bswap(x);
|
||||
return x;
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
return (val[0] << 16) | (val[1] << 8) | val[2];
|
||||
}
|
||||
}
|
||||
|
||||
BigEndian &operator=(T x) {
|
||||
if constexpr (sizeof(T) == SIZE) {
|
||||
if constexpr (std::endian::native == std::endian::little)
|
||||
x = bswap(x);
|
||||
memcpy(val, &x, sizeof(T));
|
||||
} else {
|
||||
static_assert(SIZE == 3);
|
||||
val[0] = x >> 16;
|
||||
val[1] = x >> 8;
|
||||
val[2] = x;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
BigEndian &operator++() {
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
BigEndian operator++(int) {
|
||||
T ret = *this;
|
||||
*this = *this + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator--() {
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
BigEndian operator--(int) {
|
||||
T ret = *this;
|
||||
*this = *this - 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
BigEndian &operator+=(T x) {
|
||||
return *this = *this + x;
|
||||
}
|
||||
|
||||
BigEndian &operator-=(T x) {
|
||||
return *this = *this - x;
|
||||
}
|
||||
|
||||
BigEndian &operator&=(T x) {
|
||||
return *this = *this & x;
|
||||
}
|
||||
|
||||
BigEndian &operator|=(T x) {
|
||||
return *this = *this | x;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 val[SIZE];
|
||||
};
|
||||
|
||||
using ib16 = BigEndian<i16>;
|
||||
using ib32 = BigEndian<i32>;
|
||||
using ib64 = BigEndian<i64>;
|
||||
using ub16 = BigEndian<u16>;
|
||||
using ub24 = BigEndian<u32, 3>;
|
||||
using ub32 = BigEndian<u32>;
|
||||
using ub64 = BigEndian<u64>;
|
||||
|
||||
} // namespace mold
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
#include "common.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <tbb/global_control.h>
|
||||
|
||||
#ifdef USE_SYSTEM_MIMALLOC
|
||||
#include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string mold_version_string = MOLD_VERSION;
|
||||
|
||||
namespace elf {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
namespace macho {
|
||||
int main(int argc, char **argv);
|
||||
}
|
||||
|
||||
static std::string get_mold_version() {
|
||||
std::string name = MOLD_IS_SOLD ? "mold (sold) " : "mold ";
|
||||
if (mold_git_hash.empty())
|
||||
return name + MOLD_VERSION + " (compatible with GNU ld)";
|
||||
return name + MOLD_VERSION + " (" + mold_git_hash + "; compatible with GNU ld)";
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#ifdef __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS or structured
|
||||
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
#ifdef _WIN32
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD exception_record = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *exception_information = exception_record->ExceptionInformation;
|
||||
if (exception_record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= exception_information[1] &&
|
||||
exception_information[1] < (ULONG_PTR)output_buffer_end) {
|
||||
|
||||
const char msg[] = "mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
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);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
i64 get_default_thread_count() {
|
||||
// mold doesn't scale well above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
mold::mold_version = mold::get_mold_version();
|
||||
|
||||
#if MOLD_IS_SOLD
|
||||
std::string cmd = mold::filepath(argv[0]).filename().string();
|
||||
if (cmd == "ld64" || cmd.starts_with("ld64."))
|
||||
return mold::macho::main(argc, argv);
|
||||
#endif
|
||||
|
||||
return mold::elf::main(argc, argv);
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
// This file implements the Aho-Corasick algorithm to match multiple
|
||||
// glob patterns to symbol strings as quickly as possible.
|
||||
//
|
||||
// Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// `*` is a wildcard that matches any substring. We sometimes have
|
||||
// hundreds of glob patterns and have to match them against millions
|
||||
// of symbol strings.
|
||||
//
|
||||
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
|
||||
// We handle such patterns with the Glob class. Glob is relatively
|
||||
// slow, but complex patterns are rare in practice, so it should be
|
||||
// OK.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<u32> MultiGlob::find(std::string_view str) {
|
||||
std::call_once(once, [&] { compile(); });
|
||||
u32 val = UINT32_MAX;
|
||||
|
||||
if (root) {
|
||||
// Match against simple glob patterns
|
||||
TrieNode *node = root.get();
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (;;) {
|
||||
if (node->children[c]) {
|
||||
node = node->children[c].get();
|
||||
val = std::min(val, node->value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node->suffix_link)
|
||||
return;
|
||||
node = node->suffix_link;
|
||||
}
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str)
|
||||
walk(c);
|
||||
walk('\0');
|
||||
}
|
||||
|
||||
// Match against complex glob patterns
|
||||
for (std::pair<Glob, u32> &glob : globs)
|
||||
if (glob.first.match(str))
|
||||
val = std::min(val, glob.second);
|
||||
|
||||
if (val == UINT32_MAX)
|
||||
return {};
|
||||
return val;
|
||||
}
|
||||
|
||||
static bool is_simple_pattern(std::string_view pat) {
|
||||
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
|
||||
return std::regex_match(pat.begin(), pat.end(), re);
|
||||
}
|
||||
|
||||
static std::string handle_stars(std::string_view pat) {
|
||||
std::string str(pat);
|
||||
|
||||
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
|
||||
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
|
||||
// so we use \0 as beginning/end-of-string markers.
|
||||
if (str.starts_with('*') && str.ends_with('*'))
|
||||
return str.substr(1, str.size() - 2);
|
||||
if (str.starts_with('*'))
|
||||
return str.substr(1) + "\0"s;
|
||||
if (str.ends_with('*'))
|
||||
return "\0"s + str.substr(0, str.size() - 1);
|
||||
return "\0"s + str + "\0"s;
|
||||
}
|
||||
|
||||
bool MultiGlob::add(std::string_view pat, u32 val) {
|
||||
assert(!is_compiled);
|
||||
assert(!pat.empty());
|
||||
|
||||
strings.push_back(std::string(pat));
|
||||
|
||||
// Complex glob pattern
|
||||
if (!is_simple_pattern(pat)) {
|
||||
if (std::optional<Glob> glob = Glob::compile(pat)) {
|
||||
globs.push_back({std::move(*glob), val});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simple glob pattern
|
||||
if (!root)
|
||||
root.reset(new TrieNode);
|
||||
TrieNode *node = root.get();
|
||||
|
||||
for (u8 c : handle_stars(pat)) {
|
||||
if (!node->children[c])
|
||||
node->children[c].reset(new TrieNode);
|
||||
node = node->children[c].get();
|
||||
}
|
||||
|
||||
node->value = std::min(node->value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiGlob::compile() {
|
||||
is_compiled = true;
|
||||
if (root) {
|
||||
fix_suffix_links(*root);
|
||||
fix_values();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiGlob::fix_suffix_links(TrieNode &node) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
if (!node.children[i])
|
||||
continue;
|
||||
|
||||
TrieNode &child = *node.children[i];
|
||||
|
||||
TrieNode *cur = node.suffix_link;
|
||||
for (;;) {
|
||||
if (!cur) {
|
||||
child.suffix_link = root.get();
|
||||
break;
|
||||
}
|
||||
|
||||
if (cur->children[i]) {
|
||||
child.suffix_link = cur->children[i].get();
|
||||
break;
|
||||
}
|
||||
|
||||
cur = cur->suffix_link;
|
||||
}
|
||||
|
||||
fix_suffix_links(child);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiGlob::fix_values() {
|
||||
std::queue<TrieNode *> queue;
|
||||
queue.push(root.get());
|
||||
|
||||
do {
|
||||
TrieNode *node = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (std::unique_ptr<TrieNode> &child : node->children) {
|
||||
if (!child)
|
||||
continue;
|
||||
child->value = std::min(child->value, child->suffix_link->value);
|
||||
queue.push(child.get());
|
||||
}
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,144 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
inline u32 get_umask() {
|
||||
u32 orig_umask = umask(0);
|
||||
umask(orig_umask);
|
||||
return orig_umask;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
static std::pair<i64, char *>
|
||||
open_or_create_file(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
|
||||
char *path2 = (char *)save_string(ctx, tmpl).data();
|
||||
|
||||
i64 fd = mkstemp(path2);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
|
||||
// Reuse an existing file if exists and writable because on Linux,
|
||||
// writing to an existing file is much faster than creating a fresh
|
||||
// file and writing to it.
|
||||
if (ctx.overwrite_output_file && rename(path.c_str(), path2) == 0) {
|
||||
::close(fd);
|
||||
fd = ::open(path2, O_RDWR | O_CREAT, perm);
|
||||
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
|
||||
return {fd, path2};
|
||||
|
||||
unlink(path2);
|
||||
fd = ::open(path2, O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
|
||||
}
|
||||
|
||||
if (ftruncate(fd, filesize))
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
|
||||
if (fchmod(fd, (perm & ~get_umask())) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
return {fd, path2};
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
class MemoryMappedOutputFile : public OutputFile<Context> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context &ctx, std::string path, i64 filesize, i64 perm)
|
||||
: OutputFile<Context>(path, filesize, true) {
|
||||
std::tie(this->fd, output_tmpfile) =
|
||||
open_or_create_file(ctx, path, filesize, perm);
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, this->fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
~MemoryMappedOutputFile() {
|
||||
if (fd2 != -1)
|
||||
::close(fd2);
|
||||
}
|
||||
|
||||
void close(Context &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (this->buf2.empty()) {
|
||||
::close(this->fd);
|
||||
} else {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
fd2 = ::open(this->path.c_str(), O_RDONLY);
|
||||
if (fd2 != -1)
|
||||
unlink(this->path.c_str());
|
||||
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename Context>
|
||||
std::unique_ptr<OutputFile<Context>>
|
||||
OutputFile<Context>::open(Context &ctx, std::string path, i64 filesize, i64 perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
|
||||
is_special = true;
|
||||
}
|
||||
|
||||
OutputFile<Context> *file;
|
||||
if (is_special)
|
||||
file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
else
|
||||
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Enable transparent huge page for an output memory-mapped file.
|
||||
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
|
||||
// but it can make the linker ~10% faster. You can try it by creating
|
||||
// a tmpfs with the following commands
|
||||
//
|
||||
// $ mkdir tmp
|
||||
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
|
||||
//
|
||||
// and then specifying a path under the directory as an output file.
|
||||
madvise(file->buf, filesize, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile>(file);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,24 +0,0 @@
|
||||
#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
|
||||
@@ -1,5 +0,0 @@
|
||||
#if _WIN32
|
||||
# include "output-file-win32.h"
|
||||
#else
|
||||
# include "output-file-unix.h"
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::array<u8, 16> get_uuid_v4() {
|
||||
std::array<u8, 16> bytes;
|
||||
|
||||
std::random_device rand;
|
||||
u32 buf[4] = { rand(), rand(), rand(), rand() };
|
||||
memcpy(bytes.data(), buf, 16);
|
||||
|
||||
// Indicate that this is UUIDv4 as defined by RFC4122.
|
||||
bytes[6] = (bytes[6] & 0b00001111) | 0b01000000;
|
||||
bytes[8] = (bytes[8] & 0b00111111) | 0b10000000;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,78 +1,247 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script creates a mold binary distribution. The output is
|
||||
# written in this directory as `mold-$version-$arch-linux.tar.gz`
|
||||
# (e.g. `mold-1.0.3-x86_64-linux.tar.gz`).
|
||||
# This script creates a mold binary distribution. The output is written to
|
||||
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
|
||||
# `mold-2.40.0-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# The mold executable created by this script is statically-linked to
|
||||
# libstdc++ and libcrypto but dynamically-linked to libc, libm, libz
|
||||
# and librt, as they almost always exist on any Linux systems.
|
||||
# This script aims to produce reproducible outputs. That means each time
|
||||
# it's run on the same git commit, it generates a bit-for-bit identical
|
||||
# binary file regardless of when or where it's executed. This property
|
||||
# serves as a strong safeguard against supply chain attacks. With a
|
||||
# reproducible build, anyone can independently verify that the binary
|
||||
# files published on our GitHub release page were built from the git
|
||||
# commit tagged for release by rebuilding the binaries themselves.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct a container image pinned to a
|
||||
# particular timestamp. snapshot.debian.org is known to be very slow,
|
||||
# but that shouldn't be a big problem for us because we only need that
|
||||
# site the first time.
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++, but dynamically linked to glibc, libm and a few other
|
||||
# libraries, as these libraries are almost always available on any Linux
|
||||
# system. We can't statically link glibc because doing so would disable
|
||||
# dlopen(), which is required to load the LTO linker plugin.
|
||||
#
|
||||
# We use a reasonably old Debian version for the build environment because
|
||||
# a binary dynamically linked against a newer version of glibc won't work
|
||||
# on a system with an older version of glibc.
|
||||
#
|
||||
# We prefer to build mold with Clang rather than GCC because mold's
|
||||
# Identical Code Folding works best with the LLVM address significance
|
||||
# table (.llvm_addrsig). Building a release binary with GCC produces a
|
||||
# slightly larger binary than with Clang.
|
||||
#
|
||||
# We need a recent version of Clang to build mold. If it's not available
|
||||
# via apt-get, we'll build it ourselves.
|
||||
#
|
||||
# This script can be used to create non-native binaries (e.g., building
|
||||
# aarch64 binary on x86-64) because Podman automatically runs everything
|
||||
# under QEMU if the container image is not native. To use this script for
|
||||
# non-native builds, you may need to install the qemu-user-static package.
|
||||
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
[[ $arch = arm* ]] && arch=arm
|
||||
if [ $arch = arm64 ]; then
|
||||
arch=aarch64
|
||||
elif [[ $arch = arm* ]]; then
|
||||
arch=arm
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x ]"
|
||||
exit 1
|
||||
usage
|
||||
esac
|
||||
|
||||
echo "$arch" | grep -Eq '^(x86_64|aarch64|arm|riscv64|ppc64le|s390x)$' || \
|
||||
{ echo "Error: no docker image for $arch"; exit 1; }
|
||||
|
||||
image=mold-builder-$arch:1
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' $(dirname $0)/CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
# Create a Docker image if not exists.
|
||||
#
|
||||
# We want to use a reasonably old Linux distro because we'll dynamically
|
||||
# link glibc to mold, and a binary linked against a newer version of
|
||||
# glibc won't work on a system with a older version of glibc.
|
||||
#
|
||||
# We might want to instead statically-link everything, but that'll
|
||||
# disable dlopen(), which means we can't use the linker plugin for LTO.
|
||||
# So we don't want to do that.
|
||||
if ! docker image ls $image | grep -q $image; then
|
||||
if [ $arch = riscv64 ]; then
|
||||
# Ubuntu 18 didn't support RISC-V, so use Ubuntu 20 instead
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM riscv64/ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 cmake && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
else
|
||||
cat <<EOF | docker build --platform $arch -t $image -
|
||||
FROM ubuntu:18.04
|
||||
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 && \
|
||||
DEBIAN_FRONTEND=noninteractive TZ=UTC apt-get install -y --no-install-recommends build-essential wget libstdc++-11-dev gcc-10 g++-10 python3 libssl-dev && \
|
||||
\
|
||||
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
|
||||
EOF
|
||||
fi
|
||||
# Create a Podman image.
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 9 (Stretch) released in June 2017.
|
||||
#
|
||||
# We use a Google-provided mirror (gcr.io) instead of the official Docker
|
||||
# Hub (docker.io) because docker.io has a strict rate limit policy.
|
||||
#
|
||||
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
|
||||
# from source. We download source archives from official sites and build
|
||||
# them locally, rather than downloading pre-built binaries from somewhere
|
||||
# else, to avoid relying on unverifiable third-party binary blobs. Podman
|
||||
# caches the result of each RUN command, so rebuilding is done only once
|
||||
# per host.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./bootstrap --parallel=\$(nproc) && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 14
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir gmp mpc mpfr && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GNU binutils 2.43
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
|
||||
./configure --prefix=/usr && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -fr /build
|
||||
|
||||
# Build Python 3.12.7
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
|
||||
./configure && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
|
||||
mkdir b && \
|
||||
cd b && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
|
||||
cmake --build . -j\$(nproc) && \
|
||||
cmake --install . --strip && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 11 (Bullseye) released in August 2021
|
||||
#
|
||||
# We don't want to build Clang for these targets on QEMU becuase it
|
||||
# would take an extremely long time. Also, I believe old Linux boxes
|
||||
# are typically x86-64.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake && \
|
||||
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
riscv64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake && \
|
||||
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
loongarch64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e 's!http[^ ]*!http://snapshot.debian.org/archive/debian-ports/20250620T014755Z!g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
|
||||
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
# Source tarballs available on GitHub don't contain .git directory.
|
||||
# Clone the repo if missing.
|
||||
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp=$(git log -1 --format=%ct)
|
||||
|
||||
# `uname -m` in an ARM32 container running on an ARM64 host reports it
|
||||
# not as ARM32 but as ARM64. That confuses BLAKE3's cmake script and
|
||||
# erroneously enables NEON SIMD instructions. `setarch` can be used to
|
||||
# change the output of `uname -m`.
|
||||
setarch=
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
|
||||
mkdir -p dist
|
||||
|
||||
# Build mold in a container.
|
||||
docker run --platform linux/$arch -i --rm -v "$(realpath $(dirname $0)):/mold" \
|
||||
-e "OWNER=$(id -u):$(id -g)" $image \
|
||||
bash -c "mkdir /build &&
|
||||
cd /build &&
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DMOLD_MOSTLY_STATIC=On /mold &&
|
||||
cmake --build . -j\$(nproc) &&
|
||||
[ $arch = arm ] || ctest -j\$(nproc) &&
|
||||
cmake --install . --prefix $dest --strip &&
|
||||
tar czf /mold/$dest.tar.gz $dest &&
|
||||
chown \$OWNER /mold/$dest.tar.gz"
|
||||
#
|
||||
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
|
||||
# build artifacts to appear as if they were built at a specific time.
|
||||
# We use it to control how the compiler expands the C/C++ __DATE__ and
|
||||
# __TIME__ macros.
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
|
||||
$setarch bash -c "
|
||||
set -e
|
||||
export SOURCE_DATE_EPOCH=$timestamp
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
cmake --install .
|
||||
cmake -DMOLD_USE_MOLD=1 .
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest --output-on-failure -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date=@$timestamp
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
|
||||
cp mold /dist
|
||||
sha256sum /dist/$dest.tar.gz
|
||||
"
|
||||
@@ -0,0 +1 @@
|
||||
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated
|
||||
|
After Width: | Height: | Size: 99 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
@@ -1,3 +1,8 @@
|
||||
[This document was written in 2020, and the contents are outdated.
|
||||
Specifically, we no longer believe that object preloading is a good
|
||||
idea. That being said, most of the points in this document still hold
|
||||
even today. Therefore, I'll keep this document as-is.]
|
||||
|
||||
## Design and implementation of mold
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
|
||||
+90
-31
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "July 2023" ""
|
||||
.TH "MOLD" "1" "June 2025" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -10,9 +10,9 @@
|
||||
.SS "How to use"
|
||||
See \fIhttps://github\.com/rui314/mold#how\-to\-use\fR\.
|
||||
.SS "Compatibility"
|
||||
\fBMold\fR is designed to be a drop\-in replacement for the GNU linkers for linking user\-land programs\. If your user\-land program cannot be built due to missing command\-line options, please file a bug at \fIhttps://github\.com/rui314/mold/issues\fR\.
|
||||
\fBmold\fR is designed to be a drop\-in replacement for the GNU linkers for linking user\-land programs\. If your user\-land program cannot be built due to missing command\-line options, please file a bug at \fIhttps://github\.com/rui314/mold/issues\fR\.
|
||||
.P
|
||||
Mold supports a very limited set of linker script features, which is just sufficient to read \fB/usr/lib/x86_64\-linux\-gnu/libc\.so\fR on Linux systems (on Linux, that file is contrary to its name not a shared library but an ASCII linker script that loads a real \fBlibc\.so\fR file\.)
|
||||
\fBmold\fR supports a very limited set of linker script features, which is just sufficient to read \fB/usr/lib/x86_64\-linux\-gnu/libc\.so\fR on Linux systems (on Linux, that file is contrary to its name not a shared library but an ASCII linker script that loads a real \fBlibc\.so\fR file\.)
|
||||
.P
|
||||
Beyond that, we have no plan to support any additional linker script features\. The linker script is an ad\-hoc, over\-designed, complex language which we believe needs to be replaced by a simpler mechanism\. We have a plan to add a replacement for the linker script to \fBmold\fR instead\.
|
||||
.SS "Archive symbol resolution"
|
||||
@@ -40,15 +40,17 @@ Typically, an ELF module that exports a symbol also imports the same symbol\. Su
|
||||
.P
|
||||
Let's take \fBmalloc\fR as an example\. Assume that you define your version of \fBmalloc\fR in your main executable file\. Then, all \fBmalloc\fR calls from any module are resolved to your function instead of the one in libc, because the executable is always at the beginning of the dynamic symbol search list\. Note that even \fBmalloc\fR calls within libc are resolved to your definition since libc exports and imports \fBmalloc\fR\. Therefore, by defining \fBmalloc\fR yourself, you can overwrite a library function, and the \fBmalloc\fR in libc becomes dead code\.
|
||||
.P
|
||||
These Unix semantics are tricky and sometimes considered harmful\. For example, assume that you accidentally define \fBatoi\fR as a global function in your executable that behaves completely differently from the one in the C standard\. Then, all \fBatoi\fR function calls from any modules (even function calls within libc) are redirected to your function instead of the one in libc, which will very likely cause a problem\.
|
||||
.P
|
||||
That is a somewhat surprising consequence for an accidental name conflict\. On the other hand, this semantic is sometimes useful because it allows users to override library functions without rebuilding modules containing them\.
|
||||
These Unix semantics are tricky and sometimes considered harmful\. For example, assume that you accidentally define \fBatoi\fR as a global function in your executable that behaves completely differently from the one in the C standard\. Then, all \fBatoi\fR function calls from any modules (even function calls within libc) are redirected to your function instead of the one in libc, which will very likely cause a problem\. That is a somewhat surprising consequence for an accidental name conflict\. On the other hand, this semantic is sometimes useful because it allows users to override library functions without rebuilding modules containing them\.
|
||||
.P
|
||||
Whether good or bad, you should keep these semantics in mind to understand Unix linkers' behaviors\.
|
||||
.SS "Build reproducibility"
|
||||
\fBmold\fR's output is deterministic\. That is, if you pass the same object files and the same command\-line options to the same version of \fBmold\fR, it is guaranteed that \fBmold\fR produces the bit\-by\-bit identical output\. The linker's internal randomness, such as the timing of thread scheduling or iteration orders of hash tables, doesn't affect the output\.
|
||||
\fBmold\fR's output is deterministic\. That is, if you pass the same object files and the same command\-line options to the same version of \fBmold\fR, it is guaranteed that \fBmold\fR produces the bit\-for\-bit identical output\. The linker's internal randomness, such as the timing of thread scheduling or iteration orders of hash tables, doesn't affect the output\.
|
||||
.P
|
||||
\fBmold\fR does not have any host\-specific default settings\. This is contrary to the GNU linkers, for which some configurable values, such as system\-dependent library search paths, are hard\-coded\. \fBmold\fR depends only on its command\-line arguments\.
|
||||
.SH "OPTION NOTATIONS"
|
||||
Multi\-letter long options may precede either a single dash or double dashes, except for those starting with the letter "o"\. For historical reasons, long options beginning with "o" must precede double dashes\.
|
||||
.P
|
||||
For example, you can spell \fB\-\-as\-needed\fR as \fB\-as\-needed\fR, but \fB\-\-omagic\fR must not be spelled as \fB\-omagic\fR\. \fB\-omagic\fR will be interpreted not as \fB\-\-omagic\fR but as \fB\-o magic\fR\.
|
||||
.SH "MOLD\-SPECIFIC OPTIONS"
|
||||
.TP
|
||||
\fB\-\-chroot\fR=\fIdir\fR
|
||||
@@ -63,8 +65,13 @@ Synonym for \fB\-\-color\-diagnostics=auto\fR\.
|
||||
\fB\-\-no\-color\-diagnostics\fR
|
||||
Synonym for \fB\-\-color\-diagnostics=never\fR\.
|
||||
.TP
|
||||
\fB\-\-detach\fR, \fB\-\-no\-detach\fR
|
||||
Permit or do not permit mold to create a debug info file in the background\.
|
||||
.TP
|
||||
\fB\-\-fork\fR, \fB\-\-no\-fork\fR
|
||||
Spawn a child process and let it do the actual linking\. When linking a large program, the OS kernel can take a few hundred milliseconds to terminate a \fBmold\fR process\. \fB\-\-fork\fR hides that latency\. By default, it does fork\.
|
||||
.IP
|
||||
Note that \fB\-\-fork\fR also hides the resource usage statistics reported by time(2), since it doesn't call waitpid(2) on the child process\. If you need those statistics, pass \fB\-\-no\-fork\fR\.
|
||||
.TP
|
||||
\fB\-\-perf\fR
|
||||
Print performance statistics\.
|
||||
@@ -74,6 +81,11 @@ Print out dependency information for input files\.
|
||||
.IP
|
||||
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 \fB\-\-as\-needed\fR\.
|
||||
.TP
|
||||
\fB\-\-relocatable\-merge\-sections\fR
|
||||
By default, \fBmold\fR doesn't merge input sections by name when merging input object files into a single output object file for \fB\-r\fR\. For example, \fB\.text\.foo\fR and \fB\.text\.bar\fR aren't merged for \fB\-r\fR even though they are merged into \fB\.text\fR based on the default section merging rules\.
|
||||
.IP
|
||||
This option changes the behavior so that \fBmold\fR merges input sections by name by the default section merging rules\.
|
||||
.TP
|
||||
\fB\-\-repro\fR
|
||||
Archive input files, as well as a text file containing command line options, in a tar file so that you can run \fBmold\fR with the exact same inputs again\. This is useful for reporting a bug with a reproducer\. The output filename is \fBpath/to/output\.tar\fR, where \fBpath/to/output\fR is an output filename specified by \fB\-o\fR\.
|
||||
.TP
|
||||
@@ -85,7 +97,16 @@ This option is useful for finding bugs that depend on the initialization order o
|
||||
By reversing the order of input sections using \fB\-\-reverse\-sections\fR, you can easily test that your program works in the reversed initialization order\.
|
||||
.TP
|
||||
\fB\-\-run\fR \fIcommand\fR \fIarg\fR\|\.\|\.\|\.
|
||||
Run \fIcommand\fR with \fBmold\fR \fB/usr/bin/ld\fR\. Specifically, \fBmold\fR runs a given command with the \fBLD_PRELOAD\fR environment set to intercept exec(3) family functions and replaces \fBargv[0]\fR with itself if it is \fBld\fR, \fBld\.gold\fR, or \fBld\.lld\fR\.
|
||||
Run \fIcommand\fR with \fBmold\fR as \fB/usr/bin/ld\fR\. Specifically, \fBmold\fR runs a given command with the \fBLD_PRELOAD\fR environment set to intercept exec(3) family functions and replaces \fBargv[0]\fR with itself if it is \fBld\fR, \fBld\.gold\fR, or \fBld\.lld\fR\.
|
||||
.TP
|
||||
\fB\-\-separate\-debug\-file\fR, \fB\-\-separate\-debug\-file\fR=\fIfile\fR
|
||||
Bundle debug info sections into a separate file instead of embedding them in an output executable or a shared library\. mold creates a debug info file in the background by default, so that you can start running your executable as soon as possible\.
|
||||
.IP
|
||||
By default, the debug info file is created in the same directory as is the output file, with the \fB\.dbg\fR file extension\. That filename is embedded into the output file so that \fBgdb\fR can automatically find the debug info file for the output file\. For more info about gdb features related to separate debug files, see \fIhttps://sourceware\.org/gdb/current/onlinedocs/gdb\.html/Separate\-Debug\-Files\.html\fR\.
|
||||
.IP
|
||||
mold holds a file lock with flock(2) while creating a debug info file in the background\.
|
||||
.IP
|
||||
If you don't want to create a debug info file in the background, pass the \fB\-\-no\-detach\fR option\.
|
||||
.TP
|
||||
\fB\-\-shuffle\-sections\fR, \fB\-\-shuffle\-sections\fR=\fInumber\fR
|
||||
Randomize the output by shuffling the order of input sections before assigning them the offsets in the output file\. If a \fInumber\fR is given, it's used as a seed for the random number generator, so that the linker produces the same output for the same seed\. If no seed is given, a random number is used as a seed\.
|
||||
@@ -94,6 +115,11 @@ This option is useful for benchmarking\. Modern CPUs are sensitive to a program'
|
||||
.IP
|
||||
By running a benchmark multiple times with randomized memory layouts using \fB\-\-shuffle\-sections\fR, you can isolate your program's real performance number from the randomness caused by memory layout changes\.
|
||||
.TP
|
||||
\fB\-\-spare\-program\-headers\fR=\fInumber\fR
|
||||
Append the given number of \fBPT_NULL\fR entries to the end of the program header, so that post\-link processing tools can easily add new segments by overwriting the null entries\.
|
||||
.IP
|
||||
Note that ELF requires all \fBPT_LOAD\fR segments to be sorted by \fBp_vaddr\fR\. Therefore, if you add a new LOAD segment, you may need to sort the entire program header\.
|
||||
.TP
|
||||
\fB\-\-stats\fR
|
||||
Print input statistics\.
|
||||
.TP
|
||||
@@ -105,6 +131,17 @@ Use multiple threads\. By default, \fBmold\fR uses as many threads as the number
|
||||
.TP
|
||||
\fB\-\-quick\-exit\fR, \fB\-\-no\-quick\-exit\fR
|
||||
Use or do not use \fBquick_exit\fR to exit\.
|
||||
.TP
|
||||
\fB\-z rewrite\-endbr\fR, \fB\-z norewrite\-endbr\fR
|
||||
As a security measure, some CPU instruction sets have recently gained a feature to protect control flow integrity by disallowing indirect branches by default\. If the feature is enabled, the instruction that is executed immediately after an indirect branch must be an branch target marker instruction, or a CPU\-level fault will raise\. The marker instruction is also known as "landing pad" instruction, to which indirect branches can land\. This feature makes ROP attacks harder to conduct\.
|
||||
.IP
|
||||
To use the feature, a function whose pointer is taken needs to begin with a landing pad because a function call via a function pointer is compiled to an indirect branch\. On the other hand, if a function is called only directly (i\.e\. referred to only by \fIdirect\fR branch instructions), it doesn't have to begin with it\.
|
||||
.IP
|
||||
By default, the compiler always emits a landing pad at the beginning of each global function because it doesn't know whether or not the function's pointer is taken in another translation unit\. As a result, the resulting binary has more attack surface than necessary\.
|
||||
.IP
|
||||
If \fB\-\-rewrite\-endbr\fR is given, mold conducts a whole program analysis to identify functions whose addresses are actually taken and rewrites landing pads with no\-ops for non\-address\-taken functions, reducing the attack surface\.
|
||||
.IP
|
||||
This feature is currently available only on x86\-64\.
|
||||
.SH "GNU\-COMPATIBLE OPTIONS"
|
||||
.TP
|
||||
\fB\-\-help\fR
|
||||
@@ -168,17 +205,15 @@ Use \fIfile\fR as the output file name instead of the default name \fBa\.out\fR\
|
||||
\fB\-r\fR, \fB\-\-relocatable\fR
|
||||
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\.
|
||||
.TP
|
||||
\fB\-\-relocatable\-merge\-sections\fR
|
||||
By default, \fBmold\fR doesn't merge input sections by name when merging input object files into a single output object file for \fB\-r\fR\. For example, \fB\.text\.foo\fR and \fB\.text\.bar\fR aren't merged for \fB\-r\fR even though they are merged into \fB\.text\fR according to the default section merging rules\.
|
||||
.IP
|
||||
This option changes the behavior so that \fBmold\fR merges input sections by name by the default section merging rules\.
|
||||
.TP
|
||||
\fB\-s\fR, \fB\-\-strip\-all\fR
|
||||
Omit \fB\.symtab\fR section from the output file\.
|
||||
.TP
|
||||
\fB\-u\fR \fIsymbol\fR, \fB\-\-undefined\fR=\fIsymbol\fR
|
||||
If \fIsymbol\fR remains as an undefined symbol after reading all object files, and if there is a static archive that contains an object file defining \fIsymbol\fR, pull out the object file and link it so that the output file contains a definition of \fIsymbol\fR\.
|
||||
.TP
|
||||
\fB\-y\fR \fIsymbol\fR, \fB\-\-trace\-symbol\fR=\fIsymbol\fR
|
||||
Trace references to \fIsymbol\fR\.
|
||||
.TP
|
||||
\fB\-\-Bdynamic\fR
|
||||
Link against shared libraries\.
|
||||
.TP
|
||||
@@ -191,8 +226,14 @@ When creating a shared library, make global symbols export\-only (i\.e\. do not
|
||||
\fB\-\-Bsymbolic\-functions\fR
|
||||
This option has the same effect as \fB\-\-Bsymbolic\fR but works only for function symbols\. Data symbols remain being both imported and exported\.
|
||||
.TP
|
||||
\fB\-\-Bsymbolic\-non\-weak\fR
|
||||
This option has the same effect as \fB\-\-Bsymbolic\fR but works only for non\-weak symbols\. Weak symbols remain being both imported and exported\.
|
||||
.TP
|
||||
\fB\-\-Bsymbolic\-non\-weak\-functions\fR
|
||||
This option has the same effect as \fB\-\-Bsymbolic\fR but works only for non\-weak function symbols\. Data symbols and weak function symbols remain being both imported and exported\.
|
||||
.TP
|
||||
\fB\-\-Bno\-symbolic\fR
|
||||
Cancel \fB\-\-Bsymbolic\fR and \fB\-\-Bsymbolic\-functions\fR\.
|
||||
Cancel \fB\-\-Bsymbolic\fR, \fB\-\-Bsymbolic\-functions\fR, \fB\-\-Bsymbolic\-non\-weak\fR and \fB\-\-Bsymbolic\-non\-weak\-functions\fR\.
|
||||
.TP
|
||||
\fB\-\-Map\fR=\fIfile\fR
|
||||
Write map file to \fIfile\fR\.
|
||||
@@ -209,13 +250,20 @@ Alias for \fB\-\-section\-start=\.text=\fR\fIaddress\fR\.
|
||||
\fB\-\-allow\-multiple\-definition\fR
|
||||
Normally, the linker reports an error if there are more than one definition of a symbol\. This option changes the default behavior so that it doesn't report an error for duplicate definitions and instead use the first definition\.
|
||||
.TP
|
||||
\fB\-\-allow\-shlib\-undefined\fR, \fB\-\-no\-allow\-shlib\-undefined\fR
|
||||
Even if mold succeeds in linking a main executable without undefined symbol errors, you may still encounter symbol lookup errors at runtime because the dynamic linker cannot find some symbols in shared libraries in any ELF module\. This occurs because mold ignores undefined symbols in shared libraries by default\.
|
||||
.IP
|
||||
If you pass \fB\-\-no\-allow\-shlib\-undefined\fR, mold verifies that undefined symbols in shared libraries given to the linker can be resolved at link\-time\. In other words, this converts the runtime error to a link\-time error\.
|
||||
.IP
|
||||
Note that you need to pass all shared libraries, including indirectly dependent ones, to the linker as arguments for \fB\-l\fR\. If a shared library depends on a library that's not passed to the linker, the verification will be skipped for that file\.
|
||||
.TP
|
||||
\fB\-\-as\-needed\fR, \fB\-\-no\-as\-needed\fR
|
||||
By default, shared libraries given to the linker are unconditionally added to the list of required libraries in an output file\. However, shared libraries after \fB\-\-as\-needed\fR are added to the list only when at least one symbol is actually used by the output file\. In other words, shared libraries after \fB\-\-as\-needed\fR are not added to the list of needed libraries if they are not needed by a program\.
|
||||
.IP
|
||||
The \fB\-\-no\-as\-needed\fR option restores the default behavior for subsequent files\.
|
||||
.TP
|
||||
\fB\-\-build\-id\fR=[ \fBmd5\fR | \fBsha1\fR | \fBsha256\fR | \fBuuid\fR | \fB0x\fR\fIhexstring\fR | \fBnone\fR ]
|
||||
Create a \fB\.note\.gnu\.build\-id\fR section containing a byte string to uniquely identify an output file\. \fBsha256\fR compute a 256\-bit cryptographic hash of an output file and set it to build\-id\. \fBmd5\fR and \fBsha1\fR compute the same hash but truncate it to 128 and 160 bits, respectively, before setting it to build\-id\. \fBuuid\fR sets a random 128\-bit UUID\. \fB0x\fR\fIhexstring\fR sets \fIhexstring\fR\.
|
||||
\fB\-\-build\-id\fR=[ \fBmd5\fR | \fBsha1\fR | \fBsha256\fR | \fBfast\fR | \fBuuid\fR | \fB0x\fR\fIhexstring\fR | \fBnone\fR ]
|
||||
Create a \fB\.note\.gnu\.build\-id\fR section containing a byte string to uniquely identify an output file\. \fBsha256\fR compute a 256\-bit cryptographic hash of an output file and set it to build\-id\. \fBmd5\fR and \fBsha1\fR compute the same hash but truncate it to 128 and 160 bits, respectively, before setting it to build\-id\. \fBuuid\fR sets a random 128\-bit UUID\. \fB0x\fR\fIhexstring\fR sets \fIhexstring\fR\. \fBfast\fR is a synonym for \fBsha256\fR\.
|
||||
.TP
|
||||
\fB\-\-build\-id\fR
|
||||
Synonym for \fB\-\-build\-id=sha256\fR\.
|
||||
@@ -254,10 +302,8 @@ The \fB\-\-emit\-relocs\fR instructs the linker to leave relocation sections in
|
||||
\fB\-\-enable\-new\-dtags\fR, \fB\-\-disable\-new\-dtags\fR
|
||||
By default, \fBmold\fR emits \fBDT_RUNPATH\fR for \fB\-\-rpath\fR\. If you pass \fB\-\-disable\-new\-dtags\fR, \fBmold\fR emits \fBDT_RPATH\fR for \fB\-\-rpath\fR instead\.
|
||||
.TP
|
||||
\fB\-\-execute\-only\fR
|
||||
Traditionally, most processors require both executable and readable bits to 1 to make the page executable, which allows machine code to be read as data at runtime\. This is actually what an attacker often does after gaining a limited control of a process to find pieces of machine code they can use to gain the full control of the process\. As a mitigation, some recent processors allows "execute\-only" pages\. If a page is execute\-only, you can call a function there as long as you know its address but can't read it as data\.
|
||||
.IP
|
||||
This option marks text segments execute\-only\. This option currently works only on some ARM64 processors\.
|
||||
\fB\-\-execute\-only\fR:
|
||||
|
||||
.TP
|
||||
\fB\-\-exclude\-libs\fR=\fIlibraries\fR \|\.\|\.\|\.
|
||||
Mark all symbols in the given \fIlibraries\fR hidden\.
|
||||
@@ -309,14 +355,16 @@ Report undefined symbols (even with \fB\-\-shared\fR)\.
|
||||
\fB\-\-noinhibit\-exec\fR
|
||||
Create an output file even if errors occur\.
|
||||
.TP
|
||||
\fB\-\-package\-metadata\fR=\fIpercent\-encoded\-string\fR
|
||||
Embed a specified string into the \fB\.note\.package\fR section\. This option is designed for build scripts that generate binary packages, such as \fB\.rpm\fR or \fB\.deb\fR, to include package metadata in each executable\. It simplifies the process of identifying the corresponding package for a given executable or core file\.
|
||||
.IP
|
||||
An argument to this option is treated as percent\-encoded and decoded before being inserted into the section, allowing you to avoid the use of the comma (\fB,\fR) character in the argument\. This is useful because the compiler replaces all occurrences of commas in \fB\-Wl,\fR with spaces before forwarding them to the linker\. Note that \fBmold\fR always interprets the argument as percent\-encoded, so you also need to escape all occurrences of \fB%\fR as \fB%25\fR\.
|
||||
.TP
|
||||
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBnone\fR ]
|
||||
If \fBrelr\fR is specified, all \fBR_*_RELATIVE\fR relocations are put into \fB\.relr\.dyn\fR section instead of \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section\. Since \fB\.relr\.dyn\fR section uses a space\-efficient encoding scheme, specifying this flag can reduce the size of the output\. This is typically most effective for position\-independent executable\.
|
||||
.IP
|
||||
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2022, only ChromeOS, Android and Fuchsia support it\.
|
||||
.TP
|
||||
\fB\-\-package\-metadata\fR=\fIstring\fR
|
||||
Embed \fIstring\fR to a \fB\.note\.package\fR section\. This option is intended to be used by a package management command such as rpm(8) to embed metadata regarding a package to each executable file\.
|
||||
.TP
|
||||
\fB\-\-pie\fR, \fB\-\-pic\-executable\fR, \fB\-\-no\-pie\fR, \fB\-\-no\-pic\-executable\fR
|
||||
Create a position\-independent executable\.
|
||||
.TP
|
||||
@@ -352,7 +400,7 @@ Set \fIaddress\fR to section\. \fIaddress\fR is a hexadecimal number that may st
|
||||
Create a share library\.
|
||||
.TP
|
||||
\fB\-\-spare\-dynamic\-tags\fR=\fInumber\fR
|
||||
Reserve the given \fInumber\fR of tags in \fB\.dynamic\fR section\.
|
||||
Append the given number of \fBDT_NULL\fR entries to the end of the \fB\.dynamic\fR section, so that post\-link processing tools can easily add new dynamic tags by overwriting the null entries\.
|
||||
.TP
|
||||
\fB\-\-start\-lib\fR, \fB\-\-end\-lib\fR
|
||||
Handle object files between \fB\-\-start\-lib\fR and \fB\-\-end\-lib\fR 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)\.
|
||||
@@ -366,6 +414,9 @@ Set target system root directory to \fIdir\fR\.
|
||||
\fB\-\-trace\fR
|
||||
Print name of each input file\.
|
||||
.TP
|
||||
\fB\-\-undefined\-glob\fR=\fIpattern\fR
|
||||
Synonym for \fB\-\-undefined\fR, except that \fB\-\-undefined\-glob\fR takes a glob pattern instead of just a single symbol name\.
|
||||
.TP
|
||||
\fB\-\-undefined\-version\fR, \fB\-\-no\-undefined\-version\fR
|
||||
By default, \fBmold\fR warns on a symbol specified by a version script or by \fB\-\-export\-dynamic\-symbol\fR if it is not defined\. You can silence the warning by \fB\-\-undefined\-version\fR\.
|
||||
.TP
|
||||
@@ -418,8 +469,11 @@ By default, the pages for the stack area (i\.e\. the pages where local variables
|
||||
\fB\-z keep\-text\-section\-prefix\fR, \fB\-z nokeep\-text\-section\-prefix\fR
|
||||
Keep \fB\.text\.hot\fR, \fB\.text\.unknown\fR, \fB\.text\.unlikely\fR, \fB\.text\.startup\fR, and \fB\.text\.exit\fR as separate sections in the final binary instead of merging them as \fB\.text\fR\.
|
||||
.TP
|
||||
\fB\-z rodynamic\fR
|
||||
Make the \fB\.dynamic\fR section read\-only\.
|
||||
.TP
|
||||
\fB\-z relro\fR, \fB\-z norelro\fR
|
||||
Some sections such as \fB\.dynamic\fR 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\.
|
||||
Some sections such as \fB\.dynamic\fR have to be writable only during a module 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\.
|
||||
.IP
|
||||
\fB\-z relro\fR puts such sections into a special segment called \fBrelro\fR\. The dynamic linker makes a relro segment read\-only after it finishes its job\.
|
||||
.IP
|
||||
@@ -443,11 +497,16 @@ Report undefined symbols (even with \fB\-\-shared\fR)\.
|
||||
\fB\-z shstk\fR
|
||||
Enforce shadow stack by turning \fBGNU_PROPERTY_X86_FEATURE_1_SHSTK\fR bit in \fB\.note\.gnu\.property\fR output section\. Shadow stack is part of Intel Control\-flow Enforcement Technology (CET), which is available since Tiger Lake (2020)\.
|
||||
.TP
|
||||
\fB\-z start_stop_visibility\fR=[ \fBhidden\fR | \fBprotected\fR ]
|
||||
If a section name is valid as a C identifier (i\.e\., it matches \fB/^[_a\-zA\-Z][_a\-zA\-Z0\-9]*$/\fR), mold creates \fB__start_SECNAME\fR and \fB__stop_SECNAME\fR symbols to mark the beginning and end of the section, where \fBSECNAME\fR is the section name\.
|
||||
.IP
|
||||
You can make these marker symbols visible from other ELF modules by passing \fB\-z start_stop_visibility=protected\fR\. Default is \fBhidden\fR\.
|
||||
.TP
|
||||
\fB\-z text\fR, \fB\-z notext\fR, \fB\-z textoff\fR
|
||||
\fBmold\fR by default reports an error if dynamic relocations are created in read\-only sections\. If \fB\-z notext\fR or \fB\-z textoff\fR are given, \fBmold\fR creates such dynamic relocations without reporting an error\. \fB\-z text\fR restores the default behavior\.
|
||||
.TP
|
||||
\fB\-z max\-page\-size\fR=\fInumber\fR
|
||||
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\.
|
||||
Some CPU ISAs support multiple memory page sizes\. This option specifies the maximum page size that an output binary can run on\. In general, binaries built for a larger page size can run on a system with a smaller page size, but not vice versa\. The default value is 4 KiB for i386, x86\-64, and RISC\-V, and 64 KiB for ARM64\.
|
||||
.TP
|
||||
\fB\-z nodefaultlib\fR
|
||||
Make the dynamic loader ignore default search paths\.
|
||||
@@ -470,18 +529,18 @@ Mark DSO to be initialized first at runtime\.
|
||||
\fB\-z interpose\fR
|
||||
Mark object to interpose all DSOs but executable\.
|
||||
.TP
|
||||
\fB\-(\fR, \fB\-)\fR, \fB\-EL\fR, \fB\-O\fR\fInumber\fR, \fB\-\-allow\-shlib\-undefined\fR, \fB\-\-dc\fR, \fB\-\-dp\fR, \fB\-\-end\-group\fR, \fB\-\-no\-add\-needed\fR, \fB\-\-no\-allow\-shlib\-undefined\fR, \fB\-\-no\-copy\-dt\-needed\-entries\fR, \fB\-\-no\-fatal\-warnings\fR, \fB\-\-nostdlib\fR, \fB\-\-rpath\-link=Ar dir\fR, \fB\-\-sort\-common\fR, \fB\-\-sort\-section\fR, \fB\-\-start\-group\fR, \fB\-\-warn\-constructors\fR, \fB\-\-warn\-once\fR, \fB\-\-fix\-cortex\-a53\-835769\fR, \fB\-\-fix\-cortex\-a53\-843419\fR, \fB\-z combreloc\fR, \fB\-z common\-page\-size\fR, \fB\-z nocombreloc\fR
|
||||
\fB\-(\fR, \fB\-)\fR, \fB\-EL\fR, \fB\-O\fR\fInumber\fR, \fB\-\-dc\fR, \fB\-\-dp\fR, \fB\-\-end\-group\fR, \fB\-\-no\-add\-needed\fR, \fB\-\-no\-copy\-dt\-needed\-entries\fR, \fB\-\-nostdlib\fR, \fB\-\-rpath\-link=Ar dir\fR, \fB\-\-sort\-common\fR, \fB\-\-sort\-section\fR, \fB\-\-start\-group\fR, \fB\-\-warn\-constructors\fR, \fB\-\-warn\-once\fR, \fB\-\-fix\-cortex\-a53\-835769\fR, \fB\-\-fix\-cortex\-a53\-843419\fR, \fB\-z combreloc\fR, \fB\-z common\-page\-size\fR, \fB\-z nocombreloc\fR
|
||||
Ignored
|
||||
.SH "ENVIRONMENT VARIABLES"
|
||||
.TP
|
||||
\fBMOLD_JOBS\fR
|
||||
If this variable is set to \fB1\fR, only one process of \fBmold\fR runs actively\. A mold process invoked while another active mold process is running will wait before doing anything until the active process exits\.
|
||||
If this variable is set to \fB1\fR, only one \fBmold\fR process will run at a time\. If a new mold process is initiated while another is already active, the new process will wait until the active one completes before starting\.
|
||||
.IP
|
||||
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\.
|
||||
The primary reason for this environment variable is to minimize peak memory usage\. Since mold is designed to operate with high parallelism, running multiple mold instances simultaneously may not be beneficial\. If you execute N instances of mold concurrently, it could require N times the time and N times the memory\. On the other hand, running them one after the other might still take N times longer, but the peak memory usage would be the same as running just a single instance\.
|
||||
.IP
|
||||
If your build system tends to invoke multiple linker processes simultaneously, you may want to try to set this environment variable to \fB1\fR to see if it could improve overall performance\.
|
||||
If your build system invokes multiple linker processes simultaneously and some of them often get killed due to out\-of\-memory errors, you might consider setting this environment variable to \fB1\fR to see if it addresses the OOM issue\.
|
||||
.IP
|
||||
Currently, any value other than 1 is silently ignored\.
|
||||
Currently, any value other than \fB1\fR is silently ignored\.
|
||||
.TP
|
||||
\fBMOLD_DEBUG\fR
|
||||
If this variable is set to a non\-empty string, \fBmold\fR embeds its command\-line options in the output file's \fB\.comment\fR section\.
|
||||
|
||||
+184
-49
@@ -13,12 +13,12 @@ See <https://github.com/rui314/mold#how-to-use>.
|
||||
|
||||
### Compatibility
|
||||
|
||||
**Mold** is designed to be a drop-in replacement for the GNU linkers for
|
||||
`mold` is designed to be a drop-in replacement for the GNU linkers for
|
||||
linking user-land programs. If your user-land program cannot be built due to
|
||||
missing command-line options, please file a bug at
|
||||
<https://github.com/rui314/mold/issues>.
|
||||
|
||||
Mold supports a very limited set of linker script features, which is just
|
||||
`mold` supports a very limited set of linker script features, which is just
|
||||
sufficient to read `/usr/lib/x86_64-linux-gnu/libc.so` on Linux systems (on
|
||||
Linux, that file is contrary to its name not a shared library but an ASCII
|
||||
linker script that loads a real `libc.so` file.)
|
||||
@@ -104,11 +104,10 @@ assume that you accidentally define `atoi` as a global function in your
|
||||
executable that behaves completely differently from the one in the C standard.
|
||||
Then, all `atoi` function calls from any modules (even function calls within
|
||||
libc) are redirected to your function instead of the one in libc, which will
|
||||
very likely cause a problem.
|
||||
|
||||
That is a somewhat surprising consequence for an accidental name conflict. On
|
||||
the other hand, this semantic is sometimes useful because it allows users to
|
||||
override library functions without rebuilding modules containing them.
|
||||
very likely cause a problem. That is a somewhat surprising consequence for an
|
||||
accidental name conflict. On the other hand, this semantic is sometimes useful
|
||||
because it allows users to override library functions without rebuilding
|
||||
modules containing them.
|
||||
|
||||
Whether good or bad, you should keep these semantics in mind to understand
|
||||
Unix linkers' behaviors.
|
||||
@@ -117,7 +116,7 @@ Unix linkers' behaviors.
|
||||
|
||||
`mold`'s output is deterministic. That is, if you pass the same object files
|
||||
and the same command-line options to the same version of `mold`, it is
|
||||
guaranteed that `mold` produces the bit-by-bit identical output. The linker's
|
||||
guaranteed that `mold` produces the bit-for-bit identical output. The linker's
|
||||
internal randomness, such as the timing of thread scheduling or iteration
|
||||
orders of hash tables, doesn't affect the output.
|
||||
|
||||
@@ -126,6 +125,16 @@ 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.
|
||||
|
||||
## OPTION NOTATIONS
|
||||
|
||||
Multi-letter long options may precede either a single dash or double dashes,
|
||||
except for those starting with the letter "o". For historical reasons, long
|
||||
options beginning with "o" must precede double dashes.
|
||||
|
||||
For example, you can spell `--as-needed` as `-as-needed`, but `--omagic` must
|
||||
not be spelled as `-omagic`. `-omagic` will be interpreted not as `--omagic`
|
||||
but as `-o magic`.
|
||||
|
||||
## MOLD-SPECIFIC OPTIONS
|
||||
|
||||
* `--chroot`=_dir_:
|
||||
@@ -142,11 +151,18 @@ arguments.
|
||||
* `--no-color-diagnostics`:
|
||||
Synonym for `--color-diagnostics=never`.
|
||||
|
||||
* `--detach`, `--no-detach`:
|
||||
Permit or do not permit mold to create a debug info file in the background.
|
||||
|
||||
* `--fork`, `--no-fork`:
|
||||
Spawn a child process and let it do the actual linking. When linking a large
|
||||
program, the OS kernel can take a few hundred milliseconds to terminate a
|
||||
`mold` process. `--fork` hides that latency. By default, it does fork.
|
||||
|
||||
Note that `--fork` also hides the resource usage statistics reported by
|
||||
time(2), since it doesn't call waitpid(2) on the child process. If you
|
||||
need those statistics, pass `--no-fork`.
|
||||
|
||||
* `--perf`:
|
||||
Print performance statistics.
|
||||
|
||||
@@ -158,6 +174,15 @@ arguments.
|
||||
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`.
|
||||
|
||||
* `--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` based on the default section merging rules.
|
||||
|
||||
This option changes the behavior so that `mold` merges input sections by
|
||||
name by the default section merging rules.
|
||||
|
||||
* `--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.
|
||||
@@ -179,10 +204,29 @@ arguments.
|
||||
easily test that your program works in the reversed initialization order.
|
||||
|
||||
* `--run` _command_ _arg_...:
|
||||
Run _command_ with `mold` `/usr/bin/ld`. Specifically, `mold` runs a given
|
||||
command with the `LD_PRELOAD` environment set to intercept exec(3) family
|
||||
functions and replaces `argv[0]` with itself if it is `ld`, `ld.gold`, or
|
||||
`ld.lld`.
|
||||
Run _command_ with `mold` as `/usr/bin/ld`. Specifically, `mold` runs a
|
||||
given command with the `LD_PRELOAD` environment set to intercept exec(3)
|
||||
family functions and replaces `argv[0]` with itself if it is `ld`,
|
||||
`ld.gold`, or `ld.lld`.
|
||||
|
||||
* `--separate-debug-file`, `--separate-debug-file`=_file_:
|
||||
Bundle debug info sections into a separate file instead of embedding them in
|
||||
an output executable or a shared library. mold creates a debug info file in
|
||||
the background by default, so that you can start running your executable as
|
||||
soon as possible.
|
||||
|
||||
By default, the debug info file is created in the same directory as is the
|
||||
output file, with the `.dbg` file extension. That filename is embedded into
|
||||
the output file so that `gdb` can automatically find the debug info file for
|
||||
the output file. For more info about gdb features related to separate debug
|
||||
files, see
|
||||
<https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html>.
|
||||
|
||||
mold holds a file lock with flock(2) while creating a debug info file in the
|
||||
background.
|
||||
|
||||
If you don't want to create a debug info file in the background, pass the
|
||||
`--no-detach` option.
|
||||
|
||||
* `--shuffle-sections`, `--shuffle-sections`=_number_:
|
||||
Randomize the output by shuffling the order of input sections before
|
||||
@@ -203,6 +247,15 @@ arguments.
|
||||
`--shuffle-sections`, you can isolate your program's real performance number
|
||||
from the randomness caused by memory layout changes.
|
||||
|
||||
* `--spare-program-headers`=_number_:
|
||||
Append the given number of `PT_NULL` entries to the end of the program
|
||||
header, so that post-link processing tools can easily add new segments by
|
||||
overwriting the null entries.
|
||||
|
||||
Note that ELF requires all `PT_LOAD` segments to be sorted by `p_vaddr`.
|
||||
Therefore, if you add a new LOAD segment, you may need to sort the entire
|
||||
program header.
|
||||
|
||||
* `--stats`:
|
||||
Print input statistics.
|
||||
|
||||
@@ -218,6 +271,33 @@ arguments.
|
||||
* `--quick-exit`, `--no-quick-exit`:
|
||||
Use or do not use `quick_exit` to exit.
|
||||
|
||||
* `-z rewrite-endbr`, `-z norewrite-endbr`:
|
||||
As a security measure, some CPU instruction sets have recently gained a
|
||||
feature to protect control flow integrity by disallowing indirect branches
|
||||
by default. If the feature is enabled, the instruction that is executed
|
||||
immediately after an indirect branch must be an branch target marker
|
||||
instruction, or a CPU-level fault will raise. The marker instruction is also
|
||||
known as "landing pad" instruction, to which indirect branches can land.
|
||||
This feature makes ROP attacks harder to conduct.
|
||||
|
||||
To use the feature, a function whose pointer is taken needs to begin with a
|
||||
landing pad because a function call via a function pointer is compiled to an
|
||||
indirect branch. On the other hand, if a function is called only directly
|
||||
(i.e. referred to only by _direct_ branch instructions), it doesn't have to
|
||||
begin with it.
|
||||
|
||||
By default, the compiler always emits a landing pad at the beginning of each
|
||||
global function because it doesn't know whether or not the function's
|
||||
pointer is taken in another translation unit. As a result, the resulting
|
||||
binary has more attack surface than necessary.
|
||||
|
||||
If `--rewrite-endbr` is given, mold conducts a whole program analysis
|
||||
to identify functions whose addresses are actually taken and rewrites
|
||||
landing pads with no-ops for non-address-taken functions, reducing the
|
||||
attack surface.
|
||||
|
||||
This feature is currently available only on x86-64.
|
||||
|
||||
## GNU-COMPATIBLE OPTIONS
|
||||
|
||||
* `--help`:
|
||||
@@ -303,15 +383,6 @@ arguments.
|
||||
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.
|
||||
|
||||
@@ -321,6 +392,9 @@ arguments.
|
||||
_symbol_, pull out the object file and link it so that the output file
|
||||
contains a definition of _symbol_.
|
||||
|
||||
* `-y` _symbol_, `--trace-symbol`=_symbol_:
|
||||
Trace references to _symbol_.
|
||||
|
||||
* `--Bdynamic`:
|
||||
Link against shared libraries.
|
||||
|
||||
@@ -337,8 +411,18 @@ arguments.
|
||||
This option has the same effect as `--Bsymbolic` but works only for function
|
||||
symbols. Data symbols remain being both imported and exported.
|
||||
|
||||
* `--Bsymbolic-non-weak`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for non-weak
|
||||
symbols. Weak symbols remain being both imported and exported.
|
||||
|
||||
* `--Bsymbolic-non-weak-functions`:
|
||||
This option has the same effect as `--Bsymbolic` but works only for non-weak
|
||||
function symbols. Data symbols and weak function symbols remain being both
|
||||
imported and exported.
|
||||
|
||||
* `--Bno-symbolic`:
|
||||
Cancel `--Bsymbolic` and `--Bsymbolic-functions`.
|
||||
Cancel `--Bsymbolic`, `--Bsymbolic-functions`, `--Bsymbolic-non-weak` and
|
||||
`--Bsymbolic-non-weak-functions`.
|
||||
|
||||
* `--Map`=_file_:
|
||||
Write map file to _file_.
|
||||
@@ -358,6 +442,23 @@ arguments.
|
||||
report an error for duplicate definitions and instead use the first
|
||||
definition.
|
||||
|
||||
* `--allow-shlib-undefined`, `--no-allow-shlib-undefined`:
|
||||
Even if mold succeeds in linking a main executable without undefined symbol
|
||||
errors, you may still encounter symbol lookup errors at runtime because the
|
||||
dynamic linker cannot find some symbols in shared libraries in any ELF
|
||||
module. This occurs because mold ignores undefined symbols in shared
|
||||
libraries by default.
|
||||
|
||||
If you pass `--no-allow-shlib-undefined`, mold verifies that undefined
|
||||
symbols in shared libraries given to the linker can be resolved at
|
||||
link-time. In other words, this converts the runtime error to a link-time
|
||||
error.
|
||||
|
||||
Note that you need to pass all shared libraries, including indirectly
|
||||
dependent ones, to the linker as arguments for `-l`. If a shared library
|
||||
depends on a library that's not passed to the linker, the verification will
|
||||
be skipped for that file.
|
||||
|
||||
* `--as-needed`, `--no-as-needed`:
|
||||
By default, shared libraries given to the linker are unconditionally added
|
||||
to the list of required libraries in an output file. However, shared
|
||||
@@ -369,13 +470,13 @@ arguments.
|
||||
The `--no-as-needed` option restores the default behavior for subsequent
|
||||
files.
|
||||
|
||||
* `--build-id`=[ `md5` | `sha1` | `sha256` | `uuid` | `0x`_hexstring_ | `none` ]:
|
||||
* `--build-id`=[ `md5` | `sha1` | `sha256` | `fast` | `uuid` | `0x`_hexstring_ | `none` ]:
|
||||
Create a `.note.gnu.build-id` section containing a byte string to uniquely
|
||||
identify an output file. `sha256` compute a 256-bit cryptographic hash of an
|
||||
output file and set it to build-id. `md5` and `sha1` compute the same hash
|
||||
but truncate it to 128 and 160 bits, respectively, before setting it to
|
||||
build-id. `uuid` sets a random 128-bit UUID. `0x`_hexstring_ sets
|
||||
_hexstring_.
|
||||
_hexstring_. `fast` is a synonym for `sha256`.
|
||||
|
||||
* `--build-id`:
|
||||
Synonym for `--build-id=sha256`.
|
||||
@@ -431,17 +532,22 @@ arguments.
|
||||
`--disable-new-dtags`, `mold` emits `DT_RPATH` for `--rpath` instead.
|
||||
|
||||
* `--execute-only`:
|
||||
Traditionally, most processors require both executable and readable bits to
|
||||
1 to make the page executable, which allows machine code to be read as data
|
||||
at runtime. This is actually what an attacker often does after gaining a
|
||||
limited control of a process to find pieces of machine code they can use to
|
||||
gain the full control of the process. As a mitigation, some recent
|
||||
processors allows "execute-only" pages. If a page is execute-only, you can
|
||||
call a function there as long as you know its address but can't read it as
|
||||
data.
|
||||
|
||||
This option marks text segments execute-only. This option currently works
|
||||
only on some ARM64 processors.
|
||||
Traditionally, setting the executable bit to 1 for a memory page implies
|
||||
that the page also become readable, which allows machine code to be read
|
||||
as data at runtime. That is actually what an attacker often does after
|
||||
gaining a limited control of a process to find pieces of machine code
|
||||
they can use to gain the full control of the process. As a mitigation,
|
||||
recent processors including some ARM64 ones allows "execute-only" pages.
|
||||
If a page is execute-only, you can call a function there as long as you
|
||||
know its address but can't read it as data.
|
||||
|
||||
This option marks text segments as execute-only by setting just the "X"
|
||||
bit instead of "RX". Note that on most systems, the absence of the "R"
|
||||
bit in the text segment serves just as a hint. If you run a program
|
||||
linked with `--execute-only` on a processor that doesn't support
|
||||
execute-only pages, your executable will likely still function normally,
|
||||
but the text segment will remain readable.
|
||||
|
||||
* `--exclude-libs`=_libraries_ ...:
|
||||
Mark all symbols in the given _libraries_ hidden.
|
||||
@@ -515,6 +621,21 @@ arguments.
|
||||
* `--noinhibit-exec`:
|
||||
Create an output file even if errors occur.
|
||||
|
||||
* `--package-metadata`=_percent-encoded-string_:
|
||||
Embed a specified string into the `.note.package` section. This option
|
||||
is designed for build scripts that generate binary packages, such as
|
||||
`.rpm` or `.deb`, to include package metadata in each executable. It
|
||||
simplifies the process of identifying the corresponding package for a
|
||||
given executable or core file.
|
||||
|
||||
An argument to this option is treated as percent-encoded and decoded
|
||||
before being inserted into the section, allowing you to avoid the use of
|
||||
the comma (`,`) character in the argument. This is useful because the
|
||||
compiler replaces all occurrences of commas in `-Wl,` with spaces before
|
||||
forwarding them to the linker. Note that `mold` always interprets the
|
||||
argument as percent-encoded, so you also need to escape all occurrences
|
||||
of `%` as `%25`.
|
||||
|
||||
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
|
||||
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
|
||||
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
|
||||
@@ -526,11 +647,6 @@ arguments.
|
||||
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.
|
||||
|
||||
@@ -579,7 +695,9 @@ arguments.
|
||||
Create a share library.
|
||||
|
||||
* `--spare-dynamic-tags`=_number_:
|
||||
Reserve the given _number_ of tags in `.dynamic` section.
|
||||
Append the given number of `DT_NULL` entries to the end of the `.dynamic`
|
||||
section, so that post-link processing tools can easily add new dynamic tags
|
||||
by overwriting the null entries.
|
||||
|
||||
* `--start-lib`, `--end-lib`:
|
||||
Handle object files between `--start-lib` and `--end-lib` as if they were in
|
||||
@@ -597,6 +715,10 @@ arguments.
|
||||
* `--trace`:
|
||||
Print name of each input file.
|
||||
|
||||
* `--undefined-glob`=_pattern_:
|
||||
Synonym for `--undefined`, except that `--undefined-glob` takes a glob
|
||||
pattern instead of just a single symbol name.
|
||||
|
||||
* `--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
|
||||
@@ -684,12 +806,14 @@ arguments.
|
||||
`.text.exit` as separate sections in the final binary instead of merging
|
||||
them as `.text`.
|
||||
|
||||
* `-z rodynamic`:
|
||||
Make the `.dynamic` section read-only.
|
||||
|
||||
* `-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.
|
||||
Some sections such as `.dynamic` have to be writable only during a module 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.
|
||||
@@ -724,6 +848,15 @@ arguments.
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z start_stop_visibility`=[ `hidden` | `protected` ]:
|
||||
If a section name is valid as a C identifier (i.e., it matches
|
||||
`/^[_a-zA-Z][_a-zA-Z0-9]*$/`), mold creates `__start_SECNAME` and
|
||||
`__stop_SECNAME` symbols to mark the beginning and end of the section,
|
||||
where `SECNAME` is the section name.
|
||||
|
||||
You can make these marker symbols visible from other ELF modules by passing
|
||||
`-z start_stop_visibility=protected`. Default is `hidden`.
|
||||
|
||||
* `-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
|
||||
@@ -731,9 +864,11 @@ arguments.
|
||||
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.
|
||||
Some CPU ISAs support multiple memory page sizes. This option specifies the
|
||||
maximum page size that an output binary can run on. In general, binaries
|
||||
built for a larger page size can run on a system with a smaller page size,
|
||||
but not vice versa. 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.
|
||||
@@ -758,7 +893,7 @@ arguments.
|
||||
* `-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`:
|
||||
* `-(`, `-)`, `-EL`, `-O`_number_, `--dc`, `--dp`, `--end-group`, `--no-add-needed`, `--no-copy-dt-needed-entries`, `--nostdlib`, `--rpath-link=Ar dir`, `--sort-common`, `--sort-section`, `--start-group`, `--warn-constructors`, `--warn-once`, `--fix-cortex-a53-835769`, `--fix-cortex-a53-843419`, `-z combreloc`, `-z common-page-size`, `-z nocombreloc`:
|
||||
Ignored
|
||||
|
||||
## ENVIRONMENT VARIABLES
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
// Alpha is a 64-bit RISC ISA developed by DEC (Digital Equipment
|
||||
// Corporation) in the early '90s. It aimed to be an ISA that would last
|
||||
// 25 years. DEC expected Alpha would become 1000x faster during that time
|
||||
// span. Since the ISA was developed from scratch for future machines,
|
||||
// it's 64-bit from the beginning. There's no 32-bit variant.
|
||||
//
|
||||
// DEC ported its own Unix (Tru64) to Alpha. Microsoft also ported Windows
|
||||
// NT to it. But it wasn't a huge commercial success.
|
||||
//
|
||||
// DEC was acquired by Compaq in 1997. In the late '90s, Intel and
|
||||
// Hewlett-Packard were advertising that their upcoming Itanium processor
|
||||
// would achieve significantly better performance than RISC processors, so
|
||||
// Compaq decided to discontinue the Alpha processor line to switch to
|
||||
// Itanium. Itanium resulted in a miserable failure, but it still suceeded
|
||||
// to wipe out several RISC processors just by promising overly optimistic
|
||||
// perf numbers. Alpha as an ISA would probably have been fine after 25
|
||||
// years since its introduction (which is 1992 + 25 = 2017), but the
|
||||
// company and its market didn't last that long.
|
||||
//
|
||||
// From the linker's point of view, there are a few peculiarities in its
|
||||
// psABI as shown below:
|
||||
//
|
||||
// - Alpha lacks PC-relative memory load/store instructions, so it uses
|
||||
// register-relative load/store instructions in position-independent
|
||||
// code. Specifically, GP (which is an alias for $r29) is always
|
||||
// maintained to refer to .got+0x8000, and global variables' addresses
|
||||
// are loaded in a GP-relative manner.
|
||||
//
|
||||
// - It looks like even function addresses are first loaded to register
|
||||
// in a GP-relative manner before calling it. We can relax it to
|
||||
// convert the instruction sequence with a direct branch instruction,
|
||||
// but by default, object files don't use a direct branch to call a
|
||||
// function. Therefore, by default, we don't need to create a PLT.
|
||||
// Any function call is made by first reading its address from GOT and
|
||||
// jump to the address.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = ALPHA;
|
||||
|
||||
// A 32-bit immediate can be materialized in a register with a "load high"
|
||||
// and a "load low" instruction sequence. The first instruction sets the
|
||||
// upper 16 bits in a register, and the second one set the lower 16
|
||||
// bits. When doing so, they sign-extend an immediate. Therefore, if the
|
||||
// 15th bit of an immediate happens to be 1, setting a "low half" value
|
||||
// negates the upper 16 bit values that has already been set in a
|
||||
// register. To compensate that, we need to add 0x8000 when setting the
|
||||
// upper 16 bits.
|
||||
static u32 hi(u32 val) {
|
||||
return bits(val + 0x8000, 31, 16);
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 GP = ctx.got->shdr.sh_addr + 0x8000;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFQUAD:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
*(ul32 *)loc = S + A - GP;
|
||||
break;
|
||||
case R_ALPHA_LITERAL:
|
||||
if (A)
|
||||
*(ul16 *)loc = ctx.extra.got->get_addr(sym, A) - GP;
|
||||
else
|
||||
*(ul16 *)loc = GOT + G - GP;
|
||||
break;
|
||||
case R_ALPHA_BRSGP:
|
||||
*(ul32 *)loc |= bits(S + A - P - 4, 22, 0);
|
||||
break;
|
||||
case R_ALPHA_GPDISP:
|
||||
*(ul16 *)loc = hi(GP - P);
|
||||
*(ul16 *)(loc + A) = GP - P;
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_ALPHA_GPRELHIGH:
|
||||
*(ul16 *)loc = hi(S + A - GP);
|
||||
break;
|
||||
case R_ALPHA_GPRELLOW:
|
||||
*(ul16 *)loc = S + A - GP;
|
||||
break;
|
||||
case R_ALPHA_TLSGD:
|
||||
*(ul16 *)loc = sym.get_tlsgd_addr(ctx) - GP;
|
||||
break;
|
||||
case R_ALPHA_TLSLDM:
|
||||
*(ul16 *)loc = ctx.got->get_tlsld_addr(ctx) - GP;
|
||||
break;
|
||||
case R_ALPHA_DTPRELHI:
|
||||
*(ul16 *)loc = hi(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_ALPHA_DTPRELLO:
|
||||
*(ul16 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ALPHA_GOTTPREL:
|
||||
*(ul16 *)loc = sym.get_gottp_addr(ctx) + A - GP;
|
||||
break;
|
||||
case R_ALPHA_TPRELHI:
|
||||
*(ul16 *)loc = hi(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_ALPHA_TPRELLO:
|
||||
*(ul16 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ALPHA_LITUSE:
|
||||
case R_ALPHA_HINT:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFLONG:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_ALPHA_REFQUAD:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on Alpha";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ALPHA_REFQUAD:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_LITERAL:
|
||||
if (rel.r_addend)
|
||||
ctx.extra.got->add_symbol(sym, rel.r_addend);
|
||||
else
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ALPHA_SREL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_BRSGP:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ALPHA_TLSGD:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ALPHA_TLSLDM:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ALPHA_GOTTPREL:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ALPHA_TPRELHI:
|
||||
case R_ALPHA_TPRELLO:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ALPHA_GPREL32:
|
||||
case R_ALPHA_LITUSE:
|
||||
case R_ALPHA_GPDISP:
|
||||
case R_ALPHA_HINT:
|
||||
case R_ALPHA_GPRELHIGH:
|
||||
case R_ALPHA_GPRELLOW:
|
||||
case R_ALPHA_DTPRELHI:
|
||||
case R_ALPHA_DTPRELLO:
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An R_ALPHA_LITERAL relocation may request the linker to create a GOT
|
||||
// entry for an external symbol with a non-zero addend. This is an unusual
|
||||
// request which is not found in any other targets.
|
||||
//
|
||||
// Referring an external symbol with a non-zero addend is a bad practice
|
||||
// because we need to create as many dynamic relocations as the number of
|
||||
// distinctive addends for the same symbol.
|
||||
//
|
||||
// We don't want to mess up the implementation of the common GOT section
|
||||
// for Alpha. So we create another GOT-like section, .alpha_got. Any GOT
|
||||
// entry for an R_ALPHA_LITERAL reloc with a non-zero addend is created
|
||||
// not in .got but in .alpha_got.
|
||||
//
|
||||
// Since .alpha_got entries are accessed relative to GP, .alpha_got
|
||||
// needs to be close enough to .got. It's actually placed next to .got.
|
||||
void AlphaGotSection::add_symbol(Symbol<E> &sym, i64 addend) {
|
||||
assert(addend);
|
||||
std::scoped_lock lock(mu);
|
||||
entries.push_back({&sym, addend});
|
||||
}
|
||||
|
||||
bool operator<(const AlphaGotSection::Entry &a, const AlphaGotSection::Entry &b) {
|
||||
return std::tuple(a.sym->file->priority, a.sym->sym_idx, a.addend) <
|
||||
std::tuple(b.sym->file->priority, b.sym->sym_idx, b.addend);
|
||||
};
|
||||
|
||||
u64 AlphaGotSection::get_addr(Symbol<E> &sym, i64 addend) {
|
||||
auto it = std::lower_bound(entries.begin(), entries.end(), Entry{&sym, addend});
|
||||
assert(it != entries.end());
|
||||
return this->shdr.sh_addr + (it - entries.begin()) * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
i64 AlphaGotSection::get_reldyn_size(Context<E> &ctx) const {
|
||||
i64 n = 0;
|
||||
for (const Entry &e : entries)
|
||||
if (e.sym->is_imported || (ctx.arg.pic && !e.sym->is_absolute()))
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
void AlphaGotSection::finalize() {
|
||||
sort(entries);
|
||||
remove_duplicates(entries);
|
||||
shdr.sh_size = entries.size() * sizeof(Word<E>);
|
||||
}
|
||||
|
||||
void AlphaGotSection::copy_buf(Context<E> &ctx) {
|
||||
ElfRel<E> *dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < entries.size(); i++) {
|
||||
Entry &e = entries[i];
|
||||
u64 P = this->shdr.sh_addr + sizeof(Word<E>) * i;
|
||||
ul64 *buf = (ul64 *)(ctx.buf + this->shdr.sh_offset + sizeof(Word<E>) * i);
|
||||
|
||||
if (e.sym->is_imported) {
|
||||
*buf = ctx.arg.apply_dynamic_relocs ? e.addend : 0;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, e.sym->get_dynsym_idx(ctx), e.addend);
|
||||
} else {
|
||||
*buf = e.sym->get_addr(ctx) + e.addend;
|
||||
if (ctx.arg.pic && !e.sym->is_absolute())
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, *buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,555 +0,0 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <limits>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
typedef enum {
|
||||
NONE, ERROR, COPYREL, DYN_COPYREL, PLT, CPLT, DYN_CPLT, DYNREL,
|
||||
BASEREL, IFUNC_DYNREL,
|
||||
} Action;
|
||||
|
||||
template <typename E>
|
||||
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
|
||||
if (get_contents() != other.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<const ElfRel<E>> x = get_rels();
|
||||
std::span<const ElfRel<E>> y = other.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++) {
|
||||
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
|
||||
x[i].r_type != y[i].r_type ||
|
||||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
|
||||
get_addend(input_section, x[i]) != get_addend(other.input_section, y[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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};
|
||||
|
||||
if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
}
|
||||
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
// directly into the mmap'ed output file, but we want to uncompress
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
uncompress_to(ctx, buf);
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress_to(Context<E> &ctx, u8 *buf) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), contents.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
|
||||
|
||||
switch (hdr.ch_type) {
|
||||
case ELFCOMPRESS_ZLIB: {
|
||||
unsigned long size = sh_size;
|
||||
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
|
||||
Fatal(ctx) << *this << ": uncompress failed";
|
||||
assert(size == sh_size);
|
||||
break;
|
||||
}
|
||||
case ELFCOMPRESS_ZSTD:
|
||||
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
|
||||
Fatal(ctx) << *this << ": ZSTD_decompress failed";
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void scan_rel(Context<E> &ctx, InputSection<E> &isec, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto error = [&] {
|
||||
std::string msg = sym.is_absolute() ? "-fno-PIC" : "-fPIC";
|
||||
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with " << msg;
|
||||
};
|
||||
|
||||
auto check_textrel = [&] {
|
||||
if (!writable) {
|
||||
if (ctx.arg.z_text) {
|
||||
error();
|
||||
} else if (ctx.arg.warn_textrel) {
|
||||
Warn(ctx) << isec << ": relocation against symbol `" << sym
|
||||
<< "' in read-only section";
|
||||
}
|
||||
ctx.has_textrel = true;
|
||||
}
|
||||
};
|
||||
|
||||
auto copyrel = [&] {
|
||||
assert(sym.is_imported);
|
||||
if (sym.esym().st_visibility == STV_PROTECTED) {
|
||||
Error(ctx) << isec
|
||||
<< ": cannot make copy relocation for protected symbol '" << sym
|
||||
<< "', defined in " << *sym.file << "; recompile with -fPIC";
|
||||
}
|
||||
sym.flags |= NEEDS_COPYREL;
|
||||
};
|
||||
|
||||
auto dynrel = [&] {
|
||||
check_textrel();
|
||||
isec.file.num_dynrel++;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
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_DYNREL:
|
||||
// Create an IRELATIVE relocation for a GNU ifunc symbol.
|
||||
//
|
||||
// We usually create an IRELATIVE relocation in .got for each ifunc.
|
||||
// However, if a statically-initialized pointer is initialized to an
|
||||
// ifunc's address, we have no choice other than emitting an IRELATIVE
|
||||
// relocation for each such pointer.
|
||||
dynrel();
|
||||
ctx.num_ifunc_dynrels++;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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 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 pointer size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// pointer size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Shared object
|
||||
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_dyn_absrel_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return ctx.arg.pic ? IFUNC_DYNREL : NONE;
|
||||
|
||||
// This is a decision table for absolute relocations for the pointer
|
||||
// size data (e.g. R_X86_64_64). Unlike the absrel_table, we can emit
|
||||
// a dynamic relocation if we cannot resolve an address at link-time.
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYN_COPYREL, DYN_CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Action get_ppc64_toc_action(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (sym.is_ifunc())
|
||||
return IFUNC_DYNREL;
|
||||
|
||||
// As a special case, we do not create copy relocations nor canonical
|
||||
// PLTs for .toc sections. PPC64's .toc is a compiler-generated
|
||||
// GOT-like section, and no user-generated code directly uses values
|
||||
// in it.
|
||||
static Action table[3][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Shared object
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // Position-independent exec
|
||||
{ NONE, NONE, DYNREL, DYNREL }, // Position-dependent exec
|
||||
};
|
||||
|
||||
return table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_pcrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_dyn_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
scan_rel(ctx, *this, sym, rel, get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
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,
|
||||
u64 S, i64 A, u64 P, ElfRel<E> *&dynrel,
|
||||
Action action) {
|
||||
bool writable = (isec.shdr().sh_flags & SHF_WRITE);
|
||||
|
||||
auto emit_abs_dynrel = [&] {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_ABS, sym.get_dynsym_idx(ctx), A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = A;
|
||||
};
|
||||
|
||||
switch (action) {
|
||||
case COPYREL:
|
||||
case CPLT:
|
||||
case NONE:
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case BASEREL:
|
||||
if (isec.is_relr_reloc(ctx, rel)) {
|
||||
*(Word<E> *)loc = S + A;
|
||||
} else {
|
||||
*dynrel++ = ElfRel<E>(P, E::R_RELATIVE, 0, S + A);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = S + A;
|
||||
}
|
||||
break;
|
||||
case DYN_COPYREL:
|
||||
if (writable || !ctx.arg.z_copyreloc)
|
||||
emit_abs_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYN_CPLT:
|
||||
if (writable)
|
||||
emit_abs_dynrel();
|
||||
else
|
||||
*(Word<E> *)loc = S + A;
|
||||
break;
|
||||
case DYNREL:
|
||||
emit_abs_dynrel();
|
||||
break;
|
||||
case IFUNC_DYNREL:
|
||||
if constexpr (supports_ifunc<E>) {
|
||||
u64 addr = sym.get_addr(ctx, NO_PLT) + A;
|
||||
*dynrel++ = ElfRel<E>(P, E::R_IRELATIVE, 0, addr);
|
||||
if (ctx.arg.apply_dynamic_relocs)
|
||||
*(Word<E> *)loc = addr;
|
||||
} else {
|
||||
unreachable();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_dyn_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> **dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
|
||||
get_dyn_absrel_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::apply_toc_rel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel, u8 *loc,
|
||||
u64 S, i64 A, u64 P,
|
||||
ElfRel<E> **dynrel) {
|
||||
apply_absrel(ctx, *this, sym, rel, loc, S, A, P, *dynrel,
|
||||
get_ppc64_toc_action(ctx, sym));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data
|
||||
if constexpr (is_riscv<E>)
|
||||
copy_contents_riscv(ctx, buf);
|
||||
else
|
||||
uncompress_to(ctx, buf);
|
||||
|
||||
// Apply relocations
|
||||
if (!ctx.arg.relocatable) {
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
|
||||
if constexpr (is_x86_64<E>) {
|
||||
u8 endbr[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
|
||||
// Rewrite the leading endbr instruction with a nop if the section
|
||||
// is not address-taken.
|
||||
if (ctx.arg.z_rewrite_endbr && (shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
!address_taken && 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) 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) {
|
||||
std::string_view name = file.symbol_strtab.data() + esym.st_name;
|
||||
if (ctx.arg.demangle)
|
||||
return demangle(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Test if the symbol a given relocation refers to has already been resolved.
|
||||
// If not, record that error and returns true.
|
||||
template <typename E>
|
||||
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];
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,423 +0,0 @@
|
||||
// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually
|
||||
// a shared object file but an ASCII text file containing a linker
|
||||
// script to include a "real" libc.so file. Therefore, we need to
|
||||
// support a (very limited) subset of the linker script language.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
template <typename E>
|
||||
static thread_local MappedFile<Context<E>> *current_file;
|
||||
|
||||
template <typename E>
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
|
||||
|
||||
static std::string_view get_line(std::string_view input, const char *pos) {
|
||||
assert(input.data() <= pos);
|
||||
assert(pos < input.data() + input.size());
|
||||
|
||||
i64 start = input.rfind('\n', pos - input.data());
|
||||
if (start == input.npos)
|
||||
start = 0;
|
||||
else
|
||||
start++;
|
||||
|
||||
i64 end = input.find('\n', pos - input.data());
|
||||
if (end == input.npos)
|
||||
end = input.size();
|
||||
|
||||
return input.substr(start, end - start);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
class SyntaxError {
|
||||
public:
|
||||
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
|
||||
std::string_view contents = current_file<E>->get_contents();
|
||||
std::string_view line = get_line(contents, errpos.data());
|
||||
|
||||
i64 lineno = 1;
|
||||
for (i64 i = 0; contents.data() + i < line.data(); i++)
|
||||
if (contents[i] == '\n')
|
||||
lineno++;
|
||||
|
||||
i64 column = errpos.data() - line.data();
|
||||
|
||||
std::stringstream ss;
|
||||
ss << current_file<E>->name << ":" << lineno << ": ";
|
||||
i64 indent = (i64)ss.tellp() + strlen("mold: ");
|
||||
ss << line << "\n" << std::setw(indent + column) << " " << "^ ";
|
||||
out << ss.str();
|
||||
}
|
||||
|
||||
template <class T> SyntaxError &operator<<(T &&val) {
|
||||
out << std::forward<T>(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[noreturn]] ~SyntaxError() = default;
|
||||
|
||||
Fatal<Context<E>> out;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string_view>
|
||||
tokenize(Context<E> &ctx, std::string_view input) {
|
||||
std::vector<std::string_view> vec;
|
||||
while (!input.empty()) {
|
||||
if (isspace(input[0])) {
|
||||
input = input.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input.starts_with("/*")) {
|
||||
i64 pos = input.find("*/", 2);
|
||||
if (pos == std::string_view::npos)
|
||||
SyntaxError(ctx, input) << "unclosed comment";
|
||||
input = input.substr(pos + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[0] == '#') {
|
||||
i64 pos = input.find("\n", 1);
|
||||
if (pos == std::string_view::npos)
|
||||
break;
|
||||
input = input.substr(pos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[0] == '"') {
|
||||
i64 pos = input.find('"', 1);
|
||||
if (pos == std::string_view::npos)
|
||||
SyntaxError(ctx, input) << "unclosed string literal";
|
||||
vec.push_back(input.substr(0, pos + 1));
|
||||
input = input.substr(pos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
i64 pos = input.find_first_not_of(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789_.$/\\~=+[]*?-!^:");
|
||||
|
||||
if (pos == 0)
|
||||
pos = 1;
|
||||
else if (pos == input.npos)
|
||||
pos = input.size();
|
||||
|
||||
vec.push_back(input.substr(0, pos));
|
||||
input = input.substr(pos);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::span<std::string_view>
|
||||
skip(Context<E> &ctx, std::span<std::string_view> tok, std::string_view str) {
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected '" << str
|
||||
<< "', but got EOF";
|
||||
if (tok[0] != str)
|
||||
SyntaxError(ctx, tok[0]) << "expected '" << str << "'";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
static std::string_view unquote(std::string_view s) {
|
||||
if (s.size() > 0 && s[0] == '"') {
|
||||
assert(s[s.size() - 1] == '"');
|
||||
return s.substr(1, s.size() - 2);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::span<std::string_view>
|
||||
read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
tok = skip(ctx, tok, "(");
|
||||
while (!tok.empty() && tok[0] != ")")
|
||||
tok = tok.subspan(1);
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
|
||||
std::string rel = to_abs_path(path)
|
||||
.lexically_relative(to_abs_path(ctx.arg.sysroot))
|
||||
.string();
|
||||
return rel != "." && !rel.starts_with("../");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view tok) {
|
||||
std::string str(unquote(tok));
|
||||
|
||||
// GNU ld prepends the sysroot if a pathname starts with '/' and the
|
||||
// script being processed is in the sysroot. We do the same.
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
|
||||
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
|
||||
|
||||
if (str.starts_with('=')) {
|
||||
std::string path;
|
||||
if (ctx.arg.sysroot.empty())
|
||||
path = str.substr(1);
|
||||
else
|
||||
path = ctx.arg.sysroot + str.substr(1);
|
||||
return MappedFile<Context<E>>::must_open(ctx, path);
|
||||
}
|
||||
|
||||
if (str.starts_with("-l"))
|
||||
return find_library(ctx, str.substr(2));
|
||||
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, str))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + str;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
return mf;
|
||||
}
|
||||
|
||||
SyntaxError(ctx, tok) << "library not found: " << str;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::span<std::string_view>
|
||||
read_group(Context<E> &ctx, std::span<std::string_view> tok) {
|
||||
tok = skip(ctx, tok, "(");
|
||||
|
||||
while (!tok.empty() && tok[0] != ")") {
|
||||
if (tok[0] == "AS_NEEDED") {
|
||||
bool orig = ctx.as_needed;
|
||||
ctx.as_needed = true;
|
||||
tok = read_group(ctx, tok.subspan(1));
|
||||
ctx.as_needed = orig;
|
||||
continue;
|
||||
}
|
||||
|
||||
MappedFile<Context<E>> *mf = resolve_path(ctx, tok[0]);
|
||||
read_file(ctx, mf);
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0] == "OUTPUT_FORMAT") {
|
||||
tok = read_output_format(ctx, tok.subspan(1));
|
||||
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
|
||||
tok = read_group(ctx, tok.subspan(1));
|
||||
} else if (tok[0] == "VERSION") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script(ctx, tok);
|
||||
tok = skip(ctx, tok, "}");
|
||||
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
|
||||
get_symbol(ctx, unquote(tok[2])));
|
||||
tok = tok.subspan(4);
|
||||
} else if (tok[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
SyntaxError(ctx, tok[0]) << "unknown linker script token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string_view
|
||||
get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
|
||||
if (tok[2] == "elf64-x86-64")
|
||||
return X86_64::target_name;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return I386::target_name;
|
||||
}
|
||||
|
||||
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
|
||||
tok[1] == "(")
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(unquote(tok[2]))))
|
||||
return get_machine_type(ctx, mf);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool read_label(std::span<std::string_view> &tok,
|
||||
std::string label) {
|
||||
if (tok.size() >= 1 && tok[0] == label + ":") {
|
||||
tok = tok.subspan(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") {
|
||||
tok = tok.subspan(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
std::string_view ver_str, u16 ver_idx,
|
||||
bool is_global, bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (read_label(tok, "global")) {
|
||||
is_global = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_label(tok, "local")) {
|
||||
is_global = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands( ctx, tok, ver_str, ver_idx, is_global, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, is_global, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
ctx.default_version_from_version_script = true;
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
ver_str, VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "}")
|
||||
return;
|
||||
tok = skip(ctx, tok, ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
|
||||
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
std::string_view ver_str;
|
||||
u16 ver_idx;
|
||||
|
||||
if (tok[0] == "{") {
|
||||
ver_str = "global";
|
||||
ver_idx = VER_NDX_GLOBAL;
|
||||
} else {
|
||||
ver_str = tok[0];
|
||||
ver_idx = next_ver++;
|
||||
ctx.arg.version_definitions.push_back(std::string(tok[0]));
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_version_script_commands(ctx, tok, ver_str, ver_idx, true, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
if (!tok.empty() && tok[0] != ";")
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_version_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
read_version_script(ctx, tok);
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
} else {
|
||||
tok = skip(ctx, tok, "\"C++\"");
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, true);
|
||||
}
|
||||
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), current_file<E>->name,
|
||||
"global", VER_NDX_GLOBAL, is_cpp});
|
||||
|
||||
tok = skip(ctx, tok.subspan(1), ";");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_dynamic_list(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
current_file<E> = mf;
|
||||
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
|
||||
std::span<std::string_view> tok = vec;
|
||||
|
||||
tok = skip(ctx, tok, "{");
|
||||
read_dynamic_list_commands(ctx, tok, false);
|
||||
tok = skip(ctx, tok, "}");
|
||||
tok = skip(ctx, tok, ";");
|
||||
|
||||
if (!tok.empty())
|
||||
SyntaxError(ctx, tok[0]) << "trailing garbage token";
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void parse_linker_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::string_view get_script_output_type(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_version_script(Context<E> &, MappedFile<Context<E>> *);
|
||||
template void parse_dynamic_list(Context<E> &, MappedFile<Context<E>> *);
|
||||
|
||||
} // namespace mold::elf
|
||||
@@ -1,5 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
# include "lto-win32.cc"
|
||||
#else
|
||||
# include "lto-unix.cc"
|
||||
#endif
|
||||
-304
@@ -1,304 +0,0 @@
|
||||
// RISC instructions are usually up to 4 bytes long, so the immediates of
|
||||
// their branch instructions are naturally smaller than 32 bits. This is
|
||||
// contrary to x86-64 on which branch instructions take 4 bytes immediates
|
||||
// and can jump to anywhere within PC ± 2 GiB.
|
||||
//
|
||||
// In fact, ARM32's branch instructions can jump only within ±16 MiB and
|
||||
// ARM64's ±128 MiB, for example. If a branch target is further than that,
|
||||
// we need to let it branch to a linker-synthesized code sequence that
|
||||
// construct a full 32 bit address in a register and jump there. That
|
||||
// linker-synthesized code is called "thunk".
|
||||
//
|
||||
// The function in this file creates thunks.
|
||||
//
|
||||
// Note that although thunks play an important role in an executable, they
|
||||
// don't take up too much space in it. For example, among the clang-16's
|
||||
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
|
||||
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
|
||||
// need more thunks on ARM32 whose branch range is shorter than ARM64.
|
||||
// That said, the total size of thunks still isn't that much. Therefore,
|
||||
// we don't need to try too hard to reduce thunk size to the absolute
|
||||
// minimum.
|
||||
|
||||
#if MOLD_ARM32 || MOLD_ARM64 || MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2 || \
|
||||
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
// Returns a branch reach in bytes for a given target.
|
||||
static consteval i64 max_distance() {
|
||||
// ARM64's branch has 26 bits immediate. The immediate is padded with
|
||||
// implicit two-bit zeros because all instructions are 4 bytes aligned
|
||||
// and therefore the least two bits are always zero. So the branch
|
||||
// operand is effectively 28 bits long. That means the branch range is
|
||||
// [-2^27, 2^27) or PC ± 128 MiB.
|
||||
//
|
||||
// LoongArch's BR instruction also takes a 26 bit immediate.
|
||||
if (is_arm64<E> || is_loongarch<E>)
|
||||
return 1 << 27;
|
||||
|
||||
// ARM32's Thumb branch has 24 bits immediate, and the instructions are
|
||||
// aligned to 2, so it's effectively 25 bits. It's [-2^24, 2^24) or PC ±
|
||||
// 16 MiB.
|
||||
//
|
||||
// ARM32's non-Thumb branches have twice longer range than its Thumb
|
||||
// counterparts, but we conservatively use the Thumb's limitation.
|
||||
if (is_arm32<E>)
|
||||
return 1 << 24;
|
||||
|
||||
// PPC's branch has 24 bits immediate, and the instructions are aligned
|
||||
// to 4, therefore the reach is [-2^25, 2^25) or PC ± 32 MiB.
|
||||
assert(is_ppc<E>);
|
||||
return 1 << 25;
|
||||
}
|
||||
|
||||
// We create thunks for each 12.8/1.6/3.2 MiB code block for
|
||||
// ARM64/ARM32/PPC, respectively.
|
||||
static constexpr i64 batch_size = max_distance() / 10;
|
||||
|
||||
// We assume that a single thunk group is smaller than 100 KiB.
|
||||
static constexpr i64 max_thunk_size = 102400;
|
||||
|
||||
static_assert(max_thunk_size / E::thunk_size < INT16_MAX);
|
||||
|
||||
template <typename E>
|
||||
static bool is_reachable(Context<E> &ctx, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
// We create thunks with a pessimistic assumption that all
|
||||
// out-of-section relocations would be out-of-range.
|
||||
InputSection<E> *isec2 = sym.get_input_section();
|
||||
if (!isec2 || isec.output_section != isec2->output_section)
|
||||
return false;
|
||||
|
||||
// Even if the target is the same section, we branch to its PLT
|
||||
// if it has one. So a symbol with a PLT is also considered an
|
||||
// out-of-section reference.
|
||||
if (sym.has_plt(ctx))
|
||||
return false;
|
||||
|
||||
// If the target section is in the same output section but
|
||||
// hasn't got any address yet, that's unreacahble.
|
||||
if (isec2->offset == -1)
|
||||
return false;
|
||||
|
||||
// Thumb and ARM B instructions cannot be converted to BX, so we
|
||||
// always have to make them jump to a thunk to switch processor mode
|
||||
// even if their destinations are within their ranges.
|
||||
if constexpr (is_arm32<E>) {
|
||||
bool is_thumb = sym.get_addr(ctx) & 1;
|
||||
if ((rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
|
||||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
|
||||
(rel.r_type == R_ARM_PLT32 && is_thumb))
|
||||
return false;
|
||||
}
|
||||
|
||||
// PowerPC before Power9 lacks PC-relative load/store instructions.
|
||||
// Functions compiled for Power9 or earlier assume that r2 points to
|
||||
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
|
||||
// We need to a thunk to recompute r2 for interworking.
|
||||
if constexpr (is_ppc64v2<E>) {
|
||||
if (rel.r_type == R_PPC64_REL24 && !sym.esym().preserves_r2())
|
||||
return false;
|
||||
if (rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().uses_toc())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute a distance between the relocated place and the symbol
|
||||
// and check if they are within reach.
|
||||
i64 S = sym.get_addr(ctx, NO_OPD);
|
||||
i64 A = get_addend(isec, rel);
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
i64 val = S + A - P;
|
||||
return -max_distance() <= val && val < max_distance();
|
||||
}
|
||||
|
||||
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
|
||||
for (Symbol<E> *sym : thunk.symbols) {
|
||||
sym->extra.thunk_idx = -1;
|
||||
sym->extra.thunk_sym_idx = -1;
|
||||
sym->flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Scan relocations to collect symbols that need thunks.
|
||||
static void scan_rels(Context<E> &ctx, InputSection<E> &isec,
|
||||
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 (!is_func_call_rel(rel))
|
||||
continue;
|
||||
|
||||
// Skip if the symbol is undefined. apply_reloc() will report an error.
|
||||
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
|
||||
if (!sym.file)
|
||||
continue;
|
||||
|
||||
// Skip if the destination is within reach.
|
||||
if (is_reachable(ctx, isec, sym, rel))
|
||||
continue;
|
||||
|
||||
// This relocation needs a thunk. If the symbol is already in a
|
||||
// previous thunk, reuse it.
|
||||
if (sym.extra.thunk_idx != -1) {
|
||||
range_extn[i].thunk_idx = sym.extra.thunk_idx;
|
||||
range_extn[i].sym_idx = sym.extra.thunk_sym_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, add the symbol to the current thunk if it's not
|
||||
// added already.
|
||||
range_extn[i].thunk_idx = thunk_idx;
|
||||
range_extn[i].sym_idx = -1;
|
||||
|
||||
if (sym.flags.exchange(-1) == 0) {
|
||||
std::scoped_lock lock(thunk.mu);
|
||||
thunk.symbols.push_back(&sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
|
||||
using Thunk = RangeExtensionThunk<E>;
|
||||
|
||||
std::span<InputSection<E> *> m = members;
|
||||
if (m.empty())
|
||||
return;
|
||||
|
||||
// Initialize input sections with a dummy offset so that we can
|
||||
// distinguish sections that have got an address with the one who
|
||||
// haven't.
|
||||
tbb::parallel_for_each(m, [](InputSection<E> *isec) {
|
||||
isec->offset = -1;
|
||||
});
|
||||
|
||||
// We create thunks from the beginning of the section to the end.
|
||||
// We manage progress using four offsets which increase monotonically.
|
||||
// The locations they point to are always A <= B <= C <= D.
|
||||
//
|
||||
// Input sections between B and C are in the current batch.
|
||||
//
|
||||
// A is the input section with the smallest address than can reach
|
||||
// from the current batch.
|
||||
//
|
||||
// D is the input section with the largest address such that the thunk
|
||||
// is reachable from the current batch if it's inserted right before D.
|
||||
//
|
||||
// ................................ <input sections> ............
|
||||
// A B C D
|
||||
// ^ We insert a thunk for the current batch just before D
|
||||
// <---> The current batch, which is smaller than BATCH_SIZE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <--------> Smaller than MAX_DISTANCE
|
||||
// <-------------> Reachable from the current batch
|
||||
i64 a = 0;
|
||||
i64 b = 0;
|
||||
i64 c = 0;
|
||||
i64 d = 0;
|
||||
i64 offset = 0;
|
||||
|
||||
// The smallest thunk index that is reachable from the current batch.
|
||||
i64 t = 0;
|
||||
|
||||
while (b < m.size()) {
|
||||
// Move D foward as far as we can jump from B to a thunk at D.
|
||||
auto d_thunk_end = [&] {
|
||||
u64 d_end = align_to(offset, 1 << m[d]->p2align) + m[d]->sh_size;
|
||||
return align_to(d_end, Thunk::alignment) + max_thunk_size;
|
||||
};
|
||||
|
||||
while (d < m.size() &&
|
||||
(b == d || d_thunk_end() <= m[b]->offset + max_distance())) {
|
||||
offset = align_to(offset, 1 << m[d]->p2align);
|
||||
m[d]->offset = offset;
|
||||
offset += m[d]->sh_size;
|
||||
d++;
|
||||
}
|
||||
|
||||
// Move C forward so that C is apart from B by BATCH_SIZE. We want
|
||||
// to make sure that there's at least one section between B and C
|
||||
// to ensure progress.
|
||||
c = b + 1;
|
||||
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
|
||||
c++;
|
||||
|
||||
// Move A forward so that A is reachable from C.
|
||||
i64 c_offset = (c == m.size()) ? offset : m[c]->offset;
|
||||
while (a < b && m[a]->offset + max_distance() < c_offset)
|
||||
a++;
|
||||
|
||||
// Erase references to out-of-range thunks.
|
||||
while (t < thunks.size() && thunks[t]->offset < m[a]->offset)
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
// Create a new thunk and place it at D.
|
||||
offset = align_to(offset, Thunk::alignment);
|
||||
i64 thunk_idx = thunks.size();
|
||||
Thunk *thunk = new Thunk(*this, offset);
|
||||
thunks.emplace_back(thunk);
|
||||
|
||||
// Scan relocations between B and C to collect symbols that need
|
||||
// entries in the new thunk.
|
||||
tbb::parallel_for_each(m.begin() + b, m.begin() + c,
|
||||
[&](InputSection<E> *isec) {
|
||||
scan_rels(ctx, *isec, *thunk, thunk_idx);
|
||||
});
|
||||
|
||||
// Now that we know the number of symbols in the thunk, we can compute
|
||||
// the thunk's size.
|
||||
assert(thunk->size() < max_thunk_size);
|
||||
offset += thunk->size();
|
||||
|
||||
// Sort symbols added to the thunk to make the output deterministic.
|
||||
sort(thunk->symbols, [](Symbol<E> *a, Symbol<E> *b) {
|
||||
return std::tuple{a->file->priority, a->sym_idx} <
|
||||
std::tuple{b->file->priority, b->sym_idx};
|
||||
});
|
||||
|
||||
// Assign offsets within the thunk to the symbols.
|
||||
for (i64 i = 0; 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) {
|
||||
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_idx)
|
||||
range_extn[i].sym_idx = syms[rels[i].r_sym]->extra.thunk_sym_idx;
|
||||
});
|
||||
|
||||
// Move B forward to point to the begining of the next batch.
|
||||
b = c;
|
||||
}
|
||||
|
||||
while (t < thunks.size())
|
||||
reset_thunk(*thunks[t++]);
|
||||
|
||||
this->shdr.sh_size = offset;
|
||||
|
||||
for (InputSection<E> *isec : members)
|
||||
this->shdr.sh_addralign =
|
||||
std::max<u32>(this->shdr.sh_addralign, 1 << isec->p2align);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
+30
-33
@@ -1,54 +1,51 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to build mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# The first line for each distro installs a build dependency.
|
||||
# The second line installs extra packages for unittests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-20.* | pop-20.*)
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++ g++-10
|
||||
apt-get install -y file
|
||||
apt-get install -y cmake gcc g++ clang gdb
|
||||
;;
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++
|
||||
apt-get install -y file
|
||||
fedora | amzn | rhel | centos)
|
||||
dnf install -y gcc-g++ cmake glibc-static libstdc++-static diffutils util-linux tar
|
||||
;;
|
||||
fedora-*)
|
||||
dnf install -y gcc-g++ cmake
|
||||
dnf install -y glibc-static file libstdc++-static diffutils util-linux
|
||||
rocky)
|
||||
dnf install -y gcc-g++ cmake diffutils util-linux
|
||||
;;
|
||||
opensuse-leap-*)
|
||||
zypper install -y make cmake gcc-c++ gcc11-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
opensuse-*)
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
opensuse-tumbleweed-*)
|
||||
zypper install -y make cmake gcc-c++
|
||||
zypper install -y glibc-devel-static tar diffutils util-linux
|
||||
;;
|
||||
gentoo-*)
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
emerge dev-util/cmake
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
|
||||
;;
|
||||
arch-* | archarm-* | artix-*)
|
||||
pacman -Sy
|
||||
pacman -S --needed --noconfirm base-devel cmake util-linux
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --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
|
||||
void)
|
||||
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine-*)
|
||||
alpine)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
clear-linux-os)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
almalinux)
|
||||
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
|
||||
;;
|
||||
freebsd)
|
||||
pkg update
|
||||
pkg install -y cmake bash binutils gcc
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
|
||||
@@ -9,9 +9,9 @@ set -x
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-* | pop-* | linuxmint-* | debian-* | raspbian-*)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4,alpha}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// This file implements the Aho-Corasick algorithm to search multiple
|
||||
// strings within an input string simultaneously. It is essentially a
|
||||
// trie with additional links. For details, see
|
||||
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
|
||||
//
|
||||
// We use it for simple glob patterns in version scripts or dynamic
|
||||
// list files. Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// Aho-Corasick can do only substring search, so it cannot handle
|
||||
// complex glob patterns such as `*foo*bar*`. We handle such patterns
|
||||
// with the Glob class.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace mold {
|
||||
|
||||
bool AhoCorasick::can_handle(std::string_view pat) {
|
||||
if (pat.starts_with('*'))
|
||||
pat.remove_prefix(1);
|
||||
if (pat.ends_with('*'))
|
||||
pat.remove_suffix(1);
|
||||
return pat.find_first_of("*?[") == pat.npos;
|
||||
}
|
||||
|
||||
i64 AhoCorasick::find(std::string_view str) {
|
||||
if (nodes.empty())
|
||||
return -1;
|
||||
|
||||
i64 idx = 0;
|
||||
i64 val = -1;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (i64 j = idx; j != -1; j = nodes[j].suffix_link) {
|
||||
i64 child = nodes[j].children[c];
|
||||
if (child != -1) {
|
||||
idx = child;
|
||||
val = std::max(val, nodes[child].value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
idx = 0;
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str)
|
||||
walk(c);
|
||||
walk('\0');
|
||||
return val;
|
||||
}
|
||||
|
||||
bool AhoCorasick::add(std::string_view pat, i64 val) {
|
||||
assert(can_handle(pat));
|
||||
if (nodes.empty())
|
||||
nodes.resize(1);
|
||||
i64 idx = 0;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
if (nodes[idx].children[c] == -1) {
|
||||
nodes[idx].children[c] = nodes.size();
|
||||
nodes.resize(nodes.size() + 1);
|
||||
}
|
||||
idx = nodes[idx].children[c];
|
||||
};
|
||||
|
||||
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
|
||||
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
|
||||
// substring matching, so we use \0 as a beginning/end-of-string
|
||||
// markers.
|
||||
if (!pat.starts_with('*'))
|
||||
walk('\0');
|
||||
for (u8 c : pat)
|
||||
if (c != '*')
|
||||
walk(c);
|
||||
if (!pat.ends_with('*'))
|
||||
walk('\0');
|
||||
|
||||
nodes[idx].value = std::max(nodes[idx].value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AhoCorasick::compile() {
|
||||
if (nodes.empty())
|
||||
return;
|
||||
fix_suffix_links(0);
|
||||
fix_values();
|
||||
}
|
||||
|
||||
void AhoCorasick::fix_suffix_links(i64 idx) {
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
i64 child = nodes[idx].children[i];
|
||||
if (child == -1)
|
||||
continue;
|
||||
|
||||
i64 j = nodes[idx].suffix_link;
|
||||
for (; j != -1; j = nodes[j].suffix_link) {
|
||||
if (nodes[j].children[i] != -1) {
|
||||
nodes[child].suffix_link = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == -1)
|
||||
nodes[child].suffix_link = 0;
|
||||
fix_suffix_links(child);
|
||||
}
|
||||
}
|
||||
|
||||
void AhoCorasick::fix_values() {
|
||||
std::queue<i64> queue;
|
||||
queue.push(0);
|
||||
|
||||
do {
|
||||
i64 idx = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (i64 child : nodes[idx].children) {
|
||||
if (child != -1) {
|
||||
i64 suffix = nodes[child].suffix_link;
|
||||
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
} while (!queue.empty());
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,50 @@
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace mold {
|
||||
|
||||
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) : std::atomic<T>(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);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "integers.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace mold {
|
||||
|
||||
class BitvectorProxy {
|
||||
public:
|
||||
BitvectorProxy(u64 &word, size_t pos)
|
||||
: word(word), mask(1ULL << pos) {}
|
||||
|
||||
BitvectorProxy &operator=(bool val) {
|
||||
if (val)
|
||||
word |= mask;
|
||||
else
|
||||
word &= ~mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitvectorProxy &operator=(const BitvectorProxy &other) {
|
||||
return *this = (bool)other;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return word & mask;
|
||||
}
|
||||
|
||||
private:
|
||||
u64 &word;
|
||||
u64 mask;
|
||||
};
|
||||
|
||||
class Bitvector {
|
||||
public:
|
||||
Bitvector() = default;
|
||||
Bitvector(i64 n) : size(n), words((n + 63) / 64) {}
|
||||
|
||||
void resize(i64 n) {
|
||||
words.clear();
|
||||
words.resize((n + 63) / 64);
|
||||
size = n;
|
||||
}
|
||||
|
||||
Bitvector &operator|=(const Bitvector &x) {
|
||||
assert(size == x.size);
|
||||
for (i64 i = 0; i < words.size(); i++)
|
||||
words[i] |= x.words[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitvector &operator&=(const Bitvector &x) {
|
||||
assert(size == x.size);
|
||||
for (i64 i = 0; i < words.size(); i++)
|
||||
words[i] &= x.words[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bitvector &operator<<=(size_t n) {
|
||||
assert(n == 1);
|
||||
for (i64 i = words.size() - 1; i > 0; i--)
|
||||
words[i] = (words[i] << 1) | (words[i - 1] >> 63);
|
||||
words[0] <<= 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitvectorProxy operator[](size_t pos) {
|
||||
assert(pos < size);
|
||||
return {words[pos / 64], pos % 64};
|
||||
}
|
||||
|
||||
i64 size = 0;
|
||||
std::vector<u64> words;
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,7 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
|
||||
#cmakedefine HAVE_MADVISE 1
|
||||
#cmakedefine HAVE_UNAME 1
|
||||
#cmakedefine MOLD_USE_MIMALLOC 1
|
||||
#cmakedefine MOLD_USE_SYSTEM_MIMALLOC 1
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
// This function "forges" a CRC. That is, given the current and a desired
|
||||
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
|
||||
// the original data to yield the desired CRC. Trailing garbage is ignored
|
||||
// by many bianry file formats, so you can create a file with a desired
|
||||
// CRC using crc32_solve(). We need it for --separate-debug-file.
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired) {
|
||||
constexpr u32 poly = 0xedb88320;
|
||||
u32 x = ~desired;
|
||||
|
||||
// Each iteration computes x = (x * x^-1) mod poly.
|
||||
for (i64 i = 0; i < 32; i++) {
|
||||
x = std::rotl(x, 1);
|
||||
x ^= (x & 1) * (poly << 1);
|
||||
}
|
||||
|
||||
x ^= ~current;
|
||||
|
||||
std::vector<u8> out(4);
|
||||
out[0] = x;
|
||||
out[1] = x >> 8;
|
||||
out[2] = x >> 16;
|
||||
out[3] = x >> 24;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Compute a CRC for given data in parallel
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
|
||||
struct Shard {
|
||||
u8 *buf;
|
||||
i64 len;
|
||||
u32 crc;
|
||||
};
|
||||
|
||||
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
|
||||
std::vector<Shard> shards;
|
||||
|
||||
while (len > 0) {
|
||||
i64 sz = std::min(len, shard_size);
|
||||
shards.push_back({buf, sz, 0});
|
||||
buf += sz;
|
||||
len -= sz;
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(shards, [](Shard &shard) {
|
||||
shard.crc = crc32(0, shard.buf, shard.len);
|
||||
});
|
||||
|
||||
for (Shard &shard : shards)
|
||||
crc = crc32_combine(crc, shard.crc, shard.len);
|
||||
return crc;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
@@ -10,25 +10,7 @@
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string_view demangle(std::string_view name) {
|
||||
static thread_local char *p;
|
||||
if (p)
|
||||
free(p);
|
||||
|
||||
// Try to demangle as a Rust symbol. Since legacy-style Rust symbols
|
||||
// are also valid as a C++ mangled name, we need to call this before
|
||||
// cpp_demangle.
|
||||
p = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (p)
|
||||
return p;
|
||||
|
||||
// Try to demangle as a C++ symbol.
|
||||
if (std::optional<std::string_view> s = cpp_demangle(name))
|
||||
return *s;
|
||||
return name;
|
||||
}
|
||||
|
||||
std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
@@ -48,4 +30,13 @@ std::optional<std::string_view> cpp_demangle(std::string_view name) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
free(buf);
|
||||
buf = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (buf)
|
||||
return buf;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#if __APPLE__ || _WIN32
|
||||
fprintf(stderr, "mold: get_self_path is not supported");
|
||||
exit(1);
|
||||
#elif __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This test script takes a Gentoo package name and tries to build it
|
||||
# using mold in a Docker environment. We chose Gentoo Linux as a test
|
||||
# using mold in a Podman environment. We chose Gentoo Linux as a test
|
||||
# target, because its source-based package allows us to build programs
|
||||
# locally and run their test suites without any hassle.
|
||||
#
|
||||
# You can get a complete list of Gentoo packages availalbe for testing
|
||||
# with the following command:
|
||||
#
|
||||
# docker run --rm mold-gentoo emerge --color n -s '' | \
|
||||
# podman run --rm mold-gentoo emerge --color n -s '' | \
|
||||
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
|
||||
|
||||
package="$1"
|
||||
@@ -20,47 +20,48 @@ fi
|
||||
|
||||
set -x
|
||||
|
||||
# Create a Docker image
|
||||
if ! docker image ls mold-gentoo | grep -q mold-gentoo; then
|
||||
# Create a Podman image
|
||||
if ! podman image ls mold-gentoo | grep -q mold-gentoo; then
|
||||
set -e
|
||||
cat <<EOF | docker build -t mold-gentoo -
|
||||
FROM gentoo/stage3
|
||||
cat <<EOF | podman build -t mold-gentoo -
|
||||
FROM docker.io/gentoo/stage3
|
||||
RUN emerge-webrsync
|
||||
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary"' >> /etc/portage/make.conf && \
|
||||
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary -perl"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
|
||||
echo 'FEATURES="\${FEATURE} noclean nostrip ccache -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
|
||||
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf
|
||||
RUN emerge gdb lld clang vim emacs strace ccache xeyes dev-util/cmake dev-vcs/git && rm -rf /var/tmp/portage
|
||||
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache xeyes dev-build/cmake dev-vcs/git && \
|
||||
rm -rf /var/tmp/portage
|
||||
EOF
|
||||
set +e
|
||||
fi
|
||||
|
||||
git_hash=$(./mold --version | perl -ne '/\((\w+)/; print $1;')
|
||||
git_hash=$(./dist/mold --version | perl -ne '/\((\w+)/; print $1;')
|
||||
|
||||
if [ "$package" = dev-libs/concurrencykit ]; then
|
||||
echo "Skipping known broken package: $package"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build a given package in Docker
|
||||
cmd1='(cd /usr/bin; ln -sf /mold/mold $(realpath ld))'
|
||||
# Build a given package in Podman
|
||||
cmd1='(cd /usr/bin; ln -sf /mold/dist/mold $(realpath ld))'
|
||||
cmd2="MAKEOPTS=-'j$(nproc) --load-average=100' emerge --onlydeps $package"
|
||||
cmd3="MAKEOPTS='-j$(nproc) --load-average=100' FEATURES=test emerge $package"
|
||||
filename=`echo "$package" | sed 's!/!_!g'`
|
||||
docker="docker run --rm --cap-add=SYS_PTRACE -v `pwd`:/mold -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 1h"
|
||||
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 3h"
|
||||
dir=gentoo/$git_hash
|
||||
|
||||
mkdir -p "$dir"/success "$dir"/failure
|
||||
|
||||
$docker nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/success
|
||||
else
|
||||
mv "$dir"/"$filename".mold "$dir"/failure
|
||||
fi
|
||||
|
||||
$docker nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".ld "$dir"/success
|
||||
else
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
// This file implements a glob matcher that can run multiple glob patterns
|
||||
// against an input string. mold uses the glob matcher for symbol name
|
||||
// patterns in a version script or a dynamic list file. Since we may need to
|
||||
// match hundreds of glob patterns against millions of symbol names, the
|
||||
// speed of the matcher is very important.
|
||||
//
|
||||
// The pattern match implemented in this file is NFA-based, although the
|
||||
// cost of the function is O(n*m), where n is the number of NFA states and m
|
||||
// is the length of the input string. We do not use recursion or
|
||||
// backtracking, unlike a generic NFA-based regular expression matcher. This
|
||||
// is doable because glob patterns are very limited subsets of regexes.
|
||||
//
|
||||
// Here is the explanation of the algorithm. Observe that the only "tricky"
|
||||
// meta-character in a glob pattern is "*", which matches zero or more
|
||||
// characters. Other characters and meta-characters always match a single
|
||||
// input character. So the key of the algorithm is to handle "*" efficiently.
|
||||
//
|
||||
// We can represent a glob pattern "a*b*" with three NFA states: q_start, q1
|
||||
// and q_accept, with the following transition functions:
|
||||
//
|
||||
// δ(q_start, "a") = q1
|
||||
// δ(q1, <any character>) = q1
|
||||
// δ(q1, "b") = q_accept
|
||||
// δ(q_accept, <any character>) = q_accept
|
||||
//
|
||||
// We can construct such an NFA in a straightforward manner. We maintain NFA
|
||||
// states as a list, with the initial contents being the start state. Each
|
||||
// character except for "*" creates a new NFA state, adds a transition from
|
||||
// the last state in the list to the new one, and appends the new state at
|
||||
// the end of the list. "*" sets the "is_star" flag on the last NFA state.
|
||||
// The flag indicates that the state machine can remain in the state for any
|
||||
// input character.
|
||||
//
|
||||
// An NFA constructed this way doesn't have any complicated loops,
|
||||
// ε-transitions, or anything like that. Each state has only one incoming
|
||||
// edge. The only loops in the state transition are the self-loops on states
|
||||
// followed by a "*". Aside from that, the state machine progresses linearly
|
||||
// from the start state to the accept state.
|
||||
//
|
||||
// Each state of an NFA can be represented by a single bit. If a bit is 1,
|
||||
// the non-deterministic state machine is in that state. Otherwise, it's
|
||||
// not. Observe that a state with the "is_star" flag will continued to be 1
|
||||
// once it becomes 1, since the state machine can loop over the state on any
|
||||
// input character.
|
||||
//
|
||||
// With that observation, we can represent an NFA with a bit vector of N
|
||||
// bits, where N is the number of NFA states. For each input character, bit
|
||||
// M becomes 1 if
|
||||
//
|
||||
// - bit M-1 is 1 and there's a transition from state_{M-1} to state_M
|
||||
// with the given character, or
|
||||
// - bit M is 1 and state_M's "is_star" flag is 1.
|
||||
//
|
||||
// Initially, the 0th bit is 1 for the start state. At each step, the bits
|
||||
// propagate from least significant to most significant positions, at most
|
||||
// one bit at a time. If the most significant bit is 1 after the entire
|
||||
// input has been processed, the string matches.
|
||||
//
|
||||
// This propagation can be implemented with bitwise OR, bitwise AND, and a
|
||||
// one-bit bit shift on the bit vector. All these operations are very cheap.
|
||||
//
|
||||
// We can combine multiple glob matchers into a single matcher by simply
|
||||
// concatenating the bit vectors of their state machines.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
|
||||
std::vector<MultiGlob::State> vec(1);
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat = pat.substr(1);
|
||||
std::bitset<256> chars;
|
||||
|
||||
switch (c) {
|
||||
case '*':
|
||||
vec.back().is_star = true;
|
||||
continue;
|
||||
case '?':
|
||||
chars.set();
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
chars[pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
break;
|
||||
case '[': {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [^a-z]: Any character except lowercase letters
|
||||
bool negate = false;
|
||||
bool closed = false;
|
||||
|
||||
if (!pat.empty() && pat[0] == '^') {
|
||||
negate = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
while (!pat.empty()) {
|
||||
if (pat[0] == ']') {
|
||||
pat = pat.substr(1);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pat[0] == '\\') {
|
||||
pat = pat.substr(1);
|
||||
if (pat.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pat.size() >= 3 && pat[1] == '-') {
|
||||
u8 start = pat[0];
|
||||
u8 end = pat[2];
|
||||
pat = pat.substr(3);
|
||||
|
||||
if (end == '\\') {
|
||||
if (pat.empty())
|
||||
return {};
|
||||
end = pat[0];
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
chars[i] = true;
|
||||
} else {
|
||||
chars[pat[0]] = true;
|
||||
pat = pat.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
chars.flip();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
chars[c] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
vec.push_back({chars, false});
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Instead of returning just a match/no match boolean value, our glob
|
||||
// matcher returns an integer value associated with each given pattern.
|
||||
// If multiple patterns match at the same time, the largest associated
|
||||
// value will be returned by find().
|
||||
bool MultiGlob::add(std::string_view pat, i64 val) {
|
||||
std::vector<State> vec = parse_glob(pat);
|
||||
if (vec.empty())
|
||||
return false;
|
||||
patterns.push_back({std::move(vec), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiGlob::compile() {
|
||||
if (patterns.empty())
|
||||
return;
|
||||
|
||||
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
|
||||
|
||||
std::vector<State> states;
|
||||
for (GlobPattern &p : patterns)
|
||||
append(states, p.states);
|
||||
i64 sz = states.size();
|
||||
|
||||
start_states.resize(sz);
|
||||
for (i64 pos = 0; GlobPattern &p : patterns) {
|
||||
start_states[pos] = true;
|
||||
pos += p.states.size();
|
||||
}
|
||||
|
||||
star_mask.resize(sz);
|
||||
for (i64 i = 0; i < sz; i++)
|
||||
if (states[i].is_star)
|
||||
star_mask[i] = true;
|
||||
|
||||
for (i64 i = 0; i < 256; i++) {
|
||||
char_mask[i].resize(sz);
|
||||
for (i64 j = 0; j < sz; j++)
|
||||
if (states[j].incoming_edge[i])
|
||||
char_mask[i][j] = true;
|
||||
}
|
||||
}
|
||||
|
||||
i64 MultiGlob::find(std::string_view str) {
|
||||
if (patterns.empty())
|
||||
return -1;
|
||||
|
||||
Bitvector bits = start_states;
|
||||
Bitvector tmp;
|
||||
|
||||
for (u8 c : str) {
|
||||
// This is equivalent to
|
||||
//
|
||||
// bits = (bits & star_mask) | ((bits << 1) & char_mask[c])
|
||||
//
|
||||
// but we update the existing objects in place to avoid allocating
|
||||
// temporary objects.
|
||||
tmp = bits;
|
||||
tmp &= star_mask;
|
||||
bits <<= 1;
|
||||
bits &= char_mask[c];
|
||||
bits |= tmp;
|
||||
}
|
||||
|
||||
for (i64 pos = 0; GlobPattern &p : patterns) {
|
||||
pos += p.states.size();
|
||||
if (bits[pos - 1])
|
||||
return p.value;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Glob::add(std::string_view pat, i64 val) {
|
||||
assert(val >= 0);
|
||||
assert(!is_compiled);
|
||||
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if (aho_corasick.can_handle(pat))
|
||||
return aho_corasick.add(pat, val);
|
||||
return multi_glob.add(pat, val);
|
||||
}
|
||||
|
||||
i64 Glob::find(std::string_view str) {
|
||||
std::call_once(once, [&] {
|
||||
multi_glob.compile();
|
||||
aho_corasick.compile();
|
||||
is_compiled = true;
|
||||
});
|
||||
|
||||
return std::max(multi_glob.find(str), aho_corasick.find(str));
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -4,7 +4,7 @@
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. For example, users should be able to run mold on a
|
||||
// little-endian x86 machine to create a big-endian s390x binary.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory because of
|
||||
// archive files. Archive files (.a files) align each file only to a
|
||||
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
|
||||
// in an mmap'ed memory. Misaligned access is an undefined behavior in
|
||||
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
|
||||
// example, to read a 32 bit value.
|
||||
//
|
||||
// The data types defined in this file are independent of the host byte
|
||||
// order and are designed to avoid unaligned access.
|
||||
//
|
||||
// Note that in C/C++, memcpy is a portable and efficient way to access
|
||||
// unaligned data, as it is typically treated as an intrinsic. Compilers
|
||||
// can easily optimize memcpy calls in this file into a single load or
|
||||
// store instruction.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, bool is_le, int size = sizeof(T)>
|
||||
class Integer {
|
||||
public:
|
||||
constexpr Integer() = default;
|
||||
|
||||
constexpr Integer(T v) {
|
||||
if (std::is_constant_evaluated()) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
int j = is_le ? i : (size - i - 1);
|
||||
buf[j] = v >> (i * 8);
|
||||
}
|
||||
} else {
|
||||
store(v);
|
||||
}
|
||||
}
|
||||
|
||||
operator T() const { return load(); }
|
||||
Integer &operator=(T v) { store(v); return *this; }
|
||||
Integer &operator++() { return *this = *this + 1; }
|
||||
Integer operator++(int) { auto x = *this; ++*this; return x; }
|
||||
Integer &operator--() { return *this = *this - 1; }
|
||||
Integer operator--(int) { auto x = *this; --*this; return x; }
|
||||
Integer &operator+=(T v) { return *this = *this + v; }
|
||||
Integer &operator-=(T v) { return *this = *this - v; }
|
||||
Integer &operator&=(T v) { return *this = *this & v; }
|
||||
Integer &operator|=(T v) { return *this = *this | v; }
|
||||
|
||||
private:
|
||||
static constexpr bool is_native =
|
||||
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
|
||||
|
||||
static T bswap(T v) {
|
||||
switch (size) {
|
||||
case 2: return __builtin_bswap16(v);
|
||||
case 3: __builtin_unreachable();
|
||||
case 4: return __builtin_bswap32(v);
|
||||
case 8: return __builtin_bswap64(v);
|
||||
}
|
||||
}
|
||||
|
||||
T load() const {
|
||||
if (size == 3) {
|
||||
if (is_le)
|
||||
return buf[2] << 16 | buf[1] << 8 | buf[0];
|
||||
return buf[0] << 16 | buf[1] << 8 | buf[2];
|
||||
}
|
||||
|
||||
T v;
|
||||
memcpy(&v, buf, size);
|
||||
return is_native ? v : bswap(v);
|
||||
}
|
||||
|
||||
// We cannot merge this with the constructor because memcpy is not
|
||||
// allowed to use in a compile-time constant expression.
|
||||
void store(T v) {
|
||||
if (size == 3) {
|
||||
if (is_le) {
|
||||
buf[0] = v;
|
||||
buf[1] = v >> 8;
|
||||
buf[2] = v >> 16;
|
||||
} else {
|
||||
buf[0] = v >> 16;
|
||||
buf[1] = v >> 8;
|
||||
buf[2] = v;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_native)
|
||||
v = bswap(v);
|
||||
memcpy(buf, &v, size);
|
||||
}
|
||||
|
||||
uint8_t buf[size];
|
||||
};
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using il16 = Integer<i16, true>;
|
||||
using il32 = Integer<i32, true>;
|
||||
using il64 = Integer<i64, true>;
|
||||
|
||||
using ul16 = Integer<u16, true>;
|
||||
using ul24 = Integer<u32, true, 3>;
|
||||
using ul32 = Integer<u32, true>;
|
||||
using ul64 = Integer<u64, true>;
|
||||
|
||||
using ib16 = Integer<i16, false>;
|
||||
using ib32 = Integer<i32, false>;
|
||||
using ib64 = Integer<i64, false>;
|
||||
|
||||
using ub16 = Integer<u16, false>;
|
||||
using ub24 = Integer<u32, false, 3>;
|
||||
using ub32 = Integer<u32, false>;
|
||||
using ub64 = Integer<u64, false>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,676 @@
|
||||
#pragma once
|
||||
|
||||
#include "atomics.h"
|
||||
#include "integers.h"
|
||||
#include "bitvector.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/enumerable_thread_specific.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
#else
|
||||
# include <sys/mman.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include "../third-party/xxhash/xxhash.h"
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define unreachable() __builtin_unreachable()
|
||||
#else
|
||||
# define unreachable() assert(0 && "unreachable")
|
||||
#endif
|
||||
|
||||
inline uint64_t hash_string(std::string_view str) {
|
||||
return XXH3_64bits(str.data(), str.size());
|
||||
}
|
||||
|
||||
class HashCmp {
|
||||
public:
|
||||
static size_t hash(const std::string_view &k) {
|
||||
return hash_string(k);
|
||||
}
|
||||
|
||||
static bool equal(const std::string_view &k1, const std::string_view &k2) {
|
||||
return k1 == k2;
|
||||
}
|
||||
};
|
||||
|
||||
namespace mold {
|
||||
|
||||
namespace ranges = std::ranges;
|
||||
using namespace std::literals::string_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
inline u64 combine_hash(u64 a, u64 b) {
|
||||
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
//
|
||||
// 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.emplace_back(record);
|
||||
}
|
||||
|
||||
Timer(const Timer &) = delete;
|
||||
|
||||
~Timer() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
record->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
TimerRecord *record;
|
||||
};
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
// Some C++ libraries haven't implemented std::has_single_bit yet.
|
||||
inline bool has_single_bit(u64 val) {
|
||||
return std::popcount(val) == 1;
|
||||
}
|
||||
|
||||
// Some C++ libraries haven't implemented std::bit_ceil yet.
|
||||
inline u64 bit_ceil(u64 val) {
|
||||
if (has_single_bit(val))
|
||||
return val;
|
||||
return 1LL << (64 - std::countl_zero(val));
|
||||
}
|
||||
|
||||
inline u64 align_to(u64 val, u64 align) {
|
||||
if (align == 0)
|
||||
return val;
|
||||
assert(has_single_bit(align));
|
||||
return (val + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 align_down(u64 val, u64 align) {
|
||||
assert(has_single_bit(align));
|
||||
return val & ~(align - 1);
|
||||
}
|
||||
|
||||
inline u64 bit(u64 val, i64 pos) {
|
||||
return (val >> pos) & 1;
|
||||
};
|
||||
|
||||
// Returns [hi:lo] bits of val.
|
||||
inline u64 bits(u64 val, u64 hi, u64 lo) {
|
||||
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
|
||||
}
|
||||
|
||||
// Cast val to a signed N bit integer.
|
||||
// For example, sign_extend(x, 32) == (i32)x for any integer x.
|
||||
inline i64 sign_extend(u64 val, i64 n) {
|
||||
return (i64)(val << (64 - n)) >> (64 - n);
|
||||
}
|
||||
|
||||
inline bool is_int(u64 val, i64 n) {
|
||||
return sign_extend(val, n) == val;
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(new_val, old_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T, typename Compare = std::less<T>>
|
||||
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
|
||||
T old_val = atomic.load(std::memory_order_relaxed);
|
||||
while (cmp(old_val, new_val) &&
|
||||
!atomic.compare_exchange_weak(old_val, new_val,
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void append(std::vector<T> &x, const auto &y) {
|
||||
x.insert(x.end(), y.begin(), y.end());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void remove_duplicates(std::vector<T> &vec) {
|
||||
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||
}
|
||||
|
||||
inline i64 write_string(void *buf, std::string_view str) {
|
||||
memcpy(buf, str.data(), str.size());
|
||||
*((u8 *)buf + str.size()) = '\0';
|
||||
return str.size() + 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void write_vector(void *buf, const std::vector<T> &vec) {
|
||||
if (!vec.empty())
|
||||
memcpy(buf, vec.data(), vec.size() * sizeof(T));
|
||||
}
|
||||
|
||||
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
|
||||
do {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
vec.push_back(val ? (byte | 0x80) : byte);
|
||||
} while (val);
|
||||
}
|
||||
|
||||
inline void encode_sleb(std::vector<u8> &vec, i64 val) {
|
||||
for (;;) {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
|
||||
bool neg = (byte & 0x40);
|
||||
if ((val == 0 && !neg) || (val == -1 && neg)) {
|
||||
vec.push_back(byte);
|
||||
break;
|
||||
}
|
||||
vec.push_back(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
inline i64 write_uleb(u8 *buf, u64 val) {
|
||||
i64 i = 0;
|
||||
do {
|
||||
u8 byte = val & 0x7f;
|
||||
val >>= 7;
|
||||
buf[i++] = val ? (byte | 0x80) : byte;
|
||||
} while (val);
|
||||
return i;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 **buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *(*buf)++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return val;
|
||||
}
|
||||
|
||||
inline u64 read_uleb(u8 *buf) {
|
||||
u8 *tmp = buf;
|
||||
return read_uleb(&tmp);
|
||||
}
|
||||
|
||||
inline i64 read_sleb(u8 **buf) {
|
||||
u64 val = 0;
|
||||
u8 shift = 0;
|
||||
u8 byte;
|
||||
do {
|
||||
byte = *(*buf)++;
|
||||
val |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
return sign_extend(val, shift);
|
||||
}
|
||||
|
||||
inline i64 read_sleb(u8 *buf) {
|
||||
u8 *tmp = buf;
|
||||
return read_sleb(&tmp);
|
||||
}
|
||||
|
||||
inline u64 read_uleb(std::string_view *str) {
|
||||
u8 *start = (u8 *)str->data();
|
||||
u8 *ptr = start;
|
||||
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) {
|
||||
for (int i = 1; i < 9; i++)
|
||||
if (val < (1LL << (7 * i)))
|
||||
return i;
|
||||
return 9;
|
||||
}
|
||||
|
||||
inline void overwrite_uleb(u8 *loc, u64 val) {
|
||||
while (*loc & 0b1000'0000) {
|
||||
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
|
||||
val >>= 7;
|
||||
}
|
||||
*loc = val & 0b0111'1111;
|
||||
}
|
||||
|
||||
static inline void pause() {
|
||||
#if defined(__x86_64__)
|
||||
asm volatile("pause");
|
||||
#elif defined(__aarch64__)
|
||||
asm volatile("yield");
|
||||
#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_8A__)
|
||||
asm volatile("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Concurrent Map
|
||||
//
|
||||
|
||||
// This is an implementation of a fast concurrent hash map. Unlike
|
||||
// ordinary hash tables, this impl just aborts if it becomes full.
|
||||
// So you need to give a correct estimation of the final size before
|
||||
// using it. We use this hash map to uniquify pieces of data in
|
||||
// mergeable sections.
|
||||
//
|
||||
// We've implemented this ourselves because the performance of
|
||||
// conrurent hash map is critical for our linker.
|
||||
template <typename T>
|
||||
class ConcurrentMap {
|
||||
public:
|
||||
ConcurrentMap() = default;
|
||||
|
||||
ConcurrentMap(i64 nbuckets) {
|
||||
resize(nbuckets);
|
||||
}
|
||||
|
||||
~ConcurrentMap() {
|
||||
if (entries) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(entries);
|
||||
#else
|
||||
munmap(entries, sizeof(Entry) * nbuckets);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
u32 keylen;
|
||||
T value;
|
||||
};
|
||||
|
||||
void resize(i64 nbuckets) {
|
||||
assert(!entries);
|
||||
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
|
||||
i64 bufsize = sizeof(Entry) * this->nbuckets;
|
||||
|
||||
// Allocate a zero-initialized buffer. We use mmap() if available
|
||||
// because it's faster than malloc() and memset().
|
||||
#ifdef _WIN32
|
||||
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
|
||||
memset((void *)entries, 0, bufsize);
|
||||
#else
|
||||
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
|
||||
assert(has_single_bit(nbuckets));
|
||||
|
||||
u64 begin = hash & (nbuckets - 1);
|
||||
u64 mask = nbuckets / NUM_SHARDS - 1;
|
||||
|
||||
for (i64 i = 0; i < MAX_RETRY; i++) {
|
||||
u64 idx = (begin & ~mask) | ((begin + i) & mask);
|
||||
Entry &ent = entries[idx];
|
||||
|
||||
// It seems avoiding compare-and-swap is faster overall at least
|
||||
// on my Zen4 machine, so do it.
|
||||
if (const char *ptr = ent.key.load(std::memory_order_acquire);
|
||||
ptr != nullptr && ptr != (char *)-1) {
|
||||
if (key == std::string_view(ptr, ent.keylen))
|
||||
return {&ent.value, false};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, use CAS to atomically claim the ownership of the slot.
|
||||
const char *ptr = nullptr;
|
||||
bool claimed = ent.key.compare_exchange_strong(ptr, (char *)-1,
|
||||
std::memory_order_acquire);
|
||||
|
||||
// If we successfully claimed the ownership of the 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};
|
||||
}
|
||||
|
||||
// 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 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};
|
||||
}
|
||||
|
||||
std::cerr << "ConcurrentMap is full\n";
|
||||
abort();
|
||||
}
|
||||
|
||||
i64 get_idx(T *value) const {
|
||||
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
|
||||
return (Entry *)addr - entries;
|
||||
}
|
||||
|
||||
// Return a list of map entries sorted in a deterministic order.
|
||||
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
|
||||
if (nbuckets == 0)
|
||||
return {};
|
||||
|
||||
i64 shard_size = nbuckets / NUM_SHARDS;
|
||||
i64 begin = shard_idx * shard_size;
|
||||
i64 end = begin + shard_size;
|
||||
|
||||
i64 sz = 0;
|
||||
for (i64 i = begin; i < end; i++)
|
||||
if (entries[i].key)
|
||||
sz++;
|
||||
|
||||
std::vector<Entry *> vec;
|
||||
vec.reserve(sz);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while (begin < end && entries[end - 1].key)
|
||||
vec.push_back(entries + --end);
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
i64 last = 0;
|
||||
for (i64 i = begin; i < end;) {
|
||||
while (i < end && entries[i].key)
|
||||
vec.push_back(entries + i++);
|
||||
|
||||
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
|
||||
if (a->keylen != b->keylen)
|
||||
return a->keylen < b->keylen;
|
||||
return memcmp(a->key, b->key, a->keylen) < 0;
|
||||
});
|
||||
|
||||
last = vec.size();
|
||||
|
||||
while (i < end && !entries[i].key)
|
||||
i++;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<Entry *> get_sorted_entries_all() {
|
||||
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
|
||||
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
|
||||
vec[i] = get_sorted_entries(i);
|
||||
});
|
||||
return flatten(vec);
|
||||
}
|
||||
|
||||
static constexpr i64 MIN_NBUCKETS = 4096;
|
||||
static constexpr i64 NUM_SHARDS = 16;
|
||||
static constexpr i64 MAX_RETRY = 256;
|
||||
|
||||
Entry *entries = nullptr;
|
||||
u64 nbuckets = 0;
|
||||
};
|
||||
|
||||
//
|
||||
// random.cc
|
||||
//
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size);
|
||||
|
||||
//
|
||||
// hyperloglog.cc
|
||||
//
|
||||
|
||||
class HyperLogLog {
|
||||
public:
|
||||
void insert(u64 hash) {
|
||||
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
|
||||
}
|
||||
|
||||
i64 get_cardinality() const;
|
||||
|
||||
void merge(const HyperLogLog &other) {
|
||||
for (i64 i = 0; i < NBUCKETS; i++)
|
||||
update_maximum(buckets[i], other.buckets[i]);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr i64 NBUCKETS = 2048;
|
||||
static constexpr double ALPHA = 0.79402;
|
||||
|
||||
Atomic<u8> buckets[NBUCKETS];
|
||||
};
|
||||
|
||||
//
|
||||
// aho-corasick.cc
|
||||
//
|
||||
|
||||
class AhoCorasick {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return nodes.empty(); }
|
||||
void compile();
|
||||
i64 find(std::string_view str);
|
||||
|
||||
static bool can_handle(std::string_view str);
|
||||
|
||||
private:
|
||||
struct TrieNode {
|
||||
TrieNode() { children.fill(-1); }
|
||||
i64 value = -1;
|
||||
i32 suffix_link = -1;
|
||||
std::array<i32, 256> children;
|
||||
};
|
||||
|
||||
void fix_suffix_links(i64 idx);
|
||||
void fix_values();
|
||||
|
||||
std::vector<TrieNode> nodes;
|
||||
};
|
||||
|
||||
//
|
||||
// glob.cc
|
||||
//
|
||||
|
||||
class MultiGlob {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return patterns.empty(); }
|
||||
void compile();
|
||||
i64 find(std::string_view str);
|
||||
|
||||
struct State {
|
||||
std::bitset<256> incoming_edge;
|
||||
bool is_star = false;
|
||||
};
|
||||
|
||||
struct GlobPattern {
|
||||
std::vector<State> states;
|
||||
i64 value = -1;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<GlobPattern> patterns;
|
||||
Bitvector start_states;
|
||||
Bitvector star_mask;
|
||||
Bitvector char_mask[256];
|
||||
};
|
||||
|
||||
class Glob {
|
||||
public:
|
||||
bool add(std::string_view pat, i64 val);
|
||||
bool empty() const { return multi_glob.empty() && aho_corasick.empty(); }
|
||||
i64 find(std::string_view str);
|
||||
|
||||
private:
|
||||
std::once_flag once;
|
||||
bool is_compiled = false;
|
||||
|
||||
MultiGlob multi_glob;
|
||||
AhoCorasick aho_corasick;
|
||||
};
|
||||
|
||||
//
|
||||
// filepath.cc
|
||||
//
|
||||
|
||||
inline std::filesystem::path path_dirname(std::string_view path) {
|
||||
return std::filesystem::path(path).parent_path();
|
||||
}
|
||||
|
||||
inline std::string path_filename(std::string_view path) {
|
||||
return std::filesystem::path(path).filename().string();
|
||||
}
|
||||
|
||||
inline std::string path_clean(std::string_view path) {
|
||||
return std::filesystem::path(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
std::string get_self_path();
|
||||
|
||||
//
|
||||
// demangle.cc
|
||||
//
|
||||
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name);
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name);
|
||||
|
||||
//
|
||||
// crc32.cc
|
||||
//
|
||||
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired);
|
||||
|
||||
//
|
||||
// tar.cc
|
||||
//
|
||||
|
||||
// TarFile is a class to create a tar file.
|
||||
//
|
||||
// If you pass `--repro` to mold, mold collects all input files and
|
||||
// put them into `<output-file-path>.repro.tar`, so that it is easy to
|
||||
// run the same command with the same command line arguments.
|
||||
class TarWriter {
|
||||
public:
|
||||
static std::unique_ptr<TarWriter>
|
||||
open(std::string output_path, std::string basedir);
|
||||
|
||||
~TarWriter();
|
||||
void append(std::string path, std::string_view data);
|
||||
|
||||
private:
|
||||
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
|
||||
|
||||
FILE *out = nullptr;
|
||||
std::string basedir;
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "common.h"
|
||||
#include "lib.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/resource.h>
|
||||
@@ -16,9 +17,7 @@ i64 Counter::get_value() {
|
||||
}
|
||||
|
||||
void Counter::print() {
|
||||
sort(instances, [](Counter *a, Counter *b) {
|
||||
return a->get_value() > b->get_value();
|
||||
});
|
||||
ranges::stable_sort(instances, {}, [](Counter *x) { return x->get_value(); });
|
||||
|
||||
for (Counter *c : instances)
|
||||
std::cout << std::setw(20) << std::right << c->name
|
||||
@@ -26,19 +25,13 @@ void Counter::print() {
|
||||
}
|
||||
|
||||
static i64 now_nsec() {
|
||||
#ifdef _WIN32
|
||||
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
#else
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::pair<i64, i64> get_usage() {
|
||||
#ifdef _WIN32
|
||||
auto to_nsec = [](FILETIME t) -> i64 {
|
||||
return ((u64)t.dwHighDateTime << 32 + (u64)t.dwLowDateTime) * 100;
|
||||
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
|
||||
};
|
||||
|
||||
FILETIME creation, exit, kernel, user;
|
||||
@@ -85,9 +78,7 @@ static void print_rec(TimerRecord &rec, i64 indent) {
|
||||
std::string(indent * 2, ' ').c_str(),
|
||||
rec.name.c_str());
|
||||
|
||||
sort(rec.children, [](TimerRecord *a, TimerRecord *b) {
|
||||
return a->start < b->start;
|
||||
});
|
||||
ranges::stable_sort(rec.children, {}, &TimerRecord::start);
|
||||
|
||||
for (TimerRecord *child : rec.children)
|
||||
print_rec(*child, indent + 1);
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size) {
|
||||
std::random_device rand;
|
||||
i64 i = 0;
|
||||
|
||||
for (; i < size - 4; i += 4) {
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, 4);
|
||||
}
|
||||
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, size - i);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
// This is a header-only C++20 implementation of SipHash based on the
|
||||
// reference implementation. To use, just copy this header file into
|
||||
// your project and #include it.
|
||||
//
|
||||
// https://github.com/rui314/siphash/blob/main/siphash.h
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
|
||||
class SipHashTmpl {
|
||||
public:
|
||||
static_assert(OUTLEN == 64 || OUTLEN == 128);
|
||||
|
||||
SipHashTmpl(void *key) {
|
||||
uint64_t k0 = read64(key);
|
||||
uint64_t k1 = read64((char *)key + 8);
|
||||
|
||||
v0 = 0x736f6d6570736575 ^ k0;
|
||||
v1 = 0x646f72616e646f6d ^ k1;
|
||||
v2 = 0x6c7967656e657261 ^ k0;
|
||||
v3 = 0x7465646279746573 ^ k1;
|
||||
|
||||
if (OUTLEN == 128)
|
||||
v1 ^= 0xee;
|
||||
}
|
||||
|
||||
void update(void *msgp, int64_t msglen) {
|
||||
char *msg = (char *)msgp;
|
||||
sum += msglen;
|
||||
|
||||
if (buflen) {
|
||||
if (buflen + msglen < 8) {
|
||||
memcpy(buf + buflen, msg, msglen);
|
||||
buflen += msglen;
|
||||
return;
|
||||
}
|
||||
|
||||
int j = 8 - buflen;
|
||||
memcpy(buf + buflen, msg, j);
|
||||
compress(read64(buf));
|
||||
|
||||
msg += j;
|
||||
msglen -= j;
|
||||
buflen = 0;
|
||||
}
|
||||
|
||||
while (msglen >= 8) {
|
||||
compress(read64(msg));
|
||||
msg += 8;
|
||||
msglen -= 8;
|
||||
}
|
||||
|
||||
memcpy(buf, msg, msglen);
|
||||
buflen = msglen;
|
||||
}
|
||||
|
||||
void finish(void *out) {
|
||||
memset(buf + buflen, 0, 8 - buflen);
|
||||
compress(((uint64_t)sum << 56) | read64(buf));
|
||||
|
||||
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
|
||||
finalize();
|
||||
write64(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
|
||||
if (OUTLEN == 128) {
|
||||
v1 ^= 0xdd;
|
||||
finalize();
|
||||
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
|
||||
}
|
||||
}
|
||||
|
||||
static void hash(void *out, void *key, void *in, int inlen) {
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
|
||||
h.update(in, inlen);
|
||||
h.finish(out);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t v0, v1, v2, v3;
|
||||
uint8_t buf[8];
|
||||
uint8_t buflen = 0;
|
||||
uint8_t sum = 0;
|
||||
|
||||
uint64_t read64(void *loc) {
|
||||
uint64_t val;
|
||||
memcpy(&val, loc, 8);
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void write64(void *loc, uint64_t val) {
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
memcpy(loc, &val, 8);
|
||||
}
|
||||
|
||||
uint64_t bswap(uint64_t val) {
|
||||
return ((val << 56) & 0xff00000000000000) |
|
||||
((val << 40) & 0x00ff000000000000) |
|
||||
((val << 24) & 0x0000ff0000000000) |
|
||||
((val << 8) & 0x000000ff00000000) |
|
||||
((val >> 8) & 0x00000000ff000000) |
|
||||
((val >> 24) & 0x0000000000ff0000) |
|
||||
((val >> 40) & 0x000000000000ff00) |
|
||||
((val >> 56) & 0x00000000000000ff);
|
||||
}
|
||||
|
||||
void round() {
|
||||
v0 += v1;
|
||||
v1 = std::rotl(v1, 13);
|
||||
v1 ^= v0;
|
||||
v0 = std::rotl(v0, 32);
|
||||
v2 += v3;
|
||||
v3 = std::rotl(v3, 16);
|
||||
v3 ^= v2;
|
||||
v0 += v3;
|
||||
v3 = std::rotl(v3, 21);
|
||||
v3 ^= v0;
|
||||
v2 += v1;
|
||||
v1 = std::rotl(v1, 17);
|
||||
v1 ^= v2;
|
||||
v2 = std::rotl(v2, 32);
|
||||
}
|
||||
|
||||
void compress(uint64_t m) {
|
||||
v3 ^= m;
|
||||
for (int i = 0; i < C_ROUNDS; i++)
|
||||
round();
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
for (int i = 0; i < D_ROUNDS; i++)
|
||||
round();
|
||||
}
|
||||
};
|
||||
|
||||
using SipHash = SipHashTmpl<2, 4, 64>;
|
||||
using SipHash128 = SipHashTmpl<2, 4, 128>;
|
||||
using SipHash13 = SipHashTmpl<1, 3, 64>;
|
||||
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
+33
-34
@@ -1,7 +1,15 @@
|
||||
#include "common.h"
|
||||
// This file contains functions to create a tar file.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ftruncate _chsize_s
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
// A tar file consists of one or more Ustar header followed by data.
|
||||
// Each Ustar header represents a single file in an archive.
|
||||
//
|
||||
@@ -11,26 +19,6 @@ namespace mold {
|
||||
//
|
||||
// For simplicity, we always emit a PAX header even for a short filename.
|
||||
struct UstarHeader {
|
||||
UstarHeader() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
memset(checksum, ' ', sizeof(checksum));
|
||||
memcpy(magic, "ustar", 5);
|
||||
memcpy(version, "00", 2);
|
||||
|
||||
// Compute checksum
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(*this); i++)
|
||||
sum += ((u8 *)this)[i];
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
@@ -50,7 +38,24 @@ struct UstarHeader {
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UstarHeader) == 512);
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
|
||||
static void finalize(UstarHeader &hdr) {
|
||||
memset(hdr.checksum, ' ', sizeof(hdr.checksum));
|
||||
memcpy(hdr.magic, "ustar", 5);
|
||||
memcpy(hdr.version, "00", 2);
|
||||
|
||||
// Compute checksum
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(hdr); i++)
|
||||
sum += ((u8 *)&hdr)[i];
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
if (sum >= 01'000'000)
|
||||
unreachable();
|
||||
snprintf(hdr.checksum, sizeof(hdr.checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
@@ -78,14 +83,13 @@ TarWriter::~TarWriter() {
|
||||
|
||||
void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write PAX header
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
UstarHeader pax;
|
||||
UstarHeader pax = {};
|
||||
|
||||
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();
|
||||
finalize(pax);
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
|
||||
// Write pathname
|
||||
@@ -93,23 +97,18 @@ void TarWriter::append(std::string path, std::string_view data) {
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// Write Ustar header
|
||||
UstarHeader ustar;
|
||||
UstarHeader ustar = {};
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
ustar.finalize();
|
||||
finalize(ustar);
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
// Write file contents
|
||||
fwrite(data.data(), data.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// A tar file must ends with two empty blocks, so write such
|
||||
// terminator and seek back.
|
||||
u8 terminator[BLOCK_SIZE * 2] = {};
|
||||
fwrite(&terminator, BLOCK_SIZE * 2, 1, out);
|
||||
fseek(out, -BLOCK_SIZE * 2, SEEK_END);
|
||||
|
||||
assert(ftell(out) % BLOCK_SIZE == 0);
|
||||
// A tar file must ends with two empty blocks
|
||||
(void)!ftruncate(fileno(out), ftell(out) + BLOCK_SIZE * 2);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -17,11 +17,11 @@ if(EXISTS "${SOURCE_DIR}/.git/HEAD")
|
||||
endif()
|
||||
|
||||
# Create new file contents and update a given file if necessary.
|
||||
set(NEW_CONTENTS "#include <string>
|
||||
namespace mold {
|
||||
std::string mold_git_hash = \"${HASH}\";
|
||||
}
|
||||
")
|
||||
if("${HASH}" STREQUAL "")
|
||||
set(NEW_CONTENTS "")
|
||||
else()
|
||||
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${OUTPUT_FILE}")
|
||||
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
|
||||
@@ -31,22 +31,25 @@
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = ARM32;
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
U32<E> *arm = (U32<E> *)loc;
|
||||
U16<E> *thm = (U16<E> *)loc;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
@@ -57,83 +60,110 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
return *(il32 *)loc;
|
||||
return (I32<E>)*arm;
|
||||
case R_ARM_THM_JUMP8:
|
||||
return sign_extend(thm[0], 8) << 1;
|
||||
case R_ARM_THM_JUMP11:
|
||||
return sign_extend(*(ul16 *)loc, 10) << 1;
|
||||
return sign_extend(thm[0], 11) << 1;
|
||||
case R_ARM_THM_JUMP19: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/B--Branch-
|
||||
u32 S = bit(thm[0], 10);
|
||||
u32 J2 = bit(thm[1], 11);
|
||||
u32 J1 = bit(thm[1], 13);
|
||||
u32 imm6 = bits(thm[0], 5, 0);
|
||||
u32 imm11 = bits(thm[1], 10, 0);
|
||||
u32 val = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 21);
|
||||
}
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL: {
|
||||
u32 S = bit(*(ul16 *)loc, 10);
|
||||
u32 J1 = bit(*(ul16 *)(loc + 2), 13);
|
||||
u32 J2 = bit(*(ul16 *)(loc + 2), 11);
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/BL--BLX--immediate---Branch-with-Link-and-optional-Exchange--immediate--
|
||||
u32 S = bit(thm[0], 10);
|
||||
u32 J1 = bit(thm[1], 13);
|
||||
u32 J2 = bit(thm[1], 11);
|
||||
u32 I1 = !(J1 ^ S);
|
||||
u32 I2 = !(J2 ^ S);
|
||||
u32 imm10 = bits(*(ul16 *)loc, 9, 0);
|
||||
u32 imm11 = bits(*(ul16 *)(loc + 2), 10, 0);
|
||||
u32 imm10 = bits(thm[0], 9, 0);
|
||||
u32 imm11 = bits(thm[1], 10, 0);
|
||||
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 24);
|
||||
return sign_extend(val, 25);
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_TLS_CALL:
|
||||
return sign_extend(*(ul32 *)loc, 23) << 2;
|
||||
return sign_extend(*arm, 24) << 2;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS: {
|
||||
u32 imm12 = bits(*(ul32 *)loc, 11, 0);
|
||||
u32 imm4 = bits(*(ul32 *)loc, 19, 16);
|
||||
return sign_extend((imm4 << 12) | imm12, 15);
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOV--MOVS--immediate---Move--immediate--
|
||||
u32 imm4 = bits(*arm, 19, 16);
|
||||
u32 imm12 = bits(*arm, 11, 0);
|
||||
u32 val = (imm4 << 12) | imm12;
|
||||
return sign_extend(val, 16);
|
||||
}
|
||||
case R_ARM_PREL31:
|
||||
return sign_extend(*(ul32 *)loc, 30);
|
||||
return sign_extend(*arm, 31);
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS: {
|
||||
u32 imm4 = bits(*(ul16 *)loc, 3, 0);
|
||||
u32 i = bit(*(ul16 *)loc, 10);
|
||||
u32 imm3 = bits(*(ul16 *)(loc + 2), 14, 12);
|
||||
u32 imm8 = bits(*(ul16 *)(loc + 2), 7, 0);
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOVT--Move-Top-
|
||||
u32 imm4 = bits(thm[0], 3, 0);
|
||||
u32 i = bit(thm[0], 10);
|
||||
u32 imm3 = bits(thm[1], 14, 12);
|
||||
u32 imm8 = bits(thm[1], 7, 0);
|
||||
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
|
||||
return sign_extend(val, 15);
|
||||
return sign_extend(val, 16);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_mov_imm(u8 *loc, u32 val) {
|
||||
static void write_arm_mov(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0f000) | (imm4 << 16) | imm12;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/BL--BLX--immediate-
|
||||
u32 sign = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ sign;
|
||||
u32 J2 = !I2 ^ sign;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
static void write_thm_b21(u8 *loc, u32 val) {
|
||||
u32 S = bit(val, 20);
|
||||
u32 J2 = bit(val, 19);
|
||||
u32 J1 = bit(val, 18);
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (sign << 10) | imm10;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov_imm(u8 *loc, u32 val) {
|
||||
// https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/MOVT
|
||||
static void write_thm_b25(u8 *loc, u32 val) {
|
||||
u32 S = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ S;
|
||||
u32 J2 = !I2 ^ S;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov(u8 *loc, u32 val) {
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
ul16 *buf = (ul16 *)loc;
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
@@ -145,7 +175,6 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
@@ -156,36 +185,40 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = val;
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
write_thm_b_imm(loc, val);
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, val);
|
||||
write_arm_mov(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, val);
|
||||
write_thm_mov(loc, val);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -194,7 +227,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
constexpr ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
@@ -206,10 +239,10 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static const ul32 plt_entry[] = {
|
||||
static constexpr ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
@@ -219,45 +252,38 @@ static const ul32 plt_entry[] = {
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
// ARM does not use .eh_frame for exception handling. Instead, it uses
|
||||
// .ARM.exidx and .ARM.extab. So this function is empty.
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
u64 offset, u64 val) {}
|
||||
u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
// ARM and Thumb branch instructions can jump within ±16 MiB.
|
||||
static bool is_jump_reachable(i64 val) {
|
||||
return sign_extend(val, 24) == val;
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(U32<E> *)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);
|
||||
|
||||
auto get_tls_trampoline_addr = [&, i = 0](u64 addr) mutable {
|
||||
for (; i < output_section->thunks.size(); i++) {
|
||||
i64 disp = output_section->shdr.sh_addr + output_section->thunks[i]->offset -
|
||||
addr;
|
||||
if (is_jump_reachable(disp))
|
||||
return disp;
|
||||
}
|
||||
unreachable();
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
|
||||
@@ -266,13 +292,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
@@ -280,87 +299,98 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto get_thumb_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto get_arm_thunk_addr = [&] { return get_thunk_addr(i) + 4; };
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto get_thumb_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
|
||||
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
|
||||
|
||||
auto get_tlsdesc_trampoline_addr = [&] {
|
||||
auto it = ranges::upper_bound(output_section->thunks, P, {},
|
||||
[](std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return thunk->get_addr();
|
||||
});
|
||||
return (*it)->get_addr();
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP.W
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
// THM_CALL relocation refers either BL or BLX instruction.
|
||||
// THM_CALL relocation refers to either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BL if
|
||||
// the jump target is Thumb. Otherwise, use BLX.
|
||||
i64 val = S + A - P;
|
||||
if (is_jump_reachable(val)) {
|
||||
if (T) {
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) |= 0x1000; // rewrite to BL
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(val, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
}
|
||||
i64 val1 = S + A - P;
|
||||
i64 val2 = align_to(S + A - P, 4);
|
||||
|
||||
if (T && is_int(val1, 25)) {
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, val1);
|
||||
} else if (!T && is_int(val2, 25)) {
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
|
||||
write_thm_b25(loc, val2);
|
||||
} else {
|
||||
write_thm_b_imm(loc, align_to(get_arm_thunk_addr() + A - P, 4));
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite to BLX
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(ul32 *)loc = ((S + A) | T) - GOT;
|
||||
*(U32<E> *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(ul32 *)loc = GOT + G + A - P;
|
||||
*(U32<E> *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(ul32 *)loc = G + A;
|
||||
*(U32<E> *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)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);
|
||||
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(U32<E> *)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)) {
|
||||
i64 val = S + A - P;
|
||||
if (is_int(val, 26)) {
|
||||
if (T) {
|
||||
*(ul32 *)loc = 0xfa00'0000; // BLX
|
||||
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = 0xfa00'0000; // BLX
|
||||
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(val, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(ul32 *)loc = 0xeb00'0000; // BL
|
||||
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -370,101 +400,91 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
// required, we jump to a linker-synthesized thunk which does the
|
||||
// job with a longer code sequence.
|
||||
u64 val = S + A - P;
|
||||
if (!is_jump_reachable(val) || T)
|
||||
i64 val = S + A - P;
|
||||
if (T || !is_int(val, 26))
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0xe320'f000; // NOP
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
check(S + A - P, -(1 << 8), 1 << 8);
|
||||
*(U16<E> *)loc &= 0xff00;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
assert(T);
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(ul16 *)loc &= 0xf800;
|
||||
*(ul16 *)loc |= bits(S + A - P, 11, 1);
|
||||
*(U16<E> *)loc &= 0xf800;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP19: {
|
||||
i64 val = S + A - P;
|
||||
check(val, -(1 << 19), 1 << 19);
|
||||
|
||||
// sign:J2:J1:imm6:imm11:'0'
|
||||
u32 sign = bit(val, 20);
|
||||
u32 J2 = bit(val, 19);
|
||||
u32 J1 = bit(val, 18);
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
*(ul16 *)loc &= 0b1111'1011'1100'0000;
|
||||
*(ul16 *)loc |= (sign << 10) | imm6;
|
||||
|
||||
*(ul16 *)(loc + 2) &= 0b1101'0000'0000'0000;
|
||||
*(ul16 *)(loc + 2) |= (J2 << 13) | (J1 << 11) | imm11;
|
||||
case R_ARM_THM_JUMP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
write_thm_b21(loc, S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(ul32 *)loc = 0x8000'f3af; // NOP
|
||||
*(U32<E> *)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)
|
||||
i64 val = S + A - P;
|
||||
if (!T || !is_int(val, 25))
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b_imm(loc, val);
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_mov_imm(loc, ((S + A) | T) - P);
|
||||
write_arm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_mov_imm(loc, (S + A) | T);
|
||||
write_arm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov_imm(loc, ((S + A) | T) - P);
|
||||
write_thm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(ul32 *)loc &= 0x8000'0000;
|
||||
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
*(U32<E> *)loc &= 0x8000'0000;
|
||||
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov_imm(loc, (S + A) | T);
|
||||
write_thm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_mov_imm(loc, (S + A - P) >> 16);
|
||||
write_arm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov_imm(loc, (S + A - P) >> 16);
|
||||
write_thm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_mov_imm(loc, (S + A) >> 16);
|
||||
write_arm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov_imm(loc, (S + A) >> 16);
|
||||
write_thm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
@@ -476,50 +496,52 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// .L2: .word foo + . - .L1
|
||||
// R_ARM_TLS_GOTDESC
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: ldr r0, [pc, r0]
|
||||
// ...
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
//
|
||||
// or to the following for executable.
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: nop
|
||||
// ...
|
||||
// .L2: .word foo(tpoff)
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: ldr r0, [pc, r0]
|
||||
// ...
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
*(U32<E> *)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);
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(ul32 *)loc = S - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// BL <tls_trampoline>
|
||||
*(ul32 *)loc = 0xeb00'0000 | bits(get_tls_trampoline_addr(P + 8), 25, 2);
|
||||
*(U32<E> *)loc = 0xeb00'0000; // bl 0
|
||||
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
*(ul32 *)loc = 0xe320'f000; // nop
|
||||
*(U32<E> *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_tls_trampoline_addr(P + 4), 4);
|
||||
write_thm_b_imm(loc, val);
|
||||
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
|
||||
write_thm_b25(loc, val);
|
||||
*(U16<E> *)(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]
|
||||
*(U16<E> *)loc = 0x4478; // add r0, pc
|
||||
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
*(ul32 *)loc = 0x8000'f3af; // nop.w
|
||||
*(U32<E> *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -550,15 +572,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -571,8 +593,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -587,11 +607,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_TARGET1:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
@@ -627,15 +645,18 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_THM_JUMP8:
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_THM_JUMP19:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
@@ -647,11 +668,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
static ul32 hdr[] = {
|
||||
constexpr ul32 hdr[] = {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
@@ -660,7 +681,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
const u8 entry[] = {
|
||||
constexpr u8 entry[] = {
|
||||
// .thumb
|
||||
0x78, 0x47, // bx pc # jumps to 1f
|
||||
0xc0, 0x46, // nop
|
||||
@@ -673,24 +694,65 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
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);
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
memcpy(base, hdr, sizeof(hdr));
|
||||
|
||||
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
|
||||
|
||||
buf += sizeof(entry);
|
||||
P += sizeof(entry);
|
||||
*(U32<E> *)(buf + 12) = S - P - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return EF_ARM_EABI_VER5;
|
||||
if constexpr (E::is_le)
|
||||
return EF_ARM_EABI_VER5;
|
||||
else
|
||||
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void create_arm_exidx_section<E>(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < ctx.chunks.size(); i++) {
|
||||
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
|
||||
|
||||
if (osec && osec->shdr.sh_type == SHT_ARM_EXIDX) {
|
||||
auto *sec = new Arm32ExidxSection(*osec);
|
||||
ctx.extra.exidx = sec;
|
||||
ctx.chunks[i] = sec;
|
||||
ctx.chunk_pool.emplace_back(sec);
|
||||
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
isec->is_alive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
output_section.compute_section_size(ctx);
|
||||
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
|
||||
this->shdr.sh_link = chunk->shndx;
|
||||
}
|
||||
|
||||
// Returns the end of the text segment
|
||||
static u64 get_text_end(Context<E> &ctx) {
|
||||
u64 ret = 0;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_EXECINSTR)
|
||||
ret = std::max<u64>(ret, chunk->shdr.sh_addr + chunk->shdr.sh_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
@@ -701,14 +763,9 @@ u64 get_eflags(Context<E> &ctx) {
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function sorts .ARM.exidx records.
|
||||
void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
Timer t(ctx, "fixup_arm_exidx_section");
|
||||
|
||||
OutputSection<E> *osec = find_section(ctx, SHT_ARM_EXIDX);
|
||||
if (!osec)
|
||||
return;
|
||||
|
||||
// This function returns contents of .ARM.exidx.
|
||||
template <>
|
||||
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
@@ -721,36 +778,49 @@ void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant bit is set in (2) but
|
||||
// not in (3). So we can distinguished them just by looking at a value.
|
||||
const u32 EXIDX_CANTUNWIND = 1;
|
||||
const u32 CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
ul32 addr;
|
||||
ul32 val;
|
||||
U32<E> addr;
|
||||
U32<E> val;
|
||||
};
|
||||
|
||||
if (osec->shdr.sh_size % sizeof(Entry))
|
||||
Fatal(ctx) << "invalid .ARM.exidx section size";
|
||||
// We reserve one extra slot for the sentinel
|
||||
i64 num_entries = output_section.shdr.sh_size / sizeof(Entry) + 1;
|
||||
std::vector<u8> buf(num_entries * sizeof(Entry));
|
||||
Entry *ent = (Entry *)buf.data();
|
||||
|
||||
Entry *ent = (Entry *)(ctx.buf + osec->shdr.sh_offset);
|
||||
i64 num_entries = osec->shdr.sh_size / sizeof(Entry);
|
||||
// Write section contents to the buffer
|
||||
output_section.shdr.sh_addr = this->shdr.sh_addr;
|
||||
output_section.write_to(ctx, buf.data());
|
||||
|
||||
// Fill in sentinel fields
|
||||
u64 sentinel_addr = this->shdr.sh_addr + sizeof(Entry) * (num_entries - 1);
|
||||
ent[num_entries - 1].addr = get_text_end(ctx) - sentinel_addr;
|
||||
ent[num_entries - 1].val = CANTUNWIND;
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by addresses, we first translate them so that the addresses
|
||||
// records by address, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
return val != EXIDX_CANTUNWIND && !(val & 0x8000'0000);
|
||||
return val != CANTUNWIND && !(val & 0x8000'0000);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 30) + offset;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 31) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
tbb::parallel_sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
|
||||
return a.addr < b.addr;
|
||||
});
|
||||
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
|
||||
|
||||
// Remove duplicate adjacent entries. That is, if two adjacent functions
|
||||
// have the same compact unwind info or are both CANTUNWIND, we can
|
||||
// merge them into a single address range.
|
||||
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
|
||||
num_entries -= tail.size();
|
||||
buf.resize(num_entries * sizeof(Entry));
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
@@ -760,14 +830,87 @@ void fixup_arm_exidx_section(Context<E> &ctx) {
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (ctx.shdr) {
|
||||
if (Chunk<E> *text = find_section(ctx, ".text")) {
|
||||
osec->shdr.sh_link = text->shndx;
|
||||
ctx.shdr->copy_buf(ctx);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
#if MOLD_ARM32BE
|
||||
// Even though using ARM32 in big-endian mode is very rare, the processor
|
||||
// technically supports both little- and big-endian modes. There are two
|
||||
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
|
||||
// data are encoded in big-endian. In BE8, instructions are encoded in
|
||||
// little-endian, and only data is in big-endian. BE8 is the de facto
|
||||
// standard for ARMv6 or later. We support only BE8.
|
||||
//
|
||||
// A tricky thing is that instructions in an object file are always
|
||||
// big-endian if the file is compiled for big-endian mode. In other words,
|
||||
// the compiler always emit code in BE32 if -mbig-endian is specified. It
|
||||
// is the linker's responsibility to rewrite instructions from big-endian
|
||||
// to little-endian for an BE8 output. This function does that.
|
||||
//
|
||||
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
|
||||
// Thumb instructions, and data. We need to distinguish them to swap 4
|
||||
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
|
||||
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
|
||||
// and $d, respectively. We use mapping symbols to determine what to do
|
||||
// with the text section.
|
||||
//
|
||||
// This function is called after we copy the input section contents to the
|
||||
// output file. We rewrite instructions in the output buffer in place.
|
||||
void arm32be_swap_bytes(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Collect mapping symbols
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (Symbol<E> *sym : file->get_local_syms())
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
if (std::string_view x = sym->name();
|
||||
x == "$a" || x.starts_with("$a.") ||
|
||||
x == "$t" || x.starts_with("$t.") ||
|
||||
x == "$d" || x.starts_with("$d."))
|
||||
syms.push_back(sym);
|
||||
|
||||
// Group mapping symbols by input section and sort by address
|
||||
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
|
||||
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
|
||||
});
|
||||
|
||||
// Swap bytes
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
Symbol<E> &sym = *syms[i];
|
||||
if (sym.name().starts_with("$d"))
|
||||
continue;
|
||||
|
||||
InputSection<E> &isec = *sym.get_input_section();
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
u8 *start = base + sym.value;
|
||||
|
||||
u8 *end;
|
||||
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
|
||||
end = base + syms[i + 1]->value;
|
||||
else
|
||||
end = base + isec.sh_size;
|
||||
|
||||
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
|
||||
for (u8 *p = start; p < end; p += sz)
|
||||
std::reverse(p, p + sz);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -17,11 +17,13 @@
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
|
||||
|
||||
#if MOLD_ARM64LE || MOLD_ARM64BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = ARM64;
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
@@ -46,16 +48,16 @@ static u64 page(u64 val) {
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
constexpr ul32 insn[] = {
|
||||
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x9000'0010, // adrp x16, .got.plt[2]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[2]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
0xd503'201f, // nop
|
||||
};
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
@@ -68,7 +70,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, .got.plt[n]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[n]
|
||||
@@ -86,11 +88,11 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const ul32 insn[] = {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd503'201f, // nop
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
@@ -110,13 +112,13 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
*(ul64 *)loc = val;
|
||||
*(U64<E> *)loc = val;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
@@ -145,11 +147,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -158,22 +155,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
@@ -242,7 +235,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
i + 1 < rels.size()) {
|
||||
i64 val = S + A - P - 4;
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (sign_extend(val, 20) == val &&
|
||||
if (is_int(val, 21) &&
|
||||
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
@@ -281,14 +274,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
}
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (val < -(1 << 27) || (1 << 27) <= val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
if (!is_int(val, 28))
|
||||
val = sym.get_thunk_addr(ctx, P) + A - P;
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
@@ -296,15 +289,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
|
||||
break;
|
||||
case R_AARCH64_PREL16:
|
||||
check(S + A - P, -(1LL << 15), 1LL << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
check(S + A - P, -(1LL << 15), 1LL << 16);
|
||||
*(U16<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 32);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
*(U64<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
@@ -383,19 +376,21 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// blr x1
|
||||
// R_AARCH64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// adrp x0, :gottprel:foo
|
||||
// ldr x0, [x0, :gottprel_lo12:foo]
|
||||
//
|
||||
// or to the following for executable.
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// movz x0, :tls_offset_hi:foo, lsl #16
|
||||
// movk x0, :tls_offset_lo:foo
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// adrp x0, :gottprel:foo
|
||||
// ldr x0, [x0, :gottprel_lo12:foo]
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
@@ -450,13 +445,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -464,19 +452,21 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul64 *)loc = *val;
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_ABS32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
case R_AARCH64_ABS32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
@@ -488,8 +478,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -505,9 +493,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
@@ -567,6 +552,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
@@ -601,29 +587,81 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// The size of a thunk entry varies on ARM64 depending on the distance to
|
||||
// the branch target. This function computes the size of each thunk entry.
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
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
|
||||
void Thunk<E>::shrink_size(Context<E> &ctx) {
|
||||
offsets.clear();
|
||||
offsets.push_back(0);
|
||||
i64 off = 0;
|
||||
|
||||
// The distance between S and P is only reduced by shrink_size(), but
|
||||
// page(S) – page(P) may still increase by one page due to address
|
||||
// changes, so we add a safety margin.
|
||||
//
|
||||
// For example, page(0x1200) – page(0x1000) is 0, whereas
|
||||
// page(0x1100) – page(0xfff) is 0x1000, even though the latter
|
||||
// distance is shorter than the former.
|
||||
auto is_small = [](i64 prel) {
|
||||
return is_int(prel + 0x1000, 33) && is_int(prel - 0x1000, 33);
|
||||
};
|
||||
|
||||
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, 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;
|
||||
u64 P = get_addr() + off;
|
||||
i64 prel = page(S) - page(P);
|
||||
off += is_small(prel) ? 16 : 32;
|
||||
offsets.push_back(off);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// Short thunk with a 33 bit displacement
|
||||
constexpr ul32 insn1[] = {
|
||||
0x9000'0010, // adrp x16, 0
|
||||
0x9100'0210, // add x16, x16
|
||||
0xd61f'0200, // br x16
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
// Long thunk with a 64 bit displacement
|
||||
constexpr ul32 insn2[] = {
|
||||
0x1000'0010, // adr x16, 0
|
||||
0xd2a0'0011, // movz x17, 0, lsl #16
|
||||
0xf2c0'0011, // movk x17, 0, lsl #32
|
||||
0xf2e0'0011, // movk x17, 0, lsl #48
|
||||
0x8b11'0210, // add x16, x16, x17
|
||||
0xd61f'0200, // br x16
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
|
||||
if (offsets[i + 1] - offsets[i] == 16) {
|
||||
i64 prel = page(S) - page(P);
|
||||
assert(is_int(prel, 33));
|
||||
memcpy(buf, insn1, sizeof(insn1));
|
||||
write_adrp(buf, prel);
|
||||
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
|
||||
} else {
|
||||
memcpy(buf, insn2, sizeof(insn2));
|
||||
write_adr(buf, bits(S - P, 15, 0));
|
||||
*(ul32 *)(buf + 4) |= bits(S - P, 31, 16) << 5;
|
||||
*(ul32 *)(buf + 8) |= bits(S - P, 47, 32) << 5;
|
||||
*(ul32 *)(buf + 12) |= bits(S - P, 63, 48) << 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template class Thunk<E>;
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = I386;
|
||||
|
||||
@@ -108,25 +108,24 @@ template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 7) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0x51, // push %ecx
|
||||
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, // (padding)
|
||||
0x51, // push %ecx
|
||||
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,24 +133,22 @@ template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, // (padding)
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, // (padding)
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 5) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 11) = sym.get_gotplt_addr(ctx);
|
||||
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,20 +156,18 @@ template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xf3, 0x0f, 0x1e, 0xfb, // endbr32
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 6) = sym.get_got_pltgot_addr(ctx);
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +221,7 @@ static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
}
|
||||
|
||||
// Relax LD to LE
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
@@ -235,18 +230,17 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
0x90, // nop
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
*(ul32 *)(loc + 6) = tls_size;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -254,15 +248,38 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
|
||||
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
|
||||
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
|
||||
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
|
||||
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
|
||||
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
|
||||
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
|
||||
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
|
||||
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
|
||||
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
|
||||
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
|
||||
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
|
||||
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
|
||||
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
|
||||
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -271,19 +288,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
@@ -294,7 +308,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
@@ -346,7 +359,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
else
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
@@ -364,24 +377,45 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// mov foo@GOTTPOFF(%ebx), %eax
|
||||
// nop
|
||||
//
|
||||
// or to the following for executable.
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
// lea 0(%ebx), %reg
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// mov %reg, %eax
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// Note that the compiler always uses the local-exec TLS model
|
||||
// for -fno-pic, so TLSDESC code is always PIC (i.e. uses %ebx to
|
||||
// store the address of GOT.)
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
loc[-2] = 0x8b;
|
||||
loc[-1] = 0x83;
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
loc[-2] = 0x90;
|
||||
loc[-1] = 0xb8;
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
@@ -410,13 +444,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -425,6 +452,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
@@ -475,8 +506,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -506,9 +535,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_16:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
@@ -538,8 +564,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_TLS_GD:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static)
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
@@ -547,7 +572,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_TLS_LDM:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
@@ -558,6 +583,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_386_TLS_LE:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
@@ -569,4 +595,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -10,21 +10,22 @@
|
||||
// bootstrapping the entire ecosystem for LoongArch, sending patches to
|
||||
// Linux, GCC, LLVM, etc.
|
||||
//
|
||||
// All instructions are 4 bytes long in LoongArch and aligned to 4-byte
|
||||
// boundaries. It has 32 general-purpose registers. Among these, $t0 - $t8
|
||||
// (aliases for $r12 - $r20) are temporary registers that we can use in
|
||||
// our PLT and range extension thunks.
|
||||
// Speaking of the ISA, all instructions are 4 byte long and aligned to 4
|
||||
// byte boundaries in LoongArch. It has 32 general-purpose registers.
|
||||
// Among these, $t0 - $t8 (aliases for $r12 - $r20) are temporary
|
||||
// registers that we can use in our PLT.
|
||||
//
|
||||
// The psABI defines a few linker relaxations. We haven't supported them
|
||||
// yet.
|
||||
// Just like RISC-V, LoongArch supports section-shrinking relaxations.
|
||||
// That is, it allows linkers to rewrite certain instruction sequences to
|
||||
// shorter ones. Sections are not an atomic unit of copying.
|
||||
//
|
||||
// https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
|
||||
// https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc
|
||||
|
||||
#if MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
@@ -50,7 +51,7 @@ static u64 hi20(u64 val, u64 pc) {
|
||||
return bits(page(val + 0x800) - page(pc), 31, 12);
|
||||
}
|
||||
|
||||
static u64 hi64(u64 val, u64 pc) {
|
||||
static u64 higher(u64 val, u64 pc) {
|
||||
// A PC-relative 64-bit address is materialized with the following
|
||||
// instructions for the large code model:
|
||||
//
|
||||
@@ -64,21 +65,18 @@ static u64 hi64(u64 val, u64 pc) {
|
||||
// ADDI.D adds a sign-extended 12 bit value to a register. LU32I.D and
|
||||
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
|
||||
//
|
||||
// Compensating all the sign-extensions is a bit complicated.
|
||||
u64 x = page(val) - page(pc);
|
||||
if (val & 0x800)
|
||||
x += 0x1000 - 0x1'0000'0000;
|
||||
if (x & 0x8000'0000)
|
||||
x += 0x1'0000'0000;
|
||||
return x;
|
||||
// Compensating all the sign-extensions is a bit complicated. The
|
||||
// psABI gives the following formula.
|
||||
val = val + 0x8000'0000 + ((val & 0x800) ? (0x1000 - 0x1'0000'0000) : 0);
|
||||
return page(val) - page(pc - 8);
|
||||
}
|
||||
|
||||
static u64 higher20(u64 val, u64 pc) {
|
||||
return bits(hi64(val, pc), 51, 32);
|
||||
return bits(higher(val, pc), 51, 32);
|
||||
}
|
||||
|
||||
static u64 highest12(u64 val, u64 pc) {
|
||||
return bits(hi64(val, pc), 63, 52);
|
||||
return bits(higher(val, pc), 63, 52);
|
||||
}
|
||||
|
||||
static void write_k12(u8 *loc, u32 val) {
|
||||
@@ -113,9 +111,49 @@ static void write_d10k16(u8 *loc, u32 val) {
|
||||
*(ul32 *)loc |= bits(val, 25, 16);
|
||||
}
|
||||
|
||||
static u32 get_rd(u32 insn) {
|
||||
return bits(insn, 4, 0);
|
||||
}
|
||||
|
||||
static u32 get_rj(u32 insn) {
|
||||
return bits(insn, 9, 5);
|
||||
}
|
||||
|
||||
static void set_rj(u8 *loc, u32 rj) {
|
||||
assert(rj < 32);
|
||||
*(ul32 *)loc &= 0b111111'1111111111111111'00000'11111;
|
||||
*(ul32 *)loc |= rj << 5;
|
||||
}
|
||||
|
||||
// Returns true if isec's i'th relocation refers to the following
|
||||
// relaxable instructioon pair.
|
||||
//
|
||||
// pcalau12i $t0, 0 # R_LARCH_GOT_PC_HI20, R_LARCH_RELAX
|
||||
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12, R_LARCH_RELAX
|
||||
static bool is_relaxable_got_load(Context<E> &ctx, InputSection<E> &isec, i64 i) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
Symbol<E> &sym = *isec.file.symbols[rels[i].r_sym];
|
||||
u8 *buf = (u8 *)isec.contents.data();
|
||||
|
||||
if (ctx.arg.relax &&
|
||||
sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 3 < rels.size() &&
|
||||
rels[i + 1].r_type == R_LARCH_RELAX &&
|
||||
rels[i + 2].r_type == R_LARCH_GOT_PC_LO12 &&
|
||||
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
|
||||
rels[i + 3].r_type == R_LARCH_RELAX) {
|
||||
u32 insn1 = *(ul32 *)(buf + rels[i].r_offset);
|
||||
u32 insn2 = *(ul32 *)(buf + rels[i].r_offset + 4);
|
||||
bool is_ld_d = (insn2 & 0xffc0'0000) == 0x28c0'0000;
|
||||
return get_rd(insn1) == get_rd(insn2) && get_rd(insn2) == get_rj(insn2) &&
|
||||
is_ld_d;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn_64[] = {
|
||||
constexpr ul32 insn_64[] = {
|
||||
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
|
||||
0x0011'bdad, // sub.d $t1, $t1, $t3
|
||||
0x28c0'01cf, // ld.d $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
|
||||
@@ -126,7 +164,7 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
|
||||
0x4c00'01e0, // jr $t3
|
||||
};
|
||||
|
||||
static const ul32 insn_32[] = {
|
||||
constexpr ul32 insn_32[] = {
|
||||
0x1a00'000e, // pcalau12i $t2, %pc_hi20(.got.plt)
|
||||
0x0011'3dad, // sub.w $t1, $t1, $t3
|
||||
0x2880'01cf, // ld.w $t3, $t2, %lo12(.got.plt) # _dl_runtime_resolve
|
||||
@@ -146,18 +184,18 @@ void write_plt_header<E>(Context<E> &ctx, u8 *buf) {
|
||||
write_k12(buf + 16, gotplt);
|
||||
}
|
||||
|
||||
static const ul32 plt_entry_64[] = {
|
||||
static constexpr ul32 plt_entry_64[] = {
|
||||
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
|
||||
0x28c0'01ef, // ld.d $t3, $t3, %lo12([email protected])
|
||||
0x4c00'01ed, // jirl $t1, $t3, 0
|
||||
0x0340'0000, // nop
|
||||
0x002a'0000, // break
|
||||
};
|
||||
|
||||
static const ul32 plt_entry_32[] = {
|
||||
static constexpr ul32 plt_entry_32[] = {
|
||||
0x1a00'000f, // pcalau12i $t3, %pc_hi20([email protected])
|
||||
0x2880'01ef, // ld.w $t3, $t3, %lo12([email protected])
|
||||
0x4c00'01ed, // jirl $t1, $t3, 0
|
||||
0x0340'0000, // nop
|
||||
0x002a'0000, // break
|
||||
};
|
||||
|
||||
template <>
|
||||
@@ -232,11 +270,9 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
std::span<RelocDelta> deltas = extra.r_deltas;
|
||||
i64 k = 0;
|
||||
u8 *buf = (u8 *)contents.data();
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
@@ -246,54 +282,60 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
rel.r_type == R_LARCH_ALIGN)
|
||||
continue;
|
||||
|
||||
i64 removed_bytes = 0;
|
||||
i64 r_delta = 0;
|
||||
|
||||
if (!deltas.empty()) {
|
||||
while (k < deltas.size() && deltas[k].offset < rel.r_offset)
|
||||
k++;
|
||||
if (k < deltas.size() && deltas[k].offset == rel.r_offset)
|
||||
removed_bytes = get_removed_bytes(deltas, k);
|
||||
if (k > 0)
|
||||
r_delta = deltas[k - 1].delta;
|
||||
}
|
||||
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto check_branch = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val & 0b11)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
check(val, lo, hi);
|
||||
};
|
||||
i64 r_offset = rel.r_offset - r_delta;
|
||||
u8 *loc = base + r_offset;
|
||||
|
||||
// Unlike other psABIs, the LoongArch ABI uses the same relocation
|
||||
// types to refer to GOT entries for thread-local symbols and regular
|
||||
// ones. Therefore, G may refer to a TLSGD or a regular GOT slot
|
||||
// depending on the symbol type.
|
||||
//
|
||||
// Note that as of August 2023, both GCC and Clang treat TLSLD relocs
|
||||
// as if they were TLSGD relocs for LoongArch, which is a clear bug.
|
||||
// We need to handle TLSLD relocs as synonyms for TLSGD relocs for the
|
||||
// sake of bug compatibility.
|
||||
auto get_got_idx = [&] {
|
||||
if (sym.has_tlsgd(ctx))
|
||||
return sym.get_tlsgd_idx(ctx);
|
||||
return sym.get_got_idx(ctx);
|
||||
};
|
||||
// Note that even though LoongArch defines relocations for TLSLD, TLSLD
|
||||
// is not actually supported on it. GCC and LLVM emit identical machine
|
||||
// code for -ftls-model=global-dynamic and -ftls-model=local-dynamic,
|
||||
// and we need to handle TLSLD relocations as equivalent to TLSGD
|
||||
// relocations. This is clearly a compiler bug, but it's too late to
|
||||
// fix. The only way to fix it would be to define a new set of
|
||||
// relocations for true TLSLD and deprecate the current ones. But it
|
||||
// appears that migrating to TLSDESC is a better choice, so it's
|
||||
// unlikely to happen.
|
||||
i64 got_idx =
|
||||
sym.has_tlsgd(ctx) ? sym.get_tlsgd_idx(ctx) : sym.get_got_idx(ctx);
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = get_got_idx() * sizeof(Word<E>);
|
||||
u64 P = get_addr() + r_offset;
|
||||
u64 G = got_idx * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto check_branch = [&](i64 val, i64 lo, i64 hi) {
|
||||
check(val, lo, hi);
|
||||
if (val & 0b11)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_LARCH_32:
|
||||
if constexpr (E::is_64)
|
||||
*(ul32 *)loc = S + A;
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
assert(E::is_64);
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_LARCH_B16:
|
||||
check_branch(S + A - P, -(1 << 17), 1 << 17);
|
||||
@@ -303,13 +345,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
check_branch(S + A - P, -(1 << 22), 1 << 22);
|
||||
write_d5k16(loc, (S + A - P) >> 2);
|
||||
break;
|
||||
case R_LARCH_B26: {
|
||||
i64 val = S + A - P;
|
||||
if (val < -(1 << 27) || (1 << 27) <= val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
write_d10k16(loc, val >> 2);
|
||||
case R_LARCH_B26:
|
||||
check_branch(S + A - P, -(1 << 27), 1 << 27);
|
||||
write_d10k16(loc, (S + A - P) >> 2);
|
||||
break;
|
||||
}
|
||||
case R_LARCH_ABS_LO12:
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
@@ -328,12 +367,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// It is contrary to the psABI document, but GNU ld has special
|
||||
// code to handle it, so we accept it too.
|
||||
if ((*(ul32 *)loc & 0xfc00'0000) == 0x4c00'0000)
|
||||
write_k16(loc, sign_extend(S + A, 11) >> 2);
|
||||
write_k16(loc, sign_extend(S + A, 12) >> 2);
|
||||
else
|
||||
write_k12(loc, S + A);
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
if (removed_bytes == 0) {
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
} else {
|
||||
// Rewrite pcalau12i + addi.d with pcaddi
|
||||
assert(removed_bytes == 4);
|
||||
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
|
||||
write_j20(loc, (S + A - P) >> 2);
|
||||
i += 3;
|
||||
}
|
||||
break;
|
||||
case R_LARCH_PCALA64_LO20:
|
||||
write_j20(loc, higher20(S + A, P));
|
||||
@@ -345,7 +392,37 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write_k12(loc, GOT + G + A);
|
||||
break;
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
write_j20(loc, hi20(GOT + G + A, P));
|
||||
if (removed_bytes == 0) {
|
||||
// If the PC-relative symbol address is known at link-time, we can
|
||||
// rewrite the following GOT load
|
||||
//
|
||||
// pcalau12i $t0, 0 # R_LARCH_GOT_PC_HI20
|
||||
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12
|
||||
//
|
||||
// with the following address materialization
|
||||
//
|
||||
// pcalau12i $t0, 0
|
||||
// addi.d $t0, $t0, 0
|
||||
if (is_relaxable_got_load(ctx, *this, i)) {
|
||||
i64 dist = compute_distance(ctx, sym, *this, rel);
|
||||
if (is_int(dist, 32)) {
|
||||
u32 rd = get_rd(*(ul32 *)loc);
|
||||
*(ul32 *)(loc + 4) = 0x02c0'0000 | (rd << 5) | rd; // addi.d
|
||||
|
||||
write_j20(loc, hi20(S + A, P));
|
||||
write_k12(loc + 4, S + A);
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write_j20(loc, hi20(GOT + G + A, P));
|
||||
} else {
|
||||
// Rewrite pcalau12i + ld.d with pcaddi
|
||||
assert(removed_bytes == 4);
|
||||
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
|
||||
write_j20(loc, (S + A - P) >> 2);
|
||||
i += 3;
|
||||
}
|
||||
break;
|
||||
case R_LARCH_GOT64_PC_LO20:
|
||||
write_j20(loc, higher20(GOT + G + A, P));
|
||||
@@ -401,13 +478,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_LARCH_TLS_IE64_HI12:
|
||||
write_k12(loc, (sym.get_gottp_addr(ctx) + A) >> 52);
|
||||
break;
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_PC_HI20:
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
check(sym.get_tlsgd_addr(ctx) + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_j20(loc, hi20(sym.get_tlsgd_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
case R_LARCH_TLS_GD_HI20:
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
write_j20(loc, (sym.get_tlsgd_addr(ctx) + A) >> 12);
|
||||
break;
|
||||
case R_LARCH_ADD6:
|
||||
@@ -441,17 +518,137 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul64 *)loc -= S + A;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_LARCH_64_PCREL:
|
||||
*(ul64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_LARCH_CALL36:
|
||||
if (removed_bytes == 0) {
|
||||
i64 val = S + A - P;
|
||||
check_branch(val, -(1LL << 37) - 0x20000, (1LL << 37) - 0x20000);
|
||||
write_j20(loc, (val + 0x20000) >> 18);
|
||||
write_k16(loc + 4, val >> 2);
|
||||
} else {
|
||||
// Rewrite PCADDU18I + JIRL to B or BL
|
||||
assert(removed_bytes == 4);
|
||||
if (get_rd(*(ul32 *)(buf + rel.r_offset + 4)) == 0)
|
||||
*(ul32 *)loc = 0x5000'0000; // B
|
||||
else
|
||||
*(ul32 *)loc = 0x5400'0000; // BL
|
||||
write_d10k16(loc, (S + A - P) >> 2);
|
||||
}
|
||||
break;
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) + S + A);
|
||||
break;
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
overwrite_uleb(loc, read_uleb(loc) - S - A);
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_HI20:
|
||||
// LoongArch TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in a0.
|
||||
//
|
||||
// pcalau12i $a0, 0
|
||||
// R_LARCH_TLS_DESC_PC_HI20 foo
|
||||
// addi.[dw] $a0, $a0, 0
|
||||
// R_LARCH_TLS_DESC_PC_LO12 foo
|
||||
// ld.d $ra, $a0, 0
|
||||
// R_LARCH_TLS_DESC_LD foo
|
||||
// jirl $ra, $ra, 0
|
||||
// R_LARCH_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// lu12i.w $a0, foo@TPOFF
|
||||
// addi.w $a0, $a0, foo@TPOFF
|
||||
//
|
||||
// or to the following if the TP offset is small enough.
|
||||
//
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// ori $a0, $zero, foo@TPOFF
|
||||
//
|
||||
// If the TP-relative address is known at process startup time, we
|
||||
// may relax the instructions to the following.
|
||||
//
|
||||
// <deleted>
|
||||
// <deleted>
|
||||
// pcalau12i $a0, foo@GOTTP
|
||||
// ld.[dw] $a0, $a0, foo@GOTTP
|
||||
//
|
||||
// If we don't know anything about the symbol, we can still relax
|
||||
// the first two instructions to a single pcaddi as shown below.
|
||||
//
|
||||
// <deleted>
|
||||
// pcaddi $a0, foo@GOTDESC
|
||||
// ld.d $ra, $a0, 0
|
||||
// jirl $ra, $ra, 0
|
||||
if (sym.has_tlsdesc(ctx) && removed_bytes == 0)
|
||||
write_j20(loc, hi20(sym.get_tlsdesc_addr(ctx) + A, P));
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_LO12:
|
||||
if (sym.has_tlsdesc(ctx) && removed_bytes == 0) {
|
||||
i64 dist = sym.get_tlsdesc_addr(ctx) + A - P;
|
||||
if (is_int(dist, 22)) {
|
||||
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
|
||||
write_j20(loc, dist >> 2);
|
||||
} else {
|
||||
write_k12(loc, sym.get_tlsdesc_addr(ctx) + A);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_LD:
|
||||
if (sym.has_tlsdesc(ctx) || removed_bytes == 4) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x1a00'0004; // pcalau12i $a0, 0
|
||||
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
|
||||
} else {
|
||||
*(ul32 *)loc = 0x1400'0004; // lu12i.w $a0, 0
|
||||
write_j20(loc, (S + A + 0x800 - ctx.tp_addr) >> 12);
|
||||
}
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
if (E::is_64)
|
||||
*(ul32 *)loc = 0x28c0'0084; // ld.d $a0, $a0, 0
|
||||
else
|
||||
*(ul32 *)loc = 0x2880'0084; // ld.w $a0, $a0, 0
|
||||
write_k12(loc, sym.get_gottp_addr(ctx) + A);
|
||||
} else {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
if (0 <= val && val < 0x1000)
|
||||
*(ul32 *)loc = 0x0380'0004; // ori $a0, $zero, 0
|
||||
else
|
||||
*(ul32 *)loc = 0x0280'0084; // addi.w $a0, $a0, 0
|
||||
write_k12(loc, val);
|
||||
}
|
||||
break;
|
||||
case R_LARCH_TLS_LE_HI20_R:
|
||||
if (removed_bytes == 0)
|
||||
write_j20(loc, (S + A + 0x800 - ctx.tp_addr) >> 12);
|
||||
break;
|
||||
case R_LARCH_TLS_LE_LO12_R: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
write_k12(loc, val);
|
||||
|
||||
// Rewrite `addi.d $t0, $t0, <offset>` with `addi.d $t0, $tp, <offset>`
|
||||
// if the offset is directly accessible using tp. tp is r2.
|
||||
if (is_int(val, 12))
|
||||
set_rj(loc, 2);
|
||||
break;
|
||||
}
|
||||
case R_LARCH_64:
|
||||
case R_LARCH_TLS_LE_ADD_R:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -551,8 +748,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -569,11 +764,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
|
||||
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;
|
||||
|
||||
@@ -581,15 +771,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_LARCH_32:
|
||||
if constexpr (E::is_64)
|
||||
scan_absrel(ctx, sym, rel);
|
||||
else
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
assert(E::is_64);
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_B26:
|
||||
case R_LARCH_PCALA_HI20:
|
||||
case R_LARCH_CALL36:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
@@ -601,10 +786,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_LARCH_TLS_IE_PC_HI20:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_PC_HI20:
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
case R_LARCH_TLS_LD_PC_HI20:
|
||||
case R_LARCH_TLS_GD_HI20:
|
||||
case R_LARCH_TLS_LD_HI20:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_LARCH_32_PCREL:
|
||||
@@ -615,8 +800,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_LARCH_TLS_LE_LO12:
|
||||
case R_LARCH_TLS_LE64_LO20:
|
||||
case R_LARCH_TLS_LE64_HI12:
|
||||
case R_LARCH_TLS_LE_HI20_R:
|
||||
case R_LARCH_TLS_LE_LO12_R:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_LARCH_64:
|
||||
case R_LARCH_B16:
|
||||
case R_LARCH_B21:
|
||||
case R_LARCH_ABS_HI20:
|
||||
@@ -650,6 +841,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_LARCH_SUB64:
|
||||
case R_LARCH_ADD_ULEB128:
|
||||
case R_LARCH_SUB_ULEB128:
|
||||
case R_LARCH_TLS_DESC_PC_HI20:
|
||||
case R_LARCH_TLS_DESC_PC_LO12:
|
||||
case R_LARCH_TLS_DESC_LD:
|
||||
case R_LARCH_TLS_LE_ADD_R:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -658,29 +853,163 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ul32 insn[] = {
|
||||
0x1e00'000c, // pcaddu18i $t0, 0
|
||||
0x4c00'0180, // jirl $zero, $t0, 0
|
||||
};
|
||||
void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
|
||||
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
std::vector<RelocDelta> &deltas = isec.extra.r_deltas;
|
||||
i64 r_delta = 0;
|
||||
u8 *buf = (u8 *)isec.contents.data();
|
||||
|
||||
static_assert(E::thunk_size == sizeof(insn));
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &r = rels[i];
|
||||
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
auto remove = [&](i64 d) {
|
||||
r_delta += d;
|
||||
deltas.push_back(RelocDelta{r.r_offset, r_delta});
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
// A R_LARCH_ALIGN relocation refers to the beginning of a nop
|
||||
// sequence. We need to remove some or all of them so that the
|
||||
// instruction that immediately follows that is aligned to a specified
|
||||
// boundary. To allow that, a R_LARCH_ALIGN relocation that requests
|
||||
// 2^n alignment refers to 2^n - 4 bytes of nop instructions.
|
||||
if (r.r_type == R_LARCH_ALIGN) {
|
||||
// The actual rule for storing the alignment size is a bit weird.
|
||||
// In particular, the most significant 56 bits of r_addend is
|
||||
// sometimes used to store the upper limit of the alignment,
|
||||
// allowing the instruction that follows nops _not_ to be aligned at
|
||||
// all. I think that's a spec bug, so we don't want to support that.
|
||||
i64 alignment;
|
||||
if (r.r_sym) {
|
||||
if (r.r_addend >> 8)
|
||||
Fatal(ctx) << isec << ": ternary R_LARCH_ALIGN is not supported: " << i;
|
||||
alignment = 1 << r.r_addend;
|
||||
} else {
|
||||
if (!has_single_bit(r.r_addend + 4))
|
||||
Fatal(ctx) << isec << ": R_LARCH_ALIGN: invalid alignment requirement: "
|
||||
<< i;
|
||||
alignment = r.r_addend + 4;
|
||||
}
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_j20(buf, (S - P + 0x20000) >> 18);
|
||||
write_k16(buf + 4, (S - P) >> 2);
|
||||
u64 P = isec.get_addr() + r.r_offset - r_delta;
|
||||
u64 desired = align_to(P, alignment);
|
||||
u64 actual = P + alignment - 4;
|
||||
if (desired != actual)
|
||||
remove(actual - desired);
|
||||
continue;
|
||||
}
|
||||
|
||||
buf += sizeof(insn);
|
||||
P += sizeof(insn);
|
||||
// Handling other relocations is optional.
|
||||
if (!ctx.arg.relax || i == rels.size() - 1 ||
|
||||
rels[i + 1].r_type != R_LARCH_RELAX)
|
||||
continue;
|
||||
|
||||
// Skip linker-synthesized symbols because their final addresses
|
||||
// are not fixed yet.
|
||||
if (sym.file == ctx.internal_obj)
|
||||
continue;
|
||||
|
||||
switch (r.r_type) {
|
||||
case R_LARCH_TLS_LE_HI20_R:
|
||||
case R_LARCH_TLS_LE_ADD_R:
|
||||
// LoongArch uses the following three instructions to access
|
||||
// TP ± 2 GiB.
|
||||
//
|
||||
// lu12i.w $t0, 0 # R_LARCH_TLS_LE_HI20_R
|
||||
// add.d $t0, $t0, $tp # R_LARCH_TLS_LE_ADD_R
|
||||
// addi.d $t0, $t0, 0 # R_LARCH_TLS_LE_LO12_R
|
||||
//
|
||||
// If the thread-local variable is within TP ± 2 KiB, we can
|
||||
// relax them into the following single instruction.
|
||||
//
|
||||
// addi.d $t0, $tp, <tp-offset>
|
||||
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
is_int(val, 12))
|
||||
remove(4);
|
||||
break;
|
||||
case R_LARCH_PCALA_HI20:
|
||||
// The following two instructions are used to materialize a
|
||||
// PC-relative address with a 32 bit displacement.
|
||||
//
|
||||
// pcalau12i $t0, 0 # R_LARCH_PCALA_HI20
|
||||
// addi.d $t0, $t0, 0 # R_LARCH_PCALA_LO12
|
||||
//
|
||||
// If the displacement is within ±2 MiB, we can relax them to
|
||||
// the following instruction.
|
||||
//
|
||||
// pcaddi $t0, <offset>
|
||||
if (i + 3 < rels.size() &&
|
||||
rels[i + 2].r_type == R_LARCH_PCALA_LO12 &&
|
||||
rels[i + 2].r_offset == rels[i].r_offset + 4 &&
|
||||
rels[i + 3].r_type == R_LARCH_RELAX) {
|
||||
i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
u32 insn1 = *(ul32 *)(buf + rels[i].r_offset);
|
||||
u32 insn2 = *(ul32 *)(buf + rels[i].r_offset + 4);
|
||||
bool is_addi_d = (insn2 & 0xffc0'0000) == 0x02c0'0000;
|
||||
|
||||
if ((dist & 0b11) == 0 && is_int(dist, 22) &&
|
||||
is_addi_d && get_rd(insn1) == get_rd(insn2) &&
|
||||
get_rd(insn2) == get_rj(insn2))
|
||||
remove(4);
|
||||
}
|
||||
break;
|
||||
case R_LARCH_CALL36:
|
||||
// A CALL36 relocation referes to the following instruction pair
|
||||
// to jump to PC ± 128 GiB.
|
||||
//
|
||||
// pcaddu18i $t0, 0 # R_LARCH_CALL36
|
||||
// jirl $zero/$ra, $t0, 0
|
||||
//
|
||||
// If the displacement is PC ± 128 MiB, we can use B or BL instead.
|
||||
// Note that $zero is $r0 and $ra is $r1.
|
||||
if (i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
is_int(dist, 28))
|
||||
if (u32 jirl = *(ul32 *)(buf + rels[i].r_offset + 4);
|
||||
get_rd(jirl) == 0 || get_rd(jirl) == 1)
|
||||
remove(4);
|
||||
break;
|
||||
case R_LARCH_GOT_PC_HI20:
|
||||
// The following two instructions are used to load a symbol address
|
||||
// from the GOT.
|
||||
//
|
||||
// pcalau12i $t0, 0 # R_LARCH_GOT_PC_HI20
|
||||
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12
|
||||
//
|
||||
// If the PC-relative symbol address is known at link-time, we can
|
||||
// relax them to the following instruction.
|
||||
//
|
||||
// pcaddi $t0, <offset>
|
||||
if (is_relaxable_got_load(ctx, isec, i))
|
||||
if (i64 dist = compute_distance(ctx, sym, isec, r);
|
||||
is_int(dist, 22))
|
||||
remove(4);
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_HI20:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 P = isec.get_addr() + r.r_offset;
|
||||
i64 dist = sym.get_tlsdesc_addr(ctx) + r.r_addend - P;
|
||||
if (is_int(dist, 22))
|
||||
remove(4);
|
||||
} else {
|
||||
remove(4);
|
||||
}
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_PC_LO12:
|
||||
if (!sym.has_tlsdesc(ctx))
|
||||
remove(4);
|
||||
break;
|
||||
case R_LARCH_TLS_DESC_LD:
|
||||
if (!sym.has_tlsdesc(ctx) && !sym.has_gottp(ctx))
|
||||
if (i64 val = sym.get_addr(ctx) + r.r_addend - ctx.tp_addr;
|
||||
0 <= val && val < 0x1000)
|
||||
remove(4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isec.sh_size -= r_delta;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -1,10 +1,10 @@
|
||||
// This file contains code for the Motorola 68000 series microprocessors,
|
||||
// which is often abbreviated as m68k. Running a Unix-like system on a
|
||||
// m68k-based machine today is probably a retro-computing hobby activity,
|
||||
// but the processor was a popular choice to build Unix computers during
|
||||
// '80s. Early Sun workstations for example used m68k. Macintosh until
|
||||
// 1994 were based on m68k as well until they switched to PowerPC (and
|
||||
// then to x86 and to ARM.)
|
||||
// m68k-based machine today is a retro-computing hobby activity, but the
|
||||
// processor was a popular choice to build Unix computers during '80s.
|
||||
// Early Sun workstations for example used m68k. Macintosh until 1994 were
|
||||
// based on m68k as well until they switched to PowerPC (and then to x86
|
||||
// and to ARM.)
|
||||
//
|
||||
// From the linker's point of view, it is not hard to support m68k. It's
|
||||
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = M68K;
|
||||
|
||||
@@ -78,11 +78,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -91,11 +86,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
@@ -118,15 +116,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
@@ -251,8 +242,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -266,9 +255,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_16:
|
||||
case R_68K_8:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
@@ -312,6 +298,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_32:
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
@@ -322,4 +309,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = PPC32;
|
||||
|
||||
@@ -54,7 +54,7 @@ static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ub32 insn[] = {
|
||||
constexpr ub32 insn[] = {
|
||||
// Get the address of this PLT section
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
@@ -88,7 +88,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
loc[5] |= lo(ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4);
|
||||
}
|
||||
|
||||
static const ub32 plt_entry[] = {
|
||||
static constexpr ub32 plt_entry[] = {
|
||||
// Get the address of this PLT entry
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
@@ -148,11 +148,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -170,10 +165,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC_ADDR14:
|
||||
*(ub32 *)loc |= bits(S + A, 15, 2) << 2;
|
||||
break;
|
||||
@@ -222,15 +213,15 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC_REL24:
|
||||
case R_PPC_LOCAL24PC: {
|
||||
i64 val = S + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) - P;
|
||||
if (!is_int(val, 26))
|
||||
val = sym.get_thunk_addr(ctx, P) - P;
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
break;
|
||||
}
|
||||
case R_PPC_PLTREL24: {
|
||||
i64 val = S - P;
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) - P;
|
||||
if (sym.has_plt(ctx) || !is_int(val, 26))
|
||||
val = sym.get_thunk_addr(ctx, P) - P;
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
break;
|
||||
}
|
||||
@@ -275,6 +266,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC_GOT_TPREL16:
|
||||
*(ub16 *)loc = sym.get_gottp_addr(ctx) - GOT;
|
||||
break;
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
case R_PPC_TLSLD:
|
||||
@@ -323,8 +316,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -339,10 +330,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_ADDR14:
|
||||
case R_PPC_ADDR16:
|
||||
case R_PPC_UADDR16:
|
||||
@@ -391,6 +378,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC_TPREL16_HA:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC_ADDR32:
|
||||
case R_PPC_UADDR32:
|
||||
case R_PPC_LOCAL24PC:
|
||||
case R_PPC_TLS:
|
||||
case R_PPC_TLSGD:
|
||||
@@ -408,8 +397,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static const ub32 local_thunk[] = {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
constexpr ub32 local_thunk[] = {
|
||||
// Get this thunk's address
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4
|
||||
@@ -427,27 +416,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));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
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);
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
|
||||
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 {
|
||||
i64 val = sym->get_addr(ctx) - P - 8;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -50,7 +50,7 @@
|
||||
#include <algorithm>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = PPC64V1;
|
||||
|
||||
@@ -68,7 +68,7 @@ static u64 higha(u64 x) { return ((x + 0x8000) >> 16) & 0xffff; }
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ub32 insn[] = {
|
||||
constexpr ub32 insn[] = {
|
||||
0x7d88'02a6, // mflr r12
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
0x7d68'02a6, // mflr r11
|
||||
@@ -101,7 +101,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
// call to the PLT entry jumps to. So we need to strictly follow the PLT
|
||||
// section layout as the loader expect it to be.
|
||||
if (idx < 0x8000) {
|
||||
static const ub32 insn[] = {
|
||||
constexpr ub32 insn[] = {
|
||||
0x3800'0000, // li r0, PLT_INDEX
|
||||
0x4b00'0000, // b plt0
|
||||
};
|
||||
@@ -110,7 +110,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
loc[0] |= idx;
|
||||
loc[1] |= (ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 4) & 0x00ff'ffff;
|
||||
} else {
|
||||
static const ub32 insn[] = {
|
||||
constexpr ub32 insn[] = {
|
||||
0x3c00'0000, // lis r0, PLT_INDEX@high
|
||||
0x6000'0000, // ori r0, r0, PLT_INDEX@lo
|
||||
0x4b00'0000, // b plt0
|
||||
@@ -154,11 +154,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -167,13 +162,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
@@ -181,12 +169,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC:
|
||||
apply_toc_rel(ctx, *ctx.extra.TOC, rel, loc, TOC, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ub16 *)loc = ha(S + A - TOC);
|
||||
@@ -203,8 +191,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
break;
|
||||
case R_PPC64_REL24: {
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) + A - P;
|
||||
if (sym.has_plt(ctx) || sign_extend(val, 25) != val)
|
||||
val = get_thunk_addr(i) + A - P;
|
||||
if (sym.has_plt(ctx) || !is_int(val, 26))
|
||||
val = sym.get_thunk_addr(ctx, P) + A - P;
|
||||
|
||||
check(val, -(1 << 25), 1 << 25);
|
||||
*(ub32 *)loc |= bits(val, 25, 2) << 2;
|
||||
@@ -262,15 +250,22 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ub16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
|
||||
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;
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
*(ub16 *)loc |= (sym.get_gottp_addr(ctx) - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTCALL:
|
||||
case R_PPC64_TLS:
|
||||
@@ -295,13 +290,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -309,6 +297,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
@@ -316,12 +308,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
else
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_PPC64_ADDR32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_PPC64_ADDR32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_DTPREL64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
@@ -335,8 +325,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -356,10 +344,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_PPC_OPD;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
case R_PPC64_TOC:
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
@@ -378,8 +362,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_PPC64_TPREL16_HA:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
case R_PPC64_TOC:
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
@@ -401,6 +388,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_DTPREL16_LO_DS:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -409,11 +397,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// If the destination is .plt.got, we save the current r2, read an
|
||||
// address of a function descriptor from .got, restore %r2 and jump
|
||||
// to the function.
|
||||
static const ub32 pltgot_thunk[] = {
|
||||
constexpr ub32 pltgot_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
@@ -431,7 +419,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
};
|
||||
|
||||
// If the destination is .plt, read a function descriptor from .got.plt.
|
||||
static const ub32 plt_thunk[] = {
|
||||
constexpr ub32 plt_thunk[] = {
|
||||
// Store the caller's %r2
|
||||
0xf841'0028, // std %r2, 40(%r1)
|
||||
|
||||
@@ -450,7 +438,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to the function entry address.
|
||||
static const ub32 local_thunk[] = {
|
||||
constexpr ub32 local_thunk[] = {
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
0x398c'0000, // addi r12, r12, foo@toc@lo
|
||||
0x7d89'03a6, // mtctr r12
|
||||
@@ -464,27 +452,29 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(plt_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
if (sym->has_got(ctx)) {
|
||||
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
|
||||
if (sym.has_got(ctx)) {
|
||||
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
|
||||
i64 val = sym.get_got_addr(ctx) - TOC;
|
||||
*(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;
|
||||
} else if(sym.has_plt(ctx)) {
|
||||
memcpy(buf, plt_thunk, sizeof(plt_thunk));
|
||||
i64 val = sym.get_gotplt_addr(ctx) - TOC;
|
||||
*(ub32 *)(buf + 4) |= higha(val);
|
||||
*(ub32 *)(buf + 8) |= lo(val);
|
||||
} else {
|
||||
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
|
||||
memcpy(buf, local_thunk, sizeof(local_thunk));
|
||||
i64 val = sym.get_addr(ctx, NO_OPD) - TOC;
|
||||
*(ub32 *)buf |= higha(val);
|
||||
*(ub32 *)(buf + 4) |= lo(val);
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,34 +486,27 @@ static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
|
||||
}
|
||||
|
||||
static ElfRel<E> *
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
|
||||
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
|
||||
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
|
||||
|
||||
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
|
||||
[](const ElfRel<E> &r, i64 offset) {
|
||||
return r.r_offset < offset;
|
||||
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
|
||||
return (u64)r.r_offset;
|
||||
});
|
||||
|
||||
if (it == rels.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
if (it == rels.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct OpdSymbol {
|
||||
bool operator<(const OpdSymbol &x) const { return r_offset < x.r_offset; }
|
||||
|
||||
u64 r_offset = 0;
|
||||
Symbol<E> *sym = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
static Symbol<E> *
|
||||
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
|
||||
auto it = std::lower_bound(syms.begin(), syms.end(), OpdSymbol{offset});
|
||||
if (it == syms.end())
|
||||
return nullptr;
|
||||
if (it->r_offset != offset)
|
||||
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
|
||||
if (it == syms.end() || it->r_offset != offset)
|
||||
return nullptr;
|
||||
return it->sym;
|
||||
}
|
||||
@@ -593,7 +576,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
|
||||
Symbol<E> *sym2 = file->symbols[rel->r_sym];
|
||||
if (sym2->get_type() != STT_SECTION)
|
||||
Fatal(ctx) << *file << ": bad relocation in .opd referring " << *sym2;
|
||||
Fatal(ctx) << *file << ": bad relocation in .opd referring to " << *sym2;
|
||||
|
||||
opd_syms.push_back({sym->value, sym});
|
||||
|
||||
@@ -602,7 +585,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
// Sort symbols so that get_opd_sym_at() can do binary search.
|
||||
sort(opd_syms);
|
||||
ranges::stable_sort(opd_syms, {}, &OpdSymbol::r_offset);
|
||||
|
||||
// Rewrite relocations so that they directly refer to .opd.
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
@@ -638,15 +621,14 @@ void ppc64v1_scan_symbols(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
// 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_PPC_OPD;
|
||||
};
|
||||
if (!ctx.arg.entry->is_imported)
|
||||
ctx.arg.entry->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
mark(ctx.arg.entry);
|
||||
mark(ctx.arg.init);
|
||||
mark(ctx.arg.fini);
|
||||
if (!ctx.arg.init->is_imported)
|
||||
ctx.arg.init->flags |= NEEDS_PPC_OPD;
|
||||
|
||||
if (!ctx.arg.fini->is_imported)
|
||||
ctx.arg.fini->flags |= NEEDS_PPC_OPD;
|
||||
}
|
||||
|
||||
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
|
||||
@@ -682,4 +664,4 @@ void PPC64OpdSection::copy_buf(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = PPC64V2;
|
||||
|
||||
@@ -106,7 +106,7 @@ static void write34(u8 *loc, u64 x) {
|
||||
// resolved addresses.
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const ul32 insn[] = {
|
||||
constexpr ul32 insn[] = {
|
||||
// Get PC
|
||||
0x7c08'02a6, // mflr r0
|
||||
0x429f'0005, // bcl 20, 31, 4 // obtain PC
|
||||
@@ -172,7 +172,7 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
}
|
||||
|
||||
static u64 get_local_entry_offset(Context<E> &ctx, Symbol<E> &sym) {
|
||||
i64 val = sym.esym().ppc_local_entry;
|
||||
i64 val = sym.esym().ppc64_local_entry;
|
||||
assert(val <= 7);
|
||||
if (val == 7)
|
||||
Fatal(ctx) << sym << ": local entry offset 7 is reserved";
|
||||
@@ -186,11 +186,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -206,16 +201,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
auto r2save_thunk_addr = [&] { return get_thunk_addr(i); };
|
||||
auto no_r2save_thunk_addr = [&] { return get_thunk_addr(i) + 8; };
|
||||
auto r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
|
||||
auto no_r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 8; };
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
apply_toc_rel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
else
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_PPC64_TOC16_HA:
|
||||
*(ul16 *)loc = ha(S + A - TOC);
|
||||
break;
|
||||
@@ -227,7 +216,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_REL24:
|
||||
if (sym.has_plt(ctx) || !sym.esym().preserves_r2()) {
|
||||
if (sym.has_plt(ctx) || !sym.esym().ppc64_preserves_r2()) {
|
||||
i64 val = r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
|
||||
@@ -238,18 +227,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)(loc + 4) = 0xe841'0018; // ld r2, 24(r1)
|
||||
} else {
|
||||
i64 val = S + get_local_entry_offset(ctx, sym) + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
if (!is_int(val, 26))
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
break;
|
||||
case R_PPC64_REL24_NOTOC:
|
||||
if (sym.has_plt(ctx) || sym.esym().uses_toc()) {
|
||||
if (sym.has_plt(ctx) || sym.esym().ppc64_uses_toc()) {
|
||||
i64 val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
} else {
|
||||
i64 val = S + A - P;
|
||||
if (sign_extend(val, 25) != val)
|
||||
if (!is_int(val, 26))
|
||||
val = no_r2save_thunk_addr() + A - P;
|
||||
*(ul32 *)loc |= bits(val, 25, 2) << 2;
|
||||
}
|
||||
@@ -319,6 +308,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - ctx.dtp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_DTPREL34:
|
||||
write34(loc, S + A - ctx.dtp_addr);
|
||||
break;
|
||||
@@ -328,9 +320,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TPREL16_LO:
|
||||
*(ul16 *)loc = lo(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
*(ul16 *)loc |= (S + A - ctx.tp_addr) & 0xfffc;
|
||||
break;
|
||||
case R_PPC64_TPREL34:
|
||||
write34(loc, S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_PPC64_ADDR64:
|
||||
case R_PPC64_PLTSEQ:
|
||||
case R_PPC64_PLTSEQ_NOTOC:
|
||||
case R_PPC64_PLTCALL:
|
||||
@@ -338,6 +334,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_ENTRY:
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -357,13 +354,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -371,6 +361,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
@@ -378,12 +372,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
else
|
||||
*(ul64 *)loc = S + A;
|
||||
break;
|
||||
case R_PPC64_ADDR32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ul32 *)loc = val;
|
||||
case R_PPC64_ADDR32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_PPC64_DTPREL64:
|
||||
*(ul64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
@@ -397,8 +389,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -413,12 +403,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_PPC64_ADDR64:
|
||||
if (name() == ".toc")
|
||||
scan_toc_rel(ctx, sym, rel);
|
||||
else
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_PPC64_GOT_TPREL16_HA:
|
||||
case R_PPC64_GOT_TPREL_PCREL34:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
@@ -447,10 +431,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
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_ADDR64:
|
||||
case R_PPC64_REL32:
|
||||
case R_PPC64_REL64:
|
||||
case R_PPC64_TOC16_HA:
|
||||
@@ -470,12 +454,16 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_PPC64_GOT_TPREL16_LO_DS:
|
||||
case R_PPC64_GOT_TLSGD16_LO:
|
||||
case R_PPC64_GOT_TLSLD16_LO:
|
||||
case R_PPC64_TPREL16_LO:
|
||||
case R_PPC64_TPREL16_LO_DS:
|
||||
case R_PPC64_TLS:
|
||||
case R_PPC64_TLSGD:
|
||||
case R_PPC64_TLSLD:
|
||||
case R_PPC64_DTPREL16_HA:
|
||||
case R_PPC64_DTPREL16_LO:
|
||||
case R_PPC64_DTPREL16_LO_DS:
|
||||
case R_PPC64_DTPREL34:
|
||||
case R_PPC64_ENTRY:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -484,10 +472,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// If the destination is PLT, we read an address from .got.plt or .got
|
||||
// and jump there.
|
||||
static const ul32 plt_thunk[] = {
|
||||
constexpr ul32 plt_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@gotplt@toc@ha
|
||||
@@ -496,7 +484,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static const ul32 plt_thunk_power10[] = {
|
||||
constexpr ul32 plt_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0410'0000, // pld r12, foo@gotplt@pcrel
|
||||
@@ -507,7 +495,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
|
||||
// If the destination is a non-imported function, we directly jump
|
||||
// to its local entry point.
|
||||
static const ul32 local_thunk[] = {
|
||||
constexpr ul32 local_thunk[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x3d82'0000, // addis r12, r2, foo@toc@ha
|
||||
@@ -516,7 +504,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
0x4e80'0420, // bctr
|
||||
};
|
||||
|
||||
static const ul32 local_thunk_power10[] = {
|
||||
constexpr ul32 local_thunk_power10[] = {
|
||||
0xf841'0018, // std r2, 24(r1)
|
||||
0x6000'0000, // nop
|
||||
0x0610'0000, // pla r12, foo@pcrel
|
||||
@@ -530,15 +518,16 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
static_assert(E::thunk_size == sizeof(local_thunk));
|
||||
static_assert(E::thunk_size == sizeof(local_thunk_power10));
|
||||
|
||||
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 P = output_section.shdr.sh_addr + offset;
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
u64 TOC = ctx.extra.TOC->value;
|
||||
|
||||
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);
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
Symbol<E> &sym = *symbols[i];
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
|
||||
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);
|
||||
@@ -548,7 +537,7 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
*(ul32 *)(buf + 12) |= lo(got - TOC);
|
||||
}
|
||||
} else {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
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);
|
||||
@@ -558,15 +547,112 @@ void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
|
||||
*(ul32 *)(buf + 12) |= lo(S - TOC);
|
||||
}
|
||||
}
|
||||
|
||||
buf += E::thunk_size;
|
||||
P += E::thunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
// GCC may emit references to the following functions in function prologue
|
||||
// and epiilogue if -Os is specified. For some reason, these functions are
|
||||
// not in libgcc.a and expected to be synthesized by the linker.
|
||||
const std::vector<std::pair<std::string_view, u32>>
|
||||
ppc64_save_restore_insns = {
|
||||
{ "_savegpr0_14", 0xf9c1ff70 }, // std r14,-144(r1)
|
||||
{ "_savegpr0_15", 0xf9e1ff78 }, // std r15,-136(r1)
|
||||
{ "_savegpr0_16", 0xfa01ff80 }, // std r16,-128(r1)
|
||||
{ "_savegpr0_17", 0xfa21ff88 }, // std r17,-120(r1)
|
||||
{ "_savegpr0_18", 0xfa41ff90 }, // std r18,-112(r1)
|
||||
{ "_savegpr0_19", 0xfa61ff98 }, // std r19,-104(r1)
|
||||
{ "_savegpr0_20", 0xfa81ffa0 }, // std r20,-96(r1)
|
||||
{ "_savegpr0_21", 0xfaa1ffa8 }, // std r21,-88(r1)
|
||||
{ "_savegpr0_22", 0xfac1ffb0 }, // std r22,-80(r1)
|
||||
{ "_savegpr0_23", 0xfae1ffb8 }, // std r23,-72(r1)
|
||||
{ "_savegpr0_24", 0xfb01ffc0 }, // std r24,-64(r1)
|
||||
{ "_savegpr0_25", 0xfb21ffc8 }, // std r25,-56(r1)
|
||||
{ "_savegpr0_26", 0xfb41ffd0 }, // std r26,-48(r1)
|
||||
{ "_savegpr0_27", 0xfb61ffd8 }, // std r27,-40(r1)
|
||||
{ "_savegpr0_28", 0xfb81ffe0 }, // std r28,-32(r1)
|
||||
{ "_savegpr0_29", 0xfba1ffe8 }, // std r29,-24(r1)
|
||||
{ "_savegpr0_30", 0xfbc1fff0 }, // std r30,-16(r1)
|
||||
{ "_savegpr0_31", 0xfbe1fff8 }, // std r31,-8(r1)
|
||||
{ "", 0xf8010010 }, // std r0,16(r1)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
|
||||
{ "_restgpr0_14", 0xe9c1ff70 }, // ld r14,-144(r1)
|
||||
{ "_restgpr0_15", 0xe9e1ff78 }, // ld r15,-136(r1)
|
||||
{ "_restgpr0_16", 0xea01ff80 }, // ld r16,-128(r1)
|
||||
{ "_restgpr0_17", 0xea21ff88 }, // ld r17,-120(r1)
|
||||
{ "_restgpr0_18", 0xea41ff90 }, // ld r18,-112(r1)
|
||||
{ "_restgpr0_19", 0xea61ff98 }, // ld r19,-104(r1)
|
||||
{ "_restgpr0_20", 0xea81ffa0 }, // ld r20,-96(r1)
|
||||
{ "_restgpr0_21", 0xeaa1ffa8 }, // ld r21,-88(r1)
|
||||
{ "_restgpr0_22", 0xeac1ffb0 }, // ld r22,-80(r1)
|
||||
{ "_restgpr0_23", 0xeae1ffb8 }, // ld r23,-72(r1)
|
||||
{ "_restgpr0_24", 0xeb01ffc0 }, // ld r24,-64(r1)
|
||||
{ "_restgpr0_25", 0xeb21ffc8 }, // ld r25,-56(r1)
|
||||
{ "_restgpr0_26", 0xeb41ffd0 }, // ld r26,-48(r1)
|
||||
{ "_restgpr0_27", 0xeb61ffd8 }, // ld r27,-40(r1)
|
||||
{ "_restgpr0_28", 0xeb81ffe0 }, // ld r28,-32(r1)
|
||||
{ "_restgpr0_29", 0xe8010010 }, // ld r0,16(r1)
|
||||
{ "", 0xeba1ffe8 }, // ld r29,-24(r1)
|
||||
{ "", 0x7c0803a6 }, // mtlr r0
|
||||
{ "", 0xebc1fff0 }, // ld r30,-16(r1)
|
||||
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
{ "_restgpr0_30", 0xebc1fff0 }, // ld r30,-16(r1)
|
||||
{ "_restgpr0_31", 0xe8010010 }, // ld r0,16(r1)
|
||||
{ "", 0xebe1fff8 }, // ld r31,-8(r1)
|
||||
{ "", 0x7c0803a6 }, // mtlr r0
|
||||
{ "", 0x4e800020 }, // blr
|
||||
|
||||
{ "_savegpr1_14", 0xf9ccff70 }, // std r14,-144(r12)
|
||||
{ "_savegpr1_15", 0xf9ecff78 }, // std r15,-136(r12)
|
||||
{ "_savegpr1_16", 0xfa0cff80 }, // std r16,-128(r12)
|
||||
{ "_savegpr1_17", 0xfa2cff88 }, // std r17,-120(r12)
|
||||
{ "_savegpr1_18", 0xfa4cff90 }, // std r18,-112(r12)
|
||||
{ "_savegpr1_19", 0xfa6cff98 }, // std r19,-104(r12)
|
||||
{ "_savegpr1_20", 0xfa8cffa0 }, // std r20,-96(r12)
|
||||
{ "_savegpr1_21", 0xfaacffa8 }, // std r21,-88(r12)
|
||||
{ "_savegpr1_22", 0xfaccffb0 }, // std r22,-80(r12)
|
||||
{ "_savegpr1_23", 0xfaecffb8 }, // std r23,-72(r12)
|
||||
{ "_savegpr1_24", 0xfb0cffc0 }, // std r24,-64(r12)
|
||||
{ "_savegpr1_25", 0xfb2cffc8 }, // std r25,-56(r12)
|
||||
{ "_savegpr1_26", 0xfb4cffd0 }, // std r26,-48(r12)
|
||||
{ "_savegpr1_27", 0xfb6cffd8 }, // std r27,-40(r12)
|
||||
{ "_savegpr1_28", 0xfb8cffe0 }, // std r28,-32(r12)
|
||||
{ "_savegpr1_29", 0xfbacffe8 }, // std r29,-24(r12)
|
||||
{ "_savegpr1_30", 0xfbccfff0 }, // std r30,-16(r12)
|
||||
{ "_savegpr1_31", 0xfbecfff8 }, // std r31,-8(r12)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
|
||||
{ "_restgpr1_14", 0xe9ccff70 }, // ld r14,-144(r12)
|
||||
{ "_restgpr1_15", 0xe9ecff78 }, // ld r15,-136(r12)
|
||||
{ "_restgpr1_16", 0xea0cff80 }, // ld r16,-128(r12)
|
||||
{ "_restgpr1_17", 0xea2cff88 }, // ld r17,-120(r12)
|
||||
{ "_restgpr1_18", 0xea4cff90 }, // ld r18,-112(r12)
|
||||
{ "_restgpr1_19", 0xea6cff98 }, // ld r19,-104(r12)
|
||||
{ "_restgpr1_20", 0xea8cffa0 }, // ld r20,-96(r12)
|
||||
{ "_restgpr1_21", 0xeaacffa8 }, // ld r21,-88(r12)
|
||||
{ "_restgpr1_22", 0xeaccffb0 }, // ld r22,-80(r12)
|
||||
{ "_restgpr1_23", 0xeaecffb8 }, // ld r23,-72(r12)
|
||||
{ "_restgpr1_24", 0xeb0cffc0 }, // ld r24,-64(r12)
|
||||
{ "_restgpr1_25", 0xeb2cffc8 }, // ld r25,-56(r12)
|
||||
{ "_restgpr1_26", 0xeb4cffd0 }, // ld r26,-48(r12)
|
||||
{ "_restgpr1_27", 0xeb6cffd8 }, // ld r27,-40(r12)
|
||||
{ "_restgpr1_28", 0xeb8cffe0 }, // ld r28,-32(r12)
|
||||
{ "_restgpr1_29", 0xebacffe8 }, // ld r29,-24(r12)
|
||||
{ "_restgpr1_30", 0xebccfff0 }, // ld r30,-16(r12)
|
||||
{ "_restgpr1_31", 0xebecfff8 }, // ld r31,-8(r12)
|
||||
{ "", 0x4e800020 }, // blr
|
||||
};
|
||||
|
||||
void PPC64SaveRestoreSection::copy_buf(Context<E> &ctx) {
|
||||
ul32 *buf = (ul32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
for (auto [label, insn] : ppc64_save_restore_insns)
|
||||
*buf++ = insn;
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
File diff suppressed because it is too large.
Load diff
@@ -33,11 +33,11 @@
|
||||
// we need to add TP to a return value before use. I don't know why it is
|
||||
// different, but that is the way it is.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/s390x.pdf
|
||||
// https://github.com/IBM/s390x-abi/releases/download/v1.6.1/lzsabi_s390x.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = S390X;
|
||||
|
||||
@@ -48,15 +48,21 @@ static void write_mid20(u8 *loc, u64 val) {
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static u8 insn[] = {
|
||||
// Compute PLT_INDEX
|
||||
// Compute the offset into .rela.plt. This is equivalent to
|
||||
// (%r0 - %r1 - 48 - 14) * 3/2 where %r0 is the PLT entry address
|
||||
// plus 14, %r1 is the start address of .plt, and 48 is the size
|
||||
// of this PLT header. We multiply by 3/2 because each PLT entry
|
||||
// is 16 bytes, whereas each .rela.plt entry is 24 bytes.
|
||||
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
|
||||
// Store the computed value to 56(%r15) and .got.plt[1] to 48(%15)
|
||||
// where %r15 is the stack pointer.
|
||||
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)
|
||||
// Branch to _dl_runtime_resolve
|
||||
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
|
||||
0x07, 0xf1, // br %r1
|
||||
0x07, 0x00, 0x07, 0x00, // nopr; nopr
|
||||
@@ -116,11 +122,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -129,31 +130,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
|
||||
check(val, lo, hi);
|
||||
|
||||
// R_390_*DBL relocs should never refer a symbol at an odd address
|
||||
if (val & 1)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto check_dbl = [&](i64 val, i64 lo, i64 hi) {
|
||||
// R_390_*DBL relocs should never refer to a symbol at an odd address
|
||||
check(val, lo, hi);
|
||||
if (val & 1)
|
||||
Error(ctx) << *this << ": misaligned symbol " << sym
|
||||
<< " for relocation " << rel;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_390_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
@@ -176,9 +172,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PLT12DBL:
|
||||
check_dbl(S + A - P, -(1 << 12), 1 << 12);
|
||||
@@ -193,6 +186,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_390_PC64:
|
||||
case R_390_PLT64:
|
||||
*(ub64 *)loc = S + A - P;
|
||||
break;
|
||||
case R_390_PC16DBL:
|
||||
@@ -256,7 +250,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc = (GOT + A - P) >> 1;
|
||||
break;
|
||||
case R_390_GOTENT:
|
||||
check(GOT + G + A - P, -(1LL << 32), 1LL << 32);
|
||||
// If we can relax a GOT-loading LGRL to an address-materializing
|
||||
// LARL, do that. The format of LGRL is 0xc 0x4 <reg> 0x8 followed
|
||||
// by a 32-bit offset. LARL is 0xc 0x0 <reg> 0x0.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx)) {
|
||||
u64 op = *(ub16 *)(loc - 2);
|
||||
u64 val = S + A - P;
|
||||
if ((op & 0xff0f) == 0xc408 && A == 2 && (val & 1) == 0 &&
|
||||
is_int(val, 33)) {
|
||||
*(ub16 *)(loc - 2) = 0xc000 | (op & 0x00f0);
|
||||
*(ub32 *)loc = val >> 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
check_dbl(GOT + G + A - P, -(1LL << 32), 1LL << 32);
|
||||
*(ub32 *)loc = (GOT + G + A - P) >> 1;
|
||||
break;
|
||||
case R_390_TLS_LE32:
|
||||
@@ -303,22 +310,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_390_TLS_LDM32:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
else
|
||||
*(ub32 *)loc = ctx.dtp_addr - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDM64:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub64 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDO64:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
else
|
||||
*(ub64 *)loc = S + A - ctx.tp_addr;
|
||||
*(ub64 *)loc = ctx.dtp_addr - ctx.tp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDCALL:
|
||||
if (!ctx.got->has_tlsld(ctx)) {
|
||||
@@ -327,6 +326,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
memcpy(loc, insn, sizeof(insn));
|
||||
}
|
||||
break;
|
||||
case R_390_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_390_TLS_LDO64:
|
||||
*(ub64 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
@@ -345,13 +350,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -359,6 +357,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
@@ -385,8 +387,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -401,9 +401,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_390_64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_8:
|
||||
case R_390_12:
|
||||
case R_390_16:
|
||||
@@ -411,8 +408,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_32:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_PC12DBL:
|
||||
case R_390_PC16:
|
||||
case R_390_PC16DBL:
|
||||
case R_390_PC24DBL:
|
||||
case R_390_PC32:
|
||||
case R_390_PC32DBL:
|
||||
case R_390_PC64:
|
||||
@@ -457,8 +456,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
// We always want to relax calls to __tls_get_offset() in statically-
|
||||
// linked executables because __tls_get_offset() in libc.a just calls
|
||||
// abort().
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static) {
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// Do nothing
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
@@ -468,7 +466,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
break;
|
||||
case R_390_TLS_LDM32:
|
||||
case R_390_TLS_LDM64:
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
ctx.needs_tlsld = true;
|
||||
@@ -478,6 +476,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_390_TLS_LE64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_390_64:
|
||||
case R_390_TLS_LDO32:
|
||||
case R_390_TLS_LDO64:
|
||||
case R_390_TLS_GDCALL:
|
||||
@@ -489,4 +488,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -58,11 +58,13 @@
|
||||
// output from the linker contains lots of text relocations. That's not
|
||||
// a problem with embedded programming, I guess.
|
||||
|
||||
#if MOLD_SH4LE || MOLD_SH4BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = SH4;
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
// Even though SH-4 uses RELA-type relocations, addends are stored to
|
||||
// relocated places for some reason.
|
||||
@@ -84,104 +86,126 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTPLT32:
|
||||
return *(ul32 *)loc;
|
||||
return *(U32<E> *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
case R_SH_REL32:
|
||||
case R_SH_TLS_GD_32:
|
||||
case R_SH_TLS_LD_32:
|
||||
case R_SH_TLS_LDO_32:
|
||||
case R_SH_TLS_IE_32:
|
||||
case R_SH_TLS_LE_32:
|
||||
case R_SH_TLS_DTPMOD32:
|
||||
case R_SH_TLS_DTPOFF32:
|
||||
case R_SH_TLS_TPOFF32:
|
||||
case R_SH_GOT32:
|
||||
case R_SH_PLT32:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTPLT32:
|
||||
*(U32<E> *)loc = val;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x02, 0xd2, // mov.l 1f, r2
|
||||
0xcc, 0x32, // add r12, r2
|
||||
0x22, 0x50, // mov.l @(8, r2), r0
|
||||
0x21, 0x52, // mov.l @(4, r2), r2
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x00, 0xe0, // mov #0, r0
|
||||
0, 0, 0, 0, // 1: .long GOTPLT
|
||||
constexpr U16<E> insn[] = {
|
||||
0xd202, // mov.l 1f, r2
|
||||
0x32cc, // add r12, r2
|
||||
0x5022, // mov.l @(8, r2), r0
|
||||
0x5221, // mov.l @(4, r2), r2
|
||||
0x402b, // jmp @r0
|
||||
0xe000, // mov #0, r0
|
||||
0, 0, // 1: .long GOTPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
|
||||
*(U32<E> *)(buf + 12) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x02, 0xd2, // mov.l 1f, r2
|
||||
0x22, 0x50, // mov.l @(8, r2), r0
|
||||
0x21, 0x52, // mov.l @(4, r2), r2
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x00, 0xe0, // mov #0, r0
|
||||
0x09, 0x00, // nop
|
||||
0, 0, 0, 0, // 1: .long GOTPLT
|
||||
constexpr U16<E> insn[] = {
|
||||
0xd202, // mov.l 1f, r2
|
||||
0x5022, // mov.l @(8, r2), r0
|
||||
0x5221, // mov.l @(4, r2), r2
|
||||
0x402b, // jmp @r0
|
||||
0xe000, // mov #0, r0
|
||||
0x0009, // nop
|
||||
0, 0, // 1: .long GOTPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_hdr_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
|
||||
*(U32<E> *)(buf + 12) = ctx.gotplt->shdr.sh_addr;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0xce, 0x00, // mov.l @(r0, r12), r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x01, 0xd1, // mov.l 2f, r1
|
||||
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
|
||||
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
|
||||
constexpr U16<E> insn[] = {
|
||||
0xd001, // mov.l 1f, r0
|
||||
0x00ce, // mov.l @(r0, r12), r0
|
||||
0x402b, // jmp @r0
|
||||
0xd101, // mov.l 2f, r1
|
||||
0, 0, // 1: .long GOTPLT_ENTRY
|
||||
0, 0, // 2: .long INDEX_IN_RELPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0x02, 0x60, // mov.l @r0, r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x01, 0xd1, // mov.l 2f, r1
|
||||
0, 0, 0, 0, // 1: .long GOTPLT_ENTRY
|
||||
0, 0, 0, 0, // 2: .long INDEX_IN_RELPLT
|
||||
constexpr U16<E> insn[] = {
|
||||
0xd001, // mov.l 1f, r0
|
||||
0x6002, // mov.l @r0, r0
|
||||
0x402b, // jmp @r0
|
||||
0xd101, // mov.l 2f, r1
|
||||
0, 0, // 1: .long GOTPLT_ENTRY
|
||||
0, 0, // 2: .long INDEX_IN_RELPLT
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::plt_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_gotplt_addr(ctx);
|
||||
*(ul32 *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx);
|
||||
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0xce, 0x00, // mov.l @(r0, r12), r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x09, 0x00, // nop
|
||||
0, 0, 0, 0, // 1: .long GOT_ENTRY
|
||||
constexpr U16<E> insn[] = {
|
||||
0xd001, // mov.l 1f, r0
|
||||
0x00ce, // mov.l @(r0, r12), r0
|
||||
0x402b, // jmp @r0
|
||||
0x0009, // nop
|
||||
0, 0, // 1: .long GOT_ENTRY
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
*(U32<E> *)(buf + 8) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x01, 0xd0, // mov.l 1f, r0
|
||||
0x02, 0x60, // mov.l @r0, r0
|
||||
0x2b, 0x40, // jmp @r0
|
||||
0x09, 0x00, // nop
|
||||
0, 0, 0, 0, // 1: .long GOT_ENTRY
|
||||
constexpr U16<E> insn[] = {
|
||||
0xd001, // mov.l 1f, r0
|
||||
0x6002, // mov.l @r0, r0
|
||||
0x402b, // jmp @r0
|
||||
0x0009, // nop
|
||||
0, 0, // 1: .long GOT_ENTRY
|
||||
};
|
||||
|
||||
static_assert(sizeof(insn) == E::pltgot_size);
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
|
||||
*(U32<E> *)(buf + 8) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +218,10 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_SH_DIR32:
|
||||
*(ul32 *)loc = val;
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
@@ -208,11 +232,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -229,35 +248,34 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
case R_SH_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_SH_GOT32:
|
||||
*(ul32 *)loc = G;
|
||||
*(U32<E> *)loc = G;
|
||||
break;
|
||||
case R_SH_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_SH_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
*(U32<E> *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_GD_32:
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_LD_32:
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_SH_TLS_IE_32:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_SH_TLS_LE_32:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
@@ -287,9 +305,9 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
@@ -301,8 +319,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
@@ -316,9 +332,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SH_DIR32:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_REL32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
@@ -341,6 +354,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_SH_TLS_LE_32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_SH_DIR32:
|
||||
case R_SH_GOTPC:
|
||||
case R_SH_GOTOFF:
|
||||
case R_SH_TLS_LDO_32:
|
||||
@@ -351,4 +365,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -51,14 +51,11 @@
|
||||
// address. That also means each function needs to execute the above piece
|
||||
// of code to become position-independent.
|
||||
//
|
||||
// This scheme is very similar to i386. That may not be a coincidence
|
||||
// because the i386 ELF psABI is created by Sun Microsystems too.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/sparc.pdf
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
using E = SPARC64;
|
||||
|
||||
@@ -142,11 +139,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -155,23 +147,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = (get_addr() + rel.r_offset);
|
||||
u64 G = (sym.get_got_idx(ctx) * sizeof(Word<E>));
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_SPARC_5:
|
||||
check(S + A, 0, 1 << 5);
|
||||
*(ub32 *)loc |= bits(S + A, 4, 0);
|
||||
@@ -359,27 +345,75 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub32 *)loc |= bits(S + A, 11, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 31, 10);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 31, 10);
|
||||
} else {
|
||||
*(ub32 *)loc |= bits(~(S + A - ctx.tp_addr), 31, 10);
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_GD_LO10:
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
u32 rd = bits(*(ub32 *)loc, 29, 25);
|
||||
*(ub32 *)loc = 0x8010'2000 | (rd << 25) | (rd << 14); // or %reg, $0, %reg
|
||||
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
|
||||
} else {
|
||||
u32 rd = bits(*(ub32 *)loc, 29, 25);
|
||||
*(ub32 *)loc = 0x8018'2000 | (rd << 25) | (rd << 14); // xor %reg, $0, %reg
|
||||
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
// do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
|
||||
*(ub32 *)loc = 0xd05d'c000 | rs2; // ldx [ %l7 + %reg ], %o0
|
||||
} else {
|
||||
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
|
||||
*(ub32 *)loc = 0x9001'c000 | rs2; // add %g7, %reg, %o0
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL: {
|
||||
u64 addr;
|
||||
if (ctx.arg.is_static)
|
||||
addr = ctx.extra.tls_get_addr_sec->shdr.sh_addr;
|
||||
else
|
||||
addr = ctx.extra.tls_get_addr_sym->get_addr(ctx);
|
||||
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
if (sym.has_tlsgd(ctx)) {
|
||||
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ub32 *)loc = 0x9001'c008; // add %g7, %o0, %o0
|
||||
} else {
|
||||
*(ub32 *)loc = 0x0100'0000; // nop
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 31, 10);
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_LO10:
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ub32 *)loc |= bits(ctx.got->get_tlsld_addr(ctx) + A - GOT, 9, 0);
|
||||
else
|
||||
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
// do nothing
|
||||
} else {
|
||||
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
|
||||
*(ub32 *)loc = 0x9021'c000 | rs2; // sub %g7, %reg, %o0
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (ctx.got->has_tlsld(ctx)) {
|
||||
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
|
||||
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
|
||||
} else {
|
||||
*(ub32 *)loc = 0x0100'0000; // nop
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_LDO_HIX22:
|
||||
*(ub32 *)loc |= bits(S + A - ctx.dtp_addr, 31, 10);
|
||||
@@ -402,8 +436,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
case R_SPARC_SIZE32:
|
||||
*(ub32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_SPARC_TLS_GD_ADD:
|
||||
case R_SPARC_TLS_LDM_ADD:
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_TLS_LDO_ADD:
|
||||
case R_SPARC_TLS_IE_LD:
|
||||
case R_SPARC_TLS_IE_LDX:
|
||||
@@ -427,13 +460,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
@@ -441,6 +467,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, val, i, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_UA64:
|
||||
@@ -450,12 +480,10 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*(ub64 *)loc = S + A;
|
||||
break;
|
||||
case R_SPARC_32:
|
||||
case R_SPARC_UA32: {
|
||||
i64 val = S + A;
|
||||
check(val, 0, 1LL << 32);
|
||||
*(ub32 *)loc = val;
|
||||
case R_SPARC_UA32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
}
|
||||
case R_SPARC_TLS_DTPOFF32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
@@ -471,8 +499,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -487,9 +513,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_SPARC_64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_8:
|
||||
case R_SPARC_5:
|
||||
case R_SPARC_6:
|
||||
@@ -554,24 +577,36 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_TLS_GD_HI22:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
} else if (ctx.arg.relax && sym.is_tprel_runtime_const(ctx)) {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_LDM_HI22:
|
||||
ctx.needs_tlsld = true;
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared)) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
} else {
|
||||
ctx.needs_tlsld = true;
|
||||
}
|
||||
break;
|
||||
case R_SPARC_TLS_IE_HI22:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_SPARC_TLS_GD_CALL:
|
||||
case R_SPARC_TLS_LDM_CALL:
|
||||
if (!ctx.arg.is_static)
|
||||
if (Symbol<E> &sym = *ctx.extra.tls_get_addr_sym; sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
if (ctx.extra.tls_get_addr->is_imported)
|
||||
ctx.extra.tls_get_addr->flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_SPARC_TLS_LE_HIX22:
|
||||
case R_SPARC_TLS_LE_LOX10:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_SPARC_64:
|
||||
case R_SPARC_GOTDATA_OP_LOX10:
|
||||
case R_SPARC_GOTDATA_OP:
|
||||
case R_SPARC_GOTDATA_LOX10:
|
||||
@@ -594,25 +629,4 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// __tls_get_addr is not defined by libc.a, so we can't use that function
|
||||
// in statically-linked executables. This section provides a replacement.
|
||||
void SparcTlsGetAddrSection::copy_buf(Context<E> &ctx) {
|
||||
ub32 *buf = (ub32 *)(ctx.buf + this->shdr.sh_offset);
|
||||
|
||||
static const ub32 insn[] = {
|
||||
0x0300'0000, // sethi %hi(TP_SIZE), %g1
|
||||
0x8210'6000, // or %g1, %lo(TP_SIZE), %g1
|
||||
0x8221'c001, // sub %g7, %g1, %g1
|
||||
0xd05a'2008, // ldx [ %o0 + 8 ], %o0
|
||||
0x81c3'e008, // retl
|
||||
0x9000'4008, // add %g1, %o0, %o0
|
||||
};
|
||||
|
||||
assert(this->shdr.sh_size == sizeof(insn));
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
|
||||
buf[0] |= bits(ctx.tp_addr - ctx.tls_begin, 31, 10);
|
||||
buf[1] |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -23,12 +23,13 @@
|
||||
// TLS block as TP (with some addend). As a result, offsets from TP to
|
||||
// thread-local variables (TLVs) in the main executable are all negative.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/x86-64.pdf
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
// https://gitlab.com/x86-psABIs/x86-64-ABI
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold::elf {
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = X86_64;
|
||||
|
||||
@@ -126,54 +127,165 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
|
||||
static u32 relax_gotpcrelx(u8 *loc, const ElfRel<E> &rel) {
|
||||
if (rel.r_type == R_X86_64_GOTPCRELX) {
|
||||
switch ((loc[-2] << 8) | loc[-1]) {
|
||||
case 0xff15: return 0x40e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x40e9; // jmp *0(%rip) -> jmp 0
|
||||
}
|
||||
} else {
|
||||
assert(rel.r_type == R_X86_64_REX_GOTPCRELX ||
|
||||
rel.r_type == R_X86_64_CODE_4_GOTPCRELX);
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488b05: return 0x8d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x8d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x8d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x8d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x8d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x8d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x8d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x8d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_rex_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
static u32 relax_gottpoff(u8 *loc, const ElfRel<E> &rel) {
|
||||
if (rel.r_type == R_X86_64_GOTTPOFF) {
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
} else {
|
||||
assert(rel.r_type == R_X86_64_CODE_4_GOTTPOFF);
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488b05: return 0x18c7c0; // mov 0(%rip), %r16 -> mov $0, %r16
|
||||
case 0x488b0d: return 0x18c7c1; // mov 0(%rip), %r17 -> mov $0, %r17
|
||||
case 0x488b15: return 0x18c7c2; // mov 0(%rip), %r18 -> mov $0, %r18
|
||||
case 0x488b1d: return 0x18c7c3; // mov 0(%rip), %r19 -> mov $0, %r19
|
||||
case 0x488b25: return 0x18c7c4; // mov 0(%rip), %r20 -> mov $0, %r20
|
||||
case 0x488b2d: return 0x18c7c5; // mov 0(%rip), %r21 -> mov $0, %r21
|
||||
case 0x488b35: return 0x18c7c6; // mov 0(%rip), %r22 -> mov $0, %r22
|
||||
case 0x488b3d: return 0x18c7c7; // mov 0(%rip), %r23 -> mov $0, %r23
|
||||
case 0x4c8b05: return 0x19c7c0; // mov 0(%rip), %r24 -> mov $0, %r24
|
||||
case 0x4c8b0d: return 0x19c7c1; // mov 0(%rip), %r25 -> mov $0, %r25
|
||||
case 0x4c8b15: return 0x19c7c2; // mov 0(%rip), %r26 -> mov $0, %r26
|
||||
case 0x4c8b1d: return 0x19c7c3; // mov 0(%rip), %r27 -> mov $0, %r27
|
||||
case 0x4c8b25: return 0x19c7c4; // mov 0(%rip), %r28 -> mov $0, %r28
|
||||
case 0x4c8b2d: return 0x19c7c5; // mov 0(%rip), %r29 -> mov $0, %r29
|
||||
case 0x4c8b35: return 0x19c7c6; // mov 0(%rip), %r30 -> mov $0, %r30
|
||||
case 0x4c8b3d: return 0x19c7c7; // mov 0(%rip), %r31 -> mov $0, %r31
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gottpoff(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc, const ElfRel<E> &rel) {
|
||||
if (rel.r_type == R_X86_64_GOTPC32_TLSDESC) {
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488d05: return 0x488b05; // lea 0(%rip), %rax -> mov 0(%rip), %rax
|
||||
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %rcx -> mov 0(%rip), %rcx
|
||||
case 0x488d15: return 0x488b15; // lea 0(%rip), %rdx -> mov 0(%rip), %rdx
|
||||
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %rbx -> mov 0(%rip), %rbx
|
||||
case 0x488d25: return 0x488b25; // lea 0(%rip), %rsp -> mov 0(%rip), %rsp
|
||||
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %rbp -> mov 0(%rip), %rbp
|
||||
case 0x488d35: return 0x488b35; // lea 0(%rip), %rsi -> mov 0(%rip), %rsi
|
||||
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %rdi -> mov 0(%rip), %rdi
|
||||
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r8 -> mov 0(%rip), %r8
|
||||
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r9 -> mov 0(%rip), %r9
|
||||
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r10 -> mov 0(%rip), %r10
|
||||
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r11 -> mov 0(%rip), %r11
|
||||
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r12 -> mov 0(%rip), %r12
|
||||
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r13 -> mov 0(%rip), %r13
|
||||
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r14 -> mov 0(%rip), %r14
|
||||
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r15 -> mov 0(%rip), %r15
|
||||
}
|
||||
} else {
|
||||
assert(rel.r_type == R_X86_64_CODE_4_GOTPC32_TLSDESC);
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488d05: return 0x488b05; // lea 0(%rip), %r16 -> mov 0(%rip), %r16
|
||||
case 0x488d0d: return 0x488b0d; // lea 0(%rip), %r17 -> mov 0(%rip), %r17
|
||||
case 0x488d15: return 0x488b15; // lea 0(%rip), %r18 -> mov 0(%rip), %r18
|
||||
case 0x488d1d: return 0x488b1d; // lea 0(%rip), %r19 -> mov 0(%rip), %r19
|
||||
case 0x488d25: return 0x488b25; // lea 0(%rip), %r20 -> mov 0(%rip), %r20
|
||||
case 0x488d2d: return 0x488b2d; // lea 0(%rip), %r21 -> mov 0(%rip), %r21
|
||||
case 0x488d35: return 0x488b35; // lea 0(%rip), %r22 -> mov 0(%rip), %r22
|
||||
case 0x488d3d: return 0x488b3d; // lea 0(%rip), %r23 -> mov 0(%rip), %r23
|
||||
case 0x4c8d05: return 0x4c8b05; // lea 0(%rip), %r24 -> mov 0(%rip), %r24
|
||||
case 0x4c8d0d: return 0x4c8b0d; // lea 0(%rip), %r25 -> mov 0(%rip), %r25
|
||||
case 0x4c8d15: return 0x4c8b15; // lea 0(%rip), %r26 -> mov 0(%rip), %r26
|
||||
case 0x4c8d1d: return 0x4c8b1d; // lea 0(%rip), %r27 -> mov 0(%rip), %r27
|
||||
case 0x4c8d25: return 0x4c8b25; // lea 0(%rip), %r28 -> mov 0(%rip), %r28
|
||||
case 0x4c8d2d: return 0x4c8b2d; // lea 0(%rip), %r29 -> mov 0(%rip), %r29
|
||||
case 0x4c8d35: return 0x4c8b35; // lea 0(%rip), %r30 -> mov 0(%rip), %r30
|
||||
case 0x4c8d3d: return 0x4c8b3d; // lea 0(%rip), %r31 -> mov 0(%rip), %r31
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc, const ElfRel<E> &rel) {
|
||||
if (rel.r_type == R_X86_64_GOTPC32_TLSDESC) {
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488d05: return 0x48c7c0; // lea 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488d0d: return 0x48c7c1; // lea 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488d15: return 0x48c7c2; // lea 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488d1d: return 0x48c7c3; // lea 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488d25: return 0x48c7c4; // lea 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488d2d: return 0x48c7c5; // lea 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488d35: return 0x48c7c6; // lea 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488d3d: return 0x48c7c7; // lea 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8d05: return 0x49c7c0; // lea 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8d0d: return 0x49c7c1; // lea 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8d15: return 0x49c7c2; // lea 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8d1d: return 0x49c7c3; // lea 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8d25: return 0x49c7c4; // lea 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8d2d: return 0x49c7c5; // lea 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8d35: return 0x49c7c6; // lea 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8d3d: return 0x49c7c7; // lea 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
} else {
|
||||
assert(rel.r_type == R_X86_64_CODE_4_GOTPC32_TLSDESC);
|
||||
switch ((loc[-3] << 16) | (loc[-2] << 8) | loc[-1]) {
|
||||
case 0x488d05: return 0x18c7c0; // lea 0(%rip), %r16 -> mov $0, %r16
|
||||
case 0x488d0d: return 0x18c7c1; // lea 0(%rip), %r17 -> mov $0, %r17
|
||||
case 0x488d15: return 0x18c7c2; // lea 0(%rip), %r18 -> mov $0, %r18
|
||||
case 0x488d1d: return 0x18c7c3; // lea 0(%rip), %r19 -> mov $0, %r19
|
||||
case 0x488d25: return 0x18c7c4; // lea 0(%rip), %r20 -> mov $0, %r20
|
||||
case 0x488d2d: return 0x18c7c5; // lea 0(%rip), %r21 -> mov $0, %r21
|
||||
case 0x488d35: return 0x18c7c6; // lea 0(%rip), %r22 -> mov $0, %r22
|
||||
case 0x488d3d: return 0x18c7c7; // lea 0(%rip), %r23 -> mov $0, %r23
|
||||
case 0x4c8d05: return 0x19c7c0; // lea 0(%rip), %r24 -> mov $0, %r24
|
||||
case 0x4c8d0d: return 0x19c7c1; // lea 0(%rip), %r25 -> mov $0, %r25
|
||||
case 0x4c8d15: return 0x19c7c2; // lea 0(%rip), %r26 -> mov $0, %r26
|
||||
case 0x4c8d1d: return 0x19c7c3; // lea 0(%rip), %r27 -> mov $0, %r27
|
||||
case 0x4c8d25: return 0x19c7c4; // lea 0(%rip), %r28 -> mov $0, %r28
|
||||
case 0x4c8d2d: return 0x19c7c5; // lea 0(%rip), %r29 -> mov $0, %r29
|
||||
case 0x4c8d35: return 0x19c7c6; // lea 0(%rip), %r30 -> mov $0, %r30
|
||||
case 0x4c8d3d: return 0x19c7c7; // lea 0(%rip), %r31 -> mov $0, %r31
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -181,7 +293,7 @@ static u32 relax_gottpoff(u8 *loc) {
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. We can do this when we know the thread-local variable's TP-
|
||||
// relative address at link-time.
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static void relax_gd_to_le(u8 *loc, const ElfRel<E> &rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
@@ -225,7 +337,7 @@ static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
}
|
||||
}
|
||||
|
||||
static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static void relax_gd_to_ie(u8 *loc, const ElfRel<E> &rel, u64 val) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32:
|
||||
@@ -255,10 +367,10 @@ static void relax_gd_to_ie(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
}
|
||||
|
||||
// Rewrite a function call to __tls_get_addr to a cheaper instruction
|
||||
// sequence. The difference from relax_gd_to_le is that we are
|
||||
// materializing a Dynamic Thread Pointer for the current ELF module
|
||||
// instead of an address for a particular thread-local variable.
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
// sequence. The difference from relax_gd_to_le is that we are materializing
|
||||
// the address of the beginning of TLS block instead of an address of a
|
||||
// particular thread-local variable.
|
||||
static void relax_ld_to_le(u8 *loc, const ElfRel<E> &rel, i64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_PC32: {
|
||||
@@ -267,10 +379,9 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// e8 00 00 00 00 call __tls_get_addr
|
||||
//
|
||||
// The instructions are so short that we cannot rewrite them with
|
||||
// "mov %fs:0, %rax" which is 9 bytes long. We use a shorter code
|
||||
// sequence instead. Since "xor %eax, %eax" zero-clears %rax, the
|
||||
// meaning is equivalent.
|
||||
// Because the original instruction sequence is so short that we need a
|
||||
// little bit of code golfing here. "mov %fs:0, %rax" is 9 byte long, so
|
||||
// xor + mov is shorter. Note that `xor %eax, %eax` zero-clears %eax.
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
@@ -287,13 +398,12 @@ static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
// 48 8d 3d 00 00 00 00 lea foo@tlsld(%rip), %rdi
|
||||
// ff 15 00 00 00 00 call *__tls_get_addr@GOT(%rip)
|
||||
static const u8 insn[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x48, 0x31, 0xc0, // xor %rax, %rax
|
||||
0x64, 0x48, 0x8b, 0x00, // mov %fs:(%rax), %rax
|
||||
0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
|
||||
0x90, // nop
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
*(ul32 *)(loc + 6) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PLTOFF64: {
|
||||
@@ -324,11 +434,6 @@ template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
ElfRel<E> *dynrel = nullptr;
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
@@ -337,11 +442,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
@@ -354,12 +462,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
u64 GOT = ctx.gotplt->shdr.sh_addr;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
@@ -376,7 +478,6 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
write32s(S + A);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
apply_dyn_absrel(ctx, sym, rel, loc, S, A, P, &dynrel);
|
||||
break;
|
||||
case R_X86_64_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
@@ -416,30 +517,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul64 *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_CODE_4_GOTPCRELX:
|
||||
// We always want to relax GOTPCRELX relocs even if --no-relax
|
||||
// was given because some static PIE runtime code depends on these
|
||||
// relaxations.
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
if (sym.is_pcrel_linktime_const(ctx) && is_int(S + A - P, 32)) {
|
||||
if (u32 insn = relax_gotpcrelx(loc, rel)) {
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
write32s(G + GOT + A - P);
|
||||
break;
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (sym.is_pcrel_linktime_const(ctx)) {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
i64 val = S + A - P;
|
||||
if (insn && (i32)val == val) {
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = val;
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -472,18 +559,22 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul64 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_CODE_4_GOTTPOFF:
|
||||
if (sym.has_gottp(ctx)) {
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
u32 insn = relax_gottpoff(loc, rel);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S - ctx.tp_addr);
|
||||
assert(A == -4);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_CODE_6_GOTTPOFF:
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_CODE_4_GOTPC32_TLSDESC:
|
||||
// x86-64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %rax.
|
||||
//
|
||||
@@ -492,35 +583,48 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
// call *(%rax)
|
||||
// R_X86_64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following for non-dlopen'd DSO
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// mov $foo@TPOFF, %rax
|
||||
// nop
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// mov foo@GOTTPOFF(%rip), %rax
|
||||
// nop
|
||||
//
|
||||
// or to the following for executable.
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// mov $foo@TPOFF, %rax
|
||||
// nop
|
||||
// lea 0(%rip), %reg
|
||||
// R_X86_64_GOTPC32_TLSDESC foo
|
||||
// mov %reg, %rax
|
||||
// call *(%rax)
|
||||
// R_X86_64_TLSDESC_CALL foo
|
||||
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;
|
||||
u32 insn = relax_tlsdesc_to_ie(loc, rel);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for " << rel;
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(sym.get_gottp_addr(ctx) + A - P);
|
||||
} else {
|
||||
// mov $foo@tpoff, %rax
|
||||
loc[-3] = 0x48;
|
||||
loc[-2] = 0xc7;
|
||||
loc[-1] = 0xc0;
|
||||
u32 insn = relax_tlsdesc_to_le(loc, rel);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for " << rel;
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write32s(S - ctx.tp_addr);
|
||||
}
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
@@ -562,11 +666,15 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
Symbol<E> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
if (val < lo || hi <= val)
|
||||
Error(ctx) << *this << ": relocation " << rel << " against "
|
||||
<< sym << " out of range: " << val << " is not in ["
|
||||
<< lo << ", " << hi << ")";
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto write32 = [&](u64 val) {
|
||||
@@ -579,13 +687,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
*(ul32 *)loc = val;
|
||||
};
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
@@ -650,8 +751,6 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
@@ -686,9 +785,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_32S:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
scan_dyn_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32:
|
||||
@@ -703,6 +799,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_CODE_4_GOTPCRELX:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_X86_64_PLT32:
|
||||
@@ -711,8 +808,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_X86_64_TLSGD:
|
||||
if ((ctx.arg.relax && sym.is_tprel_linktime_const(ctx)) ||
|
||||
ctx.arg.is_static) {
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx))) {
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
i++;
|
||||
@@ -726,18 +822,19 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_TLSLD:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared))
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
if (ctx.arg.relax && relax_gottpoff(loc - 3) &&
|
||||
sym.is_tprel_linktime_const(ctx)) {
|
||||
// do nothing
|
||||
} else {
|
||||
case R_X86_64_CODE_4_GOTTPOFF:
|
||||
if (!ctx.arg.relax || !sym.is_tprel_linktime_const(ctx) ||
|
||||
!relax_gottpoff(loc, rel))
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_CODE_6_GOTTPOFF:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
@@ -746,12 +843,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
case R_X86_64_TPOFF64:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_GOTOFF64:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_CODE_4_GOTPC32_TLSDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
@@ -759,4 +858,95 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
// Intel CET is a relatively new CPU feature to enhance security by
|
||||
// protecting control flow integrity. If the feature is enabled, indirect
|
||||
// branches (i.e. branch instructions that take a register instead of an
|
||||
// immediate) must land on a "landing pad" instruction, or a CPU-level fault
|
||||
// will raise. That prevents an attacker to branch to a middle of a random
|
||||
// function, making ROP or JOP much harder to conduct.
|
||||
//
|
||||
// On x86-64, the landing pad instruction is ENDBR64. That is actually a
|
||||
// repurposed NOP instruction to provide binary compatibility with older
|
||||
// hardware that doesn't support CET.
|
||||
//
|
||||
// The problem here is that the compiler always emits a landing pad at the
|
||||
// beginning fo a global function because it doesn't know whether or not the
|
||||
// function's address is taken in other translation units. As a result, the
|
||||
// resulting binary contains more landing pads than necessary.
|
||||
//
|
||||
// This function rewrites a landing pad with a nop if the function's address
|
||||
// was not actually taken. We can do what the compiler cannot because we
|
||||
// know about all translation units.
|
||||
void rewrite_endbr(Context<E> &ctx) {
|
||||
Timer t(ctx, "rewrite_endbr");
|
||||
|
||||
constexpr u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
|
||||
constexpr u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
|
||||
|
||||
// Rewrite all endbr64 instructions referred to by function symbols with
|
||||
// NOPs. We handle only global symbols because the compiler doesn't emit
|
||||
// an endbr64 for a file-scoped function in the first place if its address
|
||||
// is not taken within the file.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file == file && sym->esym().st_type == STT_FUNC) {
|
||||
if (InputSection<E> *isec = sym->get_input_section();
|
||||
isec && (isec->shdr().sh_flags & SHF_EXECINSTR)) {
|
||||
if (OutputSection<E> *osec = isec->output_section) {
|
||||
u8 *buf = ctx.buf + osec->shdr.sh_offset + isec->offset + sym->value;
|
||||
if (memcmp(buf, endbr64, 4) == 0)
|
||||
memcpy(buf, nop, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto write_back = [&](InputSection<E> *isec, i64 offset) {
|
||||
// If isec has an endbr64 at a given offset, copy that instruction to
|
||||
// the output buffer, possibly overwriting a nop written in the above
|
||||
// loop.
|
||||
if (isec && isec->output_section &&
|
||||
(isec->shdr().sh_flags & SHF_EXECINSTR) &&
|
||||
0 <= offset && offset <= isec->contents.size() - 4 &&
|
||||
memcmp(isec->contents.data() + offset, endbr64, 4) == 0)
|
||||
memcpy(ctx.buf + isec->output_section->shdr.sh_offset + isec->offset + offset,
|
||||
endbr64, 4);
|
||||
};
|
||||
|
||||
// Write back endbr64 instructions if they are referred to by address-taking
|
||||
// relocations.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC)) {
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
if (!is_func_call_rel(rel)) {
|
||||
Symbol<E> *sym = file->symbols[rel.r_sym];
|
||||
if (sym->esym().st_type == STT_SECTION)
|
||||
write_back(sym->get_input_section(), rel.r_addend);
|
||||
else
|
||||
write_back(sym->get_input_section(), sym->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// We record addresses of some symbols in the ELF header, .dynamic or in
|
||||
// .dynsym. We need to retain endbr64s for such symbols.
|
||||
auto keep = [&](Symbol<E> *sym) {
|
||||
if (sym)
|
||||
write_back(sym->get_input_section(), sym->value);
|
||||
};
|
||||
|
||||
keep(ctx.arg.entry);
|
||||
keep(ctx.arg.init);
|
||||
keep(ctx.arg.fini);
|
||||
|
||||
if (ctx.dynsym)
|
||||
for (Symbol<E> *sym : ctx.dynsym->symbols)
|
||||
if (sym && sym->is_exported)
|
||||
keep(sym);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -23,13 +23,11 @@
|
||||
// see the contents of libc.a by running `ar t
|
||||
// /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "filetype.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
namespace {
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
@@ -75,10 +73,11 @@ struct ArHdr {
|
||||
return {ar_name, sizeof(ar_name)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Context, typename MappedFile>
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile *> vec;
|
||||
@@ -116,16 +115,17 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
|
||||
continue;
|
||||
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : (filepath(mf->name).parent_path() / name).string();
|
||||
vec.push_back(MappedFile::must_open(ctx, path));
|
||||
name : (path_dirname(mf->name) / name).string();
|
||||
vec.push_back(must_open_file(ctx, path));
|
||||
vec.back()->thin_parent = mf;
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
u8 *begin = mf->data;
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MappedFile *> vec;
|
||||
@@ -162,16 +162,27 @@ std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf)
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename Context, typename MappedFile>
|
||||
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::AR:
|
||||
template <typename E>
|
||||
std::vector<MappedFile *>
|
||||
read_archive_members(Context<E> &ctx, MappedFile *mf) {
|
||||
std::string_view str = mf->get_contents();
|
||||
|
||||
if (str.starts_with("!<arch>\n"))
|
||||
return read_fat_archive_members(ctx, mf);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
|
||||
assert(str.starts_with("!<thin>\n"));
|
||||
return read_thin_archive_members(ctx, mf);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template std::vector<MappedFile *>
|
||||
read_thin_archive_members(Context<E> &, MappedFile *);
|
||||
|
||||
template std::vector<MappedFile *>
|
||||
read_fat_archive_members(Context<E> &, MappedFile *);
|
||||
|
||||
template std::vector<MappedFile *>
|
||||
read_archive_members(Context<E> &, MappedFile *);
|
||||
|
||||
} // namespace mold
|
||||
+525
-209
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,91 @@
|
||||
// This file implements multi-threaded compression routines. We simply
|
||||
// create multiple compressed data blocks and concatenate them together.
|
||||
// Tools supporting compressed sections generally read compressed sections
|
||||
// until the section end to handle multiple concatenated streams.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is not shared between compressed streams, the compression ratio is
|
||||
// slightly sacrificed. However, if the stream size is large enough,
|
||||
// that loss is negligible in practice.
|
||||
//
|
||||
// Here, we use a u8[] buffer instead of std::vector<u8> to store
|
||||
// compressed data. This avoids the overhead of zero-initialization that
|
||||
// comes with std::vector<u8>. In large programs, compressed debug
|
||||
// sections can reach gigabyte scale, and zero-initialization alone could
|
||||
// take several hundred milliseconds.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
Compressor::~Compressor() {
|
||||
for (std::span<u8> shard : shards)
|
||||
delete[] shard.data();
|
||||
}
|
||||
|
||||
static std::vector<std::span<u8>> split(std::span<u8> input) {
|
||||
constexpr int BLOCK_SIZE = 1024 * 1024;
|
||||
std::vector<std::span<u8>> vec;
|
||||
while (!input.empty()) {
|
||||
i64 sz = std::min<i64>(BLOCK_SIZE, input.size());
|
||||
vec.push_back(input.subspan(0, sz));
|
||||
input = input.subspan(sz);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::span<u8> zlib_compress(std::span<u8> input) {
|
||||
// compression level; must be between 0 to 9
|
||||
int level = Z_BEST_SPEED; // = 1
|
||||
|
||||
unsigned long bufsize = compressBound(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
[[maybe_unused]] int r = compress2(buf, &bufsize, input.data(),
|
||||
input.size(), level);
|
||||
assert(r == Z_OK);
|
||||
return {buf, (size_t)bufsize};
|
||||
}
|
||||
|
||||
static std::span<u8> zstd_compress(std::span<u8> input) {
|
||||
// compression level; must be between 1 to 22
|
||||
int level = ZSTD_CLEVEL_DEFAULT; // = 3
|
||||
|
||||
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
return {buf, sz};
|
||||
}
|
||||
|
||||
Compressor::Compressor(i64 format, u8 *buf, i64 size) {
|
||||
assert(format == ELFCOMPRESS_ZLIB || format == ELFCOMPRESS_ZSTD);
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
if (format == ELFCOMPRESS_ZLIB)
|
||||
shards[i] = zlib_compress(inputs[i]);
|
||||
else
|
||||
shards[i] = zstd_compress(inputs[i]);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::span<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void Compressor::write_to(u8 *buf) {
|
||||
std::vector<i64> offsets(shards.size());
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
+51
-47
@@ -1,11 +1,12 @@
|
||||
#include "mold.h"
|
||||
#include "elf.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
static std::string unknown_type(u32 r_type) {
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "unknown (0x%x)", r_type);
|
||||
return buf;
|
||||
std::stringstream ss;
|
||||
ss << "unknown (0x" << std::hex << r_type << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#define CASE(x) case x: return #x
|
||||
@@ -53,6 +54,15 @@ std::string rel_to_string<X86_64>(u32 r_type) {
|
||||
CASE(R_X86_64_IRELATIVE);
|
||||
CASE(R_X86_64_GOTPCRELX);
|
||||
CASE(R_X86_64_REX_GOTPCRELX);
|
||||
CASE(R_X86_64_CODE_4_GOTPCRELX);
|
||||
CASE(R_X86_64_CODE_4_GOTTPOFF);
|
||||
CASE(R_X86_64_CODE_4_GOTPC32_TLSDESC);
|
||||
CASE(R_X86_64_CODE_5_GOTPCRELX);
|
||||
CASE(R_X86_64_CODE_5_GOTTPOFF);
|
||||
CASE(R_X86_64_CODE_5_GOTPC32_TLSDESC);
|
||||
CASE(R_X86_64_CODE_6_GOTPCRELX);
|
||||
CASE(R_X86_64_CODE_6_GOTTPOFF);
|
||||
CASE(R_X86_64_CODE_6_GOTPC32_TLSDESC);
|
||||
}
|
||||
return unknown_type(r_type);
|
||||
}
|
||||
@@ -107,7 +117,7 @@ std::string rel_to_string<I386>(u32 r_type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM64>(u32 r_type) {
|
||||
std::string rel_to_string<ARM64LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_AARCH64_NONE);
|
||||
CASE(R_AARCH64_ABS64);
|
||||
@@ -218,7 +228,12 @@ std::string rel_to_string<ARM64>(u32 r_type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
std::string rel_to_string<ARM64BE>(u32 r_type) {
|
||||
return rel_to_string<ARM64LE>(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_ARM_NONE);
|
||||
CASE(R_ARM_PC24);
|
||||
@@ -359,6 +374,11 @@ std::string rel_to_string<ARM32>(u32 r_type) {
|
||||
return unknown_type(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ARM32BE>(u32 r_type) {
|
||||
return rel_to_string<ARM32LE>(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<RV64LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
@@ -402,7 +422,6 @@ std::string rel_to_string<RV64LE>(u32 r_type) {
|
||||
CASE(R_RISCV_ALIGN);
|
||||
CASE(R_RISCV_RVC_BRANCH);
|
||||
CASE(R_RISCV_RVC_JUMP);
|
||||
CASE(R_RISCV_RVC_LUI);
|
||||
CASE(R_RISCV_RELAX);
|
||||
CASE(R_RISCV_SUB6);
|
||||
CASE(R_RISCV_SET6);
|
||||
@@ -615,6 +634,7 @@ std::string rel_to_string<PPC64V1>(u32 r_type) {
|
||||
CASE(R_PPC64_DTPREL16_HIGH);
|
||||
CASE(R_PPC64_DTPREL16_HIGHA);
|
||||
CASE(R_PPC64_REL24_NOTOC);
|
||||
CASE(R_PPC64_ENTRY);
|
||||
CASE(R_PPC64_PLTSEQ);
|
||||
CASE(R_PPC64_PLTCALL);
|
||||
CASE(R_PPC64_PLTSEQ_NOTOC);
|
||||
@@ -857,7 +877,7 @@ std::string rel_to_string<M68K>(u32 r_type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<SH4>(u32 r_type) {
|
||||
std::string rel_to_string<SH4LE>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_SH_NONE);
|
||||
CASE(R_SH_DIR32);
|
||||
@@ -891,43 +911,8 @@ std::string rel_to_string<SH4>(u32 r_type) {
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string rel_to_string<ALPHA>(u32 r_type) {
|
||||
switch (r_type) {
|
||||
CASE(R_ALPHA_NONE);
|
||||
CASE(R_ALPHA_REFLONG);
|
||||
CASE(R_ALPHA_REFQUAD);
|
||||
CASE(R_ALPHA_GPREL32);
|
||||
CASE(R_ALPHA_LITERAL);
|
||||
CASE(R_ALPHA_LITUSE);
|
||||
CASE(R_ALPHA_GPDISP);
|
||||
CASE(R_ALPHA_BRADDR);
|
||||
CASE(R_ALPHA_HINT);
|
||||
CASE(R_ALPHA_SREL16);
|
||||
CASE(R_ALPHA_SREL32);
|
||||
CASE(R_ALPHA_SREL64);
|
||||
CASE(R_ALPHA_GPRELHIGH);
|
||||
CASE(R_ALPHA_GPRELLOW);
|
||||
CASE(R_ALPHA_GPREL16);
|
||||
CASE(R_ALPHA_COPY);
|
||||
CASE(R_ALPHA_GLOB_DAT);
|
||||
CASE(R_ALPHA_JMP_SLOT);
|
||||
CASE(R_ALPHA_RELATIVE);
|
||||
CASE(R_ALPHA_BRSGP);
|
||||
CASE(R_ALPHA_TLSGD);
|
||||
CASE(R_ALPHA_TLSLDM);
|
||||
CASE(R_ALPHA_DTPMOD64);
|
||||
CASE(R_ALPHA_GOTDTPREL);
|
||||
CASE(R_ALPHA_DTPREL64);
|
||||
CASE(R_ALPHA_DTPRELHI);
|
||||
CASE(R_ALPHA_DTPRELLO);
|
||||
CASE(R_ALPHA_DTPREL16);
|
||||
CASE(R_ALPHA_GOTTPREL);
|
||||
CASE(R_ALPHA_TPREL64);
|
||||
CASE(R_ALPHA_TPRELHI);
|
||||
CASE(R_ALPHA_TPRELLO);
|
||||
CASE(R_ALPHA_TPREL16);
|
||||
}
|
||||
return unknown_type(r_type);
|
||||
std::string rel_to_string<SH4BE>(u32 r_type) {
|
||||
return rel_to_string<SH4LE>(r_type);
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -946,6 +931,8 @@ std::string rel_to_string<LOONGARCH64>(u32 r_type) {
|
||||
CASE(R_LARCH_TLS_TPREL32);
|
||||
CASE(R_LARCH_TLS_TPREL64);
|
||||
CASE(R_LARCH_IRELATIVE);
|
||||
CASE(R_LARCH_TLS_DESC32);
|
||||
CASE(R_LARCH_TLS_DESC64);
|
||||
CASE(R_LARCH_MARK_LA);
|
||||
CASE(R_LARCH_MARK_PCREL);
|
||||
CASE(R_LARCH_SOP_PUSH_PCREL);
|
||||
@@ -1031,6 +1018,23 @@ std::string rel_to_string<LOONGARCH64>(u32 r_type) {
|
||||
CASE(R_LARCH_ADD_ULEB128);
|
||||
CASE(R_LARCH_SUB_ULEB128);
|
||||
CASE(R_LARCH_64_PCREL);
|
||||
CASE(R_LARCH_CALL36);
|
||||
CASE(R_LARCH_TLS_DESC_PC_HI20);
|
||||
CASE(R_LARCH_TLS_DESC_PC_LO12);
|
||||
CASE(R_LARCH_TLS_DESC64_PC_LO20);
|
||||
CASE(R_LARCH_TLS_DESC64_PC_HI12);
|
||||
CASE(R_LARCH_TLS_DESC_HI20);
|
||||
CASE(R_LARCH_TLS_DESC_LO12);
|
||||
CASE(R_LARCH_TLS_DESC64_LO20);
|
||||
CASE(R_LARCH_TLS_DESC64_HI12);
|
||||
CASE(R_LARCH_TLS_DESC_LD);
|
||||
CASE(R_LARCH_TLS_DESC_CALL);
|
||||
CASE(R_LARCH_TLS_LE_HI20_R);
|
||||
CASE(R_LARCH_TLS_LE_ADD_R);
|
||||
CASE(R_LARCH_TLS_LE_LO12_R);
|
||||
CASE(R_LARCH_TLS_LD_PCREL20_S2);
|
||||
CASE(R_LARCH_TLS_GD_PCREL20_S2);
|
||||
CASE(R_LARCH_TLS_DESC_PCREL20_S2);
|
||||
}
|
||||
return unknown_type(r_type);
|
||||
}
|
||||
@@ -1040,4 +1044,4 @@ std::string rel_to_string<LOONGARCH32>(u32 r_type) {
|
||||
return rel_to_string<LOONGARCH64>(r_type);
|
||||
}
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
+225
-255
@@ -1,18 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../common/integers.h"
|
||||
#include "../lib/integers.h"
|
||||
|
||||
#include <concepts>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
struct X86_64;
|
||||
struct I386;
|
||||
struct ARM64;
|
||||
struct ARM32;
|
||||
struct ARM64LE;
|
||||
struct ARM64BE;
|
||||
struct ARM32LE;
|
||||
struct ARM32BE;
|
||||
struct RV64LE;
|
||||
struct RV64BE;
|
||||
struct RV32LE;
|
||||
@@ -23,8 +25,8 @@ struct PPC64V2;
|
||||
struct S390X;
|
||||
struct SPARC64;
|
||||
struct M68K;
|
||||
struct SH4;
|
||||
struct ALPHA;
|
||||
struct SH4LE;
|
||||
struct SH4BE;
|
||||
struct LOONGARCH64;
|
||||
struct LOONGARCH32;
|
||||
|
||||
@@ -50,10 +52,6 @@ std::ostream &operator<<(std::ostream &out, const ElfRel<E> &rel) {
|
||||
return out;
|
||||
}
|
||||
|
||||
enum : u32 {
|
||||
R_NONE = 0,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
SHN_UNDEF = 0,
|
||||
SHN_LORESERVE = 0xff00,
|
||||
@@ -81,15 +79,20 @@ enum : u32 {
|
||||
SHT_GROUP = 17,
|
||||
SHT_SYMTAB_SHNDX = 18,
|
||||
SHT_RELR = 19,
|
||||
SHT_CREL = 0x40000014,
|
||||
SHT_LOOS = 0x60000000,
|
||||
SHT_LLVM_ADDRSIG = 0x6fff4c03,
|
||||
SHT_GNU_HASH = 0x6ffffff6,
|
||||
SHT_GNU_VERDEF = 0x6ffffffd,
|
||||
SHT_GNU_VERNEED = 0x6ffffffe,
|
||||
SHT_GNU_VERSYM = 0x6fffffff,
|
||||
SHT_HIOS = 0x6fffffff,
|
||||
SHT_X86_64_UNWIND = 0x70000001,
|
||||
SHT_ARM_EXIDX = 0x70000001,
|
||||
SHT_ARM_ATTRIBUTES = 0x70000003,
|
||||
SHT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
SHT_LOUSER = 0x80000000,
|
||||
SHT_HIUSER = 0xffffffff,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -100,6 +103,7 @@ enum : u32 {
|
||||
SHF_STRINGS = 0x20,
|
||||
SHF_INFO_LINK = 0x40,
|
||||
SHF_LINK_ORDER = 0x80,
|
||||
SHF_OS_NONCONFORMING = 0x100,
|
||||
SHF_GROUP = 0x200,
|
||||
SHF_TLS = 0x400,
|
||||
SHF_COMPRESSED = 0x800,
|
||||
@@ -161,6 +165,7 @@ enum : u32 {
|
||||
VER_NDX_LOCAL = 0,
|
||||
VER_NDX_GLOBAL = 1,
|
||||
VER_NDX_LAST_RESERVED = 1,
|
||||
VER_NDX_UNSPECIFIED = 0xffff,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -185,6 +190,7 @@ enum : u32 {
|
||||
PT_GNU_EH_FRAME = 0x6474e550,
|
||||
PT_GNU_STACK = 0x6474e551,
|
||||
PT_GNU_RELRO = 0x6474e552,
|
||||
PT_GNU_PROPERTY = 0x6474e553,
|
||||
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
|
||||
PT_ARM_EXIDX = 0x70000001,
|
||||
PT_RISCV_ATTRIBUTES = 0x70000003,
|
||||
@@ -232,7 +238,6 @@ enum : u32 {
|
||||
EM_AARCH64 = 183,
|
||||
EM_RISCV = 243,
|
||||
EM_LOONGARCH = 258,
|
||||
EM_ALPHA = 0x9026,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -281,6 +286,8 @@ enum : u32 {
|
||||
DT_RELR = 36,
|
||||
DT_RELRENT = 37,
|
||||
DT_GNU_HASH = 0x6ffffef5,
|
||||
DT_DEPAUDIT = 0x6ffffefb,
|
||||
DT_AUDIT = 0x6ffffefc,
|
||||
DT_VERSYM = 0x6ffffff0,
|
||||
DT_RELACOUNT = 0x6ffffff9,
|
||||
DT_RELCOUNT = 0x6ffffffa,
|
||||
@@ -291,6 +298,7 @@ enum : u32 {
|
||||
DT_VERNEEDNUM = 0x6fffffff,
|
||||
DT_PPC_GOT = 0x70000000,
|
||||
DT_PPC64_GLINK = 0x70000000,
|
||||
DT_RISCV_VARIANT_CC = 0x70000001,
|
||||
DT_AARCH64_VARIANT_PCS = 0x70000005,
|
||||
DT_AUXILIARY = 0x7ffffffd,
|
||||
DT_FILTER = 0x7fffffff,
|
||||
@@ -336,17 +344,25 @@ enum : u32 {
|
||||
GNU_PROPERTY_X86_FEATURE_1_IBT = 1,
|
||||
GNU_PROPERTY_X86_FEATURE_1_SHSTK = 2,
|
||||
GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002,
|
||||
|
||||
GNU_PROPERTY_X86_ISA_1_NEEDED = 0xc0008002,
|
||||
GNU_PROPERTY_X86_ISA_1_BASELINE = 1,
|
||||
GNU_PROPERTY_X86_ISA_1_V2 = 2,
|
||||
GNU_PROPERTY_X86_ISA_1_V3 = 4,
|
||||
GNU_PROPERTY_X86_ISA_1_V4 = 8,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
ELFCOMPRESS_NONE = 0,
|
||||
ELFCOMPRESS_ZLIB = 1,
|
||||
ELFCOMPRESS_ZSTD = 2,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
EF_ARM_ABI_FLOAT_SOFT = 0x00000200,
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400,
|
||||
EF_ARM_EABI_VER5 = 0x05000000,
|
||||
EF_ARM_ABI_FLOAT_SOFT = 0x200,
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x400,
|
||||
EF_ARM_BE8 = 0x800000,
|
||||
EF_ARM_EABI_VER5 = 0x5000000,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -373,8 +389,6 @@ enum : u32 {
|
||||
|
||||
enum : u32 {
|
||||
STO_RISCV_VARIANT_CC = 0x80,
|
||||
STO_ALPHA_NOPV = 0x20,
|
||||
STO_ALPHA_STD_GPLOAD = 0x22,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -399,6 +413,10 @@ enum : u32 {
|
||||
// Relocation types
|
||||
//
|
||||
|
||||
enum : u32 {
|
||||
R_NONE = 0,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_X86_64_NONE = 0,
|
||||
R_X86_64_64 = 1,
|
||||
@@ -440,6 +458,15 @@ enum : u32 {
|
||||
R_X86_64_IRELATIVE = 37,
|
||||
R_X86_64_GOTPCRELX = 41,
|
||||
R_X86_64_REX_GOTPCRELX = 42,
|
||||
R_X86_64_CODE_4_GOTPCRELX = 43,
|
||||
R_X86_64_CODE_4_GOTTPOFF = 44,
|
||||
R_X86_64_CODE_4_GOTPC32_TLSDESC = 45,
|
||||
R_X86_64_CODE_5_GOTPCRELX = 46,
|
||||
R_X86_64_CODE_5_GOTTPOFF = 47,
|
||||
R_X86_64_CODE_5_GOTPC32_TLSDESC = 48,
|
||||
R_X86_64_CODE_6_GOTPCRELX = 49,
|
||||
R_X86_64_CODE_6_GOTTPOFF = 50,
|
||||
R_X86_64_CODE_6_GOTPC32_TLSDESC = 51,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
@@ -774,10 +801,6 @@ enum : u32 {
|
||||
R_RISCV_ALIGN = 43,
|
||||
R_RISCV_RVC_BRANCH = 44,
|
||||
R_RISCV_RVC_JUMP = 45,
|
||||
R_RISCV_RVC_LUI = 46,
|
||||
R_RISCV_GPREL_LO12_I = 47,
|
||||
R_RISCV_GPREL_LO12_S = 48,
|
||||
R_RISCV_GPREL_HI20 = 49,
|
||||
R_RISCV_RELAX = 51,
|
||||
R_RISCV_SUB6 = 52,
|
||||
R_RISCV_SET6 = 53,
|
||||
@@ -967,6 +990,7 @@ enum : u32 {
|
||||
R_PPC64_DTPREL16_HIGH = 114,
|
||||
R_PPC64_DTPREL16_HIGHA = 115,
|
||||
R_PPC64_REL24_NOTOC = 116,
|
||||
R_PPC64_ENTRY = 118,
|
||||
R_PPC64_PLTSEQ = 119,
|
||||
R_PPC64_PLTCALL = 120,
|
||||
R_PPC64_PLTSEQ_NOTOC = 121,
|
||||
@@ -1221,42 +1245,6 @@ enum : u32 {
|
||||
R_SH_GOTPLT32 = 168,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_ALPHA_NONE = 0,
|
||||
R_ALPHA_REFLONG = 1,
|
||||
R_ALPHA_REFQUAD = 2,
|
||||
R_ALPHA_GPREL32 = 3,
|
||||
R_ALPHA_LITERAL = 4,
|
||||
R_ALPHA_LITUSE = 5,
|
||||
R_ALPHA_GPDISP = 6,
|
||||
R_ALPHA_BRADDR = 7,
|
||||
R_ALPHA_HINT = 8,
|
||||
R_ALPHA_SREL16 = 9,
|
||||
R_ALPHA_SREL32 = 10,
|
||||
R_ALPHA_SREL64 = 11,
|
||||
R_ALPHA_GPRELHIGH = 17,
|
||||
R_ALPHA_GPRELLOW = 18,
|
||||
R_ALPHA_GPREL16 = 19,
|
||||
R_ALPHA_COPY = 24,
|
||||
R_ALPHA_GLOB_DAT = 25,
|
||||
R_ALPHA_JMP_SLOT = 26,
|
||||
R_ALPHA_RELATIVE = 27,
|
||||
R_ALPHA_BRSGP = 28,
|
||||
R_ALPHA_TLSGD = 29,
|
||||
R_ALPHA_TLSLDM = 30,
|
||||
R_ALPHA_DTPMOD64 = 31,
|
||||
R_ALPHA_GOTDTPREL = 32,
|
||||
R_ALPHA_DTPREL64 = 33,
|
||||
R_ALPHA_DTPRELHI = 34,
|
||||
R_ALPHA_DTPRELLO = 35,
|
||||
R_ALPHA_DTPREL16 = 36,
|
||||
R_ALPHA_GOTTPREL = 37,
|
||||
R_ALPHA_TPREL64 = 38,
|
||||
R_ALPHA_TPRELHI = 39,
|
||||
R_ALPHA_TPRELLO = 40,
|
||||
R_ALPHA_TPREL16 = 41,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
R_LARCH_NONE = 0,
|
||||
R_LARCH_32 = 1,
|
||||
@@ -1271,6 +1259,8 @@ enum : u32 {
|
||||
R_LARCH_TLS_TPREL32 = 10,
|
||||
R_LARCH_TLS_TPREL64 = 11,
|
||||
R_LARCH_IRELATIVE = 12,
|
||||
R_LARCH_TLS_DESC32 = 13,
|
||||
R_LARCH_TLS_DESC64 = 14,
|
||||
R_LARCH_MARK_LA = 20,
|
||||
R_LARCH_MARK_PCREL = 21,
|
||||
R_LARCH_SOP_PUSH_PCREL = 22,
|
||||
@@ -1356,8 +1346,35 @@ enum : u32 {
|
||||
R_LARCH_ADD_ULEB128 = 107,
|
||||
R_LARCH_SUB_ULEB128 = 108,
|
||||
R_LARCH_64_PCREL = 109,
|
||||
R_LARCH_CALL36 = 110,
|
||||
R_LARCH_TLS_DESC_PC_HI20 = 111,
|
||||
R_LARCH_TLS_DESC_PC_LO12 = 112,
|
||||
R_LARCH_TLS_DESC64_PC_LO20 = 113,
|
||||
R_LARCH_TLS_DESC64_PC_HI12 = 114,
|
||||
R_LARCH_TLS_DESC_HI20 = 115,
|
||||
R_LARCH_TLS_DESC_LO12 = 116,
|
||||
R_LARCH_TLS_DESC64_LO20 = 117,
|
||||
R_LARCH_TLS_DESC64_HI12 = 118,
|
||||
R_LARCH_TLS_DESC_LD = 119,
|
||||
R_LARCH_TLS_DESC_CALL = 120,
|
||||
R_LARCH_TLS_LE_HI20_R = 121,
|
||||
R_LARCH_TLS_LE_ADD_R = 122,
|
||||
R_LARCH_TLS_LE_LO12_R = 123,
|
||||
R_LARCH_TLS_LD_PCREL20_S2 = 124,
|
||||
R_LARCH_TLS_GD_PCREL20_S2 = 125,
|
||||
R_LARCH_TLS_DESC_PCREL20_S2 = 126,
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
inline bool is_func_call_rel(const ElfRel<E> &r) {
|
||||
for (u32 r_type : E::R_FUNCALL)
|
||||
if (r.r_type == r_type)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// DWARF data types
|
||||
//
|
||||
@@ -1472,7 +1489,7 @@ template <typename E> using U32 = std::conditional_t<E::is_le, ul32, ub32>;
|
||||
template <typename E> using U64 = std::conditional_t<E::is_le, ul64, ub64>;
|
||||
|
||||
template <typename E> using Word = std::conditional_t<E::is_64, U64<E>, U32<E>>;
|
||||
template <typename E> using IWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
|
||||
template <typename E> using SWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
|
||||
|
||||
template <typename E> requires E::is_64
|
||||
struct ElfSym<E> {
|
||||
@@ -1482,18 +1499,41 @@ struct ElfSym<E> {
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
bool ppc64_preserves_r2() const { return ppc64_local_entry != 1; }
|
||||
bool ppc64_uses_toc() const { return ppc64_local_entry > 1; }
|
||||
|
||||
U32<E> st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 6;
|
||||
union {
|
||||
u8 st_visibility : 2;
|
||||
struct {
|
||||
u8 : 7;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
};
|
||||
struct {
|
||||
u8 : 7;
|
||||
u8 riscv_variant_cc : 1;
|
||||
};
|
||||
struct {
|
||||
u8 : 5;
|
||||
u8 ppc64_local_entry : 3;
|
||||
};
|
||||
};
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
union {
|
||||
struct {
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
};
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 riscv_variant_cc : 1;
|
||||
u8 ppc64_local_entry : 3;
|
||||
};
|
||||
#endif
|
||||
|
||||
U16<E> st_shndx;
|
||||
@@ -1516,13 +1556,23 @@ struct ElfSym<E> {
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 6;
|
||||
union {
|
||||
u8 st_visibility : 2;
|
||||
struct {
|
||||
u8 : 7;
|
||||
u8 riscv_variant_cc : 1;
|
||||
};
|
||||
};
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
union {
|
||||
struct {
|
||||
u8 : 6;
|
||||
u8 st_visibility : 2;
|
||||
};
|
||||
u8 riscv_variant_cc : 1;
|
||||
};
|
||||
#endif
|
||||
|
||||
U16<E> st_shndx;
|
||||
@@ -1596,7 +1646,7 @@ struct ElfPhdr<E> {
|
||||
// We don't want to have too many `if (REL)`s and `if (RELA)`s in our
|
||||
// codebase, so ElfRel always takes r_addend as a constructor argument.
|
||||
// If it's REL, the argument will simply be ignored.
|
||||
template <typename E> requires E::is_le && E::is_rela
|
||||
template <typename E> requires (E::is_le && E::is_rela)
|
||||
struct ElfRel<E> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
@@ -1605,10 +1655,10 @@ struct ElfRel<E> {
|
||||
Word<E> r_offset;
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
std::conditional_t<E::is_64, U32<E>, U24<E>> r_sym;
|
||||
IWord<E> r_addend;
|
||||
SWord<E> r_addend;
|
||||
};
|
||||
|
||||
template <typename E> requires (!E::is_le) && E::is_rela
|
||||
template <typename E> requires (!E::is_le && E::is_rela)
|
||||
struct ElfRel<E> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
@@ -1617,10 +1667,10 @@ struct ElfRel<E> {
|
||||
Word<E> r_offset;
|
||||
std::conditional_t<E::is_64, U32<E>, U24<E>> r_sym;
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
IWord<E> r_addend;
|
||||
SWord<E> r_addend;
|
||||
};
|
||||
|
||||
template <typename E> requires E::is_le && (!E::is_rela)
|
||||
template <typename E> requires (E::is_le && !E::is_rela)
|
||||
struct ElfRel<E> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend = 0)
|
||||
@@ -1631,7 +1681,7 @@ struct ElfRel<E> {
|
||||
std::conditional_t<E::is_64, U32<E>, U24<E>> r_sym;
|
||||
};
|
||||
|
||||
template <typename E> requires (!E::is_le) && (!E::is_rela)
|
||||
template <typename E> requires (!E::is_le && !E::is_rela)
|
||||
struct ElfRel<E> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend = 0)
|
||||
@@ -1642,16 +1692,6 @@ struct ElfRel<E> {
|
||||
std::conditional_t<E::is_64, U32<E>, u8> r_type;
|
||||
};
|
||||
|
||||
// Returns true if a given relocation is of type used for direct
|
||||
// function call.
|
||||
template <typename E>
|
||||
inline bool is_func_call_rel(const ElfRel<E> &r) {
|
||||
for (u32 r_type : E::R_FUNCALL)
|
||||
if (r.r_type == r_type)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
struct ElfDyn {
|
||||
Word<E> d_tag;
|
||||
@@ -1719,94 +1759,6 @@ struct ElfNhdr {
|
||||
// Target-specific ELF data types
|
||||
//
|
||||
|
||||
template <>
|
||||
struct ElfSym<ARM64> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 5;
|
||||
u8 arm64_variant_pcs : 1; // ARM64-specific
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 arm64_variant_pcs : 1;
|
||||
u8 : 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; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
bool preserves_r2() const { return ppc_local_entry != 1; }
|
||||
bool uses_toc() const { return ppc_local_entry > 1; }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 : 3;
|
||||
u8 ppc_local_entry : 3; // This is PPC64V2-specific
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 ppc_local_entry : 3;
|
||||
u8 : 3;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfSym<ALPHA> {
|
||||
bool is_undef() const { return st_shndx == SHN_UNDEF; }
|
||||
bool is_abs() const { return st_shndx == SHN_ABS; }
|
||||
bool is_common() const { return st_shndx == SHN_COMMON; }
|
||||
bool is_weak() const { return st_bind == STB_WEAK; }
|
||||
bool is_undef_weak() const { return is_undef() && is_weak(); }
|
||||
|
||||
ul32 st_name;
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
u8 st_type : 4;
|
||||
u8 st_bind : 4;
|
||||
u8 st_visibility : 2;
|
||||
u8 alpha_st_other : 6; // contains STO_ALPHA_NOPV, STO_ALPHA_STD_GPLOAD or 0
|
||||
#else
|
||||
u8 st_bind : 4;
|
||||
u8 st_type : 4;
|
||||
u8 alpha_st_other : 6;
|
||||
u8 st_visibility : 2;
|
||||
#endif
|
||||
|
||||
ul16 st_shndx;
|
||||
ul64 st_value;
|
||||
ul64 st_size;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfRel<SPARC64> {
|
||||
ElfRel() = default;
|
||||
@@ -1821,6 +1773,34 @@ struct ElfRel<SPARC64> {
|
||||
ib64 r_addend;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfRel<SH4LE> {
|
||||
ElfRel() = default;
|
||||
|
||||
// Addend is ignored except for base relocations because even though
|
||||
// SH4 is RELA, r_addend is ignored in most cases and works as if it
|
||||
// were REL.
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_type(type), r_sym(sym), r_addend(sym ? 0 : addend) {}
|
||||
|
||||
ul32 r_offset;
|
||||
u8 r_type;
|
||||
ul24 r_sym;
|
||||
il32 r_addend;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ElfRel<SH4BE> {
|
||||
ElfRel() = default;
|
||||
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
|
||||
: r_offset(offset), r_sym(sym), r_type(type), r_addend(sym ? 0 : addend) {}
|
||||
|
||||
ub32 r_offset;
|
||||
ub24 r_sym;
|
||||
u8 r_type;
|
||||
ib32 r_addend;
|
||||
};
|
||||
|
||||
//
|
||||
// Machine descriptions
|
||||
//
|
||||
@@ -1831,8 +1811,10 @@ template <typename E> concept needs_thunk = requires { E::thunk_size; };
|
||||
|
||||
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
|
||||
template <typename E> concept is_i386 = std::same_as<E, I386>;
|
||||
template <typename E> concept is_arm64 = std::same_as<E, ARM64>;
|
||||
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
|
||||
template <typename E> concept is_arm64le = std::same_as<E, ARM64LE>;
|
||||
template <typename E> concept is_arm64be = std::same_as<E, ARM64BE>;
|
||||
template <typename E> concept is_arm32le = std::same_as<E, ARM32LE>;
|
||||
template <typename E> concept is_arm32be = std::same_as<E, ARM32BE>;
|
||||
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>;
|
||||
@@ -1843,12 +1825,14 @@ template <typename E> concept is_ppc64v2 = std::same_as<E, PPC64V2>;
|
||||
template <typename E> concept is_s390x = std::same_as<E, S390X>;
|
||||
template <typename E> concept is_sparc64 = std::same_as<E, SPARC64>;
|
||||
template <typename E> concept is_m68k = std::same_as<E, M68K>;
|
||||
template <typename E> concept is_sh4 = std::same_as<E, SH4>;
|
||||
template <typename E> concept is_alpha = std::same_as<E, ALPHA>;
|
||||
template <typename E> concept is_sh4le = std::same_as<E, SH4LE>;
|
||||
template <typename E> concept is_sh4be = std::same_as<E, SH4BE>;
|
||||
template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
|
||||
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
|
||||
|
||||
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
|
||||
template <typename E> concept is_arm32 = is_arm32le<E> || is_arm32be<E>;
|
||||
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<E>;
|
||||
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
|
||||
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
|
||||
template <typename E> concept is_rv32 = is_rv32le<E> || is_rv32be<E>;
|
||||
@@ -1856,10 +1840,11 @@ template <typename E> concept is_riscv = is_rv64<E> || is_rv32<E>;
|
||||
template <typename E> concept is_ppc64 = is_ppc64v1<E> || is_ppc64v2<E>;
|
||||
template <typename E> concept is_ppc = is_ppc64<E> || is_ppc32<E>;
|
||||
template <typename E> concept is_sparc = is_sparc64<E>;
|
||||
template <typename E> concept is_sh4 = is_sh4le<E> || is_sh4be<E>;
|
||||
template <typename E> concept is_loongarch = is_loongarch64<E> || is_loongarch32<E>;
|
||||
|
||||
struct X86_64 {
|
||||
static constexpr std::string_view target_name = "x86_64";
|
||||
static constexpr std::string_view name = "x86_64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -1868,7 +1853,7 @@ struct X86_64 {
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
static constexpr u8 filler[] = { 0xcc }; // int3
|
||||
|
||||
static constexpr u32 R_COPY = R_X86_64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
|
||||
@@ -1884,7 +1869,7 @@ struct X86_64 {
|
||||
};
|
||||
|
||||
struct I386 {
|
||||
static constexpr std::string_view target_name = "i386";
|
||||
static constexpr std::string_view name = "i386";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = false;
|
||||
@@ -1892,8 +1877,8 @@ struct I386 {
|
||||
static constexpr u32 e_machine = EM_386;
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0xcc };
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0xcc }; // int3
|
||||
|
||||
static constexpr u32 R_COPY = R_386_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
|
||||
@@ -1908,8 +1893,8 @@ struct I386 {
|
||||
static constexpr u32 R_FUNCALL[] = { R_386_PLT32 };
|
||||
};
|
||||
|
||||
struct ARM64 {
|
||||
static constexpr std::string_view target_name = "arm64";
|
||||
struct ARM64LE {
|
||||
static constexpr std::string_view name = "arm64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -1919,8 +1904,8 @@ 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 = 16;
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 };
|
||||
static constexpr u32 thunk_size = 32;
|
||||
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
|
||||
|
||||
static constexpr u32 R_COPY = R_AARCH64_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
|
||||
@@ -1935,19 +1920,24 @@ struct ARM64 {
|
||||
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
|
||||
};
|
||||
|
||||
struct ARM32 {
|
||||
static constexpr std::string_view target_name = "arm32";
|
||||
struct ARM64BE : ARM64LE {
|
||||
static constexpr std::string_view name = "arm64be";
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct ARM32LE {
|
||||
static constexpr std::string_view name = "arm32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = false;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_ARM;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 16;
|
||||
static constexpr u32 thunk_size = 16;
|
||||
static constexpr u8 filler[] = { 0xff, 0xde };
|
||||
static constexpr u8 filler[] = { 0xff, 0xde }; // udf
|
||||
|
||||
static constexpr u32 R_COPY = R_ARM_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
|
||||
@@ -1965,15 +1955,22 @@ struct ARM32 {
|
||||
};
|
||||
};
|
||||
|
||||
struct RV64 {
|
||||
struct ARM32BE : ARM32LE {
|
||||
static constexpr std::string_view name = "arm32be";
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct RV64LE {
|
||||
static constexpr std::string_view name = "riscv64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
|
||||
@@ -1988,25 +1985,22 @@ struct RV64 {
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV64LE : RV64 {
|
||||
static constexpr std::string_view target_name = "riscv64";
|
||||
static constexpr bool is_le = true;
|
||||
};
|
||||
|
||||
struct RV64BE : RV64 {
|
||||
static constexpr std::string_view target_name = "riscv64be";
|
||||
struct RV64BE : RV64LE {
|
||||
static constexpr std::string_view name = "riscv64be";
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct RV32 {
|
||||
struct RV32LE {
|
||||
static constexpr std::string_view name = "riscv32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 4096;
|
||||
static constexpr u32 e_machine = EM_RISCV;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 };
|
||||
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
|
||||
|
||||
static constexpr u32 R_COPY = R_RISCV_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
|
||||
@@ -2021,18 +2015,13 @@ struct RV32 {
|
||||
static constexpr u32 R_FUNCALL[] = { R_RISCV_CALL, R_RISCV_CALL_PLT };
|
||||
};
|
||||
|
||||
struct RV32LE : RV32 {
|
||||
static constexpr std::string_view target_name = "riscv32";
|
||||
static constexpr bool is_le = true;
|
||||
};
|
||||
|
||||
struct RV32BE : RV32 {
|
||||
static constexpr std::string_view target_name = "riscv32be";
|
||||
struct RV32BE : RV32LE {
|
||||
static constexpr std::string_view name = "riscv32be";
|
||||
static constexpr bool is_le = false;
|
||||
};
|
||||
|
||||
struct PPC32 {
|
||||
static constexpr std::string_view target_name = "ppc32";
|
||||
static constexpr std::string_view name = "ppc32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -2043,7 +2032,7 @@ struct PPC32 {
|
||||
static constexpr u32 pltgot_size = 36;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 36;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
|
||||
|
||||
static constexpr u32 R_COPY = R_PPC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
|
||||
@@ -2079,28 +2068,28 @@ struct PPC64 {
|
||||
};
|
||||
|
||||
struct PPC64V1 : PPC64 {
|
||||
static constexpr std::string_view target_name = "ppc64v1";
|
||||
static constexpr std::string_view name = "ppc64v1";
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr u32 plt_hdr_size = 44;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 28;
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 };
|
||||
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
|
||||
};
|
||||
|
||||
struct PPC64V2 : PPC64 {
|
||||
static constexpr std::string_view target_name = "ppc64v2";
|
||||
static constexpr std::string_view name = "ppc64v2";
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr u32 plt_hdr_size = 52;
|
||||
static constexpr u32 plt_size = 4;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 24;
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f };
|
||||
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
|
||||
};
|
||||
|
||||
struct S390X {
|
||||
static constexpr std::string_view target_name = "s390x";
|
||||
static constexpr std::string_view name = "s390x";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -2109,7 +2098,7 @@ struct S390X {
|
||||
static constexpr u32 plt_hdr_size = 48;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x07, 0x00 }; // nop
|
||||
|
||||
static constexpr u32 R_COPY = R_390_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
|
||||
@@ -2124,7 +2113,7 @@ struct S390X {
|
||||
};
|
||||
|
||||
struct SPARC64 {
|
||||
static constexpr std::string_view target_name = "sparc64";
|
||||
static constexpr std::string_view name = "sparc64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -2133,7 +2122,7 @@ struct SPARC64 {
|
||||
static constexpr u32 plt_hdr_size = 128;
|
||||
static constexpr u32 plt_size = 32;
|
||||
static constexpr u32 pltgot_size = 32;
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 };
|
||||
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
|
||||
|
||||
static constexpr u32 R_COPY = R_SPARC_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
|
||||
@@ -2148,7 +2137,7 @@ struct SPARC64 {
|
||||
};
|
||||
|
||||
struct M68K {
|
||||
static constexpr std::string_view target_name = "m68k";
|
||||
static constexpr std::string_view name = "m68k";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -2157,7 +2146,7 @@ struct M68K {
|
||||
static constexpr u32 plt_hdr_size = 18;
|
||||
static constexpr u32 plt_size = 14;
|
||||
static constexpr u32 pltgot_size = 8;
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc };
|
||||
static constexpr u8 filler[] = { 0x4a, 0xfc }; // illegal
|
||||
|
||||
static constexpr u32 R_COPY = R_68K_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
|
||||
@@ -2170,8 +2159,8 @@ struct M68K {
|
||||
static constexpr u32 R_FUNCALL[] = { R_68K_PLT32 };
|
||||
};
|
||||
|
||||
struct SH4 {
|
||||
static constexpr std::string_view target_name = "sh4";
|
||||
struct SH4LE {
|
||||
static constexpr std::string_view name = "sh4";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
@@ -2180,7 +2169,7 @@ struct SH4 {
|
||||
static constexpr u32 plt_hdr_size = 16;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 12;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x90 }; // nop
|
||||
|
||||
static constexpr u32 R_COPY = R_SH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
|
||||
@@ -2193,42 +2182,23 @@ struct SH4 {
|
||||
static constexpr u32 R_FUNCALL[] = { R_SH_PLT32 };
|
||||
};
|
||||
|
||||
struct ALPHA {
|
||||
static constexpr std::string_view target_name = "alpha";
|
||||
struct SH4BE : SH4LE {
|
||||
static constexpr std::string_view name = "sh4be";
|
||||
static constexpr bool is_le = false;
|
||||
static constexpr u8 filler[] = { 0x90, 0x00 }; // nop
|
||||
};
|
||||
|
||||
struct LOONGARCH64 {
|
||||
static constexpr std::string_view name = "loongarch64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_ALPHA;
|
||||
static constexpr u32 plt_hdr_size = 0;
|
||||
static constexpr u32 plt_size = 0;
|
||||
static constexpr u32 pltgot_size = 0;
|
||||
static constexpr u8 filler[] = { 0x81, 0x00, 0x00, 0x00 };
|
||||
|
||||
static constexpr u32 R_COPY = R_ALPHA_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_ALPHA_GLOB_DAT;
|
||||
static constexpr u32 R_JUMP_SLOT = R_ALPHA_JMP_SLOT;
|
||||
static constexpr u32 R_ABS = R_ALPHA_REFQUAD;
|
||||
static constexpr u32 R_RELATIVE = R_ALPHA_RELATIVE;
|
||||
static constexpr u32 R_DTPOFF = R_ALPHA_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_ALPHA_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_ALPHA_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = {};
|
||||
};
|
||||
|
||||
struct LOONGARCH64 {
|
||||
static constexpr std::string_view target_name = "loongarch64";
|
||||
static constexpr bool is_64 = true;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 e_machine = EM_LOONGARCH;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
|
||||
@@ -2239,22 +2209,21 @@ struct LOONGARCH64 {
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL64;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL64;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
static constexpr u32 R_TLSDESC = R_LARCH_TLS_DESC64;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26, R_LARCH_CALL36 };
|
||||
};
|
||||
|
||||
struct LOONGARCH32 {
|
||||
static constexpr std::string_view target_name = "loongarch32";
|
||||
static constexpr std::string_view name = "loongarch32";
|
||||
static constexpr bool is_64 = false;
|
||||
static constexpr bool is_le = true;
|
||||
static constexpr bool is_rela = true;
|
||||
static constexpr u32 page_size = 16384;
|
||||
static constexpr u32 page_size = 65536;
|
||||
static constexpr u32 e_machine = EM_LOONGARCH;
|
||||
static constexpr u32 plt_hdr_size = 32;
|
||||
static constexpr u32 plt_size = 16;
|
||||
static constexpr u32 pltgot_size = 16;
|
||||
static constexpr u32 thunk_hdr_size = 0;
|
||||
static constexpr u32 thunk_size = 8;
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 };
|
||||
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
|
||||
|
||||
static constexpr u32 R_COPY = R_LARCH_COPY;
|
||||
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
|
||||
@@ -2265,7 +2234,8 @@ struct LOONGARCH32 {
|
||||
static constexpr u32 R_DTPOFF = R_LARCH_TLS_DTPREL32;
|
||||
static constexpr u32 R_TPOFF = R_LARCH_TLS_TPREL32;
|
||||
static constexpr u32 R_DTPMOD = R_LARCH_TLS_DTPMOD32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26 };
|
||||
static constexpr u32 R_TLSDESC = R_LARCH_TLS_DESC32;
|
||||
static constexpr u32 R_FUNCALL[] = { R_LARCH_B26, R_LARCH_CALL36 };
|
||||
};
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
#include "mold-git-hash.h"
|
||||
|
||||
#if MOLD_USE_MIMALLOC
|
||||
# include <mimalloc.h>
|
||||
#endif
|
||||
|
||||
#if MOLD_USE_SYSTEM_MIMALLOC
|
||||
// Including mimalloc-new-delete.h overrides the new/delete operators.
|
||||
// We need it only when using mimalloc as a dynamic library.
|
||||
// This header should be included in only one source file, so we do
|
||||
// it in this file.
|
||||
# include <mimalloc-new-delete.h>
|
||||
#endif
|
||||
|
||||
std::string mold::mold_version =
|
||||
#ifdef MOLD_GIT_HASH
|
||||
"mold " MOLD_VERSION " (" MOLD_GIT_HASH "; compatible with GNU ld)";
|
||||
#else
|
||||
"mold " MOLD_VERSION " (compatible with GNU ld)";
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#if MOLD_USE_MIMALLOC
|
||||
// Silence mimalloc warnings that users can ignore
|
||||
mi_option_disable(mi_option_verbose);
|
||||
mi_option_disable(mi_option_show_errors);
|
||||
#endif
|
||||
|
||||
return mold::mold_main<mold::X86_64>(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
static std::string_view fatal_mono = "mold: fatal: ";
|
||||
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
|
||||
static std::string_view error_mono = "mold: error: ";
|
||||
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
|
||||
static std::string_view warning_mono = "mold: warning: ";
|
||||
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
|
||||
|
||||
template <typename E>
|
||||
Fatal<E>::Fatal(Context<E> &ctx) {
|
||||
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
[[noreturn]] Fatal<E>::~Fatal() {
|
||||
out.emit();
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Error<E>::Error(Context<E> &ctx) {
|
||||
if (ctx.arg.noinhibit_exec) {
|
||||
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
} else {
|
||||
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
Warn<E>::Warn(Context<E> &ctx) {
|
||||
if (ctx.arg.suppress_warnings)
|
||||
return;
|
||||
|
||||
out.emplace(std::cerr);
|
||||
|
||||
if (ctx.arg.fatal_warnings) {
|
||||
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
|
||||
ctx.has_error = true;
|
||||
} else {
|
||||
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class Fatal<E>;
|
||||
template class Error<E>;
|
||||
template class Warn<E>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,39 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "../elf/elf.h"
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
enum class FileType {
|
||||
UNKNOWN,
|
||||
EMPTY,
|
||||
ELF_OBJ,
|
||||
ELF_DSO,
|
||||
MACH_OBJ,
|
||||
MACH_EXE,
|
||||
MACH_DYLIB,
|
||||
MACH_BUNDLE,
|
||||
MACH_UNIVERSAL,
|
||||
AR,
|
||||
THIN_AR,
|
||||
TAPI,
|
||||
TEXT,
|
||||
GCC_LTO_OBJ,
|
||||
LLVM_BITCODE,
|
||||
};
|
||||
static bool is_text_file(MappedFile *mf) {
|
||||
auto istext = [](char c) {
|
||||
return isprint(c) || c == '\n' || c == '\t';
|
||||
};
|
||||
|
||||
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]);
|
||||
return mf->size >= 4 && istext(data[0]) && istext(data[1]) &&
|
||||
istext(data[2]) && istext(data[3]);
|
||||
}
|
||||
|
||||
template <typename E, typename Context, typename MappedFile>
|
||||
inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
|
||||
using namespace mold::elf;
|
||||
|
||||
template <typename E>
|
||||
static bool is_gcc_lto_obj(MappedFile *mf, bool has_plugin) {
|
||||
const char *data = mf->get_contents().data();
|
||||
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
|
||||
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
|
||||
@@ -50,10 +30,10 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
|
||||
// the LTO linker plugin is available and falls back as regular
|
||||
// objects otherwise. GCC FAT LTO object can be identified by the
|
||||
// presence of `.gcc.lto_.symtab` section.
|
||||
if (!ctx.arg.plugin.empty()) {
|
||||
if (has_plugin) {
|
||||
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
|
||||
if (name.starts_with(".gnu.lto_.symtab."))
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sec.sh_type != SHT_SYMTAB)
|
||||
@@ -85,11 +65,10 @@ inline bool is_gcc_lto_obj(Context &ctx, MappedFile *mf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Context, typename MappedFile>
|
||||
FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
using namespace elf;
|
||||
|
||||
template <typename E>
|
||||
FileType get_file_type(Context<E> &ctx, MappedFile *mf) {
|
||||
std::string_view data = mf->get_contents();
|
||||
bool has_plugin = !ctx.arg.plugin.empty();
|
||||
|
||||
if (data.empty())
|
||||
return FileType::EMPTY;
|
||||
@@ -102,10 +81,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<I386>(ctx, mf))
|
||||
if (is_gcc_lto_obj<I386>(mf, has_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<X86_64>(ctx, mf))
|
||||
if (is_gcc_lto_obj<X86_64>(mf, has_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
@@ -118,10 +97,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
|
||||
if (ehdr.e_type == ET_REL) {
|
||||
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
if (is_gcc_lto_obj<M68K>(ctx, mf))
|
||||
if (is_gcc_lto_obj<M68K>(mf, has_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
} else {
|
||||
if (is_gcc_lto_obj<SPARC64>(ctx, mf))
|
||||
if (is_gcc_lto_obj<SPARC64>(mf, has_plugin))
|
||||
return FileType::GCC_LTO_OBJ;
|
||||
}
|
||||
return FileType::ELF_OBJ;
|
||||
@@ -133,28 +112,10 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("\xcf\xfa\xed\xfe")) {
|
||||
switch (*(ul32 *)(data.data() + 12)) {
|
||||
case 1: // MH_OBJECT
|
||||
return FileType::MACH_OBJ;
|
||||
case 2: // MH_EXECUTE
|
||||
return FileType::MACH_EXE;
|
||||
case 6: // MH_DYLIB
|
||||
return FileType::MACH_DYLIB;
|
||||
case 8: // MH_BUNDLE
|
||||
return FileType::MACH_BUNDLE;
|
||||
}
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
if (data.starts_with("!<arch>\n"))
|
||||
return FileType::AR;
|
||||
if (data.starts_with("!<thin>\n"))
|
||||
return FileType::THIN_AR;
|
||||
if (data.starts_with("--- !tapi-tbd"))
|
||||
return FileType::TAPI;
|
||||
if (data.starts_with("\xca\xfe\xba\xbe"))
|
||||
return FileType::MACH_UNIVERSAL;
|
||||
if (is_text_file(mf))
|
||||
return FileType::TEXT;
|
||||
if (data.starts_with("\xde\xc0\x17\x0b"))
|
||||
@@ -164,30 +125,79 @@ FileType get_file_type(Context &ctx, MappedFile *mf) {
|
||||
return FileType::UNKNOWN;
|
||||
}
|
||||
|
||||
inline std::string filetype_to_string(FileType type) {
|
||||
switch (type) {
|
||||
case FileType::UNKNOWN: return "UNKNOWN";
|
||||
case FileType::EMPTY: return "EMPTY";
|
||||
case FileType::ELF_OBJ: return "ELF_OBJ";
|
||||
case FileType::ELF_DSO: return "ELF_DSO";
|
||||
case FileType::MACH_EXE: return "MACH_EXE";
|
||||
case FileType::MACH_OBJ: return "MACH_OBJ";
|
||||
case FileType::MACH_DYLIB: return "MACH_DYLIB";
|
||||
case FileType::MACH_BUNDLE: return "MACH_BUNDLE";
|
||||
case FileType::MACH_UNIVERSAL: return "MACH_UNIVERSAL";
|
||||
case FileType::AR: return "AR";
|
||||
case FileType::THIN_AR: return "THIN_AR";
|
||||
case FileType::TAPI: return "TAPI";
|
||||
case FileType::TEXT: return "TEXT";
|
||||
case FileType::GCC_LTO_OBJ: return "GCC_LTO_OBJ";
|
||||
case FileType::LLVM_BITCODE: return "LLVM_BITCODE";
|
||||
static std::string_view get_elf_type(u8 *buf) {
|
||||
bool is_le = (buf[EI_DATA] == ELFDATA2LSB);
|
||||
bool is_64 = (buf[EI_CLASS] == ELFCLASS64);
|
||||
|
||||
auto *ehdr_le = (ElfEhdr<I386> *)buf;
|
||||
auto *ehdr_be = (ElfEhdr<M68K> *)buf;
|
||||
|
||||
switch (is_le ? ehdr_le->e_machine : ehdr_be->e_machine) {
|
||||
case EM_386:
|
||||
return I386::name;
|
||||
case EM_X86_64:
|
||||
return X86_64::name;
|
||||
case EM_ARM:
|
||||
return is_le ? ARM32LE::name : ARM32BE::name;
|
||||
case EM_AARCH64:
|
||||
return is_le ? ARM64LE::name : ARM64BE::name;
|
||||
case EM_RISCV:
|
||||
if (is_le)
|
||||
return is_64 ? RV64LE::name : RV32LE::name;
|
||||
return is_64 ? RV64BE::name : RV32BE::name;
|
||||
case EM_PPC:
|
||||
return PPC32::name;
|
||||
case EM_PPC64:
|
||||
return is_le ? PPC64V2::name : PPC64V1::name;
|
||||
case EM_S390X:
|
||||
return S390X::name;
|
||||
case EM_SPARC64:
|
||||
return SPARC64::name;
|
||||
case EM_68K:
|
||||
return M68K::name;
|
||||
case EM_SH:
|
||||
return is_le ? SH4LE::name : SH4BE::name;
|
||||
case EM_LOONGARCH:
|
||||
return is_64 ? LOONGARCH64::name : LOONGARCH32::name;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, FileType type) {
|
||||
out << filetype_to_string(type);
|
||||
return out;
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
std::string_view
|
||||
get_machine_type(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile *child : read_fat_archive_members(ctx, mf))
|
||||
if (FileType ty = get_file_type(ctx, child);
|
||||
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile *child : read_thin_archive_members(ctx, mf))
|
||||
if (FileType ty = get_file_type(ctx, child);
|
||||
ty == FileType::ELF_OBJ || ty == FileType::GCC_LTO_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
return Script(ctx, rctx, mf).get_script_output_type();
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template FileType get_file_type(Context<E> &, MappedFile *);
|
||||
|
||||
template std::string_view
|
||||
get_machine_type(Context<E> &, ReaderContext &, MappedFile *);
|
||||
|
||||
} // namespace mold
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
static bool should_keep(const InputSection<E> &isec) {
|
||||
@@ -15,12 +15,15 @@ static bool should_keep(const InputSection<E> &isec) {
|
||||
u32 flags = isec.shdr().sh_flags;
|
||||
std::string_view name = isec.name();
|
||||
|
||||
if constexpr (is_ppc32<E>)
|
||||
if (name == ".got2")
|
||||
return true;
|
||||
|
||||
return (flags & SHF_GNU_RETAIN) ||
|
||||
type == SHT_NOTE ||
|
||||
type == SHT_INIT_ARRAY ||
|
||||
type == SHT_FINI_ARRAY ||
|
||||
type == SHT_PREINIT_ARRAY ||
|
||||
(is_arm32<E> && type == SHT_ARM_EXIDX) ||
|
||||
name.starts_with(".ctors") ||
|
||||
name.starts_with(".dtors") ||
|
||||
name.starts_with(".init") ||
|
||||
@@ -34,44 +37,10 @@ static bool mark_section(InputSection<E> *isec) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// If this is a text section, .eh_frame may contain records
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
if (mark_section(sym->get_input_section()))
|
||||
feeder.add(sym->get_input_section());
|
||||
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
|
||||
// Symbol can refer either a section fragment or an input section.
|
||||
// Mark a fragment as alive.
|
||||
if (SectionFragment<E> *frag = sym.get_frag()) {
|
||||
frag->is_alive = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
if (mark_section(sym.get_input_section())) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sym.get_input_section(), feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sym.get_input_section());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void collect_root_set(Context<E> &ctx,
|
||||
tbb::concurrent_vector<InputSection<E> *> &rootset) {
|
||||
static tbb::concurrent_vector<InputSection<E> *>
|
||||
collect_root_set(Context<E> &ctx) {
|
||||
Timer t(ctx, "collect_root_set");
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
|
||||
auto enqueue_section = [&](InputSection<E> *isec) {
|
||||
if (mark_section(isec))
|
||||
@@ -106,22 +75,13 @@ static void collect_root_set(Context<E> &ctx,
|
||||
}
|
||||
});
|
||||
|
||||
// Add sections containing exported symbols
|
||||
// Add sections containing gc root or exported symbols
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file && sym->is_exported)
|
||||
if (sym->file == file && (sym->gc_root || sym->is_exported))
|
||||
enqueue_symbol(sym);
|
||||
});
|
||||
|
||||
// Add sections referenced by root symbols.
|
||||
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
|
||||
|
||||
for (std::string_view name : ctx.arg.undefined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
enqueue_symbol(get_symbol(ctx, name));
|
||||
|
||||
// .eh_frame consists of variable-length records called CIE and FDE
|
||||
// records, and they are a unit of inclusion or exclusion.
|
||||
// We just keep all CIEs and everything that are referenced by them.
|
||||
@@ -130,6 +90,45 @@ static void collect_root_set(Context<E> &ctx,
|
||||
for (const ElfRel<E> &rel : cie.get_rels())
|
||||
enqueue_symbol(file->symbols[rel.r_sym]);
|
||||
});
|
||||
|
||||
return rootset;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void visit(Context<E> &ctx, InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
|
||||
assert(isec->is_visited);
|
||||
|
||||
// Mark a section alive. For better performacne, we don't call
|
||||
// `feeder.add` too often.
|
||||
auto mark = [&](InputSection<E> *sec) {
|
||||
if (mark_section(sec)) {
|
||||
if (depth < 3)
|
||||
visit(ctx, sec, feeder, depth + 1);
|
||||
else
|
||||
feeder.add(sec);
|
||||
}
|
||||
};
|
||||
|
||||
// If this is a text section, .eh_frame may contain records
|
||||
// describing how to handle exceptions for that function.
|
||||
// We want to keep associated .eh_frame records.
|
||||
for (FdeRecord<E> &fde : isec->get_fdes())
|
||||
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
|
||||
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
|
||||
mark(sym->get_input_section());
|
||||
|
||||
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
|
||||
// Symbol can refer to either a section fragment or an input section.
|
||||
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
|
||||
if (SectionFragment<E> *frag = sym.get_frag())
|
||||
frag->is_alive = true;
|
||||
else
|
||||
mark(sym.get_input_section());
|
||||
}
|
||||
|
||||
if constexpr (is_arm32<E>)
|
||||
mark(isec->extra.exidx);
|
||||
}
|
||||
|
||||
// Mark all reachable sections
|
||||
@@ -139,7 +138,7 @@ static void mark(Context<E> &ctx,
|
||||
Timer t(ctx, "mark");
|
||||
|
||||
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
tbb::feeder<InputSection<E> *> &feeder) {
|
||||
visit(ctx, isec, feeder, 0);
|
||||
});
|
||||
}
|
||||
@@ -154,7 +153,7 @@ static void sweep(Context<E> &ctx) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && !isec->is_visited) {
|
||||
if (ctx.arg.print_gc_sections)
|
||||
SyncOut(ctx) << "removing unused section " << *isec;
|
||||
Out(ctx) << "removing unused section " << *isec;
|
||||
isec->kill();
|
||||
counter++;
|
||||
}
|
||||
@@ -165,9 +164,7 @@ static void sweep(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void gc_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "gc");
|
||||
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset;
|
||||
collect_root_set(ctx, rootset);
|
||||
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
|
||||
mark(ctx, rootset);
|
||||
sweep(ctx);
|
||||
}
|
||||
@@ -176,4 +173,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void gc_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -58,9 +58,8 @@
|
||||
|
||||
#include "mold.h"
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
enum DwarfKind { DWARF2_32, DWARF5_32, DWARF2_64, DWARF5_64 };
|
||||
|
||||
@@ -127,21 +126,15 @@ struct SectionHeader {
|
||||
};
|
||||
|
||||
struct NameType {
|
||||
bool operator==(const NameType &) const = default;
|
||||
|
||||
bool operator<(const NameType &other) const {
|
||||
return std::tuple(hash, type, name) <
|
||||
std::tuple(other.hash, other.type, other.name);
|
||||
}
|
||||
|
||||
std::string_view name;
|
||||
u32 hash;
|
||||
auto operator<=>(const NameType &) const = default;
|
||||
u64 hash;
|
||||
u8 type;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
struct MapValue {
|
||||
u32 hash = 0;
|
||||
Atomic<u32> count = 0;
|
||||
u32 gdb_hash = 0;
|
||||
Atomic<u32> count;
|
||||
u32 name_offset = 0;
|
||||
u32 type_offset = 0;
|
||||
};
|
||||
@@ -539,7 +532,7 @@ static i64 read_pubnames_cu(Context<E> &ctx, const PubnamesHdr &hdr,
|
||||
u8 type = *p++;
|
||||
std::string_view name = (char *)p;
|
||||
p += name.size() + 1;
|
||||
cu->nametypes.push_back({name, gdb_hash(name), type});
|
||||
cu->nametypes.push_back(NameType{hash_string(name), type, name});
|
||||
}
|
||||
|
||||
return size;
|
||||
@@ -626,8 +619,8 @@ static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
|
||||
// Uniquify elements because GCC 11 seems to emit one record for each
|
||||
// comdat group which results in having a lot of duplicate records.
|
||||
tbb::parallel_for_each(cus, [&](Compunit &cu) {
|
||||
sort(cu.nametypes);
|
||||
tbb::parallel_for_each(cus, [](Compunit &cu) {
|
||||
ranges::stable_sort(cu.nametypes);
|
||||
remove_duplicates(cu.nametypes);
|
||||
});
|
||||
|
||||
@@ -636,8 +629,10 @@ static std::vector<Compunit> read_compunits(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
std::span<u8> get_buffer(Context<E> &ctx, Chunk<E> *chunk) {
|
||||
if (chunk->is_compressed)
|
||||
return chunk->uncompressed_data;
|
||||
if (chunk->is_compressed) {
|
||||
CompressedSection<E> &sec = *(CompressedSection<E> *)chunk;
|
||||
return {sec.uncompressed_data.get(), (size_t)sec.chdr.ch_size};
|
||||
}
|
||||
return {ctx.buf + chunk->shdr.sh_offset, (size_t)chunk->shdr.sh_size};
|
||||
}
|
||||
|
||||
@@ -683,7 +678,8 @@ void write_gdb_index(Context<E> &ctx) {
|
||||
for (NameType &nt : cu.nametypes) {
|
||||
MapValue *ent;
|
||||
bool inserted;
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash, MapValue{nt.hash});
|
||||
std::tie(ent, inserted) = map.insert(nt.name, nt.hash,
|
||||
MapValue{gdb_hash(nt.name)});
|
||||
ent->count++;
|
||||
cu.entries.push_back(ent);
|
||||
}
|
||||
@@ -750,7 +746,7 @@ void write_gdb_index(Context<E> &ctx) {
|
||||
ul32 *ht = (ul32 *)(buf + hdr.symtab_offset);
|
||||
|
||||
for (Entry *ent : entries) {
|
||||
u32 hash = ent->value.hash;
|
||||
u32 hash = ent->value.gdb_hash;
|
||||
u32 step = ((hash * 17) & mask) | 1;
|
||||
u32 j = hash & mask;
|
||||
|
||||
@@ -790,4 +786,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void write_gdb_index(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
+63
-56
@@ -65,7 +65,7 @@
|
||||
// conditions.
|
||||
|
||||
#include "mold.h"
|
||||
#include "blake3.h"
|
||||
#include "../lib/siphash.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
@@ -78,34 +78,43 @@
|
||||
|
||||
static constexpr int64_t HASH_SIZE = 16;
|
||||
|
||||
typedef std::array<uint8_t, HASH_SIZE> Digest;
|
||||
using Digest = std::array<uint8_t, HASH_SIZE>;
|
||||
|
||||
namespace std {
|
||||
template <> struct hash<Digest> {
|
||||
size_t operator()(const Digest &k) const {
|
||||
return *(int64_t *)&k[0];
|
||||
static_assert(sizeof(size_t) <= HASH_SIZE);
|
||||
size_t val;
|
||||
memcpy(&val, k.data(), sizeof(size_t));
|
||||
return val;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
static u8 hmac_key[16];
|
||||
|
||||
template <typename E>
|
||||
static void uniquify_cies(Context<E> &ctx) {
|
||||
Timer t(ctx, "uniquify_cies");
|
||||
std::vector<CieRecord<E> *> cies;
|
||||
|
||||
auto find = [&](CieRecord<E> &cie) -> i64 {
|
||||
for (i64 i = 0; i < cies.size(); i++)
|
||||
if (cie_equals(cie, *cies[i]))
|
||||
return i;
|
||||
return -1;
|
||||
};
|
||||
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
for (CieRecord<E> &cie : file->cies) {
|
||||
for (i64 i = 0; i < cies.size(); i++) {
|
||||
if (cie.equals(*cies[i])) {
|
||||
cie.icf_idx = i;
|
||||
goto found;
|
||||
}
|
||||
if (i64 idx = find(cie); idx != -1) {
|
||||
cie.icf_idx = idx;
|
||||
} else {
|
||||
cie.icf_idx = cies.size();
|
||||
cies.push_back(&cie);
|
||||
}
|
||||
cie.icf_idx = cies.size();
|
||||
cies.push_back(&cie);
|
||||
found:;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,26 +128,14 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
|
||||
shdr.sh_type == SHT_NOBITS || is_c_identifier(name))
|
||||
return false;
|
||||
|
||||
if (shdr.sh_flags & SHF_EXECINSTR) {
|
||||
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(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);
|
||||
return digest;
|
||||
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
|
||||
bool is_relro = isec.name().starts_with(".data.rel.ro");
|
||||
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
|
||||
(is_readonly || is_relro);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -195,7 +192,7 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
static Counter non_eligible("icf_non_eligibles");
|
||||
static Counter leaf("icf_leaf_nodes");
|
||||
|
||||
tbb::concurrent_unordered_map<InputSection<E> *, InputSection<E> *,
|
||||
tbb::concurrent_unordered_map<InputSection<E> *, Atomic<InputSection<E> *>,
|
||||
LeafHasher<E>, LeafEq<E>> map;
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
|
||||
@@ -212,8 +209,11 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
leaf++;
|
||||
isec->icf_leaf = true;
|
||||
auto [it, inserted] = map.insert({isec.get(), isec.get()});
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec.get();
|
||||
if (!inserted) {
|
||||
InputSection<E> *isec2 = it->second.load();
|
||||
while (isec->get_priority() < isec2->get_priority() &&
|
||||
!it->second.compare_exchange_strong(isec2, isec.get()));
|
||||
}
|
||||
} else {
|
||||
eligible++;
|
||||
isec->icf_eligible = true;
|
||||
@@ -234,16 +234,15 @@ static void merge_leaf_nodes(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
SipHash13_128 hasher(hmac_key);
|
||||
|
||||
auto hash = [&](auto val) {
|
||||
blake3_hasher_update(&hasher, (u8 *)&val, sizeof(val));
|
||||
hasher.update((u8 *)&val, sizeof(val));
|
||||
};
|
||||
|
||||
auto hash_string = [&](std::string_view str) {
|
||||
hash(str.size());
|
||||
blake3_hasher_update(&hasher, (u8 *)str.data(), str.size());
|
||||
hasher.update((u8 *)str.data(), str.size());
|
||||
};
|
||||
|
||||
auto hash_symbol = [&](Symbol<E> &sym) {
|
||||
@@ -299,7 +298,9 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
|
||||
hash_symbol(*isec.file.symbols[rel.r_sym]);
|
||||
}
|
||||
|
||||
return digest_final(&hasher);
|
||||
Digest digest;
|
||||
hasher.finish(digest.data());
|
||||
return digest;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -400,7 +401,7 @@ static void gather_edges(Context<E> &ctx,
|
||||
template <typename E>
|
||||
static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
std::span<u32> edges, std::span<u32> edge_indices,
|
||||
bool &slot, BitVector &converged,
|
||||
bool &slot, std::span<u8> converged,
|
||||
tbb::affinity_partitioner &ap) {
|
||||
static Counter round("icf_round");
|
||||
round++;
|
||||
@@ -409,25 +410,24 @@ static i64 propagate(std::span<std::vector<Digest>> digests,
|
||||
tbb::enumerable_thread_specific<i64> changed;
|
||||
|
||||
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
|
||||
if (converged.get(i))
|
||||
if (converged[i])
|
||||
return;
|
||||
|
||||
blake3_hasher hasher;
|
||||
blake3_hasher_init(&hasher);
|
||||
blake3_hasher_update(&hasher, digests[2][i].data(), HASH_SIZE);
|
||||
SipHash13_128 hasher(hmac_key);
|
||||
hasher.update(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))
|
||||
blake3_hasher_update(&hasher, digests[slot][j].data(), HASH_SIZE);
|
||||
hasher.update(digests[slot][j].data(), HASH_SIZE);
|
||||
|
||||
digests[!slot][i] = digest_final(&hasher);
|
||||
hasher.finish(digests[!slot][i].data());
|
||||
|
||||
if (digests[slot][i] == digests[!slot][i]) {
|
||||
// This node has converged. Skip further iterations as it will
|
||||
// yield the same hash.
|
||||
converged.set(i);
|
||||
converged[i] = true;
|
||||
} else {
|
||||
changed.local()++;
|
||||
}
|
||||
@@ -468,7 +468,7 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
});
|
||||
|
||||
tbb::parallel_sort(leaders.begin(), leaders.end(),
|
||||
[&](InputSection<E> *a, InputSection<E> *b) {
|
||||
[](InputSection<E> *a, InputSection<E> *b) {
|
||||
return a->get_priority() < b->get_priority();
|
||||
});
|
||||
|
||||
@@ -479,17 +479,17 @@ static void print_icf_sections(Context<E> &ctx) {
|
||||
if (begin == end)
|
||||
continue;
|
||||
|
||||
SyncOut(ctx) << "selected section " << *leader;
|
||||
Out(ctx) << "selected section " << *leader;
|
||||
|
||||
i64 n = 0;
|
||||
for (auto it = begin; it != end; it++) {
|
||||
SyncOut(ctx) << " removing identical section " << *it->second;
|
||||
Out(ctx) << " removing identical section " << *it->second;
|
||||
n++;
|
||||
}
|
||||
saved_bytes += leader->contents.size() * n;
|
||||
}
|
||||
|
||||
SyncOut(ctx) << "ICF saved " << saved_bytes << " bytes";
|
||||
Out(ctx) << "ICF saved " << saved_bytes << " bytes";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -498,6 +498,8 @@ void icf_sections(Context<E> &ctx) {
|
||||
if (ctx.objs.empty())
|
||||
return;
|
||||
|
||||
get_random_bytes(hmac_key, sizeof(hmac_key));
|
||||
|
||||
uniquify_cies(ctx);
|
||||
merge_leaf_nodes(ctx);
|
||||
|
||||
@@ -522,7 +524,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
std::vector<u32> edge_indices;
|
||||
gather_edges<E>(ctx, sections, edges, edge_indices);
|
||||
|
||||
BitVector converged(digests[0].size());
|
||||
std::vector<u8> converged(digests[0].size());
|
||||
bool slot = 0;
|
||||
|
||||
// Execute the propagation rounds until convergence is obtained.
|
||||
@@ -565,18 +567,23 @@ void icf_sections(Context<E> &ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Group sections by BLAKE3 digest.
|
||||
// Group sections by hash values.
|
||||
{
|
||||
Timer t(ctx, "group");
|
||||
|
||||
auto *map = new tbb::concurrent_unordered_map<Digest, InputSection<E> *>;
|
||||
auto *map =
|
||||
new tbb::concurrent_unordered_map<Digest, Atomic<InputSection<E> *>>;
|
||||
|
||||
std::span<Digest> digest = digests[slot];
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
InputSection<E> *isec = sections[i];
|
||||
auto [it, inserted] = map->insert({digest[i], isec});
|
||||
if (!inserted && isec->get_priority() < it->second->get_priority())
|
||||
it->second = isec;
|
||||
if (!inserted) {
|
||||
InputSection<E> *isec2 = it->second.load();
|
||||
while (isec->get_priority() < isec2->get_priority() &&
|
||||
!it->second.compare_exchange_strong(isec2, isec));
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
|
||||
@@ -600,7 +607,7 @@ void icf_sections(Context<E> &ctx) {
|
||||
static Counter eliminated("icf_eliminated");
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
|
||||
if (isec && isec->is_alive && isec->is_killed_by_icf()) {
|
||||
if (isec && isec->is_alive && isec->icf_removed()) {
|
||||
isec->kill();
|
||||
eliminated++;
|
||||
}
|
||||
@@ -613,4 +620,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void icf_sections(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,449 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static i64 to_p2align(u64 alignment) {
|
||||
if (alignment == 0)
|
||||
return 0;
|
||||
return std::countr_zero(alignment);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool cie_equals(const CieRecord<E> &a, const CieRecord<E> &b) {
|
||||
if (a.get_contents() != b.get_contents())
|
||||
return false;
|
||||
|
||||
std::span<const ElfRel<E>> x = a.get_rels();
|
||||
std::span<const ElfRel<E>> y = b.get_rels();
|
||||
if (x.size() != y.size())
|
||||
return false;
|
||||
|
||||
for (i64 i = 0; i < x.size(); i++)
|
||||
if (x[i].r_offset - a.input_offset != y[i].r_offset - b.input_offset ||
|
||||
x[i].r_type != y[i].r_type ||
|
||||
a.file.symbols[x[i].r_sym] != b.file.symbols[y[i].r_sym] ||
|
||||
get_addend(a.input_section, x[i]) != get_addend(b.input_section, y[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
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};
|
||||
|
||||
if (shdr().sh_flags & SHF_COMPRESSED) {
|
||||
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
|
||||
sh_size = chdr.ch_size;
|
||||
p2align = to_p2align(chdr.ch_addralign);
|
||||
} else {
|
||||
sh_size = shdr().sh_size;
|
||||
p2align = to_p2align(shdr().sh_addralign);
|
||||
}
|
||||
|
||||
// Sections may have been compressed. We usually uncompress them
|
||||
// directly into the mmap'ed output file, but we want to uncompress
|
||||
// early for REL-type ELF types to read relocation addends from
|
||||
// section contents. For RELA-type, we don't need to do this because
|
||||
// addends are in relocations.
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::uncompress(Context<E> &ctx) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
|
||||
return;
|
||||
|
||||
u8 *buf = new u8[sh_size];
|
||||
copy_contents_to(ctx, buf, sh_size);
|
||||
contents = std::string_view((char *)buf, sh_size);
|
||||
ctx.string_pool.emplace_back(buf);
|
||||
uncompressed = true;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
|
||||
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
|
||||
memcpy(buf, contents.data(), sz);
|
||||
return;
|
||||
}
|
||||
|
||||
if (contents.size() < sizeof(ElfChdr<E>))
|
||||
Fatal(ctx) << *this << ": corrupted compressed section";
|
||||
|
||||
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
|
||||
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
|
||||
|
||||
switch (hdr.ch_type) {
|
||||
case ELFCOMPRESS_ZLIB: {
|
||||
z_stream s = {};
|
||||
inflateInit(&s);
|
||||
s.next_in = (u8 *)data.data();
|
||||
s.avail_in = data.size();
|
||||
s.next_out = buf;
|
||||
s.avail_out = sz;
|
||||
|
||||
int r;
|
||||
while (s.total_out < sz && (r = inflate(&s, Z_NO_FLUSH)) == Z_OK);
|
||||
if (s.total_out < sz && r != Z_STREAM_END)
|
||||
Fatal(ctx) << *this << ": uncompress failed: " << s.msg;
|
||||
inflateEnd(&s);
|
||||
break;
|
||||
}
|
||||
case ELFCOMPRESS_ZSTD: {
|
||||
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||
ZSTD_inBuffer in = { data.data(), data.size() };
|
||||
ZSTD_outBuffer out = { buf, (size_t)sz };
|
||||
|
||||
while (out.pos < out.size) {
|
||||
size_t r = ZSTD_decompressStream(dctx, &out, &in);
|
||||
if (ZSTD_isError(r))
|
||||
Fatal(ctx) << *this << ": uncompress failed: " << ZSTD_getErrorName(r);
|
||||
if (r == 0 && out.pos < out.size)
|
||||
Fatal(ctx) << *this << ": uncompress failed: premature end of input";
|
||||
}
|
||||
ZSTD_freeDCtx(dctx);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Fatal(ctx) << *this << ": unsupported compression type: 0x"
|
||||
<< std::hex << hdr.ch_type;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum : u8 { NONE, ERROR, COPYREL, PLT, CPLT } Action;
|
||||
|
||||
template <typename E>
|
||||
static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
|
||||
Symbol<E> &sym, const ElfRel<E> &rel) {
|
||||
switch (action) {
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR:
|
||||
Error(ctx) << isec << ": " << rel << " relocation at offset 0x"
|
||||
<< std::hex << rel.r_offset << " against symbol `"
|
||||
<< sym << "' can not be used; recompile with -fPIC";
|
||||
break;
|
||||
case COPYREL:
|
||||
sym.flags |= NEEDS_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;
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
// This is for PC-relative relocations (e.g. R_X86_64_PC32).
|
||||
// We cannot promote them to dynamic relocations because the dynamic
|
||||
// linker generally does not support PC-relative relocations.
|
||||
static Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, PLT }, // Shared object
|
||||
{ ERROR, NONE, COPYREL, CPLT }, // Position-independent exec
|
||||
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
|
||||
};
|
||||
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
do_action(ctx, action, *this, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
|
||||
const ElfRel<E> &rel) {
|
||||
// This is a decision table for absolute relocations that is smaller
|
||||
// than the pointer size (e.g. R_X86_64_32). Since the dynamic linker
|
||||
// generally does not support dynamic relocations smaller than the
|
||||
// pointer size, we need to report an error if a relocation cannot be
|
||||
// resolved at link-time.
|
||||
static Action table[][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
|
||||
};
|
||||
|
||||
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
|
||||
do_action(ctx, action, *this, sym, rel);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void InputSection<E>::scan_tlsdesc(Context<E> &ctx, Symbol<E> &sym) {
|
||||
if (ctx.arg.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>
|
||||
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
|
||||
if (shdr().sh_type == SHT_NOBITS || sh_size == 0)
|
||||
return;
|
||||
|
||||
// Copy data. In RISC-V and LoongArch object files, sections are not
|
||||
// atomic unit of copying because of relaxation. That is, some
|
||||
// relocations are allowed to remove bytes from the middle of a
|
||||
// section and shrink the overall size of it.
|
||||
if constexpr (is_riscv<E> || is_loongarch<E>) {
|
||||
std::span<RelocDelta> deltas = extra.r_deltas;
|
||||
|
||||
if (deltas.empty()) {
|
||||
// If a section is not relaxed, we can copy it as a one big chunk.
|
||||
copy_contents_to(ctx, buf, sh_size);
|
||||
} else {
|
||||
// A relaxed section is copied piece-wise.
|
||||
memcpy(buf, contents.data(), deltas[0].offset);
|
||||
|
||||
for (i64 i = 0; i < deltas.size(); i++) {
|
||||
i64 offset = deltas[i].offset;
|
||||
i64 delta = deltas[i].delta;
|
||||
i64 end = (i + 1 == deltas.size()) ? contents.size() : deltas[i + 1].offset;
|
||||
i64 removed_bytes = get_removed_bytes(deltas, i);
|
||||
memcpy(buf + offset + removed_bytes - delta,
|
||||
contents.data() + offset + removed_bytes,
|
||||
end - offset - removed_bytes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy_contents_to(ctx, buf, sh_size);
|
||||
}
|
||||
|
||||
// Apply relocations
|
||||
if (!ctx.arg.relocatable) {
|
||||
if (shdr().sh_flags & SHF_ALLOC)
|
||||
apply_reloc_alloc(ctx, buf);
|
||||
else
|
||||
apply_reloc_nonalloc(ctx, buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the name of a function containin a given offset.
|
||||
template <typename E>
|
||||
std::string_view
|
||||
InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
|
||||
for (Symbol<E> *sym : file.symbols)
|
||||
if (sym->file == &file)
|
||||
if (const ElfSym<E> &esym = sym->esym();
|
||||
esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
|
||||
esym.st_value <= offset && offset < esym.st_value + esym.st_size)
|
||||
return ctx.arg.demangle ? demangle(*sym) : sym->name();
|
||||
return "";
|
||||
}
|
||||
|
||||
// Test if the symbol a given relocation refers to has already been resolved.
|
||||
// If not, record that error and returns true.
|
||||
template <typename E>
|
||||
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];
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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 << ")";
|
||||
ss << '\n';
|
||||
|
||||
typename decltype(ctx.undef_errors)::accessor acc;
|
||||
ctx.undef_errors.insert(acc, {&sym, {}});
|
||||
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, we need to report an
|
||||
// error or warn on it.
|
||||
//
|
||||
// Every ELF file has an absolute local symbol as its first symbol.
|
||||
// Referring to that symbol is always valid.
|
||||
bool is_undef = esym.is_undef() && !esym.is_weak() && sym.sym_idx;
|
||||
|
||||
if (is_undef && sym.esym().is_undef()) {
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR && !sym.is_imported) {
|
||||
record();
|
||||
return true;
|
||||
}
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_WARN) {
|
||||
record();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
|
||||
std::unique_ptr<InputSection<E>> &isec)
|
||||
: parent(parent), p2align(isec->p2align), input_section(std::move(isec)) {
|
||||
input_section->uncompress(ctx);
|
||||
|
||||
std::scoped_lock lock(parent.mu);
|
||||
parent.members.push_back(this);
|
||||
}
|
||||
|
||||
static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
|
||||
if (entsize == 1)
|
||||
return data.find('\0', pos);
|
||||
|
||||
for (; pos <= data.size() - entsize; pos += entsize)
|
||||
if (data.substr(pos, entsize).find_first_not_of('\0') == data.npos)
|
||||
return pos;
|
||||
|
||||
return data.npos;
|
||||
}
|
||||
|
||||
// Mergeable sections (sections with SHF_MERGE bit) typically contain
|
||||
// string literals. Linker is expected to split the section contents
|
||||
// into null-terminated strings, merge them with mergeable strings
|
||||
// from other object files, and emit uniquified strings to an output
|
||||
// file.
|
||||
//
|
||||
// This mechanism reduces the size of an output file. If two source
|
||||
// files happen to contain the same string literal, the output will
|
||||
// contain only a single copy of it.
|
||||
//
|
||||
// It is less common than string literals, but mergeable sections can
|
||||
// contain fixed-sized read-only records too.
|
||||
//
|
||||
// This function splits the section contents into small pieces that we
|
||||
// call "section fragments". Section fragment is a unit of merging.
|
||||
//
|
||||
// We do not support mergeable sections that have relocations.
|
||||
template <typename E>
|
||||
void MergeableSection<E>::split_contents(Context<E> &ctx) {
|
||||
std::string_view data = input_section->contents;
|
||||
if (data.size() > UINT32_MAX)
|
||||
Fatal(ctx) << *input_section << ": mergeable section too large";
|
||||
|
||||
i64 entsize = parent.shdr.sh_entsize;
|
||||
|
||||
// Split sections
|
||||
if (parent.shdr.sh_flags & SHF_STRINGS) {
|
||||
for (i64 pos = 0; pos < data.size();) {
|
||||
frag_offsets.push_back(pos);
|
||||
size_t end = find_null(data, pos, entsize);
|
||||
if (end == data.npos)
|
||||
Fatal(ctx) << *input_section << ": string is not null terminated";
|
||||
pos = end + entsize;
|
||||
}
|
||||
} else {
|
||||
if (data.size() % entsize)
|
||||
Fatal(ctx) << *input_section
|
||||
<< ": section size is not multiple of sh_entsize";
|
||||
frag_offsets.reserve(data.size() / entsize);
|
||||
|
||||
for (i64 pos = 0; pos < data.size(); pos += entsize)
|
||||
frag_offsets.push_back(pos);
|
||||
}
|
||||
|
||||
// Compute hashes for section pieces
|
||||
HyperLogLog estimator;
|
||||
hashes.reserve(frag_offsets.size());
|
||||
|
||||
for (i64 i = 0; i < frag_offsets.size(); i++) {
|
||||
u64 hash = hash_string(get_contents(i));
|
||||
hashes.push_back(hash);
|
||||
estimator.insert(hash);
|
||||
}
|
||||
|
||||
parent.estimator.merge(estimator);
|
||||
|
||||
static Counter counter("string_fragments");
|
||||
counter += frag_offsets.size();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void MergeableSection<E>::resolve_contents(Context<E> &ctx) {
|
||||
fragments.reserve(frag_offsets.size());
|
||||
for (i64 i = 0; i < frag_offsets.size(); i++)
|
||||
fragments.push_back(parent.insert(ctx, get_contents(i), hashes[i], p2align));
|
||||
|
||||
// Reclaim memory as we'll never use this vector again
|
||||
hashes.clear();
|
||||
hashes.shrink_to_fit();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template bool cie_equals(const CieRecord<E> &, const CieRecord<E> &);
|
||||
template class InputSection<E>;
|
||||
template class MergeableSection<E>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -11,19 +11,18 @@
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <fcntl.h>
|
||||
# include <pwd.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
void acquire_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
static int lock_fd = -1;
|
||||
|
||||
void acquire_global_lock() {
|
||||
char *jobs = getenv("MOLD_JOBS");
|
||||
if (!jobs || jobs != "1"s)
|
||||
return;
|
||||
@@ -40,22 +39,12 @@ void acquire_global_lock(Context<E> &ctx) {
|
||||
|
||||
if (lockf(fd, F_LOCK, 0) == -1)
|
||||
return;
|
||||
|
||||
ctx.global_lock_fd = fd;
|
||||
#endif
|
||||
lock_fd = fd;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void release_global_lock(Context<E> &ctx) {
|
||||
#ifndef _WIN32
|
||||
if (ctx.global_lock_fd)
|
||||
close(*ctx.global_lock_fd);
|
||||
#endif
|
||||
void release_global_lock() {
|
||||
if (lock_fd != -1)
|
||||
close(lock_fd);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void acquire_global_lock(Context<E> &);
|
||||
template void release_global_lock(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mold {
|
||||
|
||||
void acquire_global_lock() {}
|
||||
void release_global_lock() {}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,422 @@
|
||||
// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually
|
||||
// a shared object file but an ASCII text file containing a linker
|
||||
// script to include a "real" libc.so file. Therefore, we need to
|
||||
// support a (very limited) subset of the linker script language.
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static std::string_view get_line(std::string_view input, const char *pos) {
|
||||
assert(input.data() <= pos);
|
||||
assert(pos < input.data() + input.size());
|
||||
|
||||
i64 start = input.rfind('\n', pos - input.data());
|
||||
if (start == input.npos)
|
||||
start = 0;
|
||||
else
|
||||
start++;
|
||||
|
||||
i64 end = input.find('\n', pos - input.data());
|
||||
if (end == input.npos)
|
||||
end = input.size();
|
||||
|
||||
return input.substr(start, end - start);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void Script<E>::error(std::string_view pos, std::string msg) {
|
||||
std::string_view input = mf->get_contents();
|
||||
std::string_view line = get_line(input, pos.data());
|
||||
|
||||
i64 lineno = 1;
|
||||
for (i64 i = 0; input.data() + i < line.data(); i++)
|
||||
if (input[i] == '\n')
|
||||
lineno++;
|
||||
|
||||
std::string label = mf->name + ":" + std::to_string(lineno) + ": ";
|
||||
i64 indent = strlen("mold: fatal: ") + label.size();
|
||||
i64 column = pos.data() - line.data();
|
||||
|
||||
Fatal(ctx) << label << line << "\n"
|
||||
<< std::string(indent + column, ' ') << "^ " << msg;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void Script<E>::tokenize() {
|
||||
std::string_view input = mf->get_contents();
|
||||
|
||||
while (!input.empty()) {
|
||||
if (isspace(input[0])) {
|
||||
input = input.substr(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input.starts_with("/*")) {
|
||||
i64 pos = input.find("*/", 2);
|
||||
if (pos == std::string_view::npos)
|
||||
error(input, "unclosed comment");
|
||||
input = input.substr(pos + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[0] == '#') {
|
||||
i64 pos = input.find("\n", 1);
|
||||
if (pos == std::string_view::npos)
|
||||
break;
|
||||
input = input.substr(pos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[0] == '"') {
|
||||
i64 pos = input.find('"', 1);
|
||||
if (pos == std::string_view::npos)
|
||||
error(input, "unclosed string literal");
|
||||
tokens.push_back(input.substr(0, pos + 1));
|
||||
input = input.substr(pos + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
i64 pos = input.find_first_not_of(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789_.$/\\~=+[]*?-!^:");
|
||||
|
||||
if (pos == 0)
|
||||
pos = 1;
|
||||
else if (pos == input.npos)
|
||||
pos = input.size();
|
||||
|
||||
tokens.push_back(input.substr(0, pos));
|
||||
input = input.substr(pos);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<std::string_view>
|
||||
Script<E>::skip(std::span<std::string_view> tok, std::string_view str) {
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << mf->name << ": expected '" << str << "', but got EOF";
|
||||
if (tok[0] != str)
|
||||
error(tok[0], "expected '" + std::string(str) + "'");
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
static std::string_view unquote(std::string_view s) {
|
||||
if (s.size() > 0 && s[0] == '"') {
|
||||
assert(s[s.size() - 1] == '"');
|
||||
return s.substr(1, s.size() - 2);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<std::string_view>
|
||||
Script<E>::read_output_format(std::span<std::string_view> tok) {
|
||||
tok = skip(tok, "(");
|
||||
while (!tok.empty() && tok[0] != ")")
|
||||
tok = tok.subspan(1);
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << mf->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
|
||||
std::string rel = std::filesystem::relative(path, ctx.arg.sysroot).string();
|
||||
return rel != "." && !rel.starts_with("../");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile *Script<E>::resolve_path(std::string_view tok, bool check_target) {
|
||||
std::string str(unquote(tok));
|
||||
|
||||
auto open = [&](const std::string &path) -> MappedFile * {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
if (check_target) {
|
||||
std::string_view target = get_machine_type(ctx, rctx, mf);
|
||||
if (!target.empty() && target != E::name) {
|
||||
Warn(ctx) << path << ": skipping incompatible file: " << target
|
||||
<< " (e_machine " << (int)E::e_machine << ")";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return mf;
|
||||
};
|
||||
|
||||
// GNU ld prepends the sysroot if a pathname starts with '/' and the
|
||||
// script being processed is in the sysroot. We do the same.
|
||||
if (str.starts_with('/') && is_in_sysroot(ctx, mf->name))
|
||||
return must_open_file(ctx, ctx.arg.sysroot + str);
|
||||
|
||||
if (str.starts_with('=')) {
|
||||
std::string path;
|
||||
if (ctx.arg.sysroot.empty())
|
||||
path = str.substr(1);
|
||||
else
|
||||
path = ctx.arg.sysroot + str.substr(1);
|
||||
return must_open_file(ctx, path);
|
||||
}
|
||||
|
||||
if (str.starts_with("-l"))
|
||||
return find_library(ctx, rctx, str.substr(2));
|
||||
|
||||
if (!str.starts_with('/'))
|
||||
if (MappedFile *mf2 = open(path_clean(mf->name + "/../" + str)))
|
||||
return mf2;
|
||||
|
||||
if (MappedFile *mf = open(str))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + str;
|
||||
if (MappedFile *mf = open(path))
|
||||
return mf;
|
||||
}
|
||||
|
||||
error(tok, "library not found: " + str);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<std::string_view>
|
||||
Script<E>::read_group(std::span<std::string_view> tok) {
|
||||
tok = skip(tok, "(");
|
||||
|
||||
while (!tok.empty() && tok[0] != ")") {
|
||||
if (tok[0] == "AS_NEEDED") {
|
||||
bool orig = rctx.as_needed;
|
||||
rctx.as_needed = true;
|
||||
tok = read_group(tok.subspan(1));
|
||||
rctx.as_needed = orig;
|
||||
continue;
|
||||
}
|
||||
|
||||
MappedFile *mf = resolve_path(tok[0], true);
|
||||
read_file(ctx, rctx, mf);
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
if (tok.empty())
|
||||
Fatal(ctx) << mf->name << ": expected ')', but got EOF";
|
||||
return tok.subspan(1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void Script<E>::parse_linker_script() {
|
||||
std::call_once(once, [&] { tokenize(); });
|
||||
std::span<std::string_view> tok = tokens;
|
||||
|
||||
while (!tok.empty()) {
|
||||
if (tok[0] == "OUTPUT_FORMAT") {
|
||||
tok = read_output_format(tok.subspan(1));
|
||||
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
|
||||
tok = read_group(tok.subspan(1));
|
||||
} else if (tok[0] == "VERSION") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(tok, "{");
|
||||
tok = read_version_script(tok);
|
||||
tok = skip(tok, "}");
|
||||
} else if (tok.size() > 3 && tok[1] == "=" && tok[3] == ";") {
|
||||
ctx.arg.defsyms.emplace_back(get_symbol(ctx, unquote(tok[0])),
|
||||
get_symbol(ctx, unquote(tok[2])));
|
||||
tok = tok.subspan(4);
|
||||
} else if (tok[0] == ";") {
|
||||
tok = tok.subspan(1);
|
||||
} else {
|
||||
error(tok[0], "unknown linker script token");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string_view Script<E>::get_script_output_type() {
|
||||
std::call_once(once, [&] { tokenize(); });
|
||||
std::span<std::string_view> tok = tokens;
|
||||
|
||||
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
|
||||
if (tok[2] == "elf64-x86-64")
|
||||
return X86_64::name;
|
||||
if (tok[2] == "elf32-i386")
|
||||
return I386::name;
|
||||
}
|
||||
|
||||
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
|
||||
tok[1] == "(")
|
||||
if (MappedFile *mf = resolve_path(tok[2], false))
|
||||
return get_machine_type(ctx, rctx, mf);
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool read_label(std::span<std::string_view> &tok, std::string label) {
|
||||
if (tok.size() >= 1 && tok[0] == label + ":") {
|
||||
tok = tok.subspan(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") {
|
||||
tok = tok.subspan(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<std::string_view>
|
||||
Script<E>::read_version_script_commands(std::span<std::string_view> tok,
|
||||
std::string_view ver_str, u16 ver_idx,
|
||||
bool is_global, bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (read_label(tok, "global")) {
|
||||
is_global = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read_label(tok, "local")) {
|
||||
is_global = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(tok, "{");
|
||||
tok = read_version_script_commands(tok, ver_str, ver_idx, is_global, false);
|
||||
} else {
|
||||
tok = skip(tok, "\"C++\"");
|
||||
tok = skip(tok, "{");
|
||||
tok = read_version_script_commands(tok, ver_str, ver_idx, is_global, true);
|
||||
}
|
||||
|
||||
tok = skip(tok, "}");
|
||||
tok = skip(tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok[0] == "*") {
|
||||
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
|
||||
} else if (is_global) {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), mf->name, ver_str,
|
||||
ver_idx, is_cpp});
|
||||
} else {
|
||||
ctx.version_patterns.push_back({unquote(tok[0]), mf->name, ver_str,
|
||||
VER_NDX_LOCAL, is_cpp});
|
||||
}
|
||||
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "}")
|
||||
break;
|
||||
tok = skip(tok, ";");
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<std::string_view>
|
||||
Script<E>::read_version_script(std::span<std::string_view> tok) {
|
||||
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
|
||||
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
std::string_view ver_str;
|
||||
u16 ver_idx;
|
||||
|
||||
if (tok[0] == "{") {
|
||||
ver_str = "global";
|
||||
ver_idx = VER_NDX_GLOBAL;
|
||||
} else {
|
||||
ver_str = tok[0];
|
||||
ver_idx = next_ver++;
|
||||
ctx.arg.version_definitions.emplace_back(tok[0]);
|
||||
tok = tok.subspan(1);
|
||||
}
|
||||
|
||||
tok = skip(tok, "{");
|
||||
tok = read_version_script_commands(tok, ver_str, ver_idx, true, false);
|
||||
tok = skip(tok, "}");
|
||||
if (!tok.empty() && tok[0] != ";")
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(tok, ";");
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void Script<E>::parse_version_script() {
|
||||
std::call_once(once, [&] { tokenize(); });
|
||||
std::span<std::string_view> tok = tokens;
|
||||
tok = read_version_script(tok);
|
||||
if (!tok.empty())
|
||||
error(tok[0], "trailing garbage token");
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::span<std::string_view>
|
||||
Script<E>::read_dynamic_list_commands(std::span<std::string_view> tok,
|
||||
std::vector<DynamicPattern> &result,
|
||||
bool is_cpp) {
|
||||
while (!tok.empty() && tok[0] != "}") {
|
||||
if (tok[0] == "extern") {
|
||||
tok = tok.subspan(1);
|
||||
|
||||
if (!tok.empty() && tok[0] == "\"C\"") {
|
||||
tok = tok.subspan(1);
|
||||
tok = skip(tok, "{");
|
||||
tok = read_dynamic_list_commands(tok, result, false);
|
||||
} else {
|
||||
tok = skip(tok, "\"C++\"");
|
||||
tok = skip(tok, "{");
|
||||
tok = read_dynamic_list_commands(tok, result, true);
|
||||
}
|
||||
|
||||
tok = skip(tok, "}");
|
||||
tok = skip(tok, ";");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back({unquote(tok[0]), "", is_cpp});
|
||||
tok = skip(tok.subspan(1), ";");
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<DynamicPattern> Script<E>::parse_dynamic_list() {
|
||||
std::call_once(once, [&] { tokenize(); });
|
||||
std::span<std::string_view> tok = tokens;
|
||||
std::vector<DynamicPattern> result;
|
||||
|
||||
tok = skip(tok, "{");
|
||||
tok = read_dynamic_list_commands(tok, result, false);
|
||||
tok = skip(tok, "}");
|
||||
tok = skip(tok, ";");
|
||||
|
||||
if (!tok.empty())
|
||||
error(tok[0], "trailing garbage token");
|
||||
|
||||
for (DynamicPattern &p : result)
|
||||
p.source = mf->name;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<DynamicPattern>
|
||||
parse_dynamic_list(Context<E> &ctx, std::string_view path) {
|
||||
ReaderContext rctx;
|
||||
MappedFile *mf = must_open_file(ctx, std::string(path));
|
||||
return Script(ctx, rctx, mf).parse_dynamic_list();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class Script<E>;
|
||||
|
||||
template
|
||||
std::vector<DynamicPattern> parse_dynamic_list(Context<E> &, std::string_view);
|
||||
|
||||
} // namespace mold
|
||||
+155
-139
@@ -29,8 +29,8 @@
|
||||
//
|
||||
// 4. Continue the rest of the linking process as usual.
|
||||
//
|
||||
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
|
||||
// the linker. The given .so file provides a way to call the compiler
|
||||
// When gcc or clang inovkes ld, they pass `-plugin /path/to/linker-plugin.so`
|
||||
// to the linker. The given .so file provides a way to call the compiler
|
||||
// backend.
|
||||
//
|
||||
// The linker plugin API is documented at
|
||||
@@ -95,7 +95,7 @@
|
||||
# define LOG std::ostringstream()
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
// Global variables
|
||||
// We store LTO-related information to global variables,
|
||||
@@ -126,7 +126,7 @@ static PluginStatus message(PluginLevel level, const char *fmt, ...) {
|
||||
|
||||
switch (level) {
|
||||
case LDPL_INFO:
|
||||
SyncOut(ctx) << buf;
|
||||
Out(ctx) << buf;
|
||||
break;
|
||||
case LDPL_WARNING:
|
||||
Warn(ctx) << buf;
|
||||
@@ -176,14 +176,15 @@ static PluginStatus add_input_file(const char *path) {
|
||||
Context<E> &ctx = *gctx<E>;
|
||||
static i64 file_priority = 100;
|
||||
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::must_open(ctx, path);
|
||||
MappedFile *mf = must_open_file(ctx, path);
|
||||
mf->is_dependency = false;
|
||||
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, "", false);
|
||||
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, "");
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
lto_objects<E>.push_back(file);
|
||||
|
||||
file->priority = file_priority++;
|
||||
file->is_alive = true;
|
||||
file->is_reachable = true;
|
||||
file->parse(ctx);
|
||||
file->resolve_symbols(ctx);
|
||||
return LDPS_OK;
|
||||
@@ -200,10 +201,7 @@ static PluginStatus release_input_file(const void *handle) {
|
||||
LOG << "release_input_file\n";
|
||||
|
||||
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
|
||||
if (file.mf->fd != -1) {
|
||||
close(file.mf->fd);
|
||||
file.mf->fd = -1;
|
||||
}
|
||||
file.mf->close_fd();
|
||||
return LDPS_OK;
|
||||
}
|
||||
|
||||
@@ -287,7 +285,7 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
|
||||
// If file is an archive member which was not chose to be included in
|
||||
// to the final result, we need to make the plugin to ignore all
|
||||
// symbols.
|
||||
if (!file.is_alive) {
|
||||
if (!file.is_reachable) {
|
||||
assert(!is_v2);
|
||||
for (int i = 0; i < nsyms; i++)
|
||||
psyms[i].resolution = LDPR_PREEMPTED_REG;
|
||||
@@ -345,7 +343,7 @@ static void restart_process(Context<E> &ctx) {
|
||||
args.push_back(strdup(std::string(arg).c_str()));
|
||||
|
||||
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
|
||||
if (file->is_lto_obj && !file->is_alive)
|
||||
if (file->is_lto_obj && !file->is_reachable)
|
||||
args.push_back(strdup(("--:ignore-ir-file=" +
|
||||
file->mf->get_identifier()).c_str()));
|
||||
|
||||
@@ -439,72 +437,77 @@ get_api_version(const char *plugin_identifier,
|
||||
return LAPI_V0;
|
||||
}
|
||||
|
||||
// dlopen the linker plugin file
|
||||
template <typename E>
|
||||
static void load_plugin(Context<E> &ctx) {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
static void load_lto_plugin(Context<E> &ctx) {
|
||||
static std::once_flag flag;
|
||||
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
std::call_once(flag, [&] {
|
||||
assert(phase == 0);
|
||||
phase = 1;
|
||||
gctx<E> = &ctx;
|
||||
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle)
|
||||
Fatal(ctx) << "could not open plugin file: " << dlerror();
|
||||
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
|
||||
if (!onload)
|
||||
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
|
||||
<< dlerror();
|
||||
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
auto save = [&](std::string_view str) {
|
||||
return save_string(ctx, std::string(str).c_str()).data();
|
||||
};
|
||||
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
std::vector<PluginTagValue> tv;
|
||||
tv.emplace_back(LDPT_MESSAGE, message<E>);
|
||||
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
if (ctx.arg.shared)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
|
||||
else if (ctx.arg.pie)
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
|
||||
else
|
||||
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
|
||||
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
for (std::string_view opt : ctx.arg.plugin_opt)
|
||||
tv.emplace_back(LDPT_OPTION, save(opt));
|
||||
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
|
||||
register_all_symbols_read_hook<E>);
|
||||
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
|
||||
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
|
||||
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
|
||||
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
|
||||
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
|
||||
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
|
||||
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
|
||||
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
|
||||
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
|
||||
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
|
||||
allow_unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
|
||||
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
|
||||
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
|
||||
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
|
||||
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
|
||||
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
|
||||
tv.emplace_back(LDPT_NULL, 0);
|
||||
|
||||
[[maybe_unused]] PluginStatus status = onload(tv.data());
|
||||
assert(status == LDPS_OK);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
@@ -533,8 +536,6 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
}
|
||||
|
||||
switch (psym.symbol_type) {
|
||||
case LDST_UNKNOWN:
|
||||
break;
|
||||
case LDST_FUNCTION:
|
||||
esym.st_type = STT_FUNC;
|
||||
break;
|
||||
@@ -544,8 +545,6 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
};
|
||||
|
||||
switch (psym.visibility) {
|
||||
case LDPV_DEFAULT:
|
||||
break;
|
||||
case LDPV_PROTECTED:
|
||||
esym.st_visibility = STV_PROTECTED;
|
||||
break;
|
||||
@@ -565,7 +564,7 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
|
||||
// Returns false if it's GCC.
|
||||
template <typename E>
|
||||
static bool is_llvm(Context<E> &ctx) {
|
||||
return ctx.arg.plugin.ends_with("LLVMgold.so");
|
||||
return ctx.arg.plugin.find("LLVMgold.") != ctx.arg.plugin.npos;
|
||||
}
|
||||
|
||||
// Returns true if a given linker plugin supports the get_symbols_v3 API.
|
||||
@@ -576,23 +575,40 @@ static bool supports_v3_api(Context<E> &ctx) {
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
static PluginInputFile
|
||||
create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
|
||||
PluginInputFile file;
|
||||
MappedFile *mf2 = mf->parent ? mf->parent : mf;
|
||||
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
|
||||
mf2->reopen_fd(file.name);
|
||||
|
||||
file.fd = mf2->fd;
|
||||
|
||||
if (!file.fd)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": unable to handle this LTO object file because "
|
||||
<< "the -plugin option was not provided. Please make sure you "
|
||||
<< "added -flto not only when creating object files but also "
|
||||
<< "when linking the final executable.";
|
||||
|
||||
load_lto_plugin(ctx);
|
||||
|
||||
// V0 API's claim_file is not thread-safe.
|
||||
static std::mutex mu;
|
||||
std::unique_lock lock(mu, std::defer_lock);
|
||||
if (!is_gcc_linker_api_v1)
|
||||
lock.lock();
|
||||
|
||||
if (ctx.arg.plugin.empty())
|
||||
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
|
||||
<< "because no -plugin option was given. Please make sure you "
|
||||
<< "added -flto not only for creating object files but also for "
|
||||
<< "creating the final executable.";
|
||||
|
||||
// dlopen the linker plugin file
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&] { load_plugin(ctx); });
|
||||
|
||||
// Create mold's object instance
|
||||
ObjectFile<E> *obj = new ObjectFile<E>;
|
||||
ctx.obj_pool.emplace_back(obj);
|
||||
@@ -602,23 +618,10 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
obj->first_global = 1;
|
||||
obj->is_lto_obj = true;
|
||||
obj->mf = mf;
|
||||
obj->archive_name = mf->parent ? mf->parent->name : "";
|
||||
|
||||
// Create plugin's object instance
|
||||
PluginInputFile file = {};
|
||||
|
||||
MappedFile<Context<E>> *mf2 = mf->parent ? mf->parent : mf;
|
||||
file.name = save_string(ctx, mf2->name).data();
|
||||
if (mf2->fd == -1)
|
||||
mf2->fd = open(file.name, O_RDONLY);
|
||||
file.fd = mf2->fd;
|
||||
if (file.fd == -1)
|
||||
Fatal(ctx) << "cannot open " << file.name << ": " << errno_string();
|
||||
|
||||
if (mf->parent)
|
||||
obj->archive_name = mf->parent->name;
|
||||
|
||||
file.offset = mf->get_offset();
|
||||
file.filesize = mf->size;
|
||||
PluginInputFile file = create_plugin_input_file(ctx, mf);
|
||||
file.handle = (void *)obj;
|
||||
|
||||
LOG << "read_lto_symbols: "<< mf->name << "\n";
|
||||
@@ -631,52 +634,53 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
<< " please make sure you are using the same compiler of the"
|
||||
<< " same version for all object files";
|
||||
|
||||
// It looks like GCC doesn't need fd after claim_file_hook() while
|
||||
// LLVM needs it and takes the ownership of fd. To prevent "too many
|
||||
// open files" issue, we close fd only for GCC. This is ugly, though.
|
||||
if (!is_llvm(ctx)) {
|
||||
close(mf2->fd);
|
||||
mf2->fd = -1;
|
||||
}
|
||||
if (mf->parent)
|
||||
mf->parent->close_fd();
|
||||
else
|
||||
mf->close_fd();
|
||||
|
||||
// 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());
|
||||
// Create a symbol strtab
|
||||
i64 strtab_size = 1;
|
||||
for (PluginSymbol &psym : plugin_symbols)
|
||||
strtab_size += strlen(psym.name) + 1;
|
||||
std::string strtab(strtab_size, '\0');
|
||||
|
||||
// Initialize esyms
|
||||
obj->lto_elf_syms.resize(plugin_symbols.size() + 1);
|
||||
obj->lto_comdat_groups.resize(plugin_symbols.size() + 1);
|
||||
i64 strtab_offset = 1;
|
||||
|
||||
for (i64 i = 0; i < plugin_symbols.size(); i++) {
|
||||
PluginSymbol &psym = plugin_symbols[i];
|
||||
esyms->push_back(to_elf_sym<E>(psym));
|
||||
obj->lto_elf_syms[i + 1] = to_elf_sym<E>(psym);
|
||||
obj->lto_elf_syms[i + 1].st_name = strtab_offset;
|
||||
|
||||
std::string_view key = save_string(ctx, psym.name);
|
||||
std::string_view name = key;
|
||||
i64 len = strlen(psym.name);
|
||||
memcpy(strtab.data() + strtab_offset, psym.name, len);
|
||||
strtab_offset += len + 1;
|
||||
|
||||
// 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);
|
||||
}
|
||||
// comdat_key is non-null if the symbol is defined in a comdat member
|
||||
// section. We handle such symbols differently than comdat symbols in
|
||||
// a regular file because, unlike regular object files, IR files don't
|
||||
// have input sections.
|
||||
if (psym.comdat_key) {
|
||||
std::string_view key = save_string(ctx, psym.comdat_key);
|
||||
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, key);
|
||||
}
|
||||
|
||||
obj->symbols.push_back(get_symbol(ctx, key, name));
|
||||
}
|
||||
|
||||
obj->elf_syms = *esyms;
|
||||
obj->has_symver.resize(esyms->size());
|
||||
obj->symbol_strtab = save_string(ctx, strtab);
|
||||
obj->elf_syms = obj->lto_elf_syms;
|
||||
obj->initialize_symbols(ctx);
|
||||
plugin_symbols.clear();
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Entry point
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
Timer t(ctx, "do_lto");
|
||||
std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
|
||||
Timer t(ctx, "run_lto_plugin");
|
||||
load_lto_plugin(ctx);
|
||||
|
||||
if (!ctx.arg.lto_pass2 && !supports_v3_api(ctx))
|
||||
restart_process(ctx);
|
||||
@@ -685,7 +689,7 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
phase = 2;
|
||||
|
||||
// Set `referenced_by_regular_obj` bit.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
if (!file->is_lto_obj) {
|
||||
for (Symbol<E> *sym : file->get_global_syms()) {
|
||||
if (sym->file && !sym->file->is_dso &&
|
||||
@@ -709,8 +713,21 @@ 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;
|
||||
// Keep some symbols
|
||||
for (Symbol<E> *sym : ctx.arg.undefined)
|
||||
sym->referenced_by_regular_obj = true;
|
||||
|
||||
// Object files containing .gnu.offload_lto_.* sections need to be
|
||||
// given to the LTO backend. Such sections contains code and data for
|
||||
// peripherails (typically GPUs).
|
||||
for (ObjectFile<E> *file : ctx.objs) {
|
||||
if (file->is_reachable && !file->is_lto_obj && file->is_gcc_offload_obj) {
|
||||
PluginInputFile pfile = create_plugin_input_file(ctx, file->mf);
|
||||
int claimed = false;
|
||||
claim_file_hook(&pfile, &claimed);
|
||||
assert(!claimed);
|
||||
}
|
||||
}
|
||||
|
||||
// all_symbols_read_hook() calls add_input_file() and add_input_library()
|
||||
LOG << "all symbols read\n";
|
||||
@@ -723,15 +740,14 @@ std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
template <typename E>
|
||||
void lto_cleanup(Context<E> &ctx) {
|
||||
Timer t(ctx, "lto_cleanup");
|
||||
|
||||
if (cleanup_hook)
|
||||
cleanup_hook();
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
|
||||
template std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "mold.h"
|
||||
#include "lto.h"
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
|
||||
Fatal(ctx) << "LTO is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<ObjectFile<E> *> do_lto(Context<E> &ctx) {
|
||||
std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ void lto_cleanup(Context<E> &ctx) {}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile<Context<E>> *);
|
||||
template std::vector<ObjectFile<E> *> do_lto(Context<E> &);
|
||||
template ObjectFile<E> *read_lto_object(Context<E> &, MappedFile *);
|
||||
template std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &);
|
||||
template void lto_cleanup(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../common/integers.h"
|
||||
#include "../lib/integers.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
@@ -73,7 +73,11 @@ enum PluginOutputFileType {
|
||||
|
||||
struct PluginInputFile {
|
||||
const char *name;
|
||||
i32 fd;
|
||||
#if __MINGW32__
|
||||
HANDLE fd;
|
||||
#else
|
||||
int fd;
|
||||
#endif
|
||||
u64 offset;
|
||||
u64 filesize;
|
||||
void *handle;
|
||||
+245
-286
@@ -1,133 +1,52 @@
|
||||
#include "mold.h"
|
||||
#include "../common/archive-file.h"
|
||||
#include "../common/cmdline.h"
|
||||
#include "../common/output-file.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <direct.h>
|
||||
# define _chdir chdir
|
||||
# define chdir _chdir
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold::elf {
|
||||
|
||||
// Read the beginning of a given file and returns its machine type
|
||||
// (e.g. EM_X86_64 or EM_386).
|
||||
template <typename E>
|
||||
std::string_view get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
auto get_elf_type = [&](u8 *buf) -> std::string_view {
|
||||
bool is_le = (((ElfEhdr<I386> *)buf)->e_ident[EI_DATA] == ELFDATA2LSB);
|
||||
bool is_64;
|
||||
u32 e_machine;
|
||||
|
||||
if (is_le) {
|
||||
auto &ehdr = *(ElfEhdr<I386> *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
} else {
|
||||
auto &ehdr = *(ElfEhdr<M68K> *)buf;
|
||||
is_64 = (ehdr.e_ident[EI_CLASS] == ELFCLASS64);
|
||||
e_machine = ehdr.e_machine;
|
||||
}
|
||||
|
||||
switch (e_machine) {
|
||||
case EM_386:
|
||||
return I386::target_name;
|
||||
case EM_X86_64:
|
||||
return X86_64::target_name;
|
||||
case EM_ARM:
|
||||
return ARM32::target_name;
|
||||
case EM_AARCH64:
|
||||
return ARM64::target_name;
|
||||
case EM_RISCV:
|
||||
if (is_le)
|
||||
return is_64 ? RV64LE::target_name : RV32LE::target_name;
|
||||
return is_64 ? RV64BE::target_name : RV32BE::target_name;
|
||||
case EM_PPC:
|
||||
return PPC32::target_name;
|
||||
case EM_PPC64:
|
||||
return is_le ? PPC64V2::target_name : PPC64V1::target_name;
|
||||
case EM_S390X:
|
||||
return S390X::target_name;
|
||||
case EM_SPARC64:
|
||||
return SPARC64::target_name;
|
||||
case EM_68K:
|
||||
return M68K::target_name;
|
||||
case EM_SH:
|
||||
return SH4::target_name;
|
||||
case EM_ALPHA:
|
||||
return ALPHA::target_name;
|
||||
case EM_LOONGARCH:
|
||||
return is_64 ? LOONGARCH64::target_name : LOONGARCH32::target_name;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
case FileType::ELF_DSO:
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
return get_elf_type(mf->data);
|
||||
case FileType::AR:
|
||||
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
|
||||
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(ctx, child) == FileType::ELF_OBJ)
|
||||
return get_elf_type(child->data);
|
||||
return "";
|
||||
case FileType::TEXT:
|
||||
return get_script_output_type(ctx, mf);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
static void
|
||||
check_file_compatibility(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
check_file_compatibility(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
std::string_view target = get_machine_type(ctx, rctx, mf);
|
||||
if (target != ctx.arg.emulation)
|
||||
Fatal(ctx) << mf->name << ": incompatible file type: "
|
||||
<< ctx.arg.emulation << " is expected but got " << target;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
|
||||
MappedFile *mf, std::string archive_name) {
|
||||
static Counter count("parsed_objs");
|
||||
count++;
|
||||
|
||||
check_file_compatibility(ctx, mf);
|
||||
check_file_compatibility(ctx, rctx, mf);
|
||||
|
||||
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
|
||||
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
|
||||
ctx.obj_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
Out(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
std::string archive_name) {
|
||||
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
|
||||
MappedFile *mf, std::string archive_name) {
|
||||
static Counter count("parsed_lto_objs");
|
||||
count++;
|
||||
|
||||
@@ -137,50 +56,49 @@ static ObjectFile<E> *new_lto_obj(Context<E> &ctx, MappedFile<Context<E>> *mf,
|
||||
ObjectFile<E> *file = read_lto_object(ctx, mf);
|
||||
file->priority = ctx.file_priority++;
|
||||
file->archive_name = archive_name;
|
||||
file->is_in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
|
||||
file->is_alive = !file->is_in_lib;
|
||||
ctx.has_lto_object = true;
|
||||
file->as_needed =
|
||||
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
|
||||
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
Out(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static SharedFile<E> *
|
||||
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
check_file_compatibility(ctx, mf);
|
||||
new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
check_file_compatibility(ctx, rctx, mf);
|
||||
|
||||
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
|
||||
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
|
||||
ctx.dso_pool.emplace_back(file);
|
||||
file->priority = ctx.file_priority++;
|
||||
ctx.tg.run([file, &ctx] { file->parse(ctx); });
|
||||
file->as_needed = rctx.as_needed;
|
||||
|
||||
rctx.tg->run([file, &ctx] { file->parse(ctx); });
|
||||
if (ctx.arg.trace)
|
||||
SyncOut(ctx) << "trace: " << *file;
|
||||
Out(ctx) << "trace: " << *file;
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
if (ctx.visited.contains(mf->name))
|
||||
return;
|
||||
|
||||
void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
|
||||
switch (get_file_type(ctx, mf)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, mf, ""));
|
||||
ctx.objs.push_back(new_object_file(ctx, rctx, mf, ""));
|
||||
return;
|
||||
case FileType::ELF_DSO:
|
||||
ctx.dsos.push_back(new_shared_file(ctx, mf));
|
||||
ctx.visited.insert(mf->name);
|
||||
ctx.dsos.push_back(new_shared_file(ctx, rctx, mf));
|
||||
return;
|
||||
case FileType::AR:
|
||||
case FileType::THIN_AR:
|
||||
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf)) {
|
||||
for (MappedFile *child : read_archive_members(ctx, mf)) {
|
||||
switch (get_file_type(ctx, child)) {
|
||||
case FileType::ELF_OBJ:
|
||||
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
|
||||
ctx.objs.push_back(new_object_file(ctx, rctx, child, mf->name));
|
||||
break;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, child, mf->name))
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, rctx, child, mf->name))
|
||||
ctx.objs.push_back(file);
|
||||
break;
|
||||
case FileType::ELF_DSO:
|
||||
@@ -191,15 +109,13 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ctx.whole_archive)
|
||||
ctx.visited.insert(mf->name);
|
||||
return;
|
||||
case FileType::TEXT:
|
||||
parse_linker_script(ctx, mf);
|
||||
Script(ctx, rctx, mf).parse_linker_script();
|
||||
return;
|
||||
case FileType::GCC_LTO_OBJ:
|
||||
case FileType::LLVM_BITCODE:
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, mf, ""))
|
||||
if (ObjectFile<E> *file = new_lto_obj(ctx, rctx, mf, ""))
|
||||
ctx.objs.push_back(file);
|
||||
return;
|
||||
default:
|
||||
@@ -209,36 +125,60 @@ void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
|
||||
|
||||
template <typename E>
|
||||
static std::string_view
|
||||
deduce_machine_type(Context<E> &ctx, std::span<std::string> args) {
|
||||
for (std::string_view arg : args)
|
||||
if (!arg.starts_with('-'))
|
||||
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
|
||||
if (std::string_view target = get_machine_type(ctx, mf);
|
||||
!target.empty())
|
||||
return target;
|
||||
detect_machine_type(Context<E> &ctx, std::vector<std::string> args) {
|
||||
for (ReaderContext rctx; const std::string &arg : args) {
|
||||
if (arg == "--Bstatic") {
|
||||
rctx.static_ = true;
|
||||
} else if (arg == "--Bdynamic") {
|
||||
rctx.static_ = false;
|
||||
} else if (!arg.starts_with('-')) {
|
||||
if (MappedFile *mf = open_file(ctx, arg))
|
||||
if (get_file_type(ctx, mf) != FileType::TEXT)
|
||||
if (std::string_view target = get_machine_type(ctx, rctx, mf);
|
||||
!target.empty())
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
for (ReaderContext rctx; const std::string &arg : args) {
|
||||
if (arg == "--Bstatic") {
|
||||
rctx.static_ = true;
|
||||
} else if (arg == "--Bdynamic") {
|
||||
rctx.static_ = false;
|
||||
} else if (!arg.starts_with('-')) {
|
||||
if (MappedFile *mf = open_file(ctx, arg))
|
||||
if (get_file_type(ctx, mf) == FileType::TEXT)
|
||||
if (std::string_view target =
|
||||
Script(ctx, rctx, mf).get_script_output_type();
|
||||
!target.empty())
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
Fatal(ctx) << "-m option is missing";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
|
||||
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
|
||||
MappedFile *open_library(Context<E> &ctx, ReaderContext &rctx, std::string path) {
|
||||
MappedFile *mf = open_file(ctx, path);
|
||||
if (!mf)
|
||||
return nullptr;
|
||||
|
||||
std::string_view target = get_machine_type(ctx, mf);
|
||||
if (target.empty() || target == E::target_name)
|
||||
return mf;
|
||||
Warn(ctx) << path << ": skipping incompatible file " << target
|
||||
<< " " << (int)E::e_machine;
|
||||
return nullptr;
|
||||
std::string_view target = get_machine_type(ctx, rctx, mf);
|
||||
if (!target.empty() && target != E::name) {
|
||||
Warn(ctx) << path << ": skipping incompatible file: " << target
|
||||
<< " (e_machine " << (int)E::e_machine << ")";
|
||||
return nullptr;
|
||||
}
|
||||
return mf;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
MappedFile *find_library(Context<E> &ctx, ReaderContext &rctx, std::string name) {
|
||||
if (name.starts_with(':')) {
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string path = std::string(dir) + "/" + name.substr(1);
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
|
||||
if (MappedFile *mf = open_library(ctx, rctx, path))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
@@ -246,110 +186,88 @@ MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths) {
|
||||
std::string stem = std::string(dir) + "/lib" + name;
|
||||
if (!ctx.is_static)
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".so"))
|
||||
if (!rctx.static_)
|
||||
if (MappedFile *mf = open_library(ctx, rctx, stem + ".so"))
|
||||
return mf;
|
||||
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
|
||||
if (MappedFile *mf = open_library(ctx, rctx, stem + ".a"))
|
||||
return mf;
|
||||
}
|
||||
Fatal(ctx) << "library not found: " << name;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
MappedFile<Context<E>> *find_from_search_paths(Context<E> &ctx, std::string name) {
|
||||
if (MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, name))
|
||||
return mf;
|
||||
|
||||
for (std::string_view dir : ctx.arg.library_paths)
|
||||
if (MappedFile<Context<E>> *mf =
|
||||
MappedFile<Context<E>>::open(ctx, std::string(dir) + "/" + name))
|
||||
return mf;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
|
||||
Timer t(ctx, "read_input_files");
|
||||
|
||||
std::vector<std::tuple<bool, bool, bool, bool>> state;
|
||||
ctx.is_static = ctx.arg.is_static;
|
||||
ReaderContext rctx;
|
||||
std::vector<ReaderContext> stack;
|
||||
std::unordered_set<std::string_view> visited;
|
||||
|
||||
tbb::task_group tg;
|
||||
rctx.tg = &tg;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg = args[0];
|
||||
args = args.subspan(1);
|
||||
|
||||
if (arg == "--as-needed") {
|
||||
ctx.as_needed = true;
|
||||
rctx.as_needed = true;
|
||||
} else if (arg == "--no-as-needed") {
|
||||
ctx.as_needed = false;
|
||||
rctx.as_needed = false;
|
||||
} else if (arg == "--whole-archive") {
|
||||
ctx.whole_archive = true;
|
||||
rctx.whole_archive = true;
|
||||
} else if (arg == "--no-whole-archive") {
|
||||
ctx.whole_archive = false;
|
||||
rctx.whole_archive = false;
|
||||
} else if (arg == "--Bstatic") {
|
||||
ctx.is_static = true;
|
||||
rctx.static_ = true;
|
||||
} else if (arg == "--Bdynamic") {
|
||||
ctx.is_static = false;
|
||||
rctx.static_ = false;
|
||||
} else if (arg == "--start-lib") {
|
||||
ctx.in_lib = true;
|
||||
rctx.in_lib = true;
|
||||
} else if (arg == "--end-lib") {
|
||||
ctx.in_lib = false;
|
||||
} else if (remove_prefix(arg, "--version-script=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--version-script: file not found: " << arg;
|
||||
parse_version_script(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--dynamic-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--dynamic-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol=")) {
|
||||
if (arg == "*")
|
||||
ctx.default_version = VER_NDX_GLOBAL;
|
||||
else
|
||||
ctx.version_patterns.push_back({arg, "--export-dynamic-symbol",
|
||||
"global", VER_NDX_GLOBAL, false});
|
||||
} else if (remove_prefix(arg, "--export-dynamic-symbol-list=")) {
|
||||
MappedFile<Context<E>> *mf = find_from_search_paths(ctx, std::string(arg));
|
||||
if (!mf)
|
||||
Fatal(ctx) << "--export-dynamic-symbol-list: file not found: " << arg;
|
||||
parse_dynamic_list(ctx, mf);
|
||||
rctx.in_lib = false;
|
||||
} else if (arg == "--push-state") {
|
||||
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
|
||||
ctx.in_lib});
|
||||
stack.push_back(rctx);
|
||||
} else if (arg == "--pop-state") {
|
||||
if (state.empty())
|
||||
if (stack.empty())
|
||||
Fatal(ctx) << "no state pushed before popping";
|
||||
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
|
||||
state.back();
|
||||
state.pop_back();
|
||||
} else if (remove_prefix(arg, "-l")) {
|
||||
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
|
||||
rctx = stack.back();
|
||||
stack.pop_back();
|
||||
} else if (arg.starts_with("-l")) {
|
||||
arg = arg.substr(2);
|
||||
if (visited.contains(arg))
|
||||
continue;
|
||||
visited.insert(arg);
|
||||
|
||||
MappedFile *mf = find_library(ctx, rctx, std::string(arg));
|
||||
mf->given_fullpath = false;
|
||||
read_file(ctx, mf);
|
||||
read_file(ctx, rctx, mf);
|
||||
} else {
|
||||
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(arg)));
|
||||
read_file(ctx, rctx, must_open_file(ctx, std::string(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.objs.empty())
|
||||
if (ctx.objs.empty() && ctx.dsos.empty())
|
||||
Fatal(ctx) << "no input files";
|
||||
|
||||
ctx.tg.wait();
|
||||
tg.wait();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int elf_main(int argc, char **argv) {
|
||||
static bool has_lto_obj(Context<E> &ctx) {
|
||||
for (ObjectFile<E> *file : ctx.objs)
|
||||
if (file->is_reachable && (file->is_lto_obj || file->is_gcc_offload_obj))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
int mold_main(int argc, char **argv) {
|
||||
Context<E> ctx;
|
||||
|
||||
// Process -run option first. process_run_subcommand() does not return.
|
||||
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv)) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
Fatal(ctx) << "-run is supported only on Unix";
|
||||
#endif
|
||||
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv))
|
||||
process_run_subcommand(ctx, argc, argv);
|
||||
}
|
||||
|
||||
// Parse non-positional command line options
|
||||
ctx.cmdline_args = expand_response_files(ctx, argv);
|
||||
@@ -357,11 +275,11 @@ int elf_main(int argc, char **argv) {
|
||||
|
||||
// If no -m option is given, deduce it from input files.
|
||||
if (ctx.arg.emulation.empty())
|
||||
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
|
||||
ctx.arg.emulation = detect_machine_type(ctx, file_args);
|
||||
|
||||
// Redo if -m is not x86-64.
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.emulation != X86_64::target_name)
|
||||
if (ctx.arg.emulation != X86_64::name)
|
||||
return redo_main(ctx, argc, argv);
|
||||
|
||||
Timer t_all(ctx, "all");
|
||||
@@ -374,17 +292,13 @@ int elf_main(int argc, char **argv) {
|
||||
<< ": " << errno_string();
|
||||
|
||||
// Fork a subprocess unless --no-fork is given.
|
||||
std::function<void()> on_complete;
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
if (ctx.arg.fork)
|
||||
on_complete = fork_child();
|
||||
#endif
|
||||
fork_child();
|
||||
|
||||
acquire_global_lock(ctx);
|
||||
acquire_global_lock();
|
||||
|
||||
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
|
||||
ctx.arg.thread_count);
|
||||
|
||||
// Handle --wrap options if any.
|
||||
for (std::string_view name : ctx.arg.wrap)
|
||||
@@ -392,8 +306,8 @@ int elf_main(int argc, char **argv) {
|
||||
|
||||
// Handle --retain-symbols-file options if any.
|
||||
if (ctx.arg.retain_symbols_file)
|
||||
for (std::string_view name : *ctx.arg.retain_symbols_file)
|
||||
get_symbol(ctx, name)->write_to_symtab = true;
|
||||
for (Symbol<E> *sym : *ctx.arg.retain_symbols_file)
|
||||
sym->write_to_symtab = true;
|
||||
|
||||
for (std::string_view arg : ctx.arg.trace_symbol)
|
||||
get_symbol(ctx, arg)->is_traced = true;
|
||||
@@ -409,6 +323,10 @@ int elf_main(int argc, char **argv) {
|
||||
});
|
||||
}
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
|
||||
Timer t_before_copy(ctx, "before_copy");
|
||||
|
||||
// Apply -exclude-libs
|
||||
@@ -418,23 +336,26 @@ int elf_main(int argc, char **argv) {
|
||||
if (!ctx.arg.relocatable)
|
||||
create_internal_file(ctx);
|
||||
|
||||
// resolve_symbols is 4 things in 1 phase:
|
||||
//
|
||||
// - Determine the set of object files to extract from archives.
|
||||
// - Remove redundant COMDAT sections (e.g. duplicate inline functions).
|
||||
// - Finally, the actual symbol resolution.
|
||||
// - LTO, which requires preliminary symbol resolution before running
|
||||
// and a follow-up re-resolution after the LTO objects are emitted.
|
||||
//
|
||||
// These passes have complex interactions, and unfortunately has to be
|
||||
// put together in a single phase.
|
||||
// Resolve symbols by choosing the most appropriate file for each
|
||||
// symbol. This pass also removes redundant comdat sections (e.g.
|
||||
// duplicate inline functions).
|
||||
resolve_symbols(ctx);
|
||||
|
||||
// "Kill" .eh_frame input sections after symbol resolution.
|
||||
kill_eh_frame_sections(ctx);
|
||||
// If there's an object file compiled with -flto, do link-time
|
||||
// optimization.
|
||||
if (has_lto_obj(ctx))
|
||||
do_lto(ctx);
|
||||
|
||||
// Resolve mergeable section pieces to merge them.
|
||||
resolve_section_pieces(ctx);
|
||||
// Now that we know which object files are to be included to the
|
||||
// final output, we can remove unnecessary files.
|
||||
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
std::erase_if(ctx.dsos, [](InputFile<E> *file) { return !file->is_reachable; });
|
||||
|
||||
// Parse .eh_frame section contents.
|
||||
parse_eh_frame_sections(ctx);
|
||||
|
||||
// Split mergeable section contents into section pieces.
|
||||
create_merged_sections(ctx);
|
||||
|
||||
// Handle --relocatable. Since the linker's behavior is quite different
|
||||
// from the normal one when the option is given, the logic is implemented
|
||||
@@ -456,10 +377,18 @@ int elf_main(int argc, char **argv) {
|
||||
// Set is_imported and is_exported bits for each symbol.
|
||||
compute_import_export(ctx);
|
||||
|
||||
// Make sure that there's no duplicate symbol
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
|
||||
// Set "address-taken" bits for input sections.
|
||||
if (ctx.arg.icf || ctx.arg.z_rewrite_endbr)
|
||||
if (ctx.arg.icf)
|
||||
compute_address_significance(ctx);
|
||||
|
||||
// Handle PPC64-specific .opd sections.
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ppc64v1_rewrite_opd(ctx);
|
||||
|
||||
// Garbage-collect unreachable sections.
|
||||
if (ctx.arg.gc_sections)
|
||||
gc_sections(ctx);
|
||||
@@ -468,25 +397,27 @@ int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.icf)
|
||||
icf_sections(ctx);
|
||||
|
||||
// Compute sizes of sections containing mergeable strings.
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
// Create linker-synthesized sections such as .got or .plt.
|
||||
create_synthetic_sections(ctx);
|
||||
|
||||
// Make sure that there's no duplicate symbol
|
||||
if (!ctx.arg.allow_multiple_definition)
|
||||
check_duplicate_symbols(ctx);
|
||||
// Handle --no-allow-shlib-undefined
|
||||
if (!ctx.arg.allow_shlib_undefined)
|
||||
check_shlib_undefined(ctx);
|
||||
|
||||
// Warn if symbols with different types are defined under the same name.
|
||||
check_symbol_types(ctx);
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ppc64v1_rewrite_opd(ctx);
|
||||
|
||||
// Bin input sections into output sections.
|
||||
create_output_sections(ctx);
|
||||
|
||||
// Convert an .ARM.exidx to a synthetic section.
|
||||
if constexpr (is_arm32<E>)
|
||||
create_arm_exidx_section(ctx);
|
||||
|
||||
// Handle --section-align options.
|
||||
if (!ctx.arg.section_align.empty())
|
||||
apply_section_align(ctx);
|
||||
|
||||
// Add synthetic symbols such as __ehdr_start or __end.
|
||||
add_synthetic_symbols(ctx);
|
||||
|
||||
@@ -518,14 +449,10 @@ int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.print_dependencies)
|
||||
print_dependencies(ctx);
|
||||
|
||||
// Handle -repro
|
||||
if (ctx.arg.repro)
|
||||
write_repro_file(ctx);
|
||||
|
||||
// Handle --require-defined
|
||||
for (std::string_view name : ctx.arg.require_defined)
|
||||
if (!get_symbol(ctx, name)->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << name;
|
||||
for (Symbol<E> *sym : ctx.arg.require_defined)
|
||||
if (!sym->file)
|
||||
Error(ctx) << "--require-defined: undefined symbol: " << *sym;
|
||||
|
||||
// .init_array and .fini_array contents have to be sorted by
|
||||
// a special rule. Sort them.
|
||||
@@ -535,21 +462,16 @@ int elf_main(int argc, char **argv) {
|
||||
// because they are superceded by .init_array/.fini_array, though.
|
||||
sort_ctor_dtor(ctx);
|
||||
|
||||
// If .ctors/.dtors are to be placed to .init_array/.fini_array,
|
||||
// we need to reverse their contents.
|
||||
fixup_ctors_in_init_array(ctx);
|
||||
|
||||
// Handle --shuffle-sections
|
||||
if (ctx.arg.shuffle_sections != SHUFFLE_SECTIONS_NONE)
|
||||
shuffle_sections(ctx);
|
||||
|
||||
// Copy string referred by .dynamic to .dynstr.
|
||||
for (SharedFile<E> *file : ctx.dsos)
|
||||
ctx.dynstr->add_string(file->soname);
|
||||
for (std::string_view str : ctx.arg.auxiliary)
|
||||
ctx.dynstr->add_string(str);
|
||||
for (std::string_view str : ctx.arg.filter)
|
||||
ctx.dynstr->add_string(str);
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.rpaths);
|
||||
if (!ctx.arg.soname.empty())
|
||||
ctx.dynstr->add_string(ctx.arg.soname);
|
||||
add_dynamic_strings(ctx);
|
||||
|
||||
if constexpr (is_ppc64v1<E>)
|
||||
ppc64v1_scan_symbols(ctx);
|
||||
@@ -558,14 +480,21 @@ int elf_main(int argc, char **argv) {
|
||||
// .got.plt, .dynsym, .dynstr, etc.
|
||||
scan_relocations(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
// Compute the is_weak bit for each imported symbol.
|
||||
compute_imported_symbol_weakness(ctx);
|
||||
|
||||
// Sort sections by section attributes so that we'll have to
|
||||
// create as few segments as possible.
|
||||
sort_output_sections(ctx);
|
||||
|
||||
// Handle --separate-debug-file.
|
||||
if (ctx.gnu_debuglink)
|
||||
separate_debug_sections(ctx);
|
||||
|
||||
// Compute sizes of output sections while assigning offsets
|
||||
// within an output section to input sections.
|
||||
compute_section_sizes(ctx);
|
||||
|
||||
// If --packed_dyn_relocs=relr was given, base relocations are stored
|
||||
// to a .relr.dyn section in a compressed form. Construct a compressed
|
||||
// relocations now so that we can fix section sizes and file layout.
|
||||
@@ -575,7 +504,11 @@ int elf_main(int argc, char **argv) {
|
||||
// Reserve a space for dynamic symbol strings in .dynstr and sort
|
||||
// .dynsym contents if necessary. Beyond this point, no symbol will
|
||||
// be added to .dynsym.
|
||||
ctx.dynsym->finalize(ctx);
|
||||
sort_dynsyms(ctx);
|
||||
|
||||
// Sort .debug_info contents so that DWARF32 debug info precedes that of
|
||||
// DWARF64. This is to mitigate the possibility of a relocation overflow.
|
||||
sort_debug_info_sections(ctx);
|
||||
|
||||
// Print reports about undefined symbols, if needed.
|
||||
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
|
||||
@@ -588,9 +521,6 @@ int elf_main(int argc, char **argv) {
|
||||
// Fill .gnu.version_r section contents.
|
||||
ctx.verneed->construct(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
create_output_symtab(ctx);
|
||||
|
||||
// .eh_frame is a special section from the linker's point of view,
|
||||
// as its contents are parsed and reconstructed by the linker,
|
||||
// unlike other sections that are regarded as opaque bytes.
|
||||
@@ -602,6 +532,10 @@ int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.emit_relocs)
|
||||
create_reloc_sections(ctx);
|
||||
|
||||
// Compute .symtab and .strtab sizes for each file.
|
||||
if (!ctx.arg.strip_all)
|
||||
create_output_symtab(ctx);
|
||||
|
||||
// Compute the section header values for all sections.
|
||||
compute_section_headers(ctx);
|
||||
|
||||
@@ -612,8 +546,25 @@ int elf_main(int argc, char **argv) {
|
||||
// that they can jump to anywhere in ±2 GiB by default. They may
|
||||
// be replaced with shorter instruction sequences if destinations
|
||||
// are close enough. Do this optimization.
|
||||
if constexpr (is_riscv<E>)
|
||||
filesize = riscv_resize_sections(ctx);
|
||||
if constexpr (is_riscv<E> || is_loongarch<E>) {
|
||||
shrink_sections(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// We've created range extension thunks with a pessimistive assumption
|
||||
// that all out-of-section references are out of range. Now that we know
|
||||
// the addresses of all sections,, we can eliminate excessive thunks.
|
||||
if constexpr (needs_thunk<E>) {
|
||||
remove_redundant_thunks(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
if constexpr (is_arm32<E>) {
|
||||
if (ctx.extra.exidx) {
|
||||
ctx.extra.exidx->remove_duplicate_entries(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, memory layout is fixed.
|
||||
|
||||
@@ -624,17 +575,22 @@ int elf_main(int argc, char **argv) {
|
||||
// GOT or PLT addresses have a correct final value.
|
||||
|
||||
// If --compress-debug-sections is given, compress .debug_* sections
|
||||
// using zlib.
|
||||
if (ctx.arg.compress_debug_sections != COMPRESS_NONE)
|
||||
filesize = compress_debug_sections(ctx);
|
||||
// using zlib or zstd.
|
||||
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE) {
|
||||
compress_debug_sections(ctx);
|
||||
filesize = set_osec_offsets(ctx);
|
||||
}
|
||||
|
||||
// At this point, both memory and file layouts are fixed.
|
||||
|
||||
// Gather thunk symbols and attach them to themselves.
|
||||
if constexpr (needs_thunk<E>)
|
||||
gather_thunk_addresses(ctx);
|
||||
|
||||
t_before_copy.stop();
|
||||
|
||||
// Create an output file
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
Timer t_copy(ctx, "copy");
|
||||
@@ -642,24 +598,31 @@ int elf_main(int argc, char **argv) {
|
||||
// Copy input sections to the output file and apply relocations.
|
||||
copy_chunks(ctx);
|
||||
|
||||
if constexpr (is_arm32be<E>)
|
||||
arm32be_swap_bytes(ctx);
|
||||
|
||||
if constexpr (is_x86_64<E>)
|
||||
if (ctx.arg.z_rewrite_endbr)
|
||||
rewrite_endbr(ctx);
|
||||
|
||||
// Dynamic linker works better with sorted .rela.dyn section,
|
||||
// so we sort them.
|
||||
ctx.reldyn->sort(ctx);
|
||||
|
||||
// Zero-clear paddings between sections
|
||||
clear_padding(ctx);
|
||||
sort_reldyn(ctx);
|
||||
|
||||
// .gdb_index's contents cannot be constructed before applying
|
||||
// relocations to other debug sections. We have relocated debug
|
||||
// sections now, so write the .gdb_index section.
|
||||
if (ctx.gdb_index)
|
||||
if (ctx.gdb_index && !ctx.gnu_debuglink)
|
||||
write_gdb_index(ctx);
|
||||
|
||||
// .note.gnu.build-id section contains a cryptographic hash of the
|
||||
// entire output file. Now that we wrote everything except build-id,
|
||||
// we can compute it.
|
||||
if (ctx.buildid)
|
||||
ctx.buildid->write_buildid(ctx);
|
||||
write_build_id(ctx);
|
||||
|
||||
if (ctx.gnu_debuglink)
|
||||
write_gnu_debuglink(ctx);
|
||||
|
||||
t_copy.stop();
|
||||
ctx.checkpoint();
|
||||
@@ -671,7 +634,7 @@ int elf_main(int argc, char **argv) {
|
||||
if (!ctx.arg.dependency_file.empty())
|
||||
write_dependency_file(ctx);
|
||||
|
||||
if (ctx.has_lto_object)
|
||||
if (!ctx.arg.plugin.empty())
|
||||
lto_cleanup(ctx);
|
||||
|
||||
t_all.stop();
|
||||
@@ -679,6 +642,9 @@ int elf_main(int argc, char **argv) {
|
||||
if (ctx.arg.print_map)
|
||||
print_map(ctx);
|
||||
|
||||
if (ctx.gnu_debuglink)
|
||||
write_separate_debug_file(ctx);
|
||||
|
||||
// Show stats numbers
|
||||
if (ctx.arg.stats)
|
||||
show_stats(ctx);
|
||||
@@ -688,10 +654,9 @@ int elf_main(int argc, char **argv) {
|
||||
|
||||
std::cout << std::flush;
|
||||
std::cerr << std::flush;
|
||||
if (on_complete)
|
||||
on_complete();
|
||||
|
||||
release_global_lock(ctx);
|
||||
notify_parent();
|
||||
release_global_lock();
|
||||
|
||||
if (ctx.arg.quick_exit)
|
||||
_exit(0);
|
||||
@@ -702,14 +667,8 @@ int elf_main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
int main(int argc, char **argv) {
|
||||
return elf_main<X86_64>(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template int elf_main<E>(int, char **);
|
||||
template int mold_main<E>(int, char **);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -4,38 +4,27 @@
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <sstream>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
using Map =
|
||||
tbb::concurrent_hash_map<InputSection<E> *, std::vector<Symbol<E> *>>;
|
||||
|
||||
template <typename E>
|
||||
static std::unique_ptr<std::ofstream> open_output_file(Context<E> &ctx) {
|
||||
std::unique_ptr<std::ofstream> file(new std::ofstream);
|
||||
file->open(ctx.arg.Map.c_str());
|
||||
if (!file->is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
|
||||
return file;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static Map<E> get_map(Context<E> &ctx) {
|
||||
Map<E> map;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols) {
|
||||
if (sym->file != file || sym->get_type() == STT_SECTION)
|
||||
continue;
|
||||
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
assert(file == &isec->file);
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
if (sym->file == file && sym->get_type() != STT_SECTION) {
|
||||
if (InputSection<E> *isec = sym->get_input_section()) {
|
||||
typename Map<E>::accessor acc;
|
||||
map.insert(acc, {isec, {}});
|
||||
acc->second.push_back(sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -44,22 +33,24 @@ static Map<E> get_map(Context<E> &ctx) {
|
||||
return map;
|
||||
|
||||
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
|
||||
for (auto it = range.begin(); it != range.end(); it++) {
|
||||
std::vector<Symbol<E> *> &vec = it->second;
|
||||
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
|
||||
}
|
||||
for (auto &[k, v] : range)
|
||||
ranges::stable_sort(v, {}, &Symbol<E>::value);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void print_map(Context<E> &ctx) {
|
||||
Timer t(ctx, "print_map");
|
||||
|
||||
std::ostream *out = &std::cout;
|
||||
std::unique_ptr<std::ofstream> file;
|
||||
std::ofstream file;
|
||||
|
||||
if (!ctx.arg.Map.empty()) {
|
||||
file = open_output_file(ctx);
|
||||
out = file.get();
|
||||
file.open(ctx.arg.Map);
|
||||
if (!file.is_open())
|
||||
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
|
||||
out = &file;
|
||||
}
|
||||
|
||||
// Construct a section-to-symbol map.
|
||||
@@ -68,29 +59,32 @@ void print_map(Context<E> &ctx) {
|
||||
// Print a mapfile.
|
||||
*out << " VMA Size Align Out In Symbol\n";
|
||||
|
||||
for (Chunk<E> *osec : ctx.chunks) {
|
||||
for (Chunk<E> *chunk : ctx.chunks) {
|
||||
*out << std::showbase
|
||||
<< std::setw(18) << std::hex << (u64)osec->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)osec->shdr.sh_size
|
||||
<< std::setw(6) << (u64)osec->shdr.sh_addralign
|
||||
<< " " << osec->name << "\n";
|
||||
<< std::setw(18) << std::hex << (u64)chunk->shdr.sh_addr << std::dec
|
||||
<< std::setw(11) << (u64)chunk->shdr.sh_size
|
||||
<< std::setw(6) << (u64)chunk->shdr.sh_addralign
|
||||
<< " " << chunk->name << "\n";
|
||||
|
||||
if (osec->kind() != OUTPUT_SECTION)
|
||||
OutputSection<E> *osec = chunk->to_osec();
|
||||
if (!osec)
|
||||
continue;
|
||||
|
||||
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
|
||||
std::span<InputSection<E> *> members = osec->members;
|
||||
std::vector<std::string> bufs(members.size());
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
|
||||
InputSection<E> *mem = members[i];
|
||||
std::ostringstream ss;
|
||||
opt_demangle = ctx.arg.demangle;
|
||||
u64 addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
u64 addr = 0;
|
||||
if (osec->shdr.sh_flags & SHF_ALLOC)
|
||||
addr = osec->shdr.sh_addr + mem->offset;
|
||||
|
||||
ss << std::showbase
|
||||
<< std::setw(18) << std::hex << addr << std::dec
|
||||
<< std::setw(11) << (u64)mem->sh_size
|
||||
<< std::setw(6) << (1 << (u64)mem->p2align)
|
||||
<< std::setw(6) << (1 << mem->p2align)
|
||||
<< " " << *mem << "\n";
|
||||
|
||||
typename Map<E>::const_accessor acc;
|
||||
@@ -113,4 +107,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void print_map(Context<E> &ctx);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error) {
|
||||
i64 fd = ::open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1) {
|
||||
if (errno != ENOENT)
|
||||
error = "opening " + path + " failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1)
|
||||
error = path + ": fstat failed: " + errno_string();
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = path;
|
||||
mf->size = st.st_size;
|
||||
|
||||
if (st.st_size > 0) {
|
||||
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE, fd, 0);
|
||||
if (mf->data == MAP_FAILED)
|
||||
error = path + ": mmap failed: " + errno_string();
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return mf;
|
||||
}
|
||||
|
||||
void MappedFile::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
munmap(data, size);
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
void MappedFile::close_fd() {
|
||||
if (fd == -1)
|
||||
return;
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
void MappedFile::reopen_fd(const std::string &path) {
|
||||
if (fd == -1)
|
||||
fd = open(path.c_str(), O_RDONLY);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
MappedFile *open_file_impl(const std::string &path, std::string &error) {
|
||||
HANDLE fd = CreateFileA(path.c_str(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
auto err = GetLastError();
|
||||
if (err != ERROR_FILE_NOT_FOUND)
|
||||
error = "opening " + path + " failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (GetFileType(fd) != FILE_TYPE_DISK) {
|
||||
CloseHandle(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DWORD size_hi;
|
||||
DWORD size_lo = GetFileSize(fd, &size_hi);
|
||||
|
||||
if (size_lo == INVALID_FILE_SIZE) {
|
||||
error = path + ": GetFileSize failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u64 size = ((u64)size_hi << 32) + size_lo;
|
||||
|
||||
MappedFile *mf = new MappedFile;
|
||||
mf->name = path;
|
||||
mf->size = size;
|
||||
mf->fd = fd;
|
||||
|
||||
if (size > 0) {
|
||||
HANDLE h = CreateFileMapping(fd, nullptr, PAGE_READONLY, 0, size, nullptr);
|
||||
if (!h) {
|
||||
error = path + ": CreateFileMapping failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mf->data = (u8 *)MapViewOfFile(h, FILE_MAP_COPY, 0, 0, size);
|
||||
CloseHandle(h);
|
||||
|
||||
if (!mf->data) {
|
||||
error = path + ": MapViewOfFile failed: " + errno_string();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return mf;
|
||||
}
|
||||
|
||||
void MappedFile::unmap() {
|
||||
if (size == 0 || parent || !data)
|
||||
return;
|
||||
|
||||
UnmapViewOfFile(data);
|
||||
if (fd != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(fd);
|
||||
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
void MappedFile::close_fd() {
|
||||
if (fd == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
CloseHandle(fd);
|
||||
fd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
void MappedFile::reopen_fd(const std::string &path) {
|
||||
if (fd == INVALID_HANDLE_VALUE)
|
||||
fd = CreateFileA(path.c_str(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,8 +1,5 @@
|
||||
#define _GNU_SOURCE 1
|
||||
|
||||
#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
#include <dlfcn.h>
|
||||
#include <spawn.h>
|
||||
#include <stdarg.h>
|
||||
@@ -12,6 +9,10 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if __has_include(<alloca.h>)
|
||||
# include <alloca.h>
|
||||
#endif
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static char *get_mold_path() {
|
||||
@@ -132,3 +133,14 @@ int posix_spawn(pid_t *pid, const char *path,
|
||||
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
|
||||
return real(pid, path, file_actions, attrp, argv, envp);
|
||||
}
|
||||
|
||||
int posix_spawnp(pid_t *pid, const char *file,
|
||||
const posix_spawn_file_actions_t *file_actions,
|
||||
const posix_spawnattr_t *attrp,
|
||||
char *const *argv, char *const *envp) {
|
||||
debug_print("posix_spawnp %s\n", file);
|
||||
if (is_ld(file))
|
||||
file = get_mold_path();
|
||||
typeof(posix_spawnp) *real = dlsym(RTLD_NEXT, "posix_spawnp");
|
||||
return real(pid, file, file_actions, attrp, argv, envp);
|
||||
}
|
||||
+1165
-423
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,194 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <sys/file.h>
|
||||
#include <sys/mman.h>
|
||||
#include <system_error>
|
||||
|
||||
namespace mold {
|
||||
|
||||
static u32 get_umask() {
|
||||
u32 orig_umask = umask(0);
|
||||
umask(orig_umask);
|
||||
return orig_umask;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static int
|
||||
open_or_create_file(Context<E> &ctx, std::string path, std::string tmpfile,
|
||||
int perm) {
|
||||
// Reuse an existing file if exists and writable because on Linux,
|
||||
// writing to an existing file is much faster than creating a fresh
|
||||
// file and writing to it.
|
||||
if (ctx.overwrite_output_file && rename(path.c_str(), tmpfile.c_str()) == 0) {
|
||||
i64 fd = ::open(tmpfile.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd != -1)
|
||||
return fd;
|
||||
unlink(tmpfile.c_str());
|
||||
}
|
||||
|
||||
i64 fd = ::open(tmpfile.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (fd == -1)
|
||||
Fatal(ctx) << "cannot open " << tmpfile << ": " << errno_string();
|
||||
return fd;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, int perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
std::string pid = std::to_string(getpid());
|
||||
std::string tmpfile =
|
||||
path_dirname(path) / ("." + path_filename(path) + "." + pid);
|
||||
|
||||
this->fd = open_or_create_file(ctx, path, tmpfile, perm);
|
||||
|
||||
if (fchmod(this->fd, perm & ~get_umask()) == -1)
|
||||
Fatal(ctx) << "fchmod failed: " << errno_string();
|
||||
|
||||
if (ftruncate(this->fd, filesize) == -1)
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
|
||||
output_tmpfile = (char *)save_string(ctx, tmpfile).data();
|
||||
|
||||
#ifdef __linux__
|
||||
fallocate(this->fd, 0, 0, filesize);
|
||||
#endif
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, this->fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << path << ": mmap failed: " << errno_string();
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
~MemoryMappedOutputFile() {
|
||||
if (fd2 != -1)
|
||||
::close(fd2);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (this->buf2.empty()) {
|
||||
::close(this->fd);
|
||||
} else {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
// If an output file already exists, open a file and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system to immediately release disk blocks occupied by the file.
|
||||
fd2 = ::open(this->path.c_str(), O_RDONLY);
|
||||
if (fd2 != -1)
|
||||
unlink(this->path.c_str());
|
||||
|
||||
if (rename(output_tmpfile, this->path.c_str()) == -1)
|
||||
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
|
||||
output_tmpfile = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd2 = -1;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, int perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
std::error_code error;
|
||||
bool is_special = path == "-" ||
|
||||
(!std::filesystem::is_regular_file(path, error) && !error);
|
||||
|
||||
OutputFile<E> *file;
|
||||
if (is_special)
|
||||
file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
else
|
||||
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Enable transparent huge page for an output memory-mapped file.
|
||||
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
|
||||
// but it can make the linker ~10% faster. You can try it by creating
|
||||
// a tmpfs with the following commands
|
||||
//
|
||||
// $ mkdir tmp
|
||||
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
|
||||
//
|
||||
// and then specifying a path under the directory as an output file.
|
||||
madvise(file->buf, filesize, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile>(file);
|
||||
}
|
||||
|
||||
// LockingOutputFile is similar to MemoryMappedOutputFile, but it doesn't
|
||||
// rename output files and instead acquires file lock using flock().
|
||||
template <typename E>
|
||||
LockingOutputFile<E>::LockingOutputFile(Context<E> &ctx, std::string path,
|
||||
int perm)
|
||||
: OutputFile<E>(path, 0, true) {
|
||||
this->fd = ::open(path.c_str(), O_RDWR | O_CREAT, perm);
|
||||
if (this->fd == -1)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
|
||||
flock(this->fd, LOCK_EX);
|
||||
|
||||
// We may be overwriting to an existing debug info file. We want to
|
||||
// make the file unusable so that gdb won't use it by accident until
|
||||
// it's ready.
|
||||
u8 buf[256] = {};
|
||||
(void)!!write(this->fd, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {
|
||||
if (ftruncate(this->fd, filesize) == -1)
|
||||
Fatal(ctx) << "ftruncate failed: " << errno_string();
|
||||
|
||||
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, this->fd, 0);
|
||||
if (this->buf == MAP_FAILED)
|
||||
Fatal(ctx) << this->path << ": mmap failed: " << errno_string();
|
||||
|
||||
this->filesize = filesize;
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void LockingOutputFile<E>::close(Context<E> &ctx) {
|
||||
if (!this->is_unmapped)
|
||||
munmap(this->buf, this->filesize);
|
||||
|
||||
if (!this->buf2.empty()) {
|
||||
FILE *out = fdopen(this->fd, "w");
|
||||
fseek(out, 0, SEEK_END);
|
||||
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
::close(this->fd);
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class OutputFile<E>;
|
||||
template class LockingOutputFile<E>;
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <windows.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename E>
|
||||
class MemoryMappedOutputFile : public OutputFile<E> {
|
||||
public:
|
||||
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, int perm)
|
||||
: OutputFile<E>(path, filesize, true) {
|
||||
// TODO: use intermediate temporary file for output.
|
||||
DWORD attrs = (perm & 0200) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY;
|
||||
|
||||
handle = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, CREATE_ALWAYS, attrs, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
Fatal(ctx) << "cannot open " << path << ": " << GetLastError();
|
||||
|
||||
HANDLE map = CreateFileMapping(handle, nullptr, PAGE_READWRITE, 0,
|
||||
filesize, nullptr);
|
||||
if (!map)
|
||||
Fatal(ctx) << path << ": CreateFileMapping failed: " << GetLastError();
|
||||
|
||||
this->buf = (u8 *)MapViewOfFile(map, FILE_MAP_WRITE, 0, 0, filesize);
|
||||
if (!this->buf)
|
||||
Fatal(ctx) << path << ": MapViewOfFile failed: " << GetLastError();
|
||||
|
||||
CloseHandle(map);
|
||||
|
||||
mold::output_buffer_start = this->buf;
|
||||
mold::output_buffer_end = this->buf + filesize;
|
||||
}
|
||||
|
||||
~MemoryMappedOutputFile() {
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(handle);
|
||||
}
|
||||
|
||||
void close(Context<E> &ctx) override {
|
||||
Timer t(ctx, "close_file");
|
||||
|
||||
UnmapViewOfFile(this->buf);
|
||||
|
||||
if (!this->buf2.empty()) {
|
||||
if (SetFilePointer(handle, 0, nullptr, FILE_END) == INVALID_SET_FILE_POINTER)
|
||||
Fatal(ctx) << this->path << ": SetFilePointer failed: "
|
||||
<< GetLastError();
|
||||
|
||||
DWORD written;
|
||||
if (!WriteFile(handle, this->buf2.data(), this->buf2.size(), &written,
|
||||
nullptr))
|
||||
Fatal(ctx) << this->path << ": WriteFile failed: " << GetLastError();
|
||||
}
|
||||
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE handle;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::unique_ptr<OutputFile<E>>
|
||||
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, int perm) {
|
||||
Timer t(ctx, "open_file");
|
||||
|
||||
if (path.starts_with('/') && !ctx.arg.chroot.empty())
|
||||
path = ctx.arg.chroot + "/" + path_clean(path);
|
||||
|
||||
bool is_special = false;
|
||||
if (path == "-") {
|
||||
is_special = true;
|
||||
} else {
|
||||
HANDLE h = CreateFileA(path.c_str(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (h != INVALID_HANDLE_VALUE) {
|
||||
if (GetFileType(h) != FILE_TYPE_DISK)
|
||||
is_special = true;
|
||||
CloseHandle(h);
|
||||
}
|
||||
}
|
||||
|
||||
OutputFile<E> *file;
|
||||
if (is_special)
|
||||
file = new MallocOutputFile(ctx, path, filesize, perm);
|
||||
else
|
||||
file = new MemoryMappedOutputFile(ctx, path, filesize, perm);
|
||||
|
||||
if (ctx.arg.filler != -1)
|
||||
memset(file->buf, ctx.arg.filler, filesize);
|
||||
return std::unique_ptr<OutputFile<E>>(file);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
LockingOutputFile<E>::LockingOutputFile(Context<E> &ctx, std::string path,
|
||||
int perm)
|
||||
: OutputFile<E>(path, 0, true) {
|
||||
Fatal(ctx) << "LockingOutputFile is not supported on Windows";
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {}
|
||||
|
||||
template <typename E>
|
||||
void LockingOutputFile<E>::close(Context<E> &ctx) {}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template class OutputFile<E>;
|
||||
template class LockingOutputFile<E>;
|
||||
|
||||
} // namespace mold
|
||||
+1592
-812
File diff suppressed because it is too large.
Load diff
@@ -35,7 +35,7 @@
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
// Create linker-synthesized sections
|
||||
template <typename E>
|
||||
@@ -53,7 +53,9 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
|
||||
ctx.strtab = push(new StrtabSection<E>);
|
||||
ctx.symtab = push(new SymtabSection<E>);
|
||||
ctx.shstrtab = push(new ShstrtabSection<E>);
|
||||
ctx.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (is_x86<E>)
|
||||
ctx.extra.note_property = push(new NotePropertySection<E>);
|
||||
|
||||
if constexpr (is_riscv<E>)
|
||||
ctx.extra.riscv_attributes = push(new RiscvAttributesSection<E>);
|
||||
@@ -109,10 +111,7 @@ template <typename E>
|
||||
static void r_claim_unresolved_symbols(Context<E> &ctx) {
|
||||
Timer t(ctx, "r_claim_unresolved_symbols");
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
if (!file->is_alive)
|
||||
return;
|
||||
|
||||
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
|
||||
for (i64 i = file->first_global; i < file->elf_syms.size(); i++) {
|
||||
const ElfSym<E> &esym = file->elf_syms[i];
|
||||
Symbol<E> &sym = *file->symbols[i];
|
||||
@@ -148,8 +147,6 @@ static u64 r_set_osec_offsets(Context<E> &ctx) {
|
||||
|
||||
template <typename E>
|
||||
void combine_objects(Context<E> &ctx) {
|
||||
compute_merged_section_sizes(ctx);
|
||||
|
||||
create_output_sections(ctx);
|
||||
|
||||
r_create_synthetic_sections(ctx);
|
||||
@@ -171,12 +168,10 @@ void combine_objects(Context<E> &ctx) {
|
||||
compute_section_headers(ctx);
|
||||
|
||||
i64 filesize = r_set_osec_offsets(ctx);
|
||||
ctx.output_file =
|
||||
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0666);
|
||||
ctx.buf = ctx.output_file->buf;
|
||||
|
||||
copy_chunks(ctx);
|
||||
clear_padding(ctx);
|
||||
ctx.output_file->close(ctx);
|
||||
ctx.checkpoint();
|
||||
|
||||
@@ -197,4 +192,4 @@ using E = MOLD_TARGET;
|
||||
|
||||
template void combine_objects(Context<E> &);
|
||||
|
||||
} // namespace mold::elf
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,134 @@
|
||||
// Since RISC instructions are generally up to 32 bits long, there's no
|
||||
// way to embed very large immediates into their branch instructions. For
|
||||
// example, RISC-V's JAL (jump and link) instruction can jump to only
|
||||
// within PC ± 1 MiB because its immediate is 21 bits long. If the
|
||||
// destination is further than that, we need to use two instructions
|
||||
// instead; the first instruction being AUIPC, which sets the upper 20
|
||||
// bits of a displacement to a register, and the second being JALR, which
|
||||
// specifies the lower 12 bits and the register. Combined, they specify a
|
||||
// 32-bit displacement, which is sufficient to support the medium code
|
||||
// model.
|
||||
//
|
||||
// However, always using two or more instructions for function calls is a
|
||||
// waste of time and space if the branch target is within a single
|
||||
// instruction's reach. There are two approaches to address this problem
|
||||
// as follows:
|
||||
//
|
||||
// 1. The compiler optimistically emits a single branch instruction for
|
||||
// all function calls. The linker then checks if the branch target is
|
||||
// reachable, and if not, redirects the branch to a linker-synthesized
|
||||
// code sequence that uses two or more instructions to branch further.
|
||||
// That linker-synthesized code is called a "thunk". All RISC psABIs
|
||||
// except RISC-V and LoongArch take this approach.
|
||||
//
|
||||
// 2. The compiler pessimistically emits two instructions to branch
|
||||
// anywhere in PC ± 2 GiB, and the linker rewrites them with a single
|
||||
// instruction if the branch target is close enough. RISC-V and
|
||||
// LoongArch take this approach.
|
||||
//
|
||||
// This file contains functions to support (2). For (1), see thunks.cc.
|
||||
//
|
||||
// With the presence of this code-shrinking relaxation, sections can no
|
||||
// longer be considered as atomic units. If we delete an instruction from
|
||||
// the middle of a section, the section contents after that point need to
|
||||
// be shifted by the size of the instruction. Symbol values and relocation
|
||||
// offsets have to be shifted too if they refer to bytes past the deleted
|
||||
// ones.
|
||||
//
|
||||
// In mold, we use `r_deltas` to memorize how many bytes have been shifted
|
||||
// for relocations. For symbols, we directly mutate their `value` member.
|
||||
//
|
||||
// RISC-V and LoongArch object files tend to have way more relocations
|
||||
// than those for other targets. This is because all branches, including
|
||||
// those that jump within the same section, are explicitly expressed with
|
||||
// relocations. Here is why we need them: all control-flow statements,
|
||||
// such as `if` or `for`, are implemented using branch instructions. For
|
||||
// other targets, the compiler doesn't emit relocations for such branches
|
||||
// because it knows at compile-time exactly how many bytes have to be
|
||||
// skipped. That's not true in RISC-V and LoongArch because the linker may
|
||||
// delete bytes between a branch and its target. Therefore, all branches,
|
||||
// including in-section ones, have to be explicitly expressed with
|
||||
// relocations.
|
||||
//
|
||||
// Note that this mechanism only shrinks sections and never enlarges them,
|
||||
// as the compiler always emits the longest instruction sequence. This
|
||||
// makes the linker implementation a bit simpler because we don't need to
|
||||
// worry about oscillation.
|
||||
|
||||
#if MOLD_RV64LE || MOLD_RV64BE || MOLD_RV32LE || MOLD_RV32BE || \
|
||||
MOLD_LOONGARCH64 || MOLD_LOONGARCH32
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template <>
|
||||
i64 get_r_delta(InputSection<E> &isec, u64 offset) {
|
||||
std::span<RelocDelta> deltas = isec.extra.r_deltas;
|
||||
auto it = ranges::upper_bound(deltas, offset, std::less_equal<>{},
|
||||
&RelocDelta::offset);
|
||||
return (it == deltas.begin()) ? 0 : (it - 1)->delta;
|
||||
}
|
||||
|
||||
template <>
|
||||
void shrink_sections(Context<E> &ctx) {
|
||||
Timer t(ctx, "shrink_sections");
|
||||
|
||||
// Find all relaxable relocations and record how many bytes we can save
|
||||
// into r_deltas.
|
||||
//
|
||||
// Technically speaking, relaxing relocations may allow more relocations
|
||||
// to be relaxed because the distance between a branch instruction and
|
||||
// its target may decrease as a result of relaxation. That said, the
|
||||
// number of such relocations is negligible (I tried to self-host mold
|
||||
// on RISC-V as an experiment and found that the mold-built .text is
|
||||
// only ~0.04% larger than that of GNU ld), so we don't bother to handle
|
||||
// them. We scan relocations only once here.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
|
||||
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
shrink_section(ctx, *isec);
|
||||
});
|
||||
|
||||
// Fix symbol values.
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
for (Symbol<E> *sym : file->symbols)
|
||||
if (sym->file == file)
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (isec->shdr().sh_flags & SHF_EXECINSTR)
|
||||
if (i64 delta = get_r_delta(*isec, sym->value))
|
||||
sym->value -= delta;
|
||||
});
|
||||
|
||||
// Recompute sizes of executable sections
|
||||
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
|
||||
if (chunk->to_osec() && (chunk->shdr.sh_flags & SHF_EXECINSTR))
|
||||
chunk->compute_section_size(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
// Returns the distance between a relocated place and a symbol.
|
||||
template <>
|
||||
i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
|
||||
InputSection<E> &isec, const ElfRel<E> &rel) {
|
||||
// We handle absolute symbols as if they were infinitely far away
|
||||
// because `shrink_section` may increase a distance between a branch
|
||||
// instruction and an absolute symbol. Branching to an absolute
|
||||
// location is extremely rare in real code, though.
|
||||
if (sym.is_absolute())
|
||||
return INT64_MAX;
|
||||
|
||||
// Compute a distance between the relocated place and the symbol.
|
||||
i64 S = sym.get_addr(ctx);
|
||||
i64 A = rel.r_addend;
|
||||
i64 P = isec.get_addr() + rel.r_offset;
|
||||
return S + A - P;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <tbb/version.h>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::string errno_string() {
|
||||
// strerror is not thread-safe, so guard it with a lock.
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock(mu);
|
||||
return strerror(errno);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// no enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
static std::string sigabrt_msg;
|
||||
|
||||
static void sighandler(int signo, siginfo_t *info, void *ucontext) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
// Handle disk full error
|
||||
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: {
|
||||
(void)!write(STDERR_FILENO, &sigabrt_msg[0], sigabrt_msg.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-throw the signal
|
||||
signal(SIGSEGV, SIG_DFL);
|
||||
signal(SIGBUS, SIG_DFL);
|
||||
signal(SIGABRT, SIG_DFL);
|
||||
|
||||
cleanup();
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGSEGV, &action, NULL);
|
||||
sigaction(SIGBUS, &action, NULL);
|
||||
|
||||
// OneTBB 2021.9.0 has the interface version 12090.
|
||||
if (TBB_runtime_interface_version() <= 12090) {
|
||||
sigabrt_msg = "mold: aborted\n"
|
||||
"mold: mold with libtbb version 2021.9.0 or older is known to be unstable "
|
||||
"under heavy load. Your libtbb version is " +
|
||||
std::string(TBB_runtime_version()) +
|
||||
". Please upgrade your libtbb library and try again.\n";
|
||||
|
||||
sigaction(SIGABRT, &action, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void cleanup() {
|
||||
if (output_tmpfile)
|
||||
_unlink(output_tmpfile);
|
||||
}
|
||||
|
||||
std::string errno_string() {
|
||||
LPVOID buf;
|
||||
DWORD dw = GetLastError();
|
||||
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, dw,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR)&buf, 0, nullptr);
|
||||
|
||||
std::string ret = (char *)buf;
|
||||
LocalFree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static LONG WINAPI vectored_handler(_EXCEPTION_POINTERS *exception_info) {
|
||||
static std::mutex mu;
|
||||
std::scoped_lock lock{mu};
|
||||
|
||||
PEXCEPTION_RECORD rec = exception_info->ExceptionRecord;
|
||||
ULONG_PTR *p = rec->ExceptionInformation;
|
||||
|
||||
if (rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR &&
|
||||
(ULONG_PTR)output_buffer_start <= p[1] &&
|
||||
p[1] < (ULONG_PTR)output_buffer_end) {
|
||||
static const char msg[] =
|
||||
"mold: failed to write to an output file. Disk full?\n";
|
||||
(void)!_write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
} else if (rec->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
|
||||
static const char msg[] =
|
||||
"mold: stack overflow\n";
|
||||
(void)!_write(_fileno(stderr), msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void install_signal_handler() {
|
||||
AddVectoredExceptionHandler(0, vectored_handler);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,23 +1,23 @@
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
|
||||
#include "mold.h"
|
||||
#include "config.h"
|
||||
#include "mold.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <filesystem>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace mold::elf {
|
||||
namespace mold {
|
||||
|
||||
#ifdef MOLD_X86_64
|
||||
static int pipe_write_fd = -1;
|
||||
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
// it may take a few hundred milliseconds. To hide the latency,
|
||||
// we fork a child and let it do the actual linking work.
|
||||
std::function<void()> fork_child() {
|
||||
void fork_child() {
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) == -1) {
|
||||
perror("pipe");
|
||||
@@ -50,12 +50,17 @@ std::function<void()> fork_child() {
|
||||
|
||||
// Child
|
||||
close(pipefd[0]);
|
||||
pipe_write_fd = pipefd[1];
|
||||
}
|
||||
|
||||
return [=] {
|
||||
char buf[] = {1};
|
||||
[[maybe_unused]] int n = write(pipefd[1], buf, 1);
|
||||
assert(n == 1);
|
||||
};
|
||||
void notify_parent() {
|
||||
if (pipe_write_fd == -1)
|
||||
return;
|
||||
|
||||
char buf[] = {1};
|
||||
[[maybe_unused]] int n = write(pipe_write_fd, buf, 1);
|
||||
assert(n == 1);
|
||||
pipe_write_fd = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -84,6 +89,9 @@ static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
|
||||
template <typename E>
|
||||
[[noreturn]]
|
||||
void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
#ifdef __APPLE__
|
||||
Fatal(ctx) << "-run is not supported on macOS";
|
||||
#else
|
||||
assert(argv[1] == "-run"s || argv[1] == "--run"s);
|
||||
|
||||
if (!argv[2])
|
||||
@@ -94,11 +102,11 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
std::string dso_path = find_dso(ctx, self);
|
||||
|
||||
// Set environment variables
|
||||
putenv(strdup(("LD_PRELOAD=" + dso_path).c_str()));
|
||||
putenv(strdup(("MOLD_PATH=" + self).c_str()));
|
||||
setenv("LD_PRELOAD", dso_path.c_str(), 1);
|
||||
setenv("MOLD_PATH", self.c_str(), 1);
|
||||
|
||||
// If ld, ld.lld or ld.gold is specified, run mold itself
|
||||
if (std::string cmd = filepath(argv[2]).filename();
|
||||
// If ld, ld.lld or ld.gold is specified, run mold instead
|
||||
if (std::string cmd = path_filename(argv[2]);
|
||||
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
|
||||
std::vector<char *> args;
|
||||
args.push_back(argv[0]);
|
||||
@@ -111,12 +119,11 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
|
||||
// Execute a given command
|
||||
execvp(argv[2], argv + 2);
|
||||
Fatal(ctx) << "mold -run failed: " << argv[2] << ": " << errno_string();
|
||||
#endif
|
||||
}
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template void process_run_subcommand(Context<E> &, int, char **);
|
||||
|
||||
} // namespace mold::elf
|
||||
|
||||
#endif
|
||||
} // namespace mold
|
||||
Loaded 100 of 1558 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user